diff --git a/.devcontainer/.blockscout_config.example b/.devcontainer/.blockscout_config.example new file mode 100644 index 0000000..6730bb5 --- /dev/null +++ b/.devcontainer/.blockscout_config.example @@ -0,0 +1,67 @@ +CHAIN_TYPE=ethereum + +ETHEREUM_JSONRPC_VARIANT=geth +ETHEREUM_JSONRPC_TRACE_URL="" + +API_RATE_LIMIT=100 +HEART_BEAT_TIMEOUT=30 +TXS_STATS_DAYS_TO_COMPILE_AT_INIT=2 +INDEXER_MEMORY_LIMIT=6 + +POOL_SIZE=50 +POOL_SIZE_API=50 +ACCOUNT_POOL_SIZE=10 + +INDEXER_DISABLE_EMPTY_BLOCKS_SANITIZER='true' +INDEXER_DISABLE_PENDING_TRANSACTIONS_FETCHER='true' +INDEXER_DISABLE_INTERNAL_TRANSACTIONS_FETCHER='true' +INDEXER_DISABLE_BLOCK_REWARD_FETCHER='true' +INDEXER_DISABLE_ADDRESS_COIN_BALANCE_FETCHER='true' +INDEXER_DISABLE_CATALOGED_TOKEN_UPDATER_FETCHER='true' +ETHEREUM_JSONRPC_DISABLE_ARCHIVE_BALANCES='true' +INDEXER_DISABLE_TOKEN_INSTANCE_RETRY_FETCHER='true' +INDEXER_DISABLE_TOKEN_INSTANCE_REALTIME_FETCHER='true' +INDEXER_DISABLE_TOKEN_INSTANCE_SANITIZE_FETCHER='true' +INDEXER_DISABLE_WITHDRAWALS_FETCHER='true' +INDEXER_DISABLE_TOKEN_INSTANCE_LEGACY_SANITIZE_FETCHER='true' + +INDEXER_CATCHUP_BLOCKS_BATCH_SIZE=5 +INDEXER_COIN_BALANCES_BATCH_SIZE=1 +TOKEN_ID_MIGRATION_BATCH_SIZE=1 +TOKEN_INSTANCE_OWNER_MIGRATION_BATCH_SIZE=1 +INDEXER_EMPTY_BLOCKS_SANITIZER_BATCH_SIZE=1 +INDEXER_BLOCK_REWARD_BATCH_SIZE=1 +INDEXER_RECEIPTS_BATCH_SIZE=10 +INDEXER_COIN_BALANCES_BATCH_SIZE=1 +INDEXER_TOKEN_BALANCES_BATCH_SIZE=1 + +INDEXER_CATCHUP_BLOCKS_CONCURRENCY=1 +TOKEN_INSTANCE_OWNER_MIGRATION_CONCURRENCY=1 +INDEXER_BLOCK_REWARD_CONCURRENCY=1 +INDEXER_RECEIPTS_CONCURRENCY=1 +INDEXER_COIN_BALANCES_CONCURRENCY=1 +INDEXER_TOKEN_CONCURRENCY=1 +INDEXER_TOKEN_BALANCES_CONCURRENCY=1 +INDEXER_TOKEN_INSTANCE_RETRY_CONCURRENCY=1 +INDEXER_TOKEN_INSTANCE_REALTIME_CONCURRENCY=1 +INDEXER_TOKEN_INSTANCE_SANITIZE_CONCURRENCY=1 +INDEXER_TOKEN_INSTANCE_LEGACY_SANITIZE_CONCURRENCY=1 +INDEXER_TOKEN_INSTANCE_RETRY_BATCH_SIZE=1 +INDEXER_TOKEN_INSTANCE_REALTIME_BATCH_SIZE=1 +INDEXER_TOKEN_INSTANCE_SANITIZE_BATCH_SIZE=1 +INDEXER_TOKEN_INSTANCE_LEGACY_SANITIZE_BATCH_SIZE=1 + +INDEXER_TOKEN_BALANCES_FETCHER_INIT_QUERY_LIMIT=2 +INDEXER_COIN_BALANCES_FETCHER_INIT_QUERY_LIMIT=2 + +DISABLE_EXCHANGE_RATES='true' +SOURCIFY_INTEGRATION_ENABLED='false' +EXCHANGE_RATES_COINGECKO_PLATFORM_ID='' +DISABLE_TOKEN_EXCHANGE_RATE='true' + +API_V2_ENABLED=true + +DISABLE_CATCHUP_INDEXER='false' +INDEXER_CATCHUP_BLOCKS_BATCH_SIZE=10 +INDEXER_CATCHUP_BLOCKS_CONCURRENCY=10 +ETHEREUM_JSONRPC_HTTP_URL="https://ethereum-sepolia-rpc.publicnode.com" diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 0000000..a030c16 --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,50 @@ +# Since this is a copy of https://github.com/blockscout/devcontainer-elixir/blob/main/Dockerfile +# So after successful testing this file, the original one must be updated as well. +ARG VARIANT="1.17.3-erlang-27.1-debian-bullseye-20240926" +FROM hexpm/elixir:${VARIANT} + +# ARGs declared before FROM are not persisted beyond the FROM instruction. +# They must be redeclared here to be available in the rest of the Dockerfile. +ARG PHOENIX_VERSION="1.7.10" +ARG NODE_VERSION="18" + +# This Dockerfile adds a non-root user with sudo access. Update the “remoteUser” property in +# devcontainer.json to use it. More info: https://aka.ms/vscode-remote/containers/non-root-user. +ARG USERNAME=vscode +ARG USER_UID=1000 +ARG USER_GID=$USER_UID + +# Options for common package install script +ARG INSTALL_ZSH="true" +ARG UPGRADE_PACKAGES="true" +ARG COMMON_SCRIPT_SOURCE="https://raw.githubusercontent.com/microsoft/vscode-dev-containers/main/script-library/common-debian.sh" + +# Options for setup nodejs +ARG NODE_SCRIPT_SOURCE="https://raw.githubusercontent.com/microsoft/vscode-dev-containers/main/script-library/node-debian.sh" +ENV NVM_DIR=/usr/local/share/nvm +ENV NVM_SYMLINK_CURRENT=true +ENV PATH=${NVM_DIR}/current/bin:${PATH} + +# Install needed packages and setup non-root user. Use a separate RUN statement to add your own dependencies. +RUN apt-get update \ + && export DEBIAN_FRONTEND=noninteractive \ + && apt-get -y install --no-install-recommends curl ca-certificates 2>&1 \ + && curl -sSL ${COMMON_SCRIPT_SOURCE} -o /tmp/common-setup.sh \ + && /bin/bash /tmp/common-setup.sh "${INSTALL_ZSH}" "${USERNAME}" "${USER_UID}" "${USER_GID}" "${UPGRADE_PACKAGES}" \ + # + # Install Node.js for use with web applications + && curl -sSL ${NODE_SCRIPT_SOURCE} -o /tmp/node-setup.sh \ + && /bin/bash /tmp/node-setup.sh "${NVM_DIR}" "${NODE_VERSION}" "${USERNAME}" \ + && npm install -g cspell@latest \ + # + # Install dependencies + && apt-get install -y build-essential inotify-tools \ + # + # Clean up + && apt-get autoremove -y \ + && apt-get clean -y \ + && rm -rf /var/lib/apt/lists/* /tmp/common-setup.sh /tmp/node-setup.sh + +RUN su ${USERNAME} -c "mix local.hex --force \ + && mix local.rebar --force \ + && mix archive.install --force hex phx_new ${PHOENIX_VERSION}" diff --git a/.devcontainer/README.md b/.devcontainer/README.md new file mode 100644 index 0000000..c32c5ad --- /dev/null +++ b/.devcontainer/README.md @@ -0,0 +1,199 @@ +# Blockscout Backend Development with VSCode Devcontainers and GitHub Codespaces + +## Table of Contents +1. [Motivation](#motivation) +2. [Setting Up VSCode Devcontainer Locally](#setting-up-vscode-devcontainer-locally) +3. [Using GitHub Codespaces in the Browser](#using-github-codespaces-in-the-browser) +4. [Configuring Postgres DB Access](#configuring-postgres-db-access) +5. [Developing Blockscout Backend](#developing-blockscout-backend) +6. [Upgrading Elixir Version](#upgrading-elixir-version) +7. [Contributing](#contributing) + +## Motivation + +Setting up a local development environment for Blockscout can be time-consuming and error-prone. This devcontainer setup streamlines the process by providing a pre-configured environment with all necessary dependencies. It ensures consistency across development environments, reduces setup time, and allows developers to focus on coding rather than configuration. + +Key benefits include: +- Pre-configured environment with Elixir, Phoenix, and Node.js +- Integrated PostgreSQL database +- Essential VS Code extensions pre-installed +- Simplified database management +- Consistent development environment across team members + +## Setting Up VSCode Devcontainer Locally + +1. Clone the Blockscout repository: + ``` + git clone https://github.com/blockscout/blockscout.git + cd blockscout + ``` + +2. Open the project in VS Code: + ``` + code . + ``` + +3. Before re-opening in the container, you may find it useful to configure SSH authorization. To do this: + + a. Ensure you have SSH access to GitHub configured on your local machine. + + b. Open `.devcontainer/devcontainer.json`. + + c. Uncomment the `mounts` section: + ```json + "mounts": [ + "source=${localEnv:HOME}/.ssh/known_hosts,target=/home/vscode/.ssh/known_hosts,type=bind,consistency=cached", + "source=${localEnv:HOME}/.ssh/config,target=/home/vscode/.ssh/config,type=bind,consistency=cached", + "source=${localEnv:HOME}/.ssh/id_rsa,target=/home/vscode/.ssh/id_rsa,type=bind,consistency=cached" + ], + ``` + + d. Adjust the paths if your SSH keys are stored in a different location. + +4. When prompted, click "Reopen in Container". If not prompted, press `F1`, type "Remote-Containers: Reopen in Container", and press Enter. + +5. VS Code will build the devcontainer. This process includes: + - Pulling the base Docker image + - Installing specified VS Code extensions + - Setting up the PostgreSQL database + - Installing project dependencies + + This may take several minutes the first time. + +6. Once the devcontainer is built, you'll be working inside the containerized environment. + +7. If you modified the `devcontainer.json` file in step 3, you may want to execute `git update-index --assume-unchanged .devcontainer/devcontainer.json` in a terminal within your devcontainer to prevent the changes to `devcontainer.json` from appearing in `git status` and VS Code's Source Control. + +### Additional Setup for Cursor.ai Users + +If you're using Cursor.ai instead of VSCode, you may need to perform some additional setup steps. Please note that these changes will not persist after reloading the devcontainer, so you may need to repeat these steps each time you start a new session. + +1. **Git Configuration**: You may encounter issues when trying to perform Git operations from the terminal or the "Source Control" tab. To resolve this, set up your Git configuration inside the devcontainer: + + a. Open a terminal in your devcontainer. + b. Set your Git username: + ``` + git config --global user.name "Your Name" + ``` + c. Set your Git email: + ``` + git config --global user.email "your.email@example.com" + ``` + + Replace "Your Name" and "your.email@example.com" with your actual name and email associated with your GitHub account. + +2. **ElixirLS: Elixir support and debugger** (JakeBecker.elixir-ls): This extension may not be automatically installed in Cursor.ai, even though it's specified in the devcontainer configuration. To install it manually: + + a. Open the Extensions tab. + b. Search for "JakeBecker.elixir-ls". + c. Look for the extension "ElixirLS: Elixir support and debugger" by JakeBecker and click "Install". + +Remember, you may need to repeat these steps each time you start a new Cursor.ai session with the devcontainer. + +### Signing in to GitHub for Pull Request Extension + +1. In the devcontainer, click on the GitHub icon in the Primary sidebar. +2. Click on "Sign in to GitHub" and follow the prompts to authenticate. + +## Using GitHub Codespaces in the Browser + +To open the project in GitHub Codespaces: + +1. Navigate to the Blockscout repository on GitHub. +2. Switch to the branch you want to work on. +3. Click the "Code" button. +4. Instead of clicking "Create codespace on [branch]" (which would use the default machine type that may not be sufficient for this Elixir-based project), click on the three dots (...) next to it. +5. Select "New with options". +6. Choose the "4-core/16GB RAM" machine type for optimal performance. +7. Click "Create codespace". + +This will create a new Codespace with the specified resources, ensuring adequate performance for the Elixir-based project. + +Note: After the container opens, you may see an error about the inability to use "GitHub Copilot Chat". This Copilot functionality will not be accessible in the Codespace environment. + +## Configuring Postgres DB Access + +To configure access to the PostgreSQL database using the VS Code extension: + +1. Click on the PostgreSQL icon in the Primary sidebar. +2. Click "+" (Add Connection) in the PostgreSQL explorer. +3. Use the following details: + - Host: `db` + - User: `postgres` + - Password: `postgres` + - Port: `5432` + - Use an ssl connection: "Standard connection" + - Database: `blockscout` + - The display name: "" + +These credentials are derived from the `DATABASE_URL` in the `bs` script. + +## Developing Blockscout Backend + +### Configuration + +Before running the Blockscout server, you need to set up the configuration: + +1. Copy the `.devcontainer/.blockscout_config.example` file to `.devcontainer/.blockscout_config`. +2. Adjust the settings in `.devcontainer/.blockscout_config` as needed for your development environment. + +For a comprehensive list of environment variables that can be set in this configuration file, refer to the [Blockscout documentation](https://docs.blockscout.com/setup/env-variables). + +### Using the `bs` Script + +The `bs` script in `.devcontainer/bin/` helps orchestrate common development tasks. Here are some key commands: + +- Initialize the project: `bs --init` +- Initialize or re-initialize the database: `bs --db-init` (This will remove all data and tables from the DB and re-create the tables) +- Run the server: `bs` +- Run the server without syncing: `bs --no-sync` +- Recompile the project: `bs --recompile` (Use this when new dependencies arrive after a merge or when switching to another `CHAIN_TYPE`) +- Run various checks: `bs --spellcheck`, `bs --dialyzer`, `bs --credo`, `bs --format` + +For a full list of options, run `bs --help`. + +### Interacting with the Blockscout API + +For local devcontainer setups (not applicable to GitHub Codespaces), you can use API testing tools like Postman or Insomnia on your host machine to interact with the Blockscout API running in the container: + +1. Ensure the Blockscout server is running in the devcontainer. +2. In the API testing tool on your host machine, use `http://127.0.0.1:4000` as the base URL. +3. Example endpoint: `GET http://127.0.0.1:4000/api/v2/blocks` + +This allows testing API endpoints directly from your host machine while the server runs in the container. + +### Troubleshooting + +If you face issues with dependency compilation or dialyzer after container creation: + +1. Check for untracked files: `git ls-files --others` +2. Remove compilation artifacts or generated files if present. +3. For persistent issues, consider cleaning all untracked files (use with caution): + ``` + git clean -fdX + bs --recompile + ``` + +This ensures a clean compilation environment within the container. + +## Upgrading Elixir Version + +To upgrade the Elixir version: + +1. Open `.devcontainer/Dockerfile`. +2. Update the `VARIANT` argument with the desired Elixir version. +3. Rebuild the devcontainer. + +Note: Ensure that the version you choose is compatible with the project dependencies. + +After testing the new Elixir version, propagate the corresponding changes in the Dockerfile to the repo https://github.com/blockscout/devcontainer-elixir. Once a new release tag is published there and a new docker image `ghcr.io/blockscout/devcontainer-elixir` appears in the GitHub registry, modify the `docker-compose.yml` file in the `.devcontainer` directory to reflect the proper docker image tag. + +## Contributing + +When contributing changes that require additional checks for specific blockchain types: + +1. Open `.devcontainer/bin/chain-specific-checks`. +2. Add your checks under the appropriate `CHAIN_TYPE` case. +3. Ensure your checks exit with a non-zero code if unsuccessful. + +Remember to document any new checks or configuration options in this README. \ No newline at end of file diff --git a/.devcontainer/bin/bs b/.devcontainer/bin/bs new file mode 100755 index 0000000..da9790b --- /dev/null +++ b/.devcontainer/bin/bs @@ -0,0 +1,237 @@ +#!/bin/bash + +# This script helps to orchestrate typical tasks when developing Blockscout + +source $(dirname $0)/utils + +# cd $(dirname $0)/../../ + +# Source and exaport environment variables related to the backend configuration +BLOCKSCOUT_CONFIG_FILE=".devcontainer/.blockscout_config" +if [ -f "./${BLOCKSCOUT_CONFIG_FILE}" ]; then + set -a # Automatically export all variables + source ./${BLOCKSCOUT_CONFIG_FILE} + set +a # Disable automatic export +else + echo "Warning: ${BLOCKSCOUT_CONFIG_FILE} file not found. Skipping configuration loading." +fi + +if [ "${DATABASE_URL}" == "" ]; then + export DATABASE_URL="postgresql://postgres:postgres@db:5432/blockscout" +fi + +# Initialize variables +INIT=false +NO_SYNC=false +DB_INIT=false +RECOMPILE=false +SPELLCHECK=false +DIALYZER=false +CREDO=false +FORMAT=false +DOCS=false +HELP=false + +# Parse command line arguments +for arg in "$@" +do + case $arg in + --help) + HELP=true + shift # Remove --help from processing + ;; + --init) + INIT=true + shift # Remove --init from processing + ;; + --no-sync) + NO_SYNC=true + shift # Remove --no-sync from processing + ;; + --db-init) + DB_INIT=true + shift # Remove --db-init from processing + ;; + --recompile) + RECOMPILE=true + shift # Remove --recompile from processing + ;; + --spellcheck) + SPELLCHECK=true + shift # Remove --spellcheck from processing + ;; + --dialyzer) + DIALYZER=true + shift # Remove --dialyzer from processing + ;; + --credo) + CREDO=true + shift # Remove --credo from processing + ;; + --format) + FORMAT=true + shift # Remove --format from processing + ;; + --docs) + DOCS=true + shift # Remove --docs from processing + ;; + esac +done + +# Define the help function +show_help() { + echo "Usage: bs [OPTION]" + echo "Orchestrate typical tasks when developing Blockscout backend server" + echo + echo "Options:" + echo " --help Show this help message and exit" + echo " --init Initialize the project directory" + echo " --format Run code formatter" + echo " --spellcheck Run spellcheck" + echo " --dialyzer Run dialyzer" + echo " --credo Run credo" + echo " --docs Generate documentation" + echo " --recompile Re-fetch dependencies and recompile" + echo " --db-init (Re)initialize the database" + echo " --no-sync Run the server with disabled indexer, so only the API is available" + echo + echo "If no option is provided, the script will run the backend server." +} + +# If --help argument is passed, show help and exit +if [ "$HELP" = true ]; then + show_help + exit 0 +fi + +# Define the project directory initialization subroutine +initialize_project() { + if [ ! -d "apps/block_scout_web/priv/cert" ]; then + mix local.rebar --force + mix deps.compile + mix compile + + # cd apps/block_scout_web/assets + # npm install && node_modules/webpack/bin/webpack.js --mode production + # cd - + # cd apps/explorer + # npm install + # cd - + + cd apps/block_scout_web + mix phx.gen.cert blockscout blockscout.local + cd - + else + echo "Looks like the project directory is already initialized" + fi +} + +# Define the initialization subroutine +initialize_db() { + echo "Initializing database. Step 1 of 2: Dropping database" + mix ecto.drop > /dev/null 2>&1 + echo "Initializing database. Step 2 of 2: Creating database" + mix do ecto.create, ecto.migrate | grep Runn +} + +# Define the recompile subroutine +recompile() { + mix deps.clean block_scout_web + mix deps.clean explorer + mix deps.clean indexer + mix deps.get + mix deps.compile --force +} + +# Define the spellcheck subroutine +spellcheck() { + cspell --config cspell.json "**/*.ex*" "**/*.eex" "**/*.js" --gitignore | less +} + +# Define the dialyzer subroutine +dialyzer() { + mix dialyzer +} + +# Define the credo subroutine +credo() { + mix credo +} + +# Define the format subroutine +format() { + mix format +} + +# Define the generate_docs subroutine +generate_docs() { + mix docs +} + +# If --init argument is passed, run the project dir initialization subroutine and exit +if [ "$INIT" = true ]; then + initialize_project + exit 0 +fi + +# If --db-init argument is passed, run the database initialization subroutine and exit +if [ "$DB_INIT" = true ]; then + initialize_db + exit 0 +fi + +# If --recompile argument is passed, run the recompile subroutine and exit +if [ "$RECOMPILE" = true ]; then + recompile + exit 0 +fi + +# If --spellcheck argument is passed, run the spellcheck subroutine and exit +if [ "$SPELLCHECK" = true ]; then + spellcheck + exit 0 +fi + +# If --dialyzer argument is passed, run the dialyzer subroutine and exit +if [ "$DIALYZER" = true ]; then + dialyzer + exit 0 +fi + +# If --credo argument is passed, run the credo subroutine and exit +if [ "$CREDO" = true ]; then + credo + exit 0 +fi + +# If --format argument is passed, run the format subroutine and exit +if [ "$FORMAT" = true ]; then + format + exit 0 +fi + +# If --doc argument is passed, run the format subroutine and exit +if [ "$DOCS" = true ]; then + generate_docs + exit 0 +fi + +if [ "${ETHEREUM_JSONRPC_HTTP_URL}" != "" ]; then + check_server_availability ${ETHEREUM_JSONRPC_HTTP_URL} + check_server_accessibility ${ETHEREUM_JSONRPC_HTTP_URL} +fi + +if [ "${CHAIN_TYPE}" != "" -o "${CHAIN_TYPE}" != "ethereum" -o "${CHAIN_TYPE}" != "default" ]; then + source $(dirname $0)/chain-specific-checks +fi + +if [ ! -d "apps/block_scout_web/priv/cert" ]; then + echo "Project directory is not initialized" + echo "Run 'bs --init' to initialize the project directory" + exit 1 +fi + +export DISABLE_INDEXER=${NO_SYNC} + +mix phx.server diff --git a/.devcontainer/bin/chain-specific-checks b/.devcontainer/bin/chain-specific-checks new file mode 100644 index 0000000..c6129f2 --- /dev/null +++ b/.devcontainer/bin/chain-specific-checks @@ -0,0 +1,17 @@ +# The script is sourced from the main script, so the unsuccessful check must exit +# with non-zero code to terminate the main script. + +source $(dirname $0)/utils + +# Run the appropriate checks based on CHAIN_TYPE +case "${CHAIN_TYPE}" in + "arbitrum") + echo "Arbitrum sepcific checks" + # if the check is not successful, exit with code 1 + check_server_availability ${INDEXER_ARBITRUM_L1_RPC} + check_server_accessibility ${INDEXER_ARBITRUM_L1_RPC} + ;; + *) + echo "No special checks for CHAIN_TYPE: $CHAIN_TYPE" + ;; +esac diff --git a/.devcontainer/bin/utils b/.devcontainer/bin/utils new file mode 100644 index 0000000..bd86034 --- /dev/null +++ b/.devcontainer/bin/utils @@ -0,0 +1,22 @@ +# Function to check server availability +check_server_availability() { + local url=$1 + + curl --connect-timeout 3 --silent ${url} 1>/dev/null + if [ $? -ne 0 ]; then + echo "VPN must be enabled to connect to ${url}" + exit 1 + fi +} + +# Function to check server accessibility with a POST request +check_server_accessibility() { + local url=$1 + local payload='[{"id":0,"params":["latest",false],"method":"eth_getBlockByNumber","jsonrpc":"2.0"}]' + + http_code=$(curl -s -o /dev/null -w "%{http_code}" -X POST ${url} -H "Content-Type: application/json" -d "${payload}") + if [ "$http_code" -ne 200 ]; then + echo "VPN must be enabled to access ${url} (HTTP status code: ${http_code})" + exit 1 + fi +} diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..f89237d --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,47 @@ +{ + "name": "Blockscout Elixir", + "dockerComposeFile": "docker-compose.yml", + "service": "elixir", + "workspaceFolder": "/workspace", + "postCreateCommand": { + "safe-directory": "git config --global --add safe.directory ${containerWorkspaceFolder}", + "deps": "mix deps.get" + }, + "remoteEnv": { + "PATH": "${containerEnv:PATH}:${containerWorkspaceFolder}/.devcontainer/bin" + }, + // Configure tool-specific properties. + "customizations": { + // Configure properties specific to VS Code. + "vscode": { + // Add the IDs of extensions you want installed when the container is created. + "extensions": [ + "JakeBecker.elixir-ls", + "ckolkman.vscode-postgres", + "GitHub.copilot", + "GitHub.copilot-chat", + "GitHub.vscode-pull-request-github" + ] + } + }, + "features": { + "ghcr.io/stuartleeks/dev-container-features/shell-history:0": {} + }, + // Use 'forwardPorts' to make a list of ports inside the container available locally. + // This can be used to network with other containers or with the host. + "forwardPorts": [ + 4000, + 4001, + 5432 + ], + // Uncomment and adjust the private key path to the one you use to authenticate on GitHub + // if you want to have ability to push to GitHub from the container. + // "mounts": [ + // "source=${localEnv:HOME}/.ssh/known_hosts,target=/home/vscode/.ssh/known_hosts,type=bind,consistency=cached", + // "source=${localEnv:HOME}/.ssh/config,target=/home/vscode/.ssh/config,type=bind,consistency=cached", + // // Make sure that the private key can be used to authenticate on GitHub + // "source=${localEnv:HOME}/.ssh/id_rsa,target=/home/vscode/.ssh/id_rsa,type=bind,consistency=cached" + // ], + // Uncomment to connect as a non-root user. See https://aka.ms/vscode-remote/containers/non-root. + "remoteUser": "vscode" +} \ No newline at end of file diff --git a/.devcontainer/docker-compose.yml b/.devcontainer/docker-compose.yml new file mode 100644 index 0000000..280ceea --- /dev/null +++ b/.devcontainer/docker-compose.yml @@ -0,0 +1,30 @@ +services: + elixir: + image: ghcr.io/blockscout/devcontainer-elixir:1.17.3-erlang-27.1 + + # Uncomment next lines to use test Dockerfile with new Elixir version + # build: + # context: . + # dockerfile: Dockerfile + + volumes: + - ..:/workspace:cached + # Runs app on the same network as the database container, allows "forwardPorts" in devcontainer.json function. + network_mode: service:db + + # Overrides default command so things don't shut down after the process ends. + command: sleep infinity + + db: + image: postgres:latest + command: postgres -c 'max_connections=250' + restart: unless-stopped + volumes: + - postgres-data:/var/lib/postgresql/data + environment: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + POSTGRES_DB: app + +volumes: + postgres-data: diff --git a/.dialyzer-ignore b/.dialyzer-ignore index 258b252..056ffa4 100644 --- a/.dialyzer-ignore +++ b/.dialyzer-ignore @@ -4,10 +4,3 @@ lib/explorer/smart_contract/vyper/publisher_worker.ex:1 lib/explorer/smart_contract/solidity/publisher_worker.ex:8 lib/explorer/smart_contract/vyper/publisher_worker.ex:8 lib/phoenix/router.ex:402 -lib/explorer/smart_contract/reader.ex:435 -lib/explorer/exchange_rates/source.ex:139 -lib/explorer/exchange_rates/source.ex:142 -lib/block_scout_web/cldr.ex:1 -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:171 diff --git a/.dockerignore b/.dockerignore index 8ff38c4..86f0f1d 100644 --- a/.dockerignore +++ b/.dockerignore @@ -10,3 +10,4 @@ test erl_crash.dump logs apps/*/test +.devcontainer \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 858bb85..5fa188d 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -81,7 +81,7 @@ body: attributes: label: Elixir & Erlang/OTP versions description: Elixir & Erlang/OTP versions. - placeholder: Elixir 1.16.3 (compiled with Erlang/OTP 26) + placeholder: Elixir 1.17.3 (compiled with Erlang/OTP 27) validations: required: true diff --git a/.github/workflows/config.yml b/.github/workflows/config.yml index 33237b7..45f887f 100644 --- a/.github/workflows/config.yml +++ b/.github/workflows/config.yml @@ -32,8 +32,8 @@ on: env: MIX_ENV: test - OTP_VERSION: ${{ github.ref_name == '9256/merge' && '26.2.5.1' || vars.OTP_VERSION }} - ELIXIR_VERSION: ${{ github.ref_name == '9256/merge' && '1.16.3' || vars.ELIXIR_VERSION }} + OTP_VERSION: ${{ github.ref_name == '10284/merge' && '27.1' || vars.OTP_VERSION }} + ELIXIR_VERSION: ${{ github.ref_name == '10284/merge' && '1.17.3' || vars.ELIXIR_VERSION }} ACCOUNT_AUTH0_DOMAIN: "blockscoutcom.us.auth0.com" jobs: @@ -49,7 +49,7 @@ jobs: // Add/remove CI matrix chain types here const defaultChainTypes = ["default"]; - const chainTypes = ["ethereum", "polygon_zkevm", "rsk", "stability", "filecoin", "optimism", "arbitrum", "celo", "zetachain", "zksync", "shibarium"]; + const chainTypes = ["ethereum", "polygon_zkevm", "rsk", "stability", "filecoin", "optimism", "arbitrum", "celo", "zetachain", "zksync", "shibarium", "blackfort", "scroll"]; const extraChainTypes = ["suave", "polygon_edge"]; // Chain type matrix we use in master branch diff --git a/.github/workflows/pre-release-arbitrum.yml b/.github/workflows/pre-release-arbitrum.yml index 3bc6cd2..7778a95 100644 --- a/.github/workflows/pre-release-arbitrum.yml +++ b/.github/workflows/pre-release-arbitrum.yml @@ -16,7 +16,7 @@ jobs: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: ${{ vars.RELEASE_VERSION }} + RELEASE_VERSION: 6.9.2 steps: - uses: actions/checkout@v4 - name: Setup repo @@ -94,4 +94,74 @@ jobs: ADMIN_PANEL_ENABLED=false BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }} RELEASE_VERSION=${{ env.RELEASE_VERSION }} - CHAIN_TYPE=arbitrum \ No newline at end of file + CHAIN_TYPE=arbitrum + + - name: Build and push Docker image for Arbitrum (indexer + API + shrink internal transactions) + uses: docker/build-push-action@v5 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: blockscout/blockscout-arbitrum:${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }}-shrink-internal-txs + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + DISABLE_WEBAPP=false + API_V1_READ_METHODS_DISABLED=false + API_V1_WRITE_METHODS_DISABLED=false + CACHE_EXCHANGE_RATES_PERIOD= + CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL= + CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED= + ADMIN_PANEL_ENABLED=false + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=arbitrum + SHRINK_INTERNAL_TRANSACTIONS_ENABLED=true + + - name: Build and push Docker image for Arbitrum (indexer + shrink internal transactions) + uses: docker/build-push-action@v5 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: blockscout/blockscout-arbitrum:${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }}-shrink-internal-txs-indexer + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + DISABLE_API=true + DISABLE_WEBAPP=true + CACHE_EXCHANGE_RATES_PERIOD= + CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED= + CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL= + ADMIN_PANEL_ENABLED=false + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=arbitrum + SHRINK_INTERNAL_TRANSACTIONS_ENABLED=true + + - name: Build and push Docker image for Arbitrum (API + shrink internal transactions) + uses: docker/build-push-action@v5 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: blockscout/blockscout-arbitrum:${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }}-shrink-internal-txs-api + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + DISABLE_INDEXER=true + DISABLE_WEBAPP=true + CACHE_EXCHANGE_RATES_PERIOD= + CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED= + ADMIN_PANEL_ENABLED=false + CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL= + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=arbitrum + SHRINK_INTERNAL_TRANSACTIONS_ENABLED=true \ No newline at end of file diff --git a/.github/workflows/pre-release-blackfort.yml b/.github/workflows/pre-release-blackfort.yml new file mode 100644 index 0000000..ba35f5e --- /dev/null +++ b/.github/workflows/pre-release-blackfort.yml @@ -0,0 +1,97 @@ +name: Pre-release for Blackfort + +on: + workflow_dispatch: + inputs: + number: + type: number + required: true + +env: + OTP_VERSION: ${{ vars.OTP_VERSION }} + ELIXIR_VERSION: ${{ vars.ELIXIR_VERSION }} + +jobs: + push_to_registry: + name: Push Docker image to Docker Hub + runs-on: ubuntu-latest + env: + RELEASE_VERSION: 6.9.2 + steps: + - uses: actions/checkout@v4 + - name: Setup repo + uses: ./.github/actions/setup-repo + id: setup + with: + docker-username: ${{ secrets.DOCKER_USERNAME }} + docker-password: ${{ secrets.DOCKER_PASSWORD }} + docker-remote-multi-platform: true + docker-arm-host: ${{ secrets.ARM_RUNNER_HOSTNAME }} + docker-arm-host-key: ${{ secrets.ARM_RUNNER_KEY }} + + - name: Build and push Docker image for Blackfort (indexer + API) + uses: docker/build-push-action@v5 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: blockscout/blockscout-blackfort:${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }} + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + DISABLE_WEBAPP=false + API_V1_READ_METHODS_DISABLED=false + API_V1_WRITE_METHODS_DISABLED=false + CACHE_EXCHANGE_RATES_PERIOD= + CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED= + CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL= + ADMIN_PANEL_ENABLED=false + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=blackfort + + - name: Build and push Docker image for Blackfort (indexer) + uses: docker/build-push-action@v5 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: blockscout/blockscout-blackfort:${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }}-indexer + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + DISABLE_API=true + DISABLE_WEBAPP=true + CACHE_EXCHANGE_RATES_PERIOD= + CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED= + CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL= + ADMIN_PANEL_ENABLED=false + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=blackfort + + - name: Build and push Docker image for Blackfort (API) + uses: docker/build-push-action@v5 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: blockscout/blockscout-blackfort:${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }}-api + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + DISABLE_INDEXER=true + DISABLE_WEBAPP=true + CACHE_EXCHANGE_RATES_PERIOD= + CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED= + CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL= + ADMIN_PANEL_ENABLED=false + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=blackfort \ No newline at end of file diff --git a/.github/workflows/pre-release-celo.yml b/.github/workflows/pre-release-celo.yml new file mode 100644 index 0000000..a5b37c9 --- /dev/null +++ b/.github/workflows/pre-release-celo.yml @@ -0,0 +1,171 @@ +name: Pre-release for CELO + +on: + workflow_dispatch: + inputs: + number: + type: number + required: true + +env: + OTP_VERSION: ${{ vars.OTP_VERSION }} + ELIXIR_VERSION: ${{ vars.ELIXIR_VERSION }} + +jobs: + push_to_registry: + name: Push Docker image to Docker Hub + runs-on: ubuntu-latest + env: + RELEASE_VERSION: 6.9.2 + API_GRAPHQL_MAX_COMPLEXITY: 10400 + steps: + - uses: actions/checkout@v4 + - name: Setup repo + uses: ./.github/actions/setup-repo + id: setup + with: + docker-username: ${{ secrets.DOCKER_USERNAME }} + docker-password: ${{ secrets.DOCKER_PASSWORD }} + docker-remote-multi-platform: true + docker-arm-host: ${{ secrets.ARM_RUNNER_HOSTNAME }} + docker-arm-host-key: ${{ secrets.ARM_RUNNER_KEY }} + + - name: Build and push Docker image for CELO (indexer + API) + uses: docker/build-push-action@v5 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: blockscout/blockscout-celo:${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }} + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + DISABLE_WEBAPP=false + API_V1_READ_METHODS_DISABLED=false + API_V1_WRITE_METHODS_DISABLED=false + CACHE_EXCHANGE_RATES_PERIOD= + CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED= + CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL= + ADMIN_PANEL_ENABLED=false + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=celo + + - name: Build and push Docker image for CELO (indexer) + uses: docker/build-push-action@v5 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: blockscout/blockscout-celo:${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }}-indexer + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + DISABLE_API=true + DISABLE_WEBAPP=true + CACHE_EXCHANGE_RATES_PERIOD= + CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED= + CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL= + ADMIN_PANEL_ENABLED=false + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=celo + + - name: Build and push Docker image for CELO (API) + uses: docker/build-push-action@v5 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: blockscout/blockscout-celo:${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }}-api + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + DISABLE_INDEXER=true + DISABLE_WEBAPP=true + CACHE_EXCHANGE_RATES_PERIOD= + CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED= + CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL= + ADMIN_PANEL_ENABLED=false + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=celo + + - name: Build and push Docker image for CELO (indexer + API + shrink internal transactions) + uses: docker/build-push-action@v5 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: blockscout/blockscout-celo:${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }}-shrink-internal-txs + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + API_GRAPHQL_MAX_COMPLEXITY=${{ env.API_GRAPHQL_MAX_COMPLEXITY }} + 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 }}-alpha.${{ inputs.number }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=celo + SHRINK_INTERNAL_TRANSACTIONS_ENABLED=true + + - name: Build and push Docker image for CELO (indexer + shrink internal transactions) + uses: docker/build-push-action@v5 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: blockscout/blockscout-celo:${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }}-shrink-internal-txs-indexer + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + API_GRAPHQL_MAX_COMPLEXITY=${{ env.API_GRAPHQL_MAX_COMPLEXITY }} + CACHE_EXCHANGE_RATES_PERIOD= + DISABLE_WEBAPP=true + DISABLE_API=true + CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED= + ADMIN_PANEL_ENABLED=false + CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL= + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=celo + SHRINK_INTERNAL_TRANSACTIONS_ENABLED=true + + - name: Build and push Docker image for CELO (API + shrink internal transactions) + uses: docker/build-push-action@v5 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: blockscout/blockscout-celo:${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }}-shrink-internal-txs-api + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + API_GRAPHQL_MAX_COMPLEXITY=${{ env.API_GRAPHQL_MAX_COMPLEXITY }} + CACHE_EXCHANGE_RATES_PERIOD= + DISABLE_WEBAPP=true + DISABLE_INDEXER=true + CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED= + ADMIN_PANEL_ENABLED=false + CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL= + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=celo + SHRINK_INTERNAL_TRANSACTIONS_ENABLED=true \ No newline at end of file diff --git a/.github/workflows/pre-release-eth.yml b/.github/workflows/pre-release-eth.yml index 07c436f..fbe7221 100644 --- a/.github/workflows/pre-release-eth.yml +++ b/.github/workflows/pre-release-eth.yml @@ -16,7 +16,7 @@ jobs: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: ${{ vars.RELEASE_VERSION }} + RELEASE_VERSION: 6.9.2 steps: - uses: actions/checkout@v4 - name: Setup repo diff --git a/.github/workflows/pre-release-filecoin.yml b/.github/workflows/pre-release-filecoin.yml new file mode 100644 index 0000000..0c34ed6 --- /dev/null +++ b/.github/workflows/pre-release-filecoin.yml @@ -0,0 +1,167 @@ +name: Pre-release for Filecoin + +on: + workflow_dispatch: + inputs: + number: + type: number + required: true + +env: + OTP_VERSION: ${{ vars.OTP_VERSION }} + ELIXIR_VERSION: ${{ vars.ELIXIR_VERSION }} + +jobs: + push_to_registry: + name: Push Docker image to Docker Hub + runs-on: ubuntu-latest + env: + RELEASE_VERSION: 6.9.2 + steps: + - uses: actions/checkout@v4 + - name: Setup repo + uses: ./.github/actions/setup-repo + id: setup + with: + docker-username: ${{ secrets.DOCKER_USERNAME }} + docker-password: ${{ secrets.DOCKER_PASSWORD }} + docker-remote-multi-platform: true + docker-arm-host: ${{ secrets.ARM_RUNNER_HOSTNAME }} + docker-arm-host-key: ${{ secrets.ARM_RUNNER_KEY }} + + - name: Build and push Docker image for Filecoin (indexer + API) + uses: docker/build-push-action@v5 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: blockscout/blockscout-filecoin:latest, blockscout/blockscout-filecoin:${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }} + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + DISABLE_WEBAPP=false + API_V1_READ_METHODS_DISABLED=false + API_V1_WRITE_METHODS_DISABLED=false + CACHE_EXCHANGE_RATES_PERIOD= + CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED= + CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL= + ADMIN_PANEL_ENABLED=false + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=filecoin + + - name: Build and push Docker image for Filecoin (indexer) + uses: docker/build-push-action@v5 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: blockscout/blockscout-filecoin:${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }}-indexer + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + DISABLE_API=true + DISABLE_WEBAPP=true + CACHE_EXCHANGE_RATES_PERIOD= + CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED= + CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL= + ADMIN_PANEL_ENABLED=false + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=filecoin + + - name: Build and push Docker image for Filecoin (API) + uses: docker/build-push-action@v5 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: blockscout/blockscout-filecoin:${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }}-api + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + DISABLE_INDEXER=true + DISABLE_WEBAPP=true + CACHE_EXCHANGE_RATES_PERIOD= + CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED= + CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL= + ADMIN_PANEL_ENABLED=false + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=filecoin + + - name: Build and push Docker image for Filecoin (indexer + API + shrink internal transactions) + uses: docker/build-push-action@v5 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: blockscout/blockscout-filecoin:${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }}-shrink-internal-txs + labels: ${{ steps.setup.outputs.docker-labels }} + 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 }}-alpha.${{ inputs.number }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=filecoin + SHRINK_INTERNAL_TRANSACTIONS_ENABLED=true + + - name: Build and push Docker image for Filecoin (indexer + shrink internal transactions) + uses: docker/build-push-action@v5 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: blockscout/blockscout-filecoin:${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }}-shrink-internal-txs-indexer + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + CACHE_EXCHANGE_RATES_PERIOD= + DISABLE_WEBAPP=true + DISABLE_API=true + CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED= + ADMIN_PANEL_ENABLED=false + CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL= + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=filecoin + SHRINK_INTERNAL_TRANSACTIONS_ENABLED=true + + - name: Build and push Docker image for Filecoin (API + shrink internal transactions) + uses: docker/build-push-action@v5 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: blockscout/blockscout-filecoin:${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }}-shrink-internal-txs-api + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + CACHE_EXCHANGE_RATES_PERIOD= + DISABLE_WEBAPP=true + DISABLE_INDEXER=true + CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED= + ADMIN_PANEL_ENABLED=false + CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL= + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=filecoin + SHRINK_INTERNAL_TRANSACTIONS_ENABLED=true \ No newline at end of file diff --git a/.github/workflows/pre-release-optimism.yml b/.github/workflows/pre-release-optimism.yml index 44683dc..ea5b14e 100644 --- a/.github/workflows/pre-release-optimism.yml +++ b/.github/workflows/pre-release-optimism.yml @@ -16,7 +16,7 @@ jobs: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: ${{ vars.RELEASE_VERSION }} + RELEASE_VERSION: 6.9.2 steps: - uses: actions/checkout@v4 - name: Setup repo @@ -94,4 +94,74 @@ jobs: ADMIN_PANEL_ENABLED=false BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }} RELEASE_VERSION=${{ env.RELEASE_VERSION }} - CHAIN_TYPE=optimism \ No newline at end of file + CHAIN_TYPE=optimism + + - name: Build and push Docker image for Optimism (indexer + API + shrink internal transactions) + uses: docker/build-push-action@v5 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: blockscout/blockscout-optimism:${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }}-shrink-internal-txs + labels: ${{ steps.setup.outputs.docker-labels }} + 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=optimism + SHRINK_INTERNAL_TRANSACTIONS_ENABLED=true + + - name: Build and push Docker image for Optimism (indexer + shrink internal transactions) + uses: docker/build-push-action@v5 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: blockscout/blockscout-optimism:${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }}-shrink-internal-txs-indexer + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + CACHE_EXCHANGE_RATES_PERIOD= + DISABLE_WEBAPP=true + DISABLE_API=true + 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=optimism + SHRINK_INTERNAL_TRANSACTIONS_ENABLED=true + + - name: Build and push Docker image for Optimism (API + shrink internal transactions) + uses: docker/build-push-action@v5 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: blockscout/blockscout-optimism:${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }}-shrink-internal-txs-api + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + CACHE_EXCHANGE_RATES_PERIOD= + DISABLE_WEBAPP=true + DISABLE_INDEXER=true + 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=optimism + SHRINK_INTERNAL_TRANSACTIONS_ENABLED=true \ No newline at end of file diff --git a/.github/workflows/pre-release-polygon-zkevm.yml b/.github/workflows/pre-release-polygon-zkevm.yml new file mode 100644 index 0000000..4b7178d --- /dev/null +++ b/.github/workflows/pre-release-polygon-zkevm.yml @@ -0,0 +1,167 @@ +name: Pre-release for Polygon ZkEVM + +on: + workflow_dispatch: + inputs: + number: + type: number + required: true + +env: + OTP_VERSION: ${{ vars.OTP_VERSION }} + ELIXIR_VERSION: ${{ vars.ELIXIR_VERSION }} + +jobs: + push_to_registry: + name: Push Docker image to Docker Hub + runs-on: ubuntu-latest + env: + RELEASE_VERSION: 6.9.2 + steps: + - uses: actions/checkout@v4 + - name: Setup repo + uses: ./.github/actions/setup-repo + id: setup + with: + docker-username: ${{ secrets.DOCKER_USERNAME }} + docker-password: ${{ secrets.DOCKER_PASSWORD }} + docker-remote-multi-platform: true + docker-arm-host: ${{ secrets.ARM_RUNNER_HOSTNAME }} + docker-arm-host-key: ${{ secrets.ARM_RUNNER_KEY }} + + - name: Build and push Docker image for Polygon ZkEVM (indexer + API) + uses: docker/build-push-action@v5 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: blockscout/blockscout-zkevm:${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }} + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + DISABLE_WEBAPP=false + API_V1_READ_METHODS_DISABLED=false + API_V1_WRITE_METHODS_DISABLED=false + CACHE_EXCHANGE_RATES_PERIOD= + CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED= + CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL= + ADMIN_PANEL_ENABLED=false + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=polygon_zkevm + + - name: Build and push Docker image for Polygon ZkEVM (indexer) + uses: docker/build-push-action@v5 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: blockscout/blockscout-zkevm:${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }}-indexer + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + DISABLE_API=true + DISABLE_WEBAPP=true + CACHE_EXCHANGE_RATES_PERIOD= + CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED= + CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL= + ADMIN_PANEL_ENABLED=false + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=polygon_zkevm + + - name: Build and push Docker image for Polygon ZkEVM (API) + uses: docker/build-push-action@v5 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: blockscout/blockscout-zkevm:${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }}-api + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + DISABLE_INDEXER=true + DISABLE_WEBAPP=true + CACHE_EXCHANGE_RATES_PERIOD= + CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED= + CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL= + ADMIN_PANEL_ENABLED=false + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=polygon_zkevm + + - name: Build and push Docker image for Polygon ZkEVM (indexer + API + shrink internal transactions) + uses: docker/build-push-action@v5 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: blockscout/blockscout-zkevm:${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }}-shrink-internal-txs + labels: ${{ steps.setup.outputs.docker-labels }} + 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_zkevm + SHRINK_INTERNAL_TRANSACTIONS_ENABLED=true + + - name: Build and push Docker image for Polygon ZkEVM (indexer + shrink internal transactions) + uses: docker/build-push-action@v5 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: blockscout/blockscout-zkevm:${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }}-shrink-internal-txs-indexer + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + CACHE_EXCHANGE_RATES_PERIOD= + DISABLE_WEBAPP=true + DISABLE_API=true + 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 + SHRINK_INTERNAL_TRANSACTIONS_ENABLED=true + + - name: Build and push Docker image for Polygon ZkEVM (API + shrink internal transactions) + uses: docker/build-push-action@v5 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: blockscout/blockscout-zkevm:${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }}-shrink-internal-txs-api + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + CACHE_EXCHANGE_RATES_PERIOD= + DISABLE_WEBAPP=true + DISABLE_INDEXER=true + 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 + SHRINK_INTERNAL_TRANSACTIONS_ENABLED=true \ No newline at end of file diff --git a/.github/workflows/pre-release-redstone.yml b/.github/workflows/pre-release-redstone.yml new file mode 100644 index 0000000..301e5f3 --- /dev/null +++ b/.github/workflows/pre-release-redstone.yml @@ -0,0 +1,100 @@ +name: Pre-release for Redstone + +on: + workflow_dispatch: + inputs: + number: + type: number + required: true + +env: + OTP_VERSION: ${{ vars.OTP_VERSION }} + ELIXIR_VERSION: ${{ vars.ELIXIR_VERSION }} + +jobs: + push_to_registry: + name: Push Docker image to Docker Hub + runs-on: ubuntu-latest + env: + RELEASE_VERSION: 6.9.2 + steps: + - uses: actions/checkout@v4 + - name: Setup repo + uses: ./.github/actions/setup-repo + id: setup + with: + docker-username: ${{ secrets.DOCKER_USERNAME }} + docker-password: ${{ secrets.DOCKER_PASSWORD }} + docker-remote-multi-platform: true + docker-arm-host: ${{ secrets.ARM_RUNNER_HOSTNAME }} + docker-arm-host-key: ${{ secrets.ARM_RUNNER_KEY }} + + - name: Build and push Docker image for Redstone (indexer + API) + uses: docker/build-push-action@v5 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: blockscout/blockscout-redstone:${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }} + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + DISABLE_WEBAPP=false + API_V1_READ_METHODS_DISABLED=false + API_V1_WRITE_METHODS_DISABLED=false + CACHE_EXCHANGE_RATES_PERIOD= + CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED= + CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL= + ADMIN_PANEL_ENABLED=false + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=optimism + MUD_INDEXER_ENABLED=true + + - name: Build and push Docker image for Redstone (indexer) + uses: docker/build-push-action@v5 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: blockscout/blockscout-redstone:${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }}-indexer + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + DISABLE_API=true + DISABLE_WEBAPP=true + CACHE_EXCHANGE_RATES_PERIOD= + CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED= + CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL= + ADMIN_PANEL_ENABLED=false + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=optimism + MUD_INDEXER_ENABLED=true + + - name: Build and push Docker image for Redstone (API) + uses: docker/build-push-action@v5 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: blockscout/blockscout-redstone:${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }}-api + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + DISABLE_INDEXER=true + DISABLE_WEBAPP=true + CACHE_EXCHANGE_RATES_PERIOD= + CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED= + CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL= + ADMIN_PANEL_ENABLED=false + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=optimism + MUD_INDEXER_ENABLED=true \ No newline at end of file diff --git a/.github/workflows/pre-release-scroll.yml b/.github/workflows/pre-release-scroll.yml new file mode 100644 index 0000000..84796e0 --- /dev/null +++ b/.github/workflows/pre-release-scroll.yml @@ -0,0 +1,167 @@ +name: Pre-release for Scroll + +on: + workflow_dispatch: + inputs: + number: + type: number + required: true + +env: + OTP_VERSION: ${{ vars.OTP_VERSION }} + ELIXIR_VERSION: ${{ vars.ELIXIR_VERSION }} + +jobs: + push_to_registry: + name: Push Docker image to Docker Hub + runs-on: ubuntu-latest + env: + RELEASE_VERSION: 6.9.2 + steps: + - uses: actions/checkout@v4 + - name: Setup repo + uses: ./.github/actions/setup-repo + id: setup + with: + docker-username: ${{ secrets.DOCKER_USERNAME }} + docker-password: ${{ secrets.DOCKER_PASSWORD }} + docker-remote-multi-platform: true + docker-arm-host: ${{ secrets.ARM_RUNNER_HOSTNAME }} + docker-arm-host-key: ${{ secrets.ARM_RUNNER_KEY }} + + - name: Build and push Docker image for Scroll (indexer + API) + uses: docker/build-push-action@v5 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: blockscout/blockscout-scroll:${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }} + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + DISABLE_WEBAPP=false + API_V1_READ_METHODS_DISABLED=false + API_V1_WRITE_METHODS_DISABLED=false + CACHE_EXCHANGE_RATES_PERIOD= + CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED= + CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL= + ADMIN_PANEL_ENABLED=false + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=scroll + + - name: Build and push Docker image for Scroll (indexer) + uses: docker/build-push-action@v5 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: blockscout/blockscout-scroll:${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }}-indexer + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + DISABLE_API=true + DISABLE_WEBAPP=true + CACHE_EXCHANGE_RATES_PERIOD= + CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED= + CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL= + ADMIN_PANEL_ENABLED=false + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=scroll + + - name: Build and push Docker image for Scroll (API) + uses: docker/build-push-action@v5 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: blockscout/blockscout-scroll:${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }}-api + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + DISABLE_INDEXER=true + DISABLE_WEBAPP=true + CACHE_EXCHANGE_RATES_PERIOD= + CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED= + CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL= + ADMIN_PANEL_ENABLED=false + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=scroll + + - name: Build and push Docker image for Scroll (indexer + API + shrink internal transactions) + uses: docker/build-push-action@v5 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: blockscout/blockscout-scroll:${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }}-shrink-internal-txs + labels: ${{ steps.setup.outputs.docker-labels }} + 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 }}-alpha.${{ inputs.number }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=scroll + SHRINK_INTERNAL_TRANSACTIONS_ENABLED=true + + - name: Build and push Docker image for Scroll (indexer + shrink internal transactions) + uses: docker/build-push-action@v5 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: blockscout/blockscout-scroll:${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }}-shrink-internal-txs-indexer + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + CACHE_EXCHANGE_RATES_PERIOD= + DISABLE_WEBAPP=true + DISABLE_API=true + CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED= + ADMIN_PANEL_ENABLED=false + CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL= + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=scroll + SHRINK_INTERNAL_TRANSACTIONS_ENABLED=true + + - name: Build and push Docker image for Scroll (API + shrink internal transactions) + uses: docker/build-push-action@v5 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: blockscout/blockscout-scroll:${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }}-shrink-internal-txs-api + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + CACHE_EXCHANGE_RATES_PERIOD= + DISABLE_WEBAPP=true + DISABLE_INDEXER=true + CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED= + ADMIN_PANEL_ENABLED=false + CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL= + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=scroll + SHRINK_INTERNAL_TRANSACTIONS_ENABLED=true \ No newline at end of file diff --git a/.github/workflows/pre-release-shibarium.yml b/.github/workflows/pre-release-shibarium.yml index 8258205..9674ac7 100644 --- a/.github/workflows/pre-release-shibarium.yml +++ b/.github/workflows/pre-release-shibarium.yml @@ -16,7 +16,7 @@ jobs: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: ${{ vars.RELEASE_VERSION }} + RELEASE_VERSION: 6.9.2 steps: - uses: actions/checkout@v4 - name: Setup repo diff --git a/.github/workflows/pre-release-zksync.yml b/.github/workflows/pre-release-zksync.yml index dc5b3b5..9f04b12 100644 --- a/.github/workflows/pre-release-zksync.yml +++ b/.github/workflows/pre-release-zksync.yml @@ -16,7 +16,7 @@ jobs: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: ${{ vars.RELEASE_VERSION }} + RELEASE_VERSION: 6.9.2 steps: - uses: actions/checkout@v4 - name: Setup repo @@ -94,4 +94,74 @@ jobs: ADMIN_PANEL_ENABLED=false BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }} RELEASE_VERSION=${{ env.RELEASE_VERSION }} - CHAIN_TYPE=zksync \ No newline at end of file + CHAIN_TYPE=zksync + + - name: Build and push Docker image for ZkSync (indexer + API + shrink internal transactions) + uses: docker/build-push-action@v5 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: blockscout/blockscout-zksync:${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }}-shrink-internal-txs + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + DISABLE_WEBAPP=false + API_V1_READ_METHODS_DISABLED=false + API_V1_WRITE_METHODS_DISABLED=false + CACHE_EXCHANGE_RATES_PERIOD= + CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED= + CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL= + ADMIN_PANEL_ENABLED=false + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=zksync + SHRINK_INTERNAL_TRANSACTIONS_ENABLED=true + + - name: Build and push Docker image for ZkSync (indexer + shrink internal transactions) + uses: docker/build-push-action@v5 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: blockscout/blockscout-zksync:${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }}-shrink-internal-txs-indexer + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + DISABLE_API=true + DISABLE_WEBAPP=true + CACHE_EXCHANGE_RATES_PERIOD= + CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED= + CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL= + ADMIN_PANEL_ENABLED=false + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=zksync + SHRINK_INTERNAL_TRANSACTIONS_ENABLED=true + + - name: Build and push Docker image for ZkSync (API + shrink internal transactions) + uses: docker/build-push-action@v5 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: blockscout/blockscout-zksync:${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }}-shrink-internal-txs-api + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + DISABLE_INDEXER=true + DISABLE_WEBAPP=true + CACHE_EXCHANGE_RATES_PERIOD= + CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED= + CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL= + ADMIN_PANEL_ENABLED=false + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=zksync + SHRINK_INTERNAL_TRANSACTIONS_ENABLED=true \ No newline at end of file diff --git a/.github/workflows/pre-release.yml b/.github/workflows/pre-release.yml index 2a83968..a8d4297 100644 --- a/.github/workflows/pre-release.yml +++ b/.github/workflows/pre-release.yml @@ -16,7 +16,7 @@ jobs: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: ${{ vars.RELEASE_VERSION }} + RELEASE_VERSION: 6.9.2 steps: - uses: actions/checkout@v4 - name: Setup repo @@ -115,3 +115,93 @@ jobs: AMPLITUDE_API_KEY= BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }} RELEASE_VERSION=${{ env.RELEASE_VERSION }} + + - name: Build & Push Core Docker image (indexer + API + shrink internal transactions) + 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:${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }}-shrink-internal-txs + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + DISABLE_WEBAPP=false + API_V1_READ_METHODS_DISABLED=false + API_V1_WRITE_METHODS_DISABLED=false + CACHE_EXCHANGE_RATES_PERIOD= + CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED= + CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL= + ADMIN_PANEL_ENABLED=false + DECODE_NOT_A_CONTRACT_CALLS=false + MIXPANEL_URL= + MIXPANEL_TOKEN= + AMPLITUDE_URL= + AMPLITUDE_API_KEY= + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + SHRINK_INTERNAL_TRANSACTIONS_ENABLED=true + + - name: Build & Push Core Docker image (indexer + shrink internal transactions) + 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:${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }}-shrink-internal-txs-indexer + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + DISABLE_API=true + DISABLE_WEBAPP=true + CACHE_EXCHANGE_RATES_PERIOD= + CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED= + CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL= + ADMIN_PANEL_ENABLED=false + DECODE_NOT_A_CONTRACT_CALLS=false + MIXPANEL_URL= + MIXPANEL_TOKEN= + AMPLITUDE_URL= + AMPLITUDE_API_KEY= + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + SHRINK_INTERNAL_TRANSACTIONS_ENABLED=true + + - name: Build & Push Core Docker image (API + shrink internal transactions) + 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:${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }}-shrink-internal-txs-api + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + DISABLE_INDEXER=true + DISABLE_WEBAPP=true + API_V1_READ_METHODS_DISABLED=false + API_V1_WRITE_METHODS_DISABLED=false + CACHE_EXCHANGE_RATES_PERIOD= + CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED= + CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL= + ADMIN_PANEL_ENABLED=false + DECODE_NOT_A_CONTRACT_CALLS=false + MIXPANEL_URL= + MIXPANEL_TOKEN= + AMPLITUDE_URL= + AMPLITUDE_API_KEY= + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + SHRINK_INTERNAL_TRANSACTIONS_ENABLED=true diff --git a/.github/workflows/publish-docker-image-every-push.yml b/.github/workflows/publish-docker-image-every-push.yml index dea446d..29efcc5 100644 --- a/.github/workflows/publish-docker-image-every-push.yml +++ b/.github/workflows/publish-docker-image-every-push.yml @@ -11,7 +11,7 @@ on: env: OTP_VERSION: ${{ vars.OTP_VERSION }} ELIXIR_VERSION: ${{ vars.ELIXIR_VERSION }} - RELEASE_VERSION: ${{ vars.RELEASE_VERSION }} + RELEASE_VERSION: 6.9.2 jobs: push_to_registry: diff --git a/.github/workflows/publish-docker-image-for-arbitrum.yml b/.github/workflows/publish-docker-image-for-arbitrum.yml index 1975715..2305387 100644 --- a/.github/workflows/publish-docker-image-for-arbitrum.yml +++ b/.github/workflows/publish-docker-image-for-arbitrum.yml @@ -10,7 +10,7 @@ jobs: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: ${{ vars.RELEASE_VERSION }} + RELEASE_VERSION: 6.9.2 DOCKER_CHAIN_NAME: arbitrum steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/publish-docker-image-for-blackfort.yml b/.github/workflows/publish-docker-image-for-blackfort.yml new file mode 100644 index 0000000..3c8fe7f --- /dev/null +++ b/.github/workflows/publish-docker-image-for-blackfort.yml @@ -0,0 +1,51 @@ +name: Blackfort Publish Docker image + +on: + workflow_dispatch: + push: + branches: + - production-blackfort +env: + OTP_VERSION: ${{ vars.OTP_VERSION }} + ELIXIR_VERSION: ${{ vars.ELIXIR_VERSION }} +jobs: + push_to_registry: + name: Push Docker image to Docker Hub + runs-on: ubuntu-latest + env: + RELEASE_VERSION: 6.9.2 + DOCKER_CHAIN_NAME: blackfort + steps: + - uses: actions/checkout@v4 + - name: Setup repo + uses: ./.github/actions/setup-repo + id: setup + with: + docker-username: ${{ secrets.DOCKER_USERNAME }} + docker-password: ${{ secrets.DOCKER_PASSWORD }} + docker-remote-multi-platform: true + docker-arm-host: ${{ secrets.ARM_RUNNER_HOSTNAME }} + docker-arm-host-key: ${{ secrets.ARM_RUNNER_KEY }} + + - 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 }} + labels: ${{ steps.setup.outputs.docker-labels }} + 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=blackfort \ No newline at end of file diff --git a/.github/workflows/publish-docker-image-for-celo.yml b/.github/workflows/publish-docker-image-for-celo.yml index ace16f1..c8170cc 100644 --- a/.github/workflows/publish-docker-image-for-celo.yml +++ b/.github/workflows/publish-docker-image-for-celo.yml @@ -10,8 +10,9 @@ jobs: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: ${{ vars.RELEASE_VERSION }} + RELEASE_VERSION: 6.9.2 DOCKER_CHAIN_NAME: celo + API_GRAPHQL_MAX_COMPLEXITY: 10400 steps: - uses: actions/checkout@v4 - name: Setup repo @@ -24,7 +25,7 @@ jobs: docker-arm-host: ${{ secrets.ARM_RUNNER_HOSTNAME }} docker-arm-host-key: ${{ secrets.ARM_RUNNER_KEY }} - - name: Build and push Docker image for Celo (indexer + API) + - name: Build and push Docker image for CELO (indexer + API) uses: docker/build-push-action@v5 with: context: . @@ -36,6 +37,7 @@ jobs: linux/amd64 linux/arm64/v8 build-args: | + API_GRAPHQL_MAX_COMPLEXITY=${{ env.API_GRAPHQL_MAX_COMPLEXITY }} CACHE_EXCHANGE_RATES_PERIOD= API_V1_READ_METHODS_DISABLED=false DISABLE_WEBAPP=false @@ -46,3 +48,122 @@ jobs: BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-beta.+commit.${{ env.SHORT_SHA }} RELEASE_VERSION=${{ env.RELEASE_VERSION }} CHAIN_TYPE=${{ env.DOCKER_CHAIN_NAME }} + + - name: Build and push Docker image for CELO (indexer) + 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 }}-indexer + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + API_GRAPHQL_MAX_COMPLEXITY=${{ env.API_GRAPHQL_MAX_COMPLEXITY }} + DISABLE_API=true + DISABLE_WEBAPP=true + CACHE_EXCHANGE_RATES_PERIOD= + CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED= + CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL= + ADMIN_PANEL_ENABLED=false + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-beta.+commit.${{ env.SHORT_SHA }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=${{ env.DOCKER_CHAIN_NAME }} + + - name: Build and push Docker image for CELO (API) + 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 }}-api + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + API_GRAPHQL_MAX_COMPLEXITY=${{ env.API_GRAPHQL_MAX_COMPLEXITY }} + DISABLE_INDEXER=true + DISABLE_WEBAPP=true + CACHE_EXCHANGE_RATES_PERIOD= + CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED= + CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL= + ADMIN_PANEL_ENABLED=false + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-beta.+commit.${{ env.SHORT_SHA }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=${{ env.DOCKER_CHAIN_NAME }} + + - name: Build and push Docker image for CELO (indexer + API + shrink internal transactions) + 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 }}-shrink-internal-txs + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + API_GRAPHQL_MAX_COMPLEXITY=${{ env.API_GRAPHQL_MAX_COMPLEXITY }} + 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=${{ env.DOCKER_CHAIN_NAME }} + SHRINK_INTERNAL_TRANSACTIONS_ENABLED=true + + - name: Build and push Docker image for CELO (indexer + shrink internal transactions) + 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 }}-shrink-internal-txs-indexer + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + API_GRAPHQL_MAX_COMPLEXITY=${{ env.API_GRAPHQL_MAX_COMPLEXITY }} + CACHE_EXCHANGE_RATES_PERIOD= + DISABLE_WEBAPP=true + DISABLE_API=true + 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=${{ env.DOCKER_CHAIN_NAME }} + SHRINK_INTERNAL_TRANSACTIONS_ENABLED=true + + - name: Build and push Docker image for CELO (API + shrink internal transactions) + 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 }}-shrink-internal-txs-api + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + API_GRAPHQL_MAX_COMPLEXITY=${{ env.API_GRAPHQL_MAX_COMPLEXITY }} + CACHE_EXCHANGE_RATES_PERIOD= + DISABLE_WEBAPP=true + DISABLE_INDEXER=true + 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=${{ env.DOCKER_CHAIN_NAME }} + SHRINK_INTERNAL_TRANSACTIONS_ENABLED=true diff --git a/.github/workflows/publish-docker-image-for-core.yml b/.github/workflows/publish-docker-image-for-core.yml index 07db871..5727e67 100644 --- a/.github/workflows/publish-docker-image-for-core.yml +++ b/.github/workflows/publish-docker-image-for-core.yml @@ -10,7 +10,7 @@ jobs: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: ${{ vars.RELEASE_VERSION }} + RELEASE_VERSION: 6.9.2 DOCKER_CHAIN_NAME: poa steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/publish-docker-image-for-eth-sepolia.yml b/.github/workflows/publish-docker-image-for-eth-sepolia.yml index 2b79769..70169aa 100644 --- a/.github/workflows/publish-docker-image-for-eth-sepolia.yml +++ b/.github/workflows/publish-docker-image-for-eth-sepolia.yml @@ -10,7 +10,7 @@ jobs: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: ${{ vars.RELEASE_VERSION }} + RELEASE_VERSION: 6.9.2 DOCKER_CHAIN_NAME: eth-sepolia steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/publish-docker-image-for-eth.yml b/.github/workflows/publish-docker-image-for-eth.yml index 8b9989e..546b054 100644 --- a/.github/workflows/publish-docker-image-for-eth.yml +++ b/.github/workflows/publish-docker-image-for-eth.yml @@ -4,13 +4,13 @@ on: workflow_dispatch: push: branches: - - production-eth-experimental + - production-eth jobs: push_to_registry: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: ${{ vars.RELEASE_VERSION }} + RELEASE_VERSION: 6.9.2 DOCKER_CHAIN_NAME: mainnet steps: - uses: actions/checkout@v4 @@ -24,13 +24,13 @@ jobs: docker-arm-host: ${{ secrets.ARM_RUNNER_HOSTNAME }} docker-arm-host-key: ${{ secrets.ARM_RUNNER_KEY }} - - name: Build and push Docker image + - name: Build and push Docker image (indexer + API) 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 + tags: blockscout/blockscout-${{ env.DOCKER_CHAIN_NAME }}:${{ env.RELEASE_VERSION }}-postrelease-${{ env.SHORT_SHA }} labels: ${{ steps.setup.outputs.docker-labels }} platforms: | linux/amd64 @@ -45,4 +45,48 @@ jobs: CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL= BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-beta.+commit.${{ env.SHORT_SHA }} RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=ethereum + + - name: Build and push Docker image (indexer) + 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 }}-indexer + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + CACHE_EXCHANGE_RATES_PERIOD= + DISABLE_WEBAPP=true + DISABLE_API=true + 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=ethereum + + - name: Build and push Docker image (API) + 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 }}-api + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + CACHE_EXCHANGE_RATES_PERIOD= + DISABLE_WEBAPP=true + DISABLE_INDEXER=true + 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=ethereum \ 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 index efc5fcf..48b1fe9 100644 --- a/.github/workflows/publish-docker-image-for-filecoin.yml +++ b/.github/workflows/publish-docker-image-for-filecoin.yml @@ -9,7 +9,7 @@ jobs: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: ${{ vars.RELEASE_VERSION }} + RELEASE_VERSION: 6.9.2 DOCKER_CHAIN_NAME: filecoin steps: - uses: actions/checkout@v4 @@ -23,7 +23,7 @@ jobs: docker-arm-host: ${{ secrets.ARM_RUNNER_HOSTNAME }} docker-arm-host-key: ${{ secrets.ARM_RUNNER_KEY }} - - name: Build and push Docker image + - name: Build and push Docker image for Filecoin (indexer + API) uses: docker/build-push-action@v5 with: context: . @@ -44,4 +44,118 @@ jobs: CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL= BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-beta.+commit.${{ env.SHORT_SHA }} RELEASE_VERSION=${{ env.RELEASE_VERSION }} - CHAIN_TYPE=filecoin \ No newline at end of file + CHAIN_TYPE=${{ env.DOCKER_CHAIN_NAME }} + + - name: Build and push Docker image for Filecoin (indexer) + 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 }}-indexer + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + DISABLE_API=true + DISABLE_WEBAPP=true + CACHE_EXCHANGE_RATES_PERIOD= + CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED= + CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL= + ADMIN_PANEL_ENABLED=false + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-beta.+commit.${{ env.SHORT_SHA }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=${{ env.DOCKER_CHAIN_NAME }} + + - name: Build and push Docker image for Filecoin (API) + 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 }}-api + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + DISABLE_INDEXER=true + DISABLE_WEBAPP=true + CACHE_EXCHANGE_RATES_PERIOD= + CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED= + CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL= + ADMIN_PANEL_ENABLED=false + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-beta.+commit.${{ env.SHORT_SHA }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=${{ env.DOCKER_CHAIN_NAME }} + + - name: Build and push Docker image for Filecoin (indexer + API + shrink internal transactions) + 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 }}-shrink-internal-txs + labels: ${{ steps.setup.outputs.docker-labels }} + 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=${{ env.DOCKER_CHAIN_NAME }} + SHRINK_INTERNAL_TRANSACTIONS_ENABLED=true + + - name: Build and push Docker image for Filecoin (indexer + shrink internal transactions) + 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 }}-shrink-internal-txs-indexer + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + CACHE_EXCHANGE_RATES_PERIOD= + DISABLE_WEBAPP=true + DISABLE_API=true + 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=${{ env.DOCKER_CHAIN_NAME }} + SHRINK_INTERNAL_TRANSACTIONS_ENABLED=true + + - name: Build and push Docker image for Filecoin (API + shrink internal transactions) + 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 }}-shrink-internal-txs-api + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + CACHE_EXCHANGE_RATES_PERIOD= + DISABLE_WEBAPP=true + DISABLE_INDEXER=true + 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=${{ env.DOCKER_CHAIN_NAME }} + SHRINK_INTERNAL_TRANSACTIONS_ENABLED=true \ 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 index 9f19e1e..7b6ba76 100644 --- a/.github/workflows/publish-docker-image-for-fuse.yml +++ b/.github/workflows/publish-docker-image-for-fuse.yml @@ -10,7 +10,7 @@ jobs: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: ${{ vars.RELEASE_VERSION }} + RELEASE_VERSION: 6.9.2 DOCKER_CHAIN_NAME: fuse steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/publish-docker-image-for-gnosis-chain.yml b/.github/workflows/publish-docker-image-for-gnosis-chain.yml index 88fff9f..f2deb08 100644 --- a/.github/workflows/publish-docker-image-for-gnosis-chain.yml +++ b/.github/workflows/publish-docker-image-for-gnosis-chain.yml @@ -10,7 +10,7 @@ jobs: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: ${{ vars.RELEASE_VERSION }} + RELEASE_VERSION: 6.9.2 DOCKER_CHAIN_NAME: xdai steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/publish-docker-image-for-l2-staging.yml b/.github/workflows/publish-docker-image-for-l2-staging.yml index c3bdc75..4c678a1 100644 --- a/.github/workflows/publish-docker-image-for-l2-staging.yml +++ b/.github/workflows/publish-docker-image-for-l2-staging.yml @@ -10,7 +10,7 @@ jobs: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: ${{ vars.RELEASE_VERSION }} + RELEASE_VERSION: 6.9.2 DOCKER_CHAIN_NAME: optimism-l2-advanced steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/publish-docker-image-for-lukso.yml b/.github/workflows/publish-docker-image-for-lukso.yml index 01f5238..9bcb23b 100644 --- a/.github/workflows/publish-docker-image-for-lukso.yml +++ b/.github/workflows/publish-docker-image-for-lukso.yml @@ -10,7 +10,7 @@ jobs: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: ${{ vars.RELEASE_VERSION }} + RELEASE_VERSION: 6.9.2 DOCKER_CHAIN_NAME: lukso steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/publish-docker-image-for-optimism.yml b/.github/workflows/publish-docker-image-for-optimism.yml index e640ce6..2766172 100644 --- a/.github/workflows/publish-docker-image-for-optimism.yml +++ b/.github/workflows/publish-docker-image-for-optimism.yml @@ -10,7 +10,7 @@ jobs: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: ${{ vars.RELEASE_VERSION }} + RELEASE_VERSION: 6.9.2 DOCKER_CHAIN_NAME: optimism steps: - uses: actions/checkout@v4 @@ -144,7 +144,7 @@ jobs: context: . file: ./docker/Dockerfile push: true - tags: blockscout/blockscout-${{ env.DOCKER_CHAIN_NAME }}:${{ env.RELEASE_VERSION }}-postrelease-${{ env.SHORT_SHA }}-shrink-internal-txs-indexer-api + tags: blockscout/blockscout-${{ env.DOCKER_CHAIN_NAME }}:${{ env.RELEASE_VERSION }}-postrelease-${{ env.SHORT_SHA }}-shrink-internal-txs-api labels: ${{ steps.setup.outputs.docker-labels }} platforms: | linux/amd64 diff --git a/.github/workflows/publish-docker-image-for-polygon-edge.yml b/.github/workflows/publish-docker-image-for-polygon-edge.yml index 4bd600b..3244436 100644 --- a/.github/workflows/publish-docker-image-for-polygon-edge.yml +++ b/.github/workflows/publish-docker-image-for-polygon-edge.yml @@ -10,7 +10,7 @@ jobs: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: ${{ vars.RELEASE_VERSION }} + RELEASE_VERSION: 6.9.2 DOCKER_CHAIN_NAME: polygon-edge steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/publish-docker-image-for-redstone.yml b/.github/workflows/publish-docker-image-for-redstone.yml index c64c06b..872b3ac 100644 --- a/.github/workflows/publish-docker-image-for-redstone.yml +++ b/.github/workflows/publish-docker-image-for-redstone.yml @@ -10,7 +10,7 @@ jobs: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: ${{ vars.RELEASE_VERSION }} + RELEASE_VERSION: 6.9.2 DOCKER_CHAIN_NAME: redstone steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/publish-docker-image-for-rootstock.yml b/.github/workflows/publish-docker-image-for-rootstock.yml index 910c9ba..0d67dbc 100644 --- a/.github/workflows/publish-docker-image-for-rootstock.yml +++ b/.github/workflows/publish-docker-image-for-rootstock.yml @@ -10,7 +10,7 @@ jobs: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: ${{ vars.RELEASE_VERSION }} + RELEASE_VERSION: 6.9.2 DOCKER_CHAIN_NAME: rsk steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/publish-docker-image-for-scroll.yml b/.github/workflows/publish-docker-image-for-scroll.yml new file mode 100644 index 0000000..4393b15 --- /dev/null +++ b/.github/workflows/publish-docker-image-for-scroll.yml @@ -0,0 +1,162 @@ +name: Scroll Publish Docker image + +on: + workflow_dispatch: + push: + branches: + - production-scroll +jobs: + push_to_registry: + name: Push Docker image to Docker Hub + runs-on: ubuntu-latest + env: + RELEASE_VERSION: 6.9.2 + DOCKER_CHAIN_NAME: scroll + steps: + - uses: actions/checkout@v4 + - name: Setup repo + uses: ./.github/actions/setup-repo + id: setup + with: + docker-username: ${{ secrets.DOCKER_USERNAME }} + docker-password: ${{ secrets.DOCKER_PASSWORD }} + docker-remote-multi-platform: true + docker-arm-host: ${{ secrets.ARM_RUNNER_HOSTNAME }} + docker-arm-host-key: ${{ secrets.ARM_RUNNER_KEY }} + + - name: Build and push Docker image (indexer + API) + 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 }} + labels: ${{ steps.setup.outputs.docker-labels }} + 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=scroll + + - name: Build and push Docker image (indexer) + 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 }}-indexer + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + CACHE_EXCHANGE_RATES_PERIOD= + DISABLE_WEBAPP=true + DISABLE_API=true + 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=scroll + + - name: Build and push Docker image (API) + 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 }}-api + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + CACHE_EXCHANGE_RATES_PERIOD= + DISABLE_WEBAPP=true + DISABLE_INDEXER=true + 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=scroll + + - name: Build and push Docker image (indexer + API + shrink internal transactions) + 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 }}-shrink-internal-txs + labels: ${{ steps.setup.outputs.docker-labels }} + 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=scroll + SHRINK_INTERNAL_TRANSACTIONS_ENABLED=true + + - name: Build and push Docker image (indexer + shrink internal transactions) + 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 }}-shrink-internal-txs-indexer + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + CACHE_EXCHANGE_RATES_PERIOD= + DISABLE_WEBAPP=true + DISABLE_API=true + 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=scroll + SHRINK_INTERNAL_TRANSACTIONS_ENABLED=true + + - name: Build and push Docker image (API + shrink internal transactions) + 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 }}-shrink-internal-txs-api + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + CACHE_EXCHANGE_RATES_PERIOD= + DISABLE_WEBAPP=true + DISABLE_INDEXER=true + 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=scroll + SHRINK_INTERNAL_TRANSACTIONS_ENABLED=true \ No newline at end of file diff --git a/.github/workflows/publish-docker-image-for-shibarium.yml b/.github/workflows/publish-docker-image-for-shibarium.yml index 32d6cfb..acebe0b 100644 --- a/.github/workflows/publish-docker-image-for-shibarium.yml +++ b/.github/workflows/publish-docker-image-for-shibarium.yml @@ -13,7 +13,7 @@ jobs: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: ${{ vars.RELEASE_VERSION }} + RELEASE_VERSION: 6.9.2 DOCKER_CHAIN_NAME: shibarium steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/publish-docker-image-for-stability.yml b/.github/workflows/publish-docker-image-for-stability.yml index d4bfd64..6ce6061 100644 --- a/.github/workflows/publish-docker-image-for-stability.yml +++ b/.github/workflows/publish-docker-image-for-stability.yml @@ -13,7 +13,7 @@ jobs: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: ${{ vars.RELEASE_VERSION }} + RELEASE_VERSION: 6.9.2 DOCKER_CHAIN_NAME: stability steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/publish-docker-image-for-suave.yml b/.github/workflows/publish-docker-image-for-suave.yml index fccbaee..072025a 100644 --- a/.github/workflows/publish-docker-image-for-suave.yml +++ b/.github/workflows/publish-docker-image-for-suave.yml @@ -13,7 +13,7 @@ jobs: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: ${{ vars.RELEASE_VERSION }} + RELEASE_VERSION: 6.9.2 DOCKER_CHAIN_NAME: suave steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/publish-docker-image-for-zetachain.yml b/.github/workflows/publish-docker-image-for-zetachain.yml index d04c699..cd18d64 100644 --- a/.github/workflows/publish-docker-image-for-zetachain.yml +++ b/.github/workflows/publish-docker-image-for-zetachain.yml @@ -10,7 +10,7 @@ jobs: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: ${{ vars.RELEASE_VERSION }} + RELEASE_VERSION: 6.9.2 DOCKER_CHAIN_NAME: zetachain steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/publish-docker-image-for-zkevm.yml b/.github/workflows/publish-docker-image-for-zkevm.yml index 30270a1..2f5cbed 100644 --- a/.github/workflows/publish-docker-image-for-zkevm.yml +++ b/.github/workflows/publish-docker-image-for-zkevm.yml @@ -10,7 +10,7 @@ jobs: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: ${{ vars.RELEASE_VERSION }} + RELEASE_VERSION: 6.9.2 DOCKER_CHAIN_NAME: zkevm steps: - uses: actions/checkout@v4 @@ -24,7 +24,7 @@ jobs: docker-arm-host: ${{ secrets.ARM_RUNNER_HOSTNAME }} docker-arm-host-key: ${{ secrets.ARM_RUNNER_KEY }} - - name: Build and push Docker image + - name: Build and push Docker image (indexer + API) uses: docker/build-push-action@v5 with: context: . @@ -45,4 +45,118 @@ jobs: 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 + CHAIN_TYPE=polygon_zkevm + + - name: Build and push Docker image (indexer) + 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 }}-indexer + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + CACHE_EXCHANGE_RATES_PERIOD= + DISABLE_WEBAPP=true + DISABLE_API=true + 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 + + - name: Build and push Docker image (API) + 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 }}-api + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + CACHE_EXCHANGE_RATES_PERIOD= + DISABLE_WEBAPP=true + DISABLE_INDEXER=true + 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 + + - name: Build and push Docker image (indexer + API + shrink internal transactions) + 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 }}-shrink-internal-txs + labels: ${{ steps.setup.outputs.docker-labels }} + 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_zkevm + SHRINK_INTERNAL_TRANSACTIONS_ENABLED=true + + - name: Build and push Docker image (indexer + shrink internal transactions) + 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 }}-shrink-internal-txs-indexer + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + CACHE_EXCHANGE_RATES_PERIOD= + DISABLE_WEBAPP=true + DISABLE_API=true + 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 + SHRINK_INTERNAL_TRANSACTIONS_ENABLED=true + + - name: Build and push Docker image (API + shrink internal transactions) + 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 }}-shrink-internal-txs-api + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + CACHE_EXCHANGE_RATES_PERIOD= + DISABLE_WEBAPP=true + DISABLE_INDEXER=true + 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 + SHRINK_INTERNAL_TRANSACTIONS_ENABLED=true \ 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 index 5083551..d3e28bf 100644 --- a/.github/workflows/publish-docker-image-for-zksync.yml +++ b/.github/workflows/publish-docker-image-for-zksync.yml @@ -9,7 +9,7 @@ jobs: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: ${{ vars.RELEASE_VERSION }} + RELEASE_VERSION: 6.9.2 DOCKER_CHAIN_NAME: zksync steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/publish-docker-image-staging-on-demand.yml b/.github/workflows/publish-docker-image-staging-on-demand.yml index f06df33..7c0237c 100644 --- a/.github/workflows/publish-docker-image-staging-on-demand.yml +++ b/.github/workflows/publish-docker-image-staging-on-demand.yml @@ -12,7 +12,7 @@ on: env: OTP_VERSION: ${{ vars.OTP_VERSION }} ELIXIR_VERSION: ${{ vars.ELIXIR_VERSION }} - RELEASE_VERSION: ${{ vars.RELEASE_VERSION }} + RELEASE_VERSION: 6.9.2 jobs: push_to_registry: diff --git a/.github/workflows/publish-regular-docker-image-on-demand.yml b/.github/workflows/publish-regular-docker-image-on-demand.yml index 54b7699..4b88e31 100644 --- a/.github/workflows/publish-regular-docker-image-on-demand.yml +++ b/.github/workflows/publish-regular-docker-image-on-demand.yml @@ -5,7 +5,7 @@ on: env: OTP_VERSION: ${{ vars.OTP_VERSION }} ELIXIR_VERSION: ${{ vars.ELIXIR_VERSION }} - RELEASE_VERSION: ${{ vars.RELEASE_VERSION }} + RELEASE_VERSION: 6.9.2 jobs: push_to_registry: diff --git a/.github/workflows/release-arbitrum.yml b/.github/workflows/release-arbitrum.yml index 0d701ff..51b70e2 100644 --- a/.github/workflows/release-arbitrum.yml +++ b/.github/workflows/release-arbitrum.yml @@ -1,8 +1,7 @@ name: Release for Arbitrum on: - release: - types: [published] + workflow_dispatch: env: OTP_VERSION: ${{ vars.OTP_VERSION }} @@ -13,7 +12,7 @@ jobs: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: ${{ vars.RELEASE_VERSION }} + RELEASE_VERSION: 6.9.2 steps: - uses: actions/checkout@v4 - name: Setup repo diff --git a/.github/workflows/release-blackfort.yml b/.github/workflows/release-blackfort.yml new file mode 100644 index 0000000..33958a3 --- /dev/null +++ b/.github/workflows/release-blackfort.yml @@ -0,0 +1,93 @@ +name: Release for Blackfort + +on: + workflow_dispatch: + +env: + OTP_VERSION: ${{ vars.OTP_VERSION }} + ELIXIR_VERSION: ${{ vars.ELIXIR_VERSION }} + +jobs: + push_to_registry: + name: Push Docker image to Docker Hub + runs-on: ubuntu-latest + env: + RELEASE_VERSION: 6.9.2 + steps: + - uses: actions/checkout@v4 + - name: Setup repo + uses: ./.github/actions/setup-repo + id: setup + with: + docker-username: ${{ secrets.DOCKER_USERNAME }} + docker-password: ${{ secrets.DOCKER_PASSWORD }} + docker-remote-multi-platform: true + docker-arm-host: ${{ secrets.ARM_RUNNER_HOSTNAME }} + docker-arm-host-key: ${{ secrets.ARM_RUNNER_KEY }} + + - name: Build and push Docker image for Blackfort (indexer + API) + uses: docker/build-push-action@v5 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: blockscout/blockscout-blackfort:latest, blockscout/blockscout-blackfort:${{ env.RELEASE_VERSION }} + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + DISABLE_WEBAPP=false + API_V1_READ_METHODS_DISABLED=false + API_V1_WRITE_METHODS_DISABLED=false + CACHE_EXCHANGE_RATES_PERIOD= + CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED= + CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL= + ADMIN_PANEL_ENABLED=false + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-beta + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=blackfort + + - name: Build and push Docker image for Blackfort (indexer) + uses: docker/build-push-action@v5 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: blockscout/blockscout-blackfort:${{ env.RELEASE_VERSION }}-indexer + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + DISABLE_API=true + DISABLE_WEBAPP=true + CACHE_EXCHANGE_RATES_PERIOD= + CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED= + CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL= + ADMIN_PANEL_ENABLED=false + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-beta + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=blackfort + + - name: Build and push Docker image for Blackfort (API) + uses: docker/build-push-action@v5 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: blockscout/blockscout-blackfort:${{ env.RELEASE_VERSION }}-api + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + DISABLE_INDEXER=true + DISABLE_WEBAPP=true + CACHE_EXCHANGE_RATES_PERIOD= + CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED= + CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL= + ADMIN_PANEL_ENABLED=false + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-beta + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=blackfort \ No newline at end of file diff --git a/.github/workflows/release-celo.yml b/.github/workflows/release-celo.yml new file mode 100644 index 0000000..6731eb3 --- /dev/null +++ b/.github/workflows/release-celo.yml @@ -0,0 +1,170 @@ +name: Release for Celo + +on: + workflow_dispatch: + +env: + OTP_VERSION: ${{ vars.OTP_VERSION }} + ELIXIR_VERSION: ${{ vars.ELIXIR_VERSION }} + +jobs: + push_to_registry: + name: Push Docker image to Docker Hub + runs-on: ubuntu-latest + env: + RELEASE_VERSION: 6.9.2 + API_GRAPHQL_MAX_COMPLEXITY: 10400 + steps: + - uses: actions/checkout@v4 + - name: Setup repo + uses: ./.github/actions/setup-repo + id: setup + with: + docker-username: ${{ secrets.DOCKER_USERNAME }} + docker-password: ${{ secrets.DOCKER_PASSWORD }} + docker-remote-multi-platform: true + docker-arm-host: ${{ secrets.ARM_RUNNER_HOSTNAME }} + docker-arm-host-key: ${{ secrets.ARM_RUNNER_KEY }} + + - name: Build and push Docker image for CELO (indexer + API) + uses: docker/build-push-action@v5 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: blockscout/blockscout-celo:latest, blockscout/blockscout-celo:${{ env.RELEASE_VERSION }} + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + API_GRAPHQL_MAX_COMPLEXITY=${{ env.API_GRAPHQL_MAX_COMPLEXITY }} + DISABLE_WEBAPP=false + API_V1_READ_METHODS_DISABLED=false + API_V1_WRITE_METHODS_DISABLED=false + CACHE_EXCHANGE_RATES_PERIOD= + CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED= + CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL= + ADMIN_PANEL_ENABLED=false + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-beta + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=celo + + - name: Build and push Docker image for CELO (indexer) + uses: docker/build-push-action@v5 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: blockscout/blockscout-celo:${{ env.RELEASE_VERSION }}-indexer + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + API_GRAPHQL_MAX_COMPLEXITY=${{ env.API_GRAPHQL_MAX_COMPLEXITY }} + DISABLE_API=true + DISABLE_WEBAPP=true + CACHE_EXCHANGE_RATES_PERIOD= + CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED= + CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL= + ADMIN_PANEL_ENABLED=false + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-beta + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=celo + + - name: Build and push Docker image for CELO (API) + uses: docker/build-push-action@v5 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: blockscout/blockscout-celo:${{ env.RELEASE_VERSION }}-api + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + API_GRAPHQL_MAX_COMPLEXITY=${{ env.API_GRAPHQL_MAX_COMPLEXITY }} + DISABLE_INDEXER=true + DISABLE_WEBAPP=true + CACHE_EXCHANGE_RATES_PERIOD= + CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED= + CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL= + ADMIN_PANEL_ENABLED=false + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-beta + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=celo + + - name: Build and push Docker image for CELO (indexer + API + shrink internal transactions) + uses: docker/build-push-action@v5 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: blockscout/blockscout-celo:${{ env.RELEASE_VERSION }}-shrink-internal-txs + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + API_GRAPHQL_MAX_COMPLEXITY=${{ env.API_GRAPHQL_MAX_COMPLEXITY }} + 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=celo + SHRINK_INTERNAL_TRANSACTIONS_ENABLED=true + + - name: Build and push Docker image for CELO (indexer + shrink internal transactions) + uses: docker/build-push-action@v5 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: blockscout/blockscout-celo:${{ env.RELEASE_VERSION }}-shrink-internal-txs-indexer + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + API_GRAPHQL_MAX_COMPLEXITY=${{ env.API_GRAPHQL_MAX_COMPLEXITY }} + CACHE_EXCHANGE_RATES_PERIOD= + DISABLE_WEBAPP=true + DISABLE_API=true + 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=celo + SHRINK_INTERNAL_TRANSACTIONS_ENABLED=true + + - name: Build and push Docker image for CELO (API + shrink internal transactions) + uses: docker/build-push-action@v5 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: blockscout/blockscout-celo:${{ env.RELEASE_VERSION }}-shrink-internal-txs-api + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + API_GRAPHQL_MAX_COMPLEXITY=${{ env.API_GRAPHQL_MAX_COMPLEXITY }} + CACHE_EXCHANGE_RATES_PERIOD= + DISABLE_WEBAPP=true + DISABLE_INDEXER=true + 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=celo + SHRINK_INTERNAL_TRANSACTIONS_ENABLED=true \ No newline at end of file diff --git a/.github/workflows/release-eth.yml b/.github/workflows/release-eth.yml index 310e20f..0d67006 100644 --- a/.github/workflows/release-eth.yml +++ b/.github/workflows/release-eth.yml @@ -1,8 +1,7 @@ name: Release for Ethereum on: - release: - types: [published] + workflow_dispatch: env: OTP_VERSION: ${{ vars.OTP_VERSION }} @@ -13,7 +12,7 @@ jobs: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: ${{ vars.RELEASE_VERSION }} + RELEASE_VERSION: 6.9.2 steps: - uses: actions/checkout@v4 - name: Setup repo diff --git a/.github/workflows/release-filecoin.yml b/.github/workflows/release-filecoin.yml index 2992a3c..1c78610 100644 --- a/.github/workflows/release-filecoin.yml +++ b/.github/workflows/release-filecoin.yml @@ -1,8 +1,7 @@ name: Release for Filecoin on: - release: - types: [published] + workflow_dispatch: env: OTP_VERSION: ${{ vars.OTP_VERSION }} @@ -13,7 +12,7 @@ jobs: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: ${{ vars.RELEASE_VERSION }} + RELEASE_VERSION: 6.9.2 steps: - uses: actions/checkout@v4 - name: Setup repo @@ -91,4 +90,74 @@ jobs: ADMIN_PANEL_ENABLED=false BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-beta RELEASE_VERSION=${{ env.RELEASE_VERSION }} - CHAIN_TYPE=filecoin \ No newline at end of file + CHAIN_TYPE=filecoin + + - name: Build and push Docker image for Filecoin (indexer + API + shrink internal transactions) + uses: docker/build-push-action@v5 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: blockscout/blockscout-filecoin:${{ env.RELEASE_VERSION }}-shrink-internal-txs + labels: ${{ steps.setup.outputs.docker-labels }} + 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=filecoin + SHRINK_INTERNAL_TRANSACTIONS_ENABLED=true + + - name: Build and push Docker image for Filecoin (indexer + shrink internal transactions) + uses: docker/build-push-action@v5 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: blockscout/blockscout-filecoin:${{ env.RELEASE_VERSION }}-shrink-internal-txs-indexer + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + CACHE_EXCHANGE_RATES_PERIOD= + DISABLE_WEBAPP=true + DISABLE_API=true + 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=filecoin + SHRINK_INTERNAL_TRANSACTIONS_ENABLED=true + + - name: Build and push Docker image for Filecoin (API + shrink internal transactions) + uses: docker/build-push-action@v5 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: blockscout/blockscout-filecoin:${{ env.RELEASE_VERSION }}-shrink-internal-txs-api + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + CACHE_EXCHANGE_RATES_PERIOD= + DISABLE_WEBAPP=true + DISABLE_INDEXER=true + 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=filecoin + SHRINK_INTERNAL_TRANSACTIONS_ENABLED=true \ No newline at end of file diff --git a/.github/workflows/release-fuse.yml b/.github/workflows/release-fuse.yml index 1a9e410..d940cd0 100644 --- a/.github/workflows/release-fuse.yml +++ b/.github/workflows/release-fuse.yml @@ -2,8 +2,6 @@ name: Release for Fuse on: workflow_dispatch: - release: - types: [published] env: OTP_VERSION: ${{ vars.OTP_VERSION }} @@ -14,7 +12,7 @@ jobs: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: ${{ vars.RELEASE_VERSION }} + RELEASE_VERSION: 6.9.2 steps: - uses: actions/checkout@v4 - name: Setup repo diff --git a/.github/workflows/release-gnosis.yml b/.github/workflows/release-gnosis.yml index 3755089..47a4514 100644 --- a/.github/workflows/release-gnosis.yml +++ b/.github/workflows/release-gnosis.yml @@ -1,8 +1,7 @@ name: Release for Gnosis Chain on: - release: - types: [published] + workflow_dispatch: env: OTP_VERSION: ${{ vars.OTP_VERSION }} @@ -13,7 +12,7 @@ jobs: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: ${{ vars.RELEASE_VERSION }} + RELEASE_VERSION: 6.9.2 steps: - uses: actions/checkout@v4 - name: Setup repo diff --git a/.github/workflows/release-optimism.yml b/.github/workflows/release-optimism.yml index 9ddfd4d..77513c7 100644 --- a/.github/workflows/release-optimism.yml +++ b/.github/workflows/release-optimism.yml @@ -1,8 +1,7 @@ name: Release for Optimism on: - release: - types: [published] + workflow_dispatch: env: OTP_VERSION: ${{ vars.OTP_VERSION }} @@ -13,7 +12,7 @@ jobs: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: ${{ vars.RELEASE_VERSION }} + RELEASE_VERSION: 6.9.2 steps: - uses: actions/checkout@v4 - name: Setup repo @@ -93,29 +92,29 @@ jobs: RELEASE_VERSION=${{ env.RELEASE_VERSION }} CHAIN_TYPE=optimism - - name: Build and push Docker image for Optimism (indexer + API + shrink internal transactions) - uses: docker/build-push-action@v5 - with: - context: . - file: ./docker/Dockerfile - push: true - tags: blockscout/blockscout-optimism:${{ env.RELEASE_VERSION }}-shrink-internal-txs - labels: ${{ steps.setup.outputs.docker-labels }} - platforms: | - linux/amd64 - linux/arm64/v8 - build-args: | - DISABLE_WEBAPP=false - API_V1_READ_METHODS_DISABLED=false - API_V1_WRITE_METHODS_DISABLED=false - CACHE_EXCHANGE_RATES_PERIOD= - CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED= - CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL= - ADMIN_PANEL_ENABLED=false - BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-beta - RELEASE_VERSION=${{ env.RELEASE_VERSION }} - CHAIN_TYPE=optimism - SHRINK_INTERNAL_TRANSACTIONS_ENABLED=true + - name: Build and push Docker image for Optimism (indexer + API + shrink internal transactions) + uses: docker/build-push-action@v5 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: blockscout/blockscout-optimism:${{ env.RELEASE_VERSION }}-shrink-internal-txs + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + DISABLE_WEBAPP=false + API_V1_READ_METHODS_DISABLED=false + API_V1_WRITE_METHODS_DISABLED=false + CACHE_EXCHANGE_RATES_PERIOD= + CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED= + CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL= + ADMIN_PANEL_ENABLED=false + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-beta + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=optimism + SHRINK_INTERNAL_TRANSACTIONS_ENABLED=true - name: Build and push Docker image for Optimism (indexer + shrink internal transactions) uses: docker/build-push-action@v5 diff --git a/.github/workflows/release-polygon-edge.yml b/.github/workflows/release-polygon-edge.yml index b2a3ee4..8a4de58 100644 --- a/.github/workflows/release-polygon-edge.yml +++ b/.github/workflows/release-polygon-edge.yml @@ -2,8 +2,6 @@ name: Release for Polygon Edge on: workflow_dispatch: - release: - types: [published] env: OTP_VERSION: ${{ vars.OTP_VERSION }} @@ -14,7 +12,7 @@ jobs: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: ${{ vars.RELEASE_VERSION }} + RELEASE_VERSION: 6.9.2 steps: - uses: actions/checkout@v4 - name: Setup repo diff --git a/.github/workflows/release-polygon-zkevm.yml b/.github/workflows/release-polygon-zkevm.yml index 101068f..b901b39 100644 --- a/.github/workflows/release-polygon-zkevm.yml +++ b/.github/workflows/release-polygon-zkevm.yml @@ -2,8 +2,6 @@ name: Release for Polygon zkEVM on: workflow_dispatch: - release: - types: [published] env: OTP_VERSION: ${{ vars.OTP_VERSION }} @@ -14,7 +12,7 @@ jobs: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: ${{ vars.RELEASE_VERSION }} + RELEASE_VERSION: 6.9.2 steps: - uses: actions/checkout@v4 - name: Setup repo @@ -92,4 +90,74 @@ jobs: ADMIN_PANEL_ENABLED=false BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-beta RELEASE_VERSION=${{ env.RELEASE_VERSION }} - CHAIN_TYPE=polygon_zkevm \ No newline at end of file + CHAIN_TYPE=polygon_zkevm + + - name: Build and push Docker image for Polygon zkEVM (indexer + API + shrink internal transactions) + uses: docker/build-push-action@v5 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: blockscout/blockscout-zkevm:${{ env.RELEASE_VERSION }}-shrink-internal-txs + labels: ${{ steps.setup.outputs.docker-labels }} + 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 + SHRINK_INTERNAL_TRANSACTIONS_ENABLED=true + + - name: Build and push Docker image for Polygon zkEVM (indexer + shrink internal transactions) + uses: docker/build-push-action@v5 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: blockscout/blockscout-zkevm:${{ env.RELEASE_VERSION }}-shrink-internal-txs-indexer + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + CACHE_EXCHANGE_RATES_PERIOD= + DISABLE_WEBAPP=true + DISABLE_API=true + 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 + SHRINK_INTERNAL_TRANSACTIONS_ENABLED=true + + - name: Build and push Docker image for Polygon zkEVM (API + shrink internal transactions) + uses: docker/build-push-action@v5 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: blockscout/blockscout-zkevm:${{ env.RELEASE_VERSION }}-shrink-internal-txs-api + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + CACHE_EXCHANGE_RATES_PERIOD= + DISABLE_WEBAPP=true + DISABLE_INDEXER=true + 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 + SHRINK_INTERNAL_TRANSACTIONS_ENABLED=true \ No newline at end of file diff --git a/.github/workflows/release-redstone.yml b/.github/workflows/release-redstone.yml index d85d7c7..d578a2b 100644 --- a/.github/workflows/release-redstone.yml +++ b/.github/workflows/release-redstone.yml @@ -1,8 +1,7 @@ name: Release for Redstone on: - release: - types: [published] + workflow_dispatch: env: OTP_VERSION: ${{ vars.OTP_VERSION }} @@ -13,7 +12,7 @@ jobs: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: ${{ vars.RELEASE_VERSION }} + RELEASE_VERSION: 6.9.2 steps: - uses: actions/checkout@v4 - name: Setup repo diff --git a/.github/workflows/release-rootstock.yml b/.github/workflows/release-rootstock.yml index a7ea065..e1f2b25 100644 --- a/.github/workflows/release-rootstock.yml +++ b/.github/workflows/release-rootstock.yml @@ -1,8 +1,7 @@ name: Release for Rootstock on: - release: - types: [published] + workflow_dispatch: env: OTP_VERSION: ${{ vars.OTP_VERSION }} @@ -13,7 +12,7 @@ jobs: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: ${{ vars.RELEASE_VERSION }} + RELEASE_VERSION: 6.9.2 steps: - uses: actions/checkout@v4 - name: Setup repo diff --git a/.github/workflows/release-scroll.yml b/.github/workflows/release-scroll.yml new file mode 100644 index 0000000..4760a0f --- /dev/null +++ b/.github/workflows/release-scroll.yml @@ -0,0 +1,163 @@ +name: Release for Scroll + +on: + workflow_dispatch: + +env: + OTP_VERSION: ${{ vars.OTP_VERSION }} + ELIXIR_VERSION: ${{ vars.ELIXIR_VERSION }} + +jobs: + push_to_registry: + name: Push Docker image to Docker Hub + runs-on: ubuntu-latest + env: + RELEASE_VERSION: 6.9.2 + steps: + - uses: actions/checkout@v4 + - name: Setup repo + uses: ./.github/actions/setup-repo + id: setup + with: + docker-username: ${{ secrets.DOCKER_USERNAME }} + docker-password: ${{ secrets.DOCKER_PASSWORD }} + docker-remote-multi-platform: true + docker-arm-host: ${{ secrets.ARM_RUNNER_HOSTNAME }} + docker-arm-host-key: ${{ secrets.ARM_RUNNER_KEY }} + + - name: Build and push Docker image for Scroll (indexer + API) + uses: docker/build-push-action@v5 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: blockscout/blockscout-scroll:latest, blockscout/blockscout-scroll:${{ env.RELEASE_VERSION }} + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + DISABLE_WEBAPP=false + API_V1_READ_METHODS_DISABLED=false + API_V1_WRITE_METHODS_DISABLED=false + CACHE_EXCHANGE_RATES_PERIOD= + CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED= + CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL= + ADMIN_PANEL_ENABLED=false + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-beta + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=scroll + + - name: Build and push Docker image for Scroll (indexer) + uses: docker/build-push-action@v5 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: blockscout/blockscout-scroll:${{ env.RELEASE_VERSION }}-indexer + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + DISABLE_API=true + DISABLE_WEBAPP=true + CACHE_EXCHANGE_RATES_PERIOD= + CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED= + CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL= + ADMIN_PANEL_ENABLED=false + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-beta + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=scroll + + - name: Build and push Docker image for Scroll (API) + uses: docker/build-push-action@v5 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: blockscout/blockscout-scroll:${{ env.RELEASE_VERSION }}-api + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + DISABLE_INDEXER=true + DISABLE_WEBAPP=true + CACHE_EXCHANGE_RATES_PERIOD= + CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED= + CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL= + ADMIN_PANEL_ENABLED=false + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-beta + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=scroll + + - name: Build and push Docker image for Scroll (indexer + API + shrink internal transactions) + uses: docker/build-push-action@v5 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: blockscout/blockscout-scroll:${{ env.RELEASE_VERSION }}-shrink-internal-txs + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + DISABLE_WEBAPP=false + API_V1_READ_METHODS_DISABLED=false + API_V1_WRITE_METHODS_DISABLED=false + CACHE_EXCHANGE_RATES_PERIOD= + CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED= + CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL= + ADMIN_PANEL_ENABLED=false + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-beta + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=scroll + SHRINK_INTERNAL_TRANSACTIONS_ENABLED=true + + - name: Build and push Docker image for Scroll (indexer + shrink internal transactions) + uses: docker/build-push-action@v5 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: blockscout/blockscout-scroll:${{ env.RELEASE_VERSION }}-shrink-internal-txs-indexer + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + DISABLE_API=true + DISABLE_WEBAPP=true + CACHE_EXCHANGE_RATES_PERIOD= + CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED= + CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL= + ADMIN_PANEL_ENABLED=false + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-beta + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=scroll + SHRINK_INTERNAL_TRANSACTIONS_ENABLED=true + + - name: Build and push Docker image for Scroll (API + shrink internal transactions) + uses: docker/build-push-action@v5 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: blockscout/blockscout-scroll:${{ env.RELEASE_VERSION }}-shrink-internal-txs-api + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + DISABLE_INDEXER=true + DISABLE_WEBAPP=true + CACHE_EXCHANGE_RATES_PERIOD= + CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED= + CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL= + ADMIN_PANEL_ENABLED=false + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-beta + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=scroll + SHRINK_INTERNAL_TRANSACTIONS_ENABLED=true \ No newline at end of file diff --git a/.github/workflows/release-shibarium.yml b/.github/workflows/release-shibarium.yml index 2147eb4..b885797 100644 --- a/.github/workflows/release-shibarium.yml +++ b/.github/workflows/release-shibarium.yml @@ -1,8 +1,7 @@ name: Release for Shibarium on: - release: - types: [published] + workflow_dispatch: env: OTP_VERSION: ${{ vars.OTP_VERSION }} @@ -13,7 +12,7 @@ jobs: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: ${{ vars.RELEASE_VERSION }} + RELEASE_VERSION: 6.9.2 steps: - uses: actions/checkout@v4 - name: Setup repo @@ -91,4 +90,74 @@ jobs: ADMIN_PANEL_ENABLED=false BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-beta RELEASE_VERSION=${{ env.RELEASE_VERSION }} - CHAIN_TYPE=shibarium \ No newline at end of file + CHAIN_TYPE=shibarium + + - name: Build and push Docker image for Shibarium (indexer + API + shrink internal transactions) + uses: docker/build-push-action@v5 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: blockscout/blockscout-shibarium:${{ env.RELEASE_VERSION }}-shrink-internal-txs + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + DISABLE_WEBAPP=false + API_V1_READ_METHODS_DISABLED=false + API_V1_WRITE_METHODS_DISABLED=false + CACHE_EXCHANGE_RATES_PERIOD= + CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED= + CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL= + ADMIN_PANEL_ENABLED=false + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-beta + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=shibarium + SHRINK_INTERNAL_TRANSACTIONS_ENABLED=true + + - name: Build and push Docker image for Shibarium (indexer + shrink internal transactions) + uses: docker/build-push-action@v5 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: blockscout/blockscout-shibarium:${{ env.RELEASE_VERSION }}-shrink-internal-txs-indexer + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + DISABLE_API=true + DISABLE_WEBAPP=true + CACHE_EXCHANGE_RATES_PERIOD= + CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED= + CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL= + ADMIN_PANEL_ENABLED=false + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-beta + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=shibarium + SHRINK_INTERNAL_TRANSACTIONS_ENABLED=true + + - name: Build and push Docker image for Shibarium (API + shrink internal transactions) + uses: docker/build-push-action@v5 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: blockscout/blockscout-shibarium:${{ env.RELEASE_VERSION }}-shrink-internal-txs-api + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + DISABLE_INDEXER=true + DISABLE_WEBAPP=true + CACHE_EXCHANGE_RATES_PERIOD= + CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED= + CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL= + ADMIN_PANEL_ENABLED=false + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-beta + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=shibarium + SHRINK_INTERNAL_TRANSACTIONS_ENABLED=true \ No newline at end of file diff --git a/.github/workflows/release-stability.yml b/.github/workflows/release-stability.yml index 2d6b928..92a1bf5 100644 --- a/.github/workflows/release-stability.yml +++ b/.github/workflows/release-stability.yml @@ -2,8 +2,6 @@ name: Release for Stability on: workflow_dispatch: - release: - types: [published] env: OTP_VERSION: ${{ vars.OTP_VERSION }} @@ -14,7 +12,7 @@ jobs: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: ${{ vars.RELEASE_VERSION }} + RELEASE_VERSION: 6.9.2 steps: - uses: actions/checkout@v4 - name: Setup repo diff --git a/.github/workflows/release-suave.yml b/.github/workflows/release-suave.yml index 8ac9dad..7cf6d39 100644 --- a/.github/workflows/release-suave.yml +++ b/.github/workflows/release-suave.yml @@ -2,8 +2,6 @@ name: Release for SUAVE on: workflow_dispatch: - release: - types: [published] env: OTP_VERSION: ${{ vars.OTP_VERSION }} @@ -14,7 +12,7 @@ jobs: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: ${{ vars.RELEASE_VERSION }} + RELEASE_VERSION: 6.9.2 steps: - uses: actions/checkout@v4 - name: Setup repo diff --git a/.github/workflows/release-zetachain.yml b/.github/workflows/release-zetachain.yml index 6d497fd..5f21205 100644 --- a/.github/workflows/release-zetachain.yml +++ b/.github/workflows/release-zetachain.yml @@ -1,8 +1,7 @@ name: Release for Zetachain on: - release: - types: [published] + workflow_dispatch: env: OTP_VERSION: ${{ vars.OTP_VERSION }} @@ -13,7 +12,7 @@ jobs: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: ${{ vars.RELEASE_VERSION }} + RELEASE_VERSION: 6.9.2 steps: - uses: actions/checkout@v4 - name: Setup repo diff --git a/.github/workflows/release-zksync.yml b/.github/workflows/release-zksync.yml index e974e44..4d38c2e 100644 --- a/.github/workflows/release-zksync.yml +++ b/.github/workflows/release-zksync.yml @@ -2,8 +2,6 @@ name: Release for ZkSync on: workflow_dispatch: - release: - types: [published] env: OTP_VERSION: ${{ vars.OTP_VERSION }} @@ -14,7 +12,7 @@ jobs: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: ${{ vars.RELEASE_VERSION }} + RELEASE_VERSION: 6.9.2 steps: - uses: actions/checkout@v4 - name: Setup repo diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 271a479..4de500f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,8 +1,7 @@ name: Release on: - release: - types: [published] + workflow_dispatch: env: OTP_VERSION: ${{ vars.OTP_VERSION }} @@ -13,7 +12,7 @@ jobs: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: ${{ vars.RELEASE_VERSION }} + RELEASE_VERSION: 6.9.2 steps: - uses: actions/checkout@v4 - name: Setup repo @@ -113,35 +112,35 @@ jobs: BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-beta RELEASE_VERSION=${{ env.RELEASE_VERSION }} - - name: Build & Push Core Docker image (indexer + API + shrink internal transactions) - 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:${{ env.RELEASE_VERSION }}-shrink-internal-txs - labels: ${{ steps.setup.outputs.docker-labels }} - platforms: | - linux/amd64 - linux/arm64/v8 - build-args: | - DISABLE_WEBAPP=false - API_V1_READ_METHODS_DISABLED=false - API_V1_WRITE_METHODS_DISABLED=false - CACHE_EXCHANGE_RATES_PERIOD= - CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED= - CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL= - ADMIN_PANEL_ENABLED=false - DECODE_NOT_A_CONTRACT_CALLS=false - MIXPANEL_URL= - MIXPANEL_TOKEN= - AMPLITUDE_URL= - AMPLITUDE_API_KEY= - BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-beta - RELEASE_VERSION=${{ env.RELEASE_VERSION }} - SHRINK_INTERNAL_TRANSACTIONS_ENABLED=true + - name: Build & Push Core Docker image (indexer + API + shrink internal transactions) + 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:${{ env.RELEASE_VERSION }}-shrink-internal-txs + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + DISABLE_WEBAPP=false + API_V1_READ_METHODS_DISABLED=false + API_V1_WRITE_METHODS_DISABLED=false + CACHE_EXCHANGE_RATES_PERIOD= + CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED= + CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL= + ADMIN_PANEL_ENABLED=false + DECODE_NOT_A_CONTRACT_CALLS=false + MIXPANEL_URL= + MIXPANEL_TOKEN= + AMPLITUDE_URL= + AMPLITUDE_API_KEY= + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-beta + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + SHRINK_INTERNAL_TRANSACTIONS_ENABLED=true - name: Build & Push Core Docker image (indexer + shrink internal transactions) uses: docker/build-push-action@v5 @@ -203,6 +202,35 @@ jobs: RELEASE_VERSION=${{ env.RELEASE_VERSION }} SHRINK_INTERNAL_TRANSACTIONS_ENABLED=true + - name: Build & Push Docker image with an old UI (indexer + API) + uses: docker/build-push-action@v5 + with: + context: . + file: ./docker/oldUI.Dockerfile + push: true + cache-from: type=registry,ref=blockscout/blockscout:buildcache + cache-to: type=registry,ref=blockscout/blockscout:buildcache,mode=max + tags: blockscout/blockscout:${{ env.RELEASE_VERSION }}-with-old-ui + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + DISABLE_WEBAPP=false + API_V1_READ_METHODS_DISABLED=false + API_V1_WRITE_METHODS_DISABLED=false + CACHE_EXCHANGE_RATES_PERIOD= + CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED= + CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL= + ADMIN_PANEL_ENABLED=false + DECODE_NOT_A_CONTRACT_CALLS=false + MIXPANEL_URL= + MIXPANEL_TOKEN= + AMPLITUDE_URL= + AMPLITUDE_API_KEY= + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-beta + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + # - name: Send release announcement to Slack workflow # id: slack # uses: slackapi/slack-github-action@v1.24.0 diff --git a/.gitignore b/.gitignore index bfd06c0..db2acc8 100644 --- a/.gitignore +++ b/.gitignore @@ -65,3 +65,4 @@ dump.rdb *.env.example *.env.local *.env.staging +.devcontainer/.blockscout_config \ No newline at end of file diff --git a/.tool-versions b/.tool-versions index 15355e5..270ab12 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1,3 +1,3 @@ -elixir 1.16.3-otp-26 -erlang 26.2.5.1 +elixir 1.17.3-otp-27 +erlang 27.1 nodejs 18.17.1 diff --git a/CHANGELOG.md b/CHANGELOG.md index ae73675..b3a7078 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,197 @@ # Changelog +## 6.9.2 + +### 🚀 Features + +- Xname app proxy ([#11010](https://github.com/blockscout/blockscout/issues/11010)) + +## 6.9.1 + +### 🐛 Bug Fixes + +- add auth0-forwarded-for header in auth0 ([#11182](https://github.com/blockscout/blockscout/issues/11182)) + +### ⚙️ Miscellaneous Tasks + +- extend recaptcha logging ([#11178](https://github.com/blockscout/blockscout/issues/11178)) + +## 6.9.0 + +### 🚀 Features + +- Support zksync foundry verification ([#11037](https://github.com/blockscout/blockscout/issues/11037)) +- Address transactions block number sorting ([#11035](https://github.com/blockscout/blockscout/issues/11035)) +- Scroll rollup: L1 fee parameters in API, `queueIndex` for L2 transactions, and L1 <->L2 messages ([#10484](https://github.com/blockscout/blockscout/issues/10484)) +- Account V2 ([#10706](https://github.com/blockscout/blockscout/issues/10706)) +- Allow to provide DB schema other than public ([#10946](https://github.com/blockscout/blockscout/issues/10946)) +- Add missing filecoin robust addresses ([#10935](https://github.com/blockscout/blockscout/issues/10935)) +- EIP-7702 support ([#10870](https://github.com/blockscout/blockscout/issues/10870)) +- Open access to re-fetch metadata button for token instances without metadata initially fetched ([#10878](https://github.com/blockscout/blockscout/issues/10878)) +- Support snake_case in metadata service ([#10722](https://github.com/blockscout/blockscout/issues/10722)) +- Token transfers list API v2 endpoint ([#10801](https://github.com/blockscout/blockscout/issues/10801)) +- Send archive balances requests to trace url ([#10820](https://github.com/blockscout/blockscout/issues/10820)) +- Add metadata info to tx interpreter request ([#10823](https://github.com/blockscout/blockscout/issues/10823)) +- Api for querying mud systems abi ([#10829](https://github.com/blockscout/blockscout/issues/10829)) +- Arbitrum L1-to-L2 messages with hashed message id ([#10751](https://github.com/blockscout/blockscout/issues/10751)) +- Support CoinMarketCap format in token supply stats ([#10853](https://github.com/blockscout/blockscout/issues/10853)) +- Address scam badge flag ([#10763](https://github.com/blockscout/blockscout/issues/10763)) +- Add verbosity to GraphQL token transfers query ([#10770](https://github.com/blockscout/blockscout/issues/10770)) +- (celo) include token information in API response for address epoch rewards ([#10831](https://github.com/blockscout/blockscout/issues/10831)) +- Add Blackfort validators ([#10744](https://github.com/blockscout/blockscout/issues/10744)) +- Retry ERC-1155 token instance metadata fetch from baseURI + tokenID ([#10766](https://github.com/blockscout/blockscout/issues/10766)) + +### 🐛 Bug Fixes + +- Fix tokennfttx API v1 endpoint ([#11083](https://github.com/blockscout/blockscout/issues/11083)) +- Fix contract codes fetching for zksync chain type ([#11055](https://github.com/blockscout/blockscout/issues/11055)) +- Filter non-traceable blocks before inserting them to internal txs fetcher queue ([#11074](https://github.com/blockscout/blockscout/issues/11074)) +- Import blocks before coin balances ([#11049](https://github.com/blockscout/blockscout/issues/11049)) +- Abi cache for non-proxied addresses ([#11065](https://github.com/blockscout/blockscout/issues/11065)) +- Celo collated gas price issue ([#11067](https://github.com/blockscout/blockscout/issues/11067)) +- Indexer memory limit for api instance ([#11066](https://github.com/blockscout/blockscout/issues/11066)) +- Fix scam badge value in some API endpoints ([#11054](https://github.com/blockscout/blockscout/issues/11054)) +- Divide by `10^decimals` when calculating token supply in CMC format ([#11036](https://github.com/blockscout/blockscout/issues/11036)) +- Rename zksync l1/l2 _tx_count columns ([#11051](https://github.com/blockscout/blockscout/issues/11051)) +- Bugs introduced in calldata decoding optimizations ([#11025](https://github.com/blockscout/blockscout/issues/11025)) +- Handle stalled async task in MapCache ([#11015](https://github.com/blockscout/blockscout/issues/11015)) +- Add tx_count, tx_types props in the response of address API v2 endpoints for compatibility with current version of the frontend ([#11012](https://github.com/blockscout/blockscout/issues/11012)) +- Chart API: add compatibility with the current frontend ([#11008](https://github.com/blockscout/blockscout/issues/11008)) +- Fix failed tests ([#11000](https://github.com/blockscout/blockscout/issues/11000)) +- Add compatibility with current frontend for some public props ([#10998](https://github.com/blockscout/blockscout/issues/10998)) +- Process foreign key violation in scam addresses assigning functionality ([#10977](https://github.com/blockscout/blockscout/issues/10977)) +- Handle import exceptions in MassiveBlocksFetcher ([#10993](https://github.com/blockscout/blockscout/issues/10993)) +- Workaround for repeating logIndex ([#10880](https://github.com/blockscout/blockscout/issues/10880)) +- Filter out nil implementations from combine_proxy_implementation_addresses_map function result ([#10943](https://github.com/blockscout/blockscout/issues/10943)) +- Delete incorrect coin balances on reorg ([#10879](https://github.com/blockscout/blockscout/issues/10879)) +- Handle delegatecall in state changes ([#10906](https://github.com/blockscout/blockscout/issues/10906)) +- Fix env. variables link in README.md ([#10898](https://github.com/blockscout/blockscout/issues/10898)) +- Add missing block timestamp in election rewards for address response ([#10907](https://github.com/blockscout/blockscout/issues/10907)) +- Add missing build arg to celo workflow ([#10895](https://github.com/blockscout/blockscout/issues/10895)) +- Do not include unrelated token transfers in `tokenTransferTxs` ([#10889](https://github.com/blockscout/blockscout/issues/10889)) +- Fix get current user in app template ([#10844](https://github.com/blockscout/blockscout/issues/10844)) +- Set `API_GRAPHQL_MAX_COMPLEXITY` in build action ([#10843](https://github.com/blockscout/blockscout/issues/10843)) +- Disable archive balances only if latest block is available ([#10851](https://github.com/blockscout/blockscout/issues/10851)) +- Dialyzer warning ([#10845](https://github.com/blockscout/blockscout/issues/10845)) +- Decode revert reason by decoding candidates from the DB ([#10827](https://github.com/blockscout/blockscout/issues/10827)) +- Filecoin stucked pending address operations ([#10832](https://github.com/blockscout/blockscout/issues/10832)) +- Sanitize replaced transactions migration ([#10784](https://github.com/blockscout/blockscout/issues/10784)) +- Repair /metrics endpoint ([#10813](https://github.com/blockscout/blockscout/issues/10813)) +- Revert the deletion of deriving current token balances ([#10811](https://github.com/blockscout/blockscout/issues/10811)) +- Clear null round blocks from missing block ranges ([#10805](https://github.com/blockscout/blockscout/issues/10805)) +- Decode addresses as checksummed ([#10777](https://github.com/blockscout/blockscout/issues/10777)) +- Preload additional sources for bytecode twin smart-contract ([#10692](https://github.com/blockscout/blockscout/issues/10692)) +- Set min query length in the search API endpoints ([#10698](https://github.com/blockscout/blockscout/issues/10698)) +- Proper handling of old batches on Arbitrum Nova ([#10786](https://github.com/blockscout/blockscout/issues/10786)) +- Get rid of heavy DB query to start Arbitrum missed messages discovery process ([#10767](https://github.com/blockscout/blockscout/issues/10767)) +- Revisited approach to choose L1 blocks to discover missing Arbitrum batches ([#10757](https://github.com/blockscout/blockscout/issues/10757)) +- Fix account db repo definition ([#10714](https://github.com/blockscout/blockscout/issues/10714)) +- Allow string IDs in JSON RPC requests ([#10759](https://github.com/blockscout/blockscout/issues/10759)) +- Filter out tokens with skip_metadata: true from token fetcher ([#10736](https://github.com/blockscout/blockscout/issues/10736)) + +### 🚜 Refactor + +- Fixate naming convention for "transaction" and "block number" entities ([#10913](https://github.com/blockscout/blockscout/issues/10913)) +- Use middleware to check if GraphQL API is enabled ([#10772](https://github.com/blockscout/blockscout/issues/10772)) + +### ⚡ Performance + +- Fix performance of Explorer.Counters.Transactions24hStats.consolidate/0 function ([#11082](https://github.com/blockscout/blockscout/issues/11082)) +- Optimize advanced filters ([#10463](https://github.com/blockscout/blockscout/issues/10463)) +- Refactor tx data decoding with fewer DB queries ([#10842](https://github.com/blockscout/blockscout/issues/10842)) + +### ⚙️ Miscellaneous Tasks + +- Update version bump script +- Remove deprecated single implementation property of the smart-contract from the API response ([#10715](https://github.com/blockscout/blockscout/issues/10715)) +- Set indexer memory limit based on system info as a fallback ([#10697](https://github.com/blockscout/blockscout/issues/10697)) +- Set user agent to metadata requests ([#10834](https://github.com/blockscout/blockscout/issues/10834)) +- Reverse internal transactions fetching order ([#10912](https://github.com/blockscout/blockscout/issues/10912)) +- Remove unused fetch_and_lock_by_hashes/1 public function +- Add shrink int txs docker image build for Celo chain type ([#10894](https://github.com/blockscout/blockscout/issues/10894)) +- Ability to work with Blockscout code base within a VSCode devcontainer ([#10838](https://github.com/blockscout/blockscout/issues/10838)) +- Add version bump script ([#10871](https://github.com/blockscout/blockscout/issues/10871)) +- Bump elixir to 1.17.3 and Erlang OTP to 27.1 ([#10284](https://github.com/blockscout/blockscout/issues/10284)) +- Reindex incorrect internal transactions migration ([#10654](https://github.com/blockscout/blockscout/issues/10654)) +- Remove old UI from base Docker image ([#10828](https://github.com/blockscout/blockscout/issues/10828)) +- Add primary key to address_tags table ([#10818](https://github.com/blockscout/blockscout/issues/10818)) +- Refactor OrderedCache preloads ([#10803](https://github.com/blockscout/blockscout/issues/10803)) +- Support non-unique log index for rsk chain type ([#10807](https://github.com/blockscout/blockscout/issues/10807)) +- Add missing symbols ([#10749](https://github.com/blockscout/blockscout/issues/10749)) + +### New ENV Variables + +| Variable | Description | Parameters | +| ------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------- | +| `INDEXER_SYSTEM_MEMORY_PERCENTAGE` | Percentage of total memory available to the VM that an application can use if `INDEXER_MEMORY_LIMIT` is not set. Implemented in [#10697](https://github.com/blockscout/blockscout/pull/10697). |

Version: v6.9.0+
Default: 60
Applications: Indexer

| +| `INDEXER_TOKEN_BALANCES_EXPONENTIAL_TIMEOUT_COEFF` | Coefficient to calculate exponential timeout. Implemented in [#10694](https://github.com/blockscout/blockscout/pull/10694). |

Version: v6.9.0+
Default: 100
Applications: Indexer

| +| `INDEXER_INTERNAL_TRANSACTIONS_FETCH_ORDER` | Order of fetching internal transactions from node. Possible values: `asc`, `desc`. Implemented in [#10912](https://github.com/blockscout/blockscout/pull/10912) |

Version: v6.9.0+
Default: asc
Applications: Indexer

| +| `HIDE_SCAM_ADDRESSES` | Hides address of EOA/smart-contract/token from search results if the value is `true` and "scam" badge is assigned to that address. Implemented in [#10763](https://github.com/blockscout/blockscout/pull/10763) |

Version: v6.9.0+
Default: (empty)
Applications: API

| +| `RE_CAPTCHA_CHECK_HOSTNAME` | Disable reCAPTCHA hostname check. More details on [reCaptcha docs](https://developers.google.com/recaptcha/docs/domain\_validation). Implemented in [#10706](https://github.com/blockscout/blockscout/pull/10706) |

Version: v6.9.0+
Default: false
Applications: API

| +| `ACCOUNT_OTP_RESEND_INTERVAL` | Time before resending otp email. Implemented in [#10706](https://github.com/blockscout/blockscout/pull/10706). |

Version: v6.9.0+
Default: 1m
Applications: API

| +| `INDEXER_SCROLL_L1_RPC` | The RPC endpoint for L1 used to fetch Deposit and Withdrawal messages. Implemented in [#10484](https://github.com/blockscout/blockscout/pull/10484). |

Version: v6.9.0+
Default: (empty)
Applications: Indexer

| +| `INDEXER_SCROLL_L1_CHAIN_CONTRACT` | The address of ScrollChain contract on L1. Used to fetch batch and bundle events. Implemented in [#10819](https://github.com/blockscout/blockscout/pull/10819). |

Version: v6.9.0+
Default: (empty)
Applications: Indexer

| +| `INDEXER_SCROLL_L1_BATCH_START_BLOCK` | The number of a start block on L1 to index L1 batches and bundles. If the table of batches is not empty, the process will continue indexing from the last indexed batch. Implemented in [#10819](https://github.com/blockscout/blockscout/pull/10819). |

Version: v6.9.0+
Default: (empty)
Applications: Indexer

| +| `INDEXER_SCROLL_L1_MESSENGER_CONTRACT` | The address of L1 Scroll Messenger contract on L1 used to fetch Deposit and Withdrawal messages. Implemented in [#10484](https://github.com/blockscout/blockscout/pull/10484). |

Version: v6.9.0+
Default: (empty)
Applications: Indexer

| +| `INDEXER_SCROLL_L1_MESSENGER_START_BLOCK` | The number of a start block on L1 to index L1 bridge messages. If the table of bridge operations is not empty, the process will continue indexing from the last indexed L1 message. Implemented in [#10484](https://github.com/blockscout/blockscout/pull/10484). |

Version: v6.9.0+
Default: (empty)
Applications: Indexer

| +| `INDEXER_SCROLL_L2_MESSENGER_CONTRACT` | The address of L2 Scroll Messenger contract on L2 used to fetch Deposit and Withdrawal messages. Implemented in [#10484](https://github.com/blockscout/blockscout/pull/10484). |

Version: v6.9.0+
Default: (empty)
Applications: Indexer

| +| `INDEXER_SCROLL_L2_MESSENGER_START_BLOCK` | The number of a start block on L2 to index L2 bridge messages. If the table of bridge operations is not empty, the process will continue indexing from the last indexed L2 message. Implemented in [#10484](https://github.com/blockscout/blockscout/pull/10484). |

Version: v6.9.0+
Default: `FIRST_BLOCK`
Applications: Indexer

| +| `INDEXER_SCROLL_L2_GAS_ORACLE_CONTRACT` | The address of L1 Gas Oracle contract on L2. Implemented in [#10484](https://github.com/blockscout/blockscout/pull/10484). |

Version: v6.9.0+
Default: (empty)
Applications: Indexer

| +| `INDEXER_SCROLL_L1_ETH_GET_LOGS_RANGE_SIZE` | Block range size for eth\_getLogs request in Scroll indexer modules for Layer 1. Implemented in [#10484](https://github.com/blockscout/blockscout/pull/10484). |

Version: v6.9.0+
Default: `250`
Applications: Indexer

| +| `INDEXER_SCROLL_L2_ETH_GET_LOGS_RANGE_SIZE` | Block range size for eth\_getLogs request in Scroll indexer modules for Layer 2. Implemented in [#10484](https://github.com/blockscout/blockscout/pull/10484). |

Version: v6.9.0+
Default: `1000`
Applications: Indexer

| +| `SCROLL_L2_CURIE_UPGRADE_BLOCK` | L2 block number of the Curie upgrade. Implemented in [#10484](https://github.com/blockscout/blockscout/pull/10484). |

Version: v6.9.0+
Default: `0`
Applications: API

| +| `SCROLL_L1_SCALAR_INIT` | Initial value for `scalar` parameter. Implemented in [#10484](https://github.com/blockscout/blockscout/pull/10484). |

Version: v6.9.0+
Default: `0`
Applications: API

| +| `SCROLL_L1_OVERHEAD_INIT` | Initial value for `overhead` parameter. Implemented in [#10484](https://github.com/blockscout/blockscout/pull/10484). |

Version: v6.9.0+
Default: `0`
Applications: API

| +| `SCROLL_L1_COMMIT_SCALAR_INIT` | Initial value for `commit_scalar` parameter. Implemented in [#10484](https://github.com/blockscout/blockscout/pull/10484). |

Version: v6.9.0+
Default: `0`
Applications: API

| +| `SCROLL_L1_BLOB_SCALAR_INIT` | Initial value for `blob_scalar` parameter. Implemented in [#10484](https://github.com/blockscout/blockscout/pull/10484). |

Version: v6.9.0+
Default: `0`
Applications: API

| +| `SCROLL_L1_BASE_FEE_INIT` | Initial value for `l1_base_fee` parameter. Implemented in [#10484](https://github.com/blockscout/blockscout/pull/10484). |

Version: v6.9.0+
Default: `0`
Applications: API

| +| `SCROLL_L1_BLOB_BASE_FEE_INIT` | Initial value for `l1_blob_base_fee` parameter. Implemented in [#10484](https://github.com/blockscout/blockscout/pull/10484). |

Version: v6.9.0+
Default: `0`
Applications: API

| +| `INDEXER_OPTIMISM_L1_DEPOSITS_TRANSACTION_TYPE` | Defines OP Deposit transaction type (numeric value) which is needed for correct L2 transaction hash calculation by the Deposits indexing module. Implemented in [#10674](https://github.com/blockscout/blockscout/pull/10674). |

Version: v6.9.0+
Default: 126
Applications: Indexer

| +| `INDEXER_DISABLE_CELO_VALIDATOR_GROUP_VOTES_FETCHER` | If set to `true`, the validator group votes fetcher will not be started. Implemented in [#10673](https://github.com/blockscout/blockscout/pull/10673). |

Version: v6.9.0+
Default: false
Applications: Indexer

| +| `FILECOIN_NETWORK_PREFIX` | Specifies the expected network prefix for Filecoin addresses. For more details, refer to the [Filecoin Spec](https://spec.filecoin.io/appendix/address/#section-appendix.address.network-prefix). Available values: `f` (for the mainnet), `t` (for testnets). Implemented in [#10468](https://github.com/blockscout/blockscout/pull/10468). |

Version: v6.9.0+
Default: f
Applications: API, Indexer

| +| `BERYX_API_TOKEN` | [Beryx API](https://docs.zondax.ch/beryx-api) token, used for retrieving Filecoin native addressing information. Implemented in [#10468](https://github.com/blockscout/blockscout/pull/10468). |

Required: ✅
Version: v6.9.0+
Default: (empty)
Applications: Indexer

| +| `BERYX_API_BASE_URL` | [Beryx API](https://docs.zondax.ch/beryx-api) base URL. Implemented in [#10468](https://github.com/blockscout/blockscout/pull/10468). |

Version: v6.9.0+
Default: https://api.zondax.ch/fil/data/v3/mainnet
Applications: Indexer

| +| `INDEXER_DISABLE_FILECOIN_ADDRESS_INFO_FETCHER` | When set to `true`, Filecoin native addressing information will not be fetched, but addresses pending fetch will still be recorded in the database. Implemented in [#10468](https://github.com/blockscout/blockscout/pull/10468). |

Version: v6.9.0+
Default: false
Applications: Indexer

| +| `INDEXER_FILECOIN_ADDRESS_INFO_CONCURRENCY` | Sets the maximum number of concurrent requests made to fetch Filecoin native addressing information. Implemented in [#10468](https://github.com/blockscout/blockscout/pull/10468). |

Version: v6.9.0+
Default: 1
Applications: Indexer

| +| `FILECOIN_PENDING_ADDRESS_OPERATIONS_MIGRATION_BATCH_SIZE` | Specifies the number of address records processed per batch during the backfill of pending address fetch operations. Implemented in [#10468](https://github.com/blockscout/blockscout/pull/10468). |

Version: v6.9.0+
Default: 100
Applications: Indexer

| +| `FILECOIN_PENDING_ADDRESS_OPERATIONS_MIGRATION_CONCURRENCY` | Specifies the number of concurrent processes used during the backfill of pending address fetch operations. Implemented in [#10468](https://github.com/blockscout/blockscout/pull/10468). |

Version: v6.9.0+
Default: 1
Applications: Indexer

| +| `BLACKFORT_VALIDATOR_API_URL` | Variable to define the URL of the Blackfort Validator API. Implemented in [#10744](https://github.com/blockscout/blockscout/pull/10744). |

Version: v6.9.0+
Default: (empty)
Applications: API, Indexer

| + +## 6.8.1 + +### 🚀 Features + +- Add `INDEXER_OPTIMISM_L1_DEPOSITS_TRANSACTION_TYPE` env variable ([#10674](https://github.com/blockscout/blockscout/issues/10674)) +- Support for filecoin native addresses ([#10468](https://github.com/blockscout/blockscout/issues/10468)) + +### 🐛 Bug Fixes + +- Decoding of zero fields in mud ([#10764](https://github.com/blockscout/blockscout/issues/10764)) +- Insert coin balances placeholders in internal transactions fetcher ([#10603](https://github.com/blockscout/blockscout/issues/10603)) +- Avoid key violation error in `Indexer.Fetcher.Optimism.TxnBatch` ([#10752](https://github.com/blockscout/blockscout/issues/10752)) +- Fix empty current token balances ([#10745](https://github.com/blockscout/blockscout/issues/10745)) +- Allow disabling group votes fetcher independently of epoch block fetcher ([#10673](https://github.com/blockscout/blockscout/issues/10673)) +- Fix gettext usage warning ([#10693](https://github.com/blockscout/blockscout/issues/10693)) +- Truncate token symbol in Explorer.Chain.PolygonZkevm.BridgeL1Token ([#10688](https://github.com/blockscout/blockscout/issues/10688)) + +### ⚡ Performance + +- Improve performance of transactions list page ([#10734](https://github.com/blockscout/blockscout/issues/10734)) + +### ⚙️ Miscellaneous Tasks + +- Add meta to migrations_status ([#10678](https://github.com/blockscout/blockscout/issues/10678)) +- Token balances fetcher slow queue ([#10694](https://github.com/blockscout/blockscout/issues/10694)) +- Shrink sample response for the trace in Filecoin chain type +- Extend missing balanceOf function with :unable_to_decode error ([#10713](https://github.com/blockscout/blockscout/issues/10713)) +- Fix flaking explorer tests ([#10676](https://github.com/blockscout/blockscout/issues/10676)) +- Change shrink internal transactions migration default batch_size ([#10689](https://github.com/blockscout/blockscout/issues/10689)) + ## 6.8.0 ### 🚀 Features +- Detect Diamond proxy pattern on unverified proxy smart-contract ([#10665](https://github.com/blockscout/blockscout/pull/10665)) - Support smart-contract verification in zkSync ([#10500](https://github.com/blockscout/blockscout/issues/10500)) - Add icon for secondary coin ([#10241](https://github.com/blockscout/blockscout/issues/10241)) - Integrate Cryptorank API ([#10550](https://github.com/blockscout/blockscout/issues/10550)) @@ -31,6 +219,16 @@ ### 🐛 Bug Fixes +- Logs list serialization ([#10565](https://github.com/blockscout/blockscout/issues/10565)) +- nil in OrderedCache ([#10647](https://github.com/blockscout/blockscout/pull/10647)) +- Fix for metadata detection at ipfs protocol([#10646](https://github.com/blockscout/blockscout/pull/10646)) +- Fix bug in update_replaced_transactions query ([#10634](https://github.com/blockscout/blockscout/issues/10634)) +- Fix mode dependent processes starting ([#10641](https://github.com/blockscout/blockscout/issues/10641)) +- Better detection IPFS links in NFT metadata fetcher ([#10638](https://github.com/blockscout/blockscout/issues/10638)) +- Change mode env name ([#10636](https://github.com/blockscout/blockscout/issues/10636)) +- Proper default value of gas used for dropped Arbitrum transactions ([#10619](https://github.com/blockscout/blockscout/issues/10619)) +- Fix fetch_first_trace tests ([#10618](https://github.com/blockscout/blockscout/issues/10618)) +- Add SHRINK_INTERNAL_TRANSACTIONS_ENABLED arg to Dockerfile ([#10616](https://github.com/blockscout/blockscout/issues/10616)) - Fix raw-trace test ([#10606](https://github.com/blockscout/blockscout/issues/10606)) - Fix internal transaction validation ([#10443](https://github.com/blockscout/blockscout/issues/10443)) - Fix internal transactions runner test for zetachain ([#10576](https://github.com/blockscout/blockscout/issues/10576)) @@ -86,6 +284,8 @@ ### ⚙️ Miscellaneous Tasks +- Make Dockerfile use specified user with uid/gid ([#10070](https://github.com/blockscout/blockscout/pull/10070)) +- Run shrink internal transactions migration for indexer instance only ([#10631](https://github.com/blockscout/blockscout/issues/10631)) - Shrink internal transactions ([#10567](https://github.com/blockscout/blockscout/issues/10567)) - Upgrade WS client ([#10407](https://github.com/blockscout/blockscout/issues/10407)) - Add API endpoint for OP batch blocks ([#10566](https://github.com/blockscout/blockscout/issues/10566)) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 694f0e4..3211238 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -59,3 +59,27 @@ There is a [PULL_REQUEST_TEMPLATE.md](PULL_REQUEST_TEMPLATE.md) for this reposit * What was changed for incompatible changes See [#255](https://github.com/blockscout/blockscout/pull/255) as an example PR that uses GitHub keywords and a Changelog to explain multiple changes. + +## Basic Naming Convention + +When contributing to the codebase, please adhere to the following naming conventions to ensure clarity and consistency: + +- Use full names for entities. Avoid abbreviations or shorthand. + - Instead of "tx" or "txn", use "transaction". + - Instead of "txs", use "transactions". + - Instead of "tx_hash" or "txn_hash", use "transaction_hash". + - Instead of "block_num", use "block_number". +- Ensure that variable names are descriptive and convey the purpose or content clearly. +- Consistent naming helps in maintaining readability and understanding of the code, especially for new contributors. + +By following these conventions, we can maintain a clean and understandable codebase. + + +### API V2 Naming Convention + +When contributing to the API v2, please adhere to the following naming conventions for response fields to ensure clarity and consistency: + +- The block number should be returned as a number in the `block_number` property. +- The transaction hash should be returned as a hex string in the `transaction_hash` property. +- All fields that contain the "index" suffix should be returned as numbers. + diff --git a/README.md b/README.md index 3216456..8e2059a 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ See the [project documentation](https://docs.blockscout.com/) for instructions: - [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) -- [ENV variables](https://docs.blockscout.com/for-developers/information-and-settings/env-variables) +- [ENV variables](https://docs.blockscout.com/setup/env-variables) - [Configuration options](https://docs.blockscout.com/for-developers/configuration-options) ## Acknowledgements diff --git a/apps/block_scout_web/.sobelow-conf b/apps/block_scout_web/.sobelow-conf index 70a8e7b..45fc345 100644 --- a/apps/block_scout_web/.sobelow-conf +++ b/apps/block_scout_web/.sobelow-conf @@ -9,6 +9,7 @@ ignore_files: [ "apps/block_scout_web/lib/block_scout_web/routers/smart_contracts_api_v2_router.ex", "apps/block_scout_web/lib/block_scout_web/routers/tokens_api_v2_router.ex", - "apps/block_scout_web/lib/block_scout_web/routers/utils_api_v2_router.ex" + "apps/block_scout_web/lib/block_scout_web/routers/utils_api_v2_router.ex", + "apps/block_scout_web/lib/block_scout_web/routers/address_badges_v2_router.ex" ] ] 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 7f36a77..eb00e5e 100644 --- a/apps/block_scout_web/assets/js/lib/csv_download.js +++ b/apps/block_scout_web/assets/js/lib/csv_download.js @@ -53,7 +53,7 @@ $button.on('click', () => { // eslint-disable-next-line grecaptcha.execute(reCaptchaV3ClientKey, { action: 'login' }) .then(function (token) { - const url = `${baseURL}&recaptcha_response=${token}` + const url = `${baseURL}&recaptcha_v3_response=${token}` download(url) }) diff --git a/apps/block_scout_web/assets/package-lock.json b/apps/block_scout_web/assets/package-lock.json index 17aa96e..6b87344 100644 --- a/apps/block_scout_web/assets/package-lock.json +++ b/apps/block_scout_web/assets/package-lock.json @@ -8,7 +8,7 @@ "license": "GPL-3.0", "dependencies": { "@amplitude/analytics-browser": "^2.9.3", - "@fortawesome/fontawesome-free": "^6.5.2", + "@fortawesome/fontawesome-free": "^6.6.0", "@tarekraafat/autocomplete.js": "^10.2.7", "@walletconnect/web3-provider": "^1.8.0", "assert": "^2.1.0", @@ -17,7 +17,7 @@ "chart.js": "^4.4.3", "chartjs-adapter-luxon": "^1.3.1", "clipboard": "^2.0.11", - "core-js": "^3.37.1", + "core-js": "^3.38.1", "crypto-browserify": "^3.12.0", "dropzone": "^5.9.3", "eth-net-props": "^1.0.41", @@ -44,9 +44,9 @@ "lodash.omit": "^4.5.0", "lodash.rangeright": "^4.2.0", "lodash.reduce": "^4.6.0", - "luxon": "^3.4.4", + "luxon": "^3.5.0", "malihu-custom-scrollbar-plugin": "3.1.5", - "mixpanel-browser": "^2.54.1", + "mixpanel-browser": "^2.55.1", "moment": "^2.30.1", "nanomorph": "^5.4.0", "numeral": "^2.0.6", @@ -61,24 +61,24 @@ "redux": "^5.0.1", "stream-browserify": "^3.0.0", "stream-http": "^3.1.1", - "sweetalert2": "^11.12.4", + "sweetalert2": "^11.14.1", "urijs": "^1.19.11", - "url": "^0.11.3", + "url": "^0.11.4", "util": "^0.12.5", "viewerjs": "^1.11.6", - "web3": "^1.10.4", + "web3": "^4.12.1", "web3modal": "^1.9.12", "xss": "^1.0.15" }, "devDependencies": { - "@babel/core": "^7.24.7", - "@babel/preset-env": "^7.24.7", - "autoprefixer": "^10.4.19", + "@babel/core": "^7.25.2", + "@babel/preset-env": "^7.25.4", + "autoprefixer": "^10.4.20", "babel-loader": "^9.1.3", "copy-webpack-plugin": "^12.0.2", "css-loader": "^7.1.2", "css-minimizer-webpack-plugin": "^7.0.0", - "eslint": "^8.57.0", + "eslint": "^8.57.1", "eslint-config-standard": "^17.1.0", "eslint-plugin-import": "^2.29.1", "eslint-plugin-node": "^11.1.0", @@ -86,13 +86,13 @@ "file-loader": "^6.2.0", "jest": "^29.7.0", "jest-environment-jsdom": "^29.7.0", - "mini-css-extract-plugin": "^2.9.0", - "postcss": "^8.4.39", + "mini-css-extract-plugin": "^2.9.1", + "postcss": "^8.4.47", "postcss-loader": "^8.1.1", - "sass": "^1.77.8", + "sass": "^1.79.4", "sass-loader": "^14.2.1", "style-loader": "^4.0.0", - "webpack": "^5.93.0", + "webpack": "^5.95.0", "webpack-cli": "^5.1.4" }, "engines": { @@ -115,6 +115,11 @@ "node": ">=0.10.0" } }, + "node_modules/@adraffy/ens-normalize": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.10.1.tgz", + "integrity": "sha512-96Z2IP3mYmF1Xg2cDm8f1gWGf/HUVedQ3FMifV4kG/PQ4yEP51xDtRAEfhVNt5f/uzpNkZHwWQuUcu6D6K+Ekw==" + }, "node_modules/@amplitude/analytics-browser": { "version": "2.9.3", "resolved": "https://registry.npmjs.org/@amplitude/analytics-browser/-/analytics-browser-2.9.3.tgz", @@ -224,28 +229,28 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.24.7.tgz", - "integrity": "sha512-qJzAIcv03PyaWqxRgO4mSU3lihncDT296vnyuE2O8uA4w3UHWI4S3hgeZd1L8W1Bft40w9JxJ2b412iDUFFRhw==", + "version": "7.25.4", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.25.4.tgz", + "integrity": "sha512-+LGRog6RAsCJrrrg/IO6LGmpphNe5DiK30dGjCoxxeGv49B10/3XYGxPsAwrDlMFcFEvdAUavDT8r9k/hSyQqQ==", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/core": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.7.tgz", - "integrity": "sha512-nykK+LEK86ahTkX/3TgauT0ikKoNCfKHEaZYTUVupJdTLzGNvrblu4u6fa7DhZONAltdf8e662t/abY8idrd/g==", + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.25.2.tgz", + "integrity": "sha512-BBt3opiCOxUr9euZ5/ro/Xv8/V7yJ5bjYMqG/C1YAo8MIKAnumZalCN+msbci3Pigy4lIQfPUpfMM27HMGaYEA==", "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.24.7", - "@babel/generator": "^7.24.7", - "@babel/helper-compilation-targets": "^7.24.7", - "@babel/helper-module-transforms": "^7.24.7", - "@babel/helpers": "^7.24.7", - "@babel/parser": "^7.24.7", - "@babel/template": "^7.24.7", - "@babel/traverse": "^7.24.7", - "@babel/types": "^7.24.7", + "@babel/generator": "^7.25.0", + "@babel/helper-compilation-targets": "^7.25.2", + "@babel/helper-module-transforms": "^7.25.2", + "@babel/helpers": "^7.25.0", + "@babel/parser": "^7.25.0", + "@babel/template": "^7.25.0", + "@babel/traverse": "^7.25.2", + "@babel/types": "^7.25.2", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -266,11 +271,11 @@ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==" }, "node_modules/@babel/generator": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.7.tgz", - "integrity": "sha512-oipXieGC3i45Y1A41t4tAqpnEZWgB/lC6Ehh6+rOviR5XWpTtMmLN+fGjz9vOiNRt0p6RtO6DtD0pdU3vpqdSA==", + "version": "7.25.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.25.6.tgz", + "integrity": "sha512-VPC82gr1seXOpkjAAKoLhP50vx4vGNlF4msF64dSFq1P8RfB+QAuJWGHPXXPc8QyfVWwwB/TNNU4+ayZmHNbZw==", "dependencies": { - "@babel/types": "^7.24.7", + "@babel/types": "^7.25.6", "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25", "jsesc": "^2.5.1" @@ -304,13 +309,13 @@ } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.24.7.tgz", - "integrity": "sha512-ctSdRHBi20qWOfy27RUb4Fhp07KSJ3sXcuSvTrXrc4aG8NSYDo1ici3Vhg9bg69y5bj0Mr1lh0aeEgTvc12rMg==", + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.2.tgz", + "integrity": "sha512-U2U5LsSaZ7TAt3cfaymQ8WHh0pxvdHoEk6HVpaexxixjyEquMh0L0YNJNM6CTGKMXV1iksi0iZkGw4AcFkPaaw==", "dependencies": { - "@babel/compat-data": "^7.24.7", - "@babel/helper-validator-option": "^7.24.7", - "browserslist": "^4.22.2", + "@babel/compat-data": "^7.25.2", + "@babel/helper-validator-option": "^7.24.8", + "browserslist": "^4.23.1", "lru-cache": "^5.1.1", "semver": "^6.3.1" }, @@ -332,19 +337,17 @@ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" }, "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.24.7.tgz", - "integrity": "sha512-kTkaDl7c9vO80zeX1rJxnuRpEsD5tA81yh11X1gQo+PhSti3JS+7qeZo9U4RHobKRiFPKaGK3svUAeb8D0Q7eg==", + "version": "7.25.4", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.25.4.tgz", + "integrity": "sha512-ro/bFs3/84MDgDmMwbcHgDa8/E6J3QKNTk4xJJnVeFtGE+tL0K26E3pNxhYz2b67fJpt7Aphw5XcploKXuCvCQ==", "dev": true, "dependencies": { "@babel/helper-annotate-as-pure": "^7.24.7", - "@babel/helper-environment-visitor": "^7.24.7", - "@babel/helper-function-name": "^7.24.7", - "@babel/helper-member-expression-to-functions": "^7.24.7", + "@babel/helper-member-expression-to-functions": "^7.24.8", "@babel/helper-optimise-call-expression": "^7.24.7", - "@babel/helper-replace-supers": "^7.24.7", + "@babel/helper-replace-supers": "^7.25.0", "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7", - "@babel/helper-split-export-declaration": "^7.24.7", + "@babel/traverse": "^7.25.4", "semver": "^6.3.1" }, "engines": { @@ -355,9 +358,9 @@ } }, "node_modules/@babel/helper-create-regexp-features-plugin": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.24.7.tgz", - "integrity": "sha512-03TCmXy2FtXJEZfbXDTSqq1fRJArk7lX9DOFC/47VthYcxyIOx+eXQmdo6DOQvrbpIix+KfXwvuXdFDZHxt+rA==", + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.25.2.tgz", + "integrity": "sha512-+wqVGP+DFmqwFD3EH6TMTfUNeqDehV3E/dl+Sd54eaXqm17tEUNbEIn4sVivVowbvUpOtIGxdo3GoXyDH9N/9g==", "dev": true, "dependencies": { "@babel/helper-annotate-as-pure": "^7.24.7", @@ -387,48 +390,14 @@ "@babel/core": "^7.4.0-0" } }, - "node_modules/@babel/helper-environment-visitor": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.24.7.tgz", - "integrity": "sha512-DoiN84+4Gnd0ncbBOM9AZENV4a5ZiL39HYMyZJGZ/AZEykHYdJw0wW3kdcsh9/Kn+BRXHLkkklZ51ecPKmI1CQ==", - "dependencies": { - "@babel/types": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-function-name": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.24.7.tgz", - "integrity": "sha512-FyoJTsj/PEUWu1/TYRiXTIHc8lbw+TDYkZuoE43opPS5TrI7MyONBE1oNvfguEXAD9yhQRrVBnXdXzSLQl9XnA==", - "dependencies": { - "@babel/template": "^7.24.7", - "@babel/types": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-hoist-variables": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.24.7.tgz", - "integrity": "sha512-MJJwhkoGy5c4ehfoRyrJ/owKeMl19U54h27YYftT0o2teQ3FJ3nQUf/I3LlJsX4l3qlw7WRXUmiyajvHXoTubQ==", - "dependencies": { - "@babel/types": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.24.7.tgz", - "integrity": "sha512-LGeMaf5JN4hAT471eJdBs/GK1DoYIJ5GCtZN/EsL6KUiiDZOvO/eKE11AMZJa2zP4zk4qe9V2O/hxAmkRc8p6w==", + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.24.8.tgz", + "integrity": "sha512-LABppdt+Lp/RlBxqrh4qgf1oEH/WxdzQNDJIu5gC/W1GyvPVrOBiItmmM8wan2fm4oYqFuFfkXmlGpLQhPY8CA==", "dev": true, "dependencies": { - "@babel/traverse": "^7.24.7", - "@babel/types": "^7.24.7" + "@babel/traverse": "^7.24.8", + "@babel/types": "^7.24.8" }, "engines": { "node": ">=6.9.0" @@ -447,15 +416,14 @@ } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.24.7.tgz", - "integrity": "sha512-1fuJEwIrp+97rM4RWdO+qrRsZlAeL1lQJoPqtCYWv0NL115XM93hIH4CSRln2w52SqvmY5hqdtauB6QFCDiZNQ==", + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.25.2.tgz", + "integrity": "sha512-BjyRAbix6j/wv83ftcVJmBt72QtHI56C7JXZoG2xATiLpmoC7dpd8WnkikExHDVPpi/3qCmO6WY1EaXOluiecQ==", "dependencies": { - "@babel/helper-environment-visitor": "^7.24.7", "@babel/helper-module-imports": "^7.24.7", "@babel/helper-simple-access": "^7.24.7", - "@babel/helper-split-export-declaration": "^7.24.7", - "@babel/helper-validator-identifier": "^7.24.7" + "@babel/helper-validator-identifier": "^7.24.7", + "@babel/traverse": "^7.25.2" }, "engines": { "node": ">=6.9.0" @@ -477,22 +445,22 @@ } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.7.tgz", - "integrity": "sha512-Rq76wjt7yz9AAc1KnlRKNAi/dMSVWgDRx43FHoJEbcYU6xOWaE2dVPwcdTukJrjxS65GITyfbvEYHvkirZ6uEg==", + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.8.tgz", + "integrity": "sha512-FFWx5142D8h2Mgr/iPVGH5G7w6jDn4jUSpZTyDnQO0Yn7Ks2Kuz6Pci8H6MPCoUJegd/UZQ3tAvfLCxQSnWWwg==", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-remap-async-to-generator": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.24.7.tgz", - "integrity": "sha512-9pKLcTlZ92hNZMQfGCHImUpDOlAgkkpqalWEeftW5FBya75k8Li2ilerxkM/uBEj01iBZXcCIB/bwvDYgWyibA==", + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.25.0.tgz", + "integrity": "sha512-NhavI2eWEIz/H9dbrG0TuOicDhNexze43i5z7lEqwYm0WEZVTwnPpA0EafUTP7+6/W79HWIP2cTe3Z5NiSTVpw==", "dev": true, "dependencies": { "@babel/helper-annotate-as-pure": "^7.24.7", - "@babel/helper-environment-visitor": "^7.24.7", - "@babel/helper-wrap-function": "^7.24.7" + "@babel/helper-wrap-function": "^7.25.0", + "@babel/traverse": "^7.25.0" }, "engines": { "node": ">=6.9.0" @@ -502,14 +470,14 @@ } }, "node_modules/@babel/helper-replace-supers": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.24.7.tgz", - "integrity": "sha512-qTAxxBM81VEyoAY0TtLrx1oAEJc09ZK67Q9ljQToqCnA+55eNwCORaxlKyu+rNfX86o8OXRUSNUnrtsAZXM9sg==", + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.25.0.tgz", + "integrity": "sha512-q688zIvQVYtZu+i2PsdIu/uWGRpfxzr5WESsfpShfZECkO+d2o+WROWezCi/Q6kJ0tfPa5+pUGUlfx2HhrA3Bg==", "dev": true, "dependencies": { - "@babel/helper-environment-visitor": "^7.24.7", - "@babel/helper-member-expression-to-functions": "^7.24.7", - "@babel/helper-optimise-call-expression": "^7.24.7" + "@babel/helper-member-expression-to-functions": "^7.24.8", + "@babel/helper-optimise-call-expression": "^7.24.7", + "@babel/traverse": "^7.25.0" }, "engines": { "node": ">=6.9.0" @@ -543,21 +511,10 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/helper-split-export-declaration": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.7.tgz", - "integrity": "sha512-oy5V7pD+UvfkEATUKvIjvIAH/xCzfsFVw7ygW2SI6NClZzquT+mwdTfgfdbUiceh6iQO0CHtCPsyze/MZ2YbAA==", - "dependencies": { - "@babel/types": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/helper-string-parser": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.7.tgz", - "integrity": "sha512-7MbVt6xrwFQbunH2DNQsAP5sTGxfqQtErvBIvIMi6EQnbgUOuVYanvREcmFrOPhoXBrTtjhhP+lW+o5UfK+tDg==", + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz", + "integrity": "sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==", "engines": { "node": ">=6.9.0" } @@ -571,35 +528,34 @@ } }, "node_modules/@babel/helper-validator-option": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.24.7.tgz", - "integrity": "sha512-yy1/KvjhV/ZCL+SM7hBrvnZJ3ZuT9OuZgIJAGpPEToANvc3iM6iDvBnRjtElWibHU6n8/LPR/EjX9EtIEYO3pw==", + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.24.8.tgz", + "integrity": "sha512-xb8t9tD1MHLungh/AIoWYN+gVHaB9kwlu8gffXGSt3FFEIT7RjS+xWbc2vUD1UTZdIpKj/ab3rdqJ7ufngyi2Q==", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-wrap-function": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.24.7.tgz", - "integrity": "sha512-N9JIYk3TD+1vq/wn77YnJOqMtfWhNewNE+DJV4puD2X7Ew9J4JvrzrFDfTfyv5EgEXVy9/Wt8QiOErzEmv5Ifw==", + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.25.0.tgz", + "integrity": "sha512-s6Q1ebqutSiZnEjaofc/UKDyC4SbzV5n5SrA2Gq8UawLycr3i04f1dX4OzoQVnexm6aOCh37SQNYlJ/8Ku+PMQ==", "dev": true, "dependencies": { - "@babel/helper-function-name": "^7.24.7", - "@babel/template": "^7.24.7", - "@babel/traverse": "^7.24.7", - "@babel/types": "^7.24.7" + "@babel/template": "^7.25.0", + "@babel/traverse": "^7.25.0", + "@babel/types": "^7.25.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helpers": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.7.tgz", - "integrity": "sha512-NlmJJtvcw72yRJRcnCmGvSi+3jDEg8qFu3z0AFoymmzLx5ERVWyzd9kVXr7Th9/8yIJi2Zc6av4Tqz3wFs8QWg==", + "version": "7.25.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.25.6.tgz", + "integrity": "sha512-Xg0tn4HcfTijTwfDwYlvVCl43V6h4KyVVX2aEm4qdO/PC6L2YvzLHFdmxhoeSA3eslcE6+ZVXHgWwopXYLNq4Q==", "dependencies": { - "@babel/template": "^7.24.7", - "@babel/types": "^7.24.7" + "@babel/template": "^7.25.0", + "@babel/types": "^7.25.6" }, "engines": { "node": ">=6.9.0" @@ -620,9 +576,12 @@ } }, "node_modules/@babel/parser": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.7.tgz", - "integrity": "sha512-9uUYRm6OqQrCqQdG1iCBwBPZgN8ciDBro2nIOFaiRz1/BCxaI7CNvQbDHvsArAC7Tw9Hda/B3U+6ui9u4HWXPw==", + "version": "7.25.6", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.6.tgz", + "integrity": "sha512-trGdfBdbD0l1ZPmcJ83eNxB9rbEax4ALFTF7fN386TMYbeCQbyme5cOEXQhbGXKebwGaB/J52w1mrklMcbgy6Q==", + "dependencies": { + "@babel/types": "^7.25.6" + }, "bin": { "parser": "bin/babel-parser.js" }, @@ -631,13 +590,28 @@ } }, "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.24.7.tgz", - "integrity": "sha512-TiT1ss81W80eQsN+722OaeQMY/G4yTb4G9JrqeiDADs3N8lbPMGldWi9x8tyqCW5NLx1Jh2AvkE6r6QvEltMMQ==", + "version": "7.25.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.25.3.tgz", + "integrity": "sha512-wUrcsxZg6rqBXG05HG1FPYgsP6EvwF4WpBbxIpWIIYnH8wG0gzx3yZY3dtEHas4sTAOGkbTsc9EGPxwff8lRoA==", "dev": true, "dependencies": { - "@babel/helper-environment-visitor": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-plugin-utils": "^7.24.8", + "@babel/traverse": "^7.25.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-class-field-initializer-scope": { + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.25.0.tgz", + "integrity": "sha512-Bm4bH2qsX880b/3ziJ8KD711LT7z4u8CFudmjqle65AZj/HNUFhEf90dqYv6O86buWvSBmeQDjv0Tn2aF/bIBA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.8" }, "engines": { "node": ">=6.9.0" @@ -647,12 +621,12 @@ } }, "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.24.7.tgz", - "integrity": "sha512-unaQgZ/iRu/By6tsjMZzpeBZjChYfLYry6HrEXPoz3KmfF0sVBQ1l8zKMQ4xRGLWVsjuvB8nQfjNP/DcfEOCsg==", + "version": "7.25.0", + "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.25.0.tgz", + "integrity": "sha512-lXwdNZtTmeVOOFtwM/WDe7yg1PL8sYhRk/XH0FzbR2HDQ0xC+EnQ/JHeoMYSavtU115tnUk0q9CDyq8si+LMAA==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-plugin-utils": "^7.24.8" }, "engines": { "node": ">=6.9.0" @@ -679,13 +653,13 @@ } }, "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.24.7.tgz", - "integrity": "sha512-utA4HuR6F4Vvcr+o4DnjL8fCOlgRFGbeeBEGNg3ZTrLFw6VWG5XmUrvcQ0FjIYMU2ST4XcR2Wsp7t9qOAPnxMg==", + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.25.0.tgz", + "integrity": "sha512-tggFrk1AIShG/RUQbEwt2Tr/E+ObkfwrPjR6BjbRvsx24+PSjK8zrq0GWPNCjo8qpRx4DuJzlcvWJqlm+0h3kw==", "dev": true, "dependencies": { - "@babel/helper-environment-visitor": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-plugin-utils": "^7.24.8", + "@babel/traverse": "^7.25.0" }, "engines": { "node": ">=6.9.0" @@ -999,15 +973,15 @@ } }, "node_modules/@babel/plugin-transform-async-generator-functions": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.24.7.tgz", - "integrity": "sha512-o+iF77e3u7ZS4AoAuJvapz9Fm001PuD2V3Lp6OSE4FYQke+cSewYtnek+THqGRWyQloRCyvWL1OkyfNEl9vr/g==", + "version": "7.25.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.25.4.tgz", + "integrity": "sha512-jz8cV2XDDTqjKPwVPJBIjORVEmSGYhdRa8e5k5+vN+uwcjSrSxUaebBRa4ko1jqNF2uxyg8G6XYk30Jv285xzg==", "dev": true, "dependencies": { - "@babel/helper-environment-visitor": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/helper-remap-async-to-generator": "^7.24.7", - "@babel/plugin-syntax-async-generators": "^7.8.4" + "@babel/helper-plugin-utils": "^7.24.8", + "@babel/helper-remap-async-to-generator": "^7.25.0", + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/traverse": "^7.25.4" }, "engines": { "node": ">=6.9.0" @@ -1049,12 +1023,12 @@ } }, "node_modules/@babel/plugin-transform-block-scoping": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.24.7.tgz", - "integrity": "sha512-Nd5CvgMbWc+oWzBsuaMcbwjJWAcp5qzrbg69SZdHSP7AMY0AbWFqFO0WTFCA1jxhMCwodRwvRec8k0QUbZk7RQ==", + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.25.0.tgz", + "integrity": "sha512-yBQjYoOjXlFv9nlXb3f1casSHOZkWr29NX+zChVanLg5Nc157CrbEX9D7hxxtTpuFy7Q0YzmmWfJxzvps4kXrQ==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-plugin-utils": "^7.24.8" }, "engines": { "node": ">=6.9.0" @@ -1064,13 +1038,13 @@ } }, "node_modules/@babel/plugin-transform-class-properties": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.24.7.tgz", - "integrity": "sha512-vKbfawVYayKcSeSR5YYzzyXvsDFWU2mD8U5TFeXtbCPLFUqe7GyCgvO6XDHzje862ODrOwy6WCPmKeWHbCFJ4w==", + "version": "7.25.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.25.4.tgz", + "integrity": "sha512-nZeZHyCWPfjkdU5pA/uHiTaDAFUEqkpzf1YoQT2NeSynCGYq9rxfyI3XpQbfx/a0hSnFH6TGlEXvae5Vi7GD8g==", "dev": true, "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-create-class-features-plugin": "^7.25.4", + "@babel/helper-plugin-utils": "^7.24.8" }, "engines": { "node": ">=6.9.0" @@ -1097,18 +1071,16 @@ } }, "node_modules/@babel/plugin-transform-classes": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.24.7.tgz", - "integrity": "sha512-CFbbBigp8ln4FU6Bpy6g7sE8B/WmCmzvivzUC6xDAdWVsjYTXijpuuGJmYkAaoWAzcItGKT3IOAbxRItZ5HTjw==", + "version": "7.25.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.25.4.tgz", + "integrity": "sha512-oexUfaQle2pF/b6E0dwsxQtAol9TLSO88kQvym6HHBWFliV2lGdrPieX+WgMRLSJDVzdYywk7jXbLPuO2KLTLg==", "dev": true, "dependencies": { "@babel/helper-annotate-as-pure": "^7.24.7", - "@babel/helper-compilation-targets": "^7.24.7", - "@babel/helper-environment-visitor": "^7.24.7", - "@babel/helper-function-name": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/helper-replace-supers": "^7.24.7", - "@babel/helper-split-export-declaration": "^7.24.7", + "@babel/helper-compilation-targets": "^7.25.2", + "@babel/helper-plugin-utils": "^7.24.8", + "@babel/helper-replace-supers": "^7.25.0", + "@babel/traverse": "^7.25.4", "globals": "^11.1.0" }, "engines": { @@ -1135,12 +1107,12 @@ } }, "node_modules/@babel/plugin-transform-destructuring": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.24.7.tgz", - "integrity": "sha512-19eJO/8kdCQ9zISOf+SEUJM/bAUIsvY3YDnXZTupUCQ8LgrWnsG/gFB9dvXqdXnRXMAM8fvt7b0CBKQHNGy1mw==", + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.24.8.tgz", + "integrity": "sha512-36e87mfY8TnRxc7yc6M9g9gOB7rKgSahqkIKwLpz4Ppk2+zC2Cy1is0uwtuSG6AE4zlTOUa+7JGz9jCJGLqQFQ==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-plugin-utils": "^7.24.8" }, "engines": { "node": ">=6.9.0" @@ -1180,6 +1152,22 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-transform-duplicate-named-capturing-groups-regex": { + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.25.0.tgz", + "integrity": "sha512-YLpb4LlYSc3sCUa35un84poXoraOiQucUTTu8X1j18JV+gNa8E0nyUf/CjZ171IRGr4jEguF+vzJU66QZhn29g==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.25.0", + "@babel/helper-plugin-utils": "^7.24.8" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, "node_modules/@babel/plugin-transform-dynamic-import": { "version": "7.24.7", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.24.7.tgz", @@ -1245,14 +1233,14 @@ } }, "node_modules/@babel/plugin-transform-function-name": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.24.7.tgz", - "integrity": "sha512-U9FcnA821YoILngSmYkW6FjyQe2TyZD5pHt4EVIhmcTkrJw/3KqcrRSxuOo5tFZJi7TE19iDyI1u+weTI7bn2w==", + "version": "7.25.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.25.1.tgz", + "integrity": "sha512-TVVJVdW9RKMNgJJlLtHsKDTydjZAbwIsn6ySBPQaEAUU5+gVvlJt/9nRmqVbsV/IBanRjzWoaAQKLoamWVOUuA==", "dev": true, "dependencies": { - "@babel/helper-compilation-targets": "^7.24.7", - "@babel/helper-function-name": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-compilation-targets": "^7.24.8", + "@babel/helper-plugin-utils": "^7.24.8", + "@babel/traverse": "^7.25.1" }, "engines": { "node": ">=6.9.0" @@ -1278,12 +1266,12 @@ } }, "node_modules/@babel/plugin-transform-literals": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.24.7.tgz", - "integrity": "sha512-vcwCbb4HDH+hWi8Pqenwnjy+UiklO4Kt1vfspcQYFhJdpthSnW8XvWGyDZWKNVrVbVViI/S7K9PDJZiUmP2fYQ==", + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.25.2.tgz", + "integrity": "sha512-HQI+HcTbm9ur3Z2DkO+jgESMAMcYLuN/A7NRw9juzxAezN9AvqvUTnpKP/9kkYANz6u7dFlAyOu44ejuGySlfw==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-plugin-utils": "^7.24.8" }, "engines": { "node": ">=6.9.0" @@ -1340,13 +1328,13 @@ } }, "node_modules/@babel/plugin-transform-modules-commonjs": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.24.7.tgz", - "integrity": "sha512-iFI8GDxtevHJ/Z22J5xQpVqFLlMNstcLXh994xifFwxxGslr2ZXXLWgtBeLctOD63UFDArdvN6Tg8RFw+aEmjQ==", + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.24.8.tgz", + "integrity": "sha512-WHsk9H8XxRs3JXKWFiqtQebdh9b/pTk4EgueygFzYlTKAg0Ud985mSevdNjdXdFBATSKVJGQXP1tv6aGbssLKA==", "dev": true, "dependencies": { - "@babel/helper-module-transforms": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-module-transforms": "^7.24.8", + "@babel/helper-plugin-utils": "^7.24.8", "@babel/helper-simple-access": "^7.24.7" }, "engines": { @@ -1357,15 +1345,15 @@ } }, "node_modules/@babel/plugin-transform-modules-systemjs": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.24.7.tgz", - "integrity": "sha512-GYQE0tW7YoaN13qFh3O1NCY4MPkUiAH3fiF7UcV/I3ajmDKEdG3l+UOcbAm4zUE3gnvUU+Eni7XrVKo9eO9auw==", + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.25.0.tgz", + "integrity": "sha512-YPJfjQPDXxyQWg/0+jHKj1llnY5f/R6a0p/vP4lPymxLu7Lvl4k2WMitqi08yxwQcCVUUdG9LCUj4TNEgAp3Jw==", "dev": true, "dependencies": { - "@babel/helper-hoist-variables": "^7.24.7", - "@babel/helper-module-transforms": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/helper-validator-identifier": "^7.24.7" + "@babel/helper-module-transforms": "^7.25.0", + "@babel/helper-plugin-utils": "^7.24.8", + "@babel/helper-validator-identifier": "^7.24.7", + "@babel/traverse": "^7.25.0" }, "engines": { "node": ">=6.9.0" @@ -1504,12 +1492,12 @@ } }, "node_modules/@babel/plugin-transform-optional-chaining": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.24.7.tgz", - "integrity": "sha512-tK+0N9yd4j+x/4hxF3F0e0fu/VdcxU18y5SevtyM/PCFlQvXbR0Zmlo2eBrKtVipGNFzpq56o8WsIIKcJFUCRQ==", + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.24.8.tgz", + "integrity": "sha512-5cTOLSMs9eypEy8JUVvIKOu6NgvbJMnpG62VpIHrTmROdQ+L5mDAaI40g25k5vXti55JWNX5jCkq3HZxXBQANw==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.8", "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7", "@babel/plugin-syntax-optional-chaining": "^7.8.3" }, @@ -1536,13 +1524,13 @@ } }, "node_modules/@babel/plugin-transform-private-methods": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.24.7.tgz", - "integrity": "sha512-COTCOkG2hn4JKGEKBADkA8WNb35TGkkRbI5iT845dB+NyqgO8Hn+ajPbSnIQznneJTa3d30scb6iz/DhH8GsJQ==", + "version": "7.25.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.25.4.tgz", + "integrity": "sha512-ao8BG7E2b/URaUQGqN3Tlsg+M3KlHY6rJ1O1gXAEUnZoyNQnvKyH87Kfg+FoxSeyWUB8ISZZsC91C44ZuBFytw==", "dev": true, "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-create-class-features-plugin": "^7.25.4", + "@babel/helper-plugin-utils": "^7.24.8" }, "engines": { "node": ">=6.9.0" @@ -1708,12 +1696,12 @@ } }, "node_modules/@babel/plugin-transform-typeof-symbol": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.24.7.tgz", - "integrity": "sha512-VtR8hDy7YLB7+Pet9IarXjg/zgCMSF+1mNS/EQEiEaUPoFXCVsHG64SIxcaaI2zJgRiv+YmgaQESUfWAdbjzgg==", + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.24.8.tgz", + "integrity": "sha512-adNTUpDCVnmAE58VEqKlAA6ZBlNkMnWD0ZcW76lyNFN3MJniyGFZfNwERVk8Ap56MCnXztmDr19T4mPTztcuaw==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-plugin-utils": "^7.24.8" }, "engines": { "node": ">=6.9.0" @@ -1770,13 +1758,13 @@ } }, "node_modules/@babel/plugin-transform-unicode-sets-regex": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.24.7.tgz", - "integrity": "sha512-2G8aAvF4wy1w/AGZkemprdGMRg5o6zPNhbHVImRz3lss55TYCBd6xStN19rt8XJHq20sqV0JbyWjOWwQRwV/wg==", + "version": "7.25.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.25.4.tgz", + "integrity": "sha512-qesBxiWkgN1Q+31xUE9RcMk79eOXXDCv6tfyGMRSs4RGlioSg2WVyQAm07k726cSE56pa+Kb0y9epX2qaXzTvA==", "dev": true, "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-create-regexp-features-plugin": "^7.25.2", + "@babel/helper-plugin-utils": "^7.24.8" }, "engines": { "node": ">=6.9.0" @@ -1786,19 +1774,20 @@ } }, "node_modules/@babel/preset-env": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.24.7.tgz", - "integrity": "sha512-1YZNsc+y6cTvWlDHidMBsQZrZfEFjRIo/BZCT906PMdzOyXtSLTgqGdrpcuTDCXyd11Am5uQULtDIcCfnTc8fQ==", - "dev": true, - "dependencies": { - "@babel/compat-data": "^7.24.7", - "@babel/helper-compilation-targets": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/helper-validator-option": "^7.24.7", - "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.24.7", - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.24.7", + "version": "7.25.4", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.25.4.tgz", + "integrity": "sha512-W9Gyo+KmcxjGahtt3t9fb14vFRWvPpu5pT6GBlovAK6BTBcxgjfVMSQCfJl4oi35ODrxP6xx2Wr8LNST57Mraw==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.25.4", + "@babel/helper-compilation-targets": "^7.25.2", + "@babel/helper-plugin-utils": "^7.24.8", + "@babel/helper-validator-option": "^7.24.8", + "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.25.3", + "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.25.0", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.25.0", "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.24.7", - "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.24.7", + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.25.0", "@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", @@ -1819,29 +1808,30 @@ "@babel/plugin-syntax-top-level-await": "^7.14.5", "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", "@babel/plugin-transform-arrow-functions": "^7.24.7", - "@babel/plugin-transform-async-generator-functions": "^7.24.7", + "@babel/plugin-transform-async-generator-functions": "^7.25.4", "@babel/plugin-transform-async-to-generator": "^7.24.7", "@babel/plugin-transform-block-scoped-functions": "^7.24.7", - "@babel/plugin-transform-block-scoping": "^7.24.7", - "@babel/plugin-transform-class-properties": "^7.24.7", + "@babel/plugin-transform-block-scoping": "^7.25.0", + "@babel/plugin-transform-class-properties": "^7.25.4", "@babel/plugin-transform-class-static-block": "^7.24.7", - "@babel/plugin-transform-classes": "^7.24.7", + "@babel/plugin-transform-classes": "^7.25.4", "@babel/plugin-transform-computed-properties": "^7.24.7", - "@babel/plugin-transform-destructuring": "^7.24.7", + "@babel/plugin-transform-destructuring": "^7.24.8", "@babel/plugin-transform-dotall-regex": "^7.24.7", "@babel/plugin-transform-duplicate-keys": "^7.24.7", + "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.25.0", "@babel/plugin-transform-dynamic-import": "^7.24.7", "@babel/plugin-transform-exponentiation-operator": "^7.24.7", "@babel/plugin-transform-export-namespace-from": "^7.24.7", "@babel/plugin-transform-for-of": "^7.24.7", - "@babel/plugin-transform-function-name": "^7.24.7", + "@babel/plugin-transform-function-name": "^7.25.1", "@babel/plugin-transform-json-strings": "^7.24.7", - "@babel/plugin-transform-literals": "^7.24.7", + "@babel/plugin-transform-literals": "^7.25.2", "@babel/plugin-transform-logical-assignment-operators": "^7.24.7", "@babel/plugin-transform-member-expression-literals": "^7.24.7", "@babel/plugin-transform-modules-amd": "^7.24.7", - "@babel/plugin-transform-modules-commonjs": "^7.24.7", - "@babel/plugin-transform-modules-systemjs": "^7.24.7", + "@babel/plugin-transform-modules-commonjs": "^7.24.8", + "@babel/plugin-transform-modules-systemjs": "^7.25.0", "@babel/plugin-transform-modules-umd": "^7.24.7", "@babel/plugin-transform-named-capturing-groups-regex": "^7.24.7", "@babel/plugin-transform-new-target": "^7.24.7", @@ -1850,9 +1840,9 @@ "@babel/plugin-transform-object-rest-spread": "^7.24.7", "@babel/plugin-transform-object-super": "^7.24.7", "@babel/plugin-transform-optional-catch-binding": "^7.24.7", - "@babel/plugin-transform-optional-chaining": "^7.24.7", + "@babel/plugin-transform-optional-chaining": "^7.24.8", "@babel/plugin-transform-parameters": "^7.24.7", - "@babel/plugin-transform-private-methods": "^7.24.7", + "@babel/plugin-transform-private-methods": "^7.25.4", "@babel/plugin-transform-private-property-in-object": "^7.24.7", "@babel/plugin-transform-property-literals": "^7.24.7", "@babel/plugin-transform-regenerator": "^7.24.7", @@ -1861,16 +1851,16 @@ "@babel/plugin-transform-spread": "^7.24.7", "@babel/plugin-transform-sticky-regex": "^7.24.7", "@babel/plugin-transform-template-literals": "^7.24.7", - "@babel/plugin-transform-typeof-symbol": "^7.24.7", + "@babel/plugin-transform-typeof-symbol": "^7.24.8", "@babel/plugin-transform-unicode-escapes": "^7.24.7", "@babel/plugin-transform-unicode-property-regex": "^7.24.7", "@babel/plugin-transform-unicode-regex": "^7.24.7", - "@babel/plugin-transform-unicode-sets-regex": "^7.24.7", + "@babel/plugin-transform-unicode-sets-regex": "^7.25.4", "@babel/preset-modules": "0.1.6-no-external-plugins", "babel-plugin-polyfill-corejs2": "^0.4.10", - "babel-plugin-polyfill-corejs3": "^0.10.4", + "babel-plugin-polyfill-corejs3": "^0.10.6", "babel-plugin-polyfill-regenerator": "^0.6.1", - "core-js-compat": "^3.31.0", + "core-js-compat": "^3.37.1", "semver": "^6.3.1" }, "engines": { @@ -1954,31 +1944,28 @@ } }, "node_modules/@babel/template": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.7.tgz", - "integrity": "sha512-jYqfPrU9JTF0PmPy1tLYHW4Mp4KlgxJD9l2nP9fD6yT/ICi554DmrWBAEYpIelzjHf1msDP3PxJIRt/nFNfBig==", + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.0.tgz", + "integrity": "sha512-aOOgh1/5XzKvg1jvVz7AVrx2piJ2XBi227DHmbY6y+bM9H2FlN+IfecYu4Xl0cNiiVejlsCri89LUsbj8vJD9Q==", "dependencies": { "@babel/code-frame": "^7.24.7", - "@babel/parser": "^7.24.7", - "@babel/types": "^7.24.7" + "@babel/parser": "^7.25.0", + "@babel/types": "^7.25.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.7.tgz", - "integrity": "sha512-yb65Ed5S/QAcewNPh0nZczy9JdYXkkAbIsEo+P7BE7yO3txAY30Y/oPa3QkQ5It3xVG2kpKMg9MsdxZaO31uKA==", + "version": "7.25.6", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.6.tgz", + "integrity": "sha512-9Vrcx5ZW6UwK5tvqsj0nGpp/XzqthkT0dqIc9g1AdtygFToNtTF67XzYS//dm+SAK9cp3B9R4ZO/46p63SCjlQ==", "dependencies": { "@babel/code-frame": "^7.24.7", - "@babel/generator": "^7.24.7", - "@babel/helper-environment-visitor": "^7.24.7", - "@babel/helper-function-name": "^7.24.7", - "@babel/helper-hoist-variables": "^7.24.7", - "@babel/helper-split-export-declaration": "^7.24.7", - "@babel/parser": "^7.24.7", - "@babel/types": "^7.24.7", + "@babel/generator": "^7.25.6", + "@babel/parser": "^7.25.6", + "@babel/template": "^7.25.0", + "@babel/types": "^7.25.6", "debug": "^4.3.1", "globals": "^11.1.0" }, @@ -1987,11 +1974,11 @@ } }, "node_modules/@babel/types": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.7.tgz", - "integrity": "sha512-XEFXSlxiG5td2EJRe8vOmRbaXVgfcBlszKujvVmWIK/UpywWljQCfzAv3RQCGujWQ1RD4YYWEAqDXfuJiy8f5Q==", + "version": "7.25.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.6.tgz", + "integrity": "sha512-/l42B1qxpG6RdfYf343Uw1vmDjeNhneUXtzhojE7pDgfpEypmRhI6j1kr17XCVv4Cgl9HdAiQY2x0GwKm7rWCw==", "dependencies": { - "@babel/helper-string-parser": "^7.24.7", + "@babel/helper-string-parser": "^7.24.8", "@babel/helper-validator-identifier": "^7.24.7", "to-fast-properties": "^2.0.0" }, @@ -2130,23 +2117,14 @@ } }, "node_modules/@eslint/js": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz", - "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==", + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", + "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, - "node_modules/@ethereumjs/common": { - "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.5" - } - }, "node_modules/@ethereumjs/rlp": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/@ethereumjs/rlp/-/rlp-4.0.1.tgz", @@ -2158,504 +2136,98 @@ "node": ">=14" } }, - "node_modules/@ethereumjs/tx": { - "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/@fortawesome/fontawesome-free": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-6.6.0.tgz", + "integrity": "sha512-60G28ke/sXdtS9KZCpZSHHkCbdsOGEhIUGlwq6yhY74UpTiToIh8np7A8yphhM4BWsvNFtIvLpi4co+h9Mr9Ow==", + "engines": { + "node": ">=6" } }, - "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==", + "node_modules/@humanwhocodes/config-array": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", + "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", + "deprecated": "Use @eslint/config-array instead", + "dev": true, "dependencies": { - "@ethereumjs/rlp": "^4.0.1", - "ethereum-cryptography": "^2.0.0", - "micro-ftch": "^0.3.1" + "@humanwhocodes/object-schema": "^2.0.3", + "debug": "^4.3.1", + "minimatch": "^3.0.5" }, "engines": { - "node": ">=14" + "node": ">=10.10.0" } }, - "node_modules/@ethereumjs/util/node_modules/ethereum-cryptography": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-2.1.3.tgz", - "integrity": "sha512-BlwbIL7/P45W8FGW2r7LGuvoEZ+7PWsniMvQ4p5s2xCyw9tmaDlpfsN9HjAucbF+t/qpVHwZUisgfK24TCW8aA==", - "dependencies": { - "@noble/curves": "1.3.0", - "@noble/hashes": "1.3.3", - "@scure/bip32": "1.3.3", - "@scure/bip39": "1.2.2" + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" } }, - "node_modules/@ethersproject/abi": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/abi/-/abi-5.7.0.tgz", - "integrity": "sha512-351ktp42TiRcYB3H1OP8yajPeAQstMW/yCFokj/AthP9bLHzQFPlOrxOcwYEDkUAICmOHljvN4K39OMTMUa9RA==", - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "dependencies": { - "@ethersproject/address": "^5.7.0", - "@ethersproject/bignumber": "^5.7.0", - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/constants": "^5.7.0", - "@ethersproject/hash": "^5.7.0", - "@ethersproject/keccak256": "^5.7.0", - "@ethersproject/logger": "^5.7.0", - "@ethersproject/properties": "^5.7.0", - "@ethersproject/strings": "^5.7.0" - } - }, - "node_modules/@ethersproject/abstract-provider": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/abstract-provider/-/abstract-provider-5.7.0.tgz", - "integrity": "sha512-R41c9UkchKCpAqStMYUpdunjo3pkEvZC3FAwZn5S5MGbXoMQOHIdHItezTETxAO5bevtMApSyEhn9+CHcDsWBw==", - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], + "node_modules/@humanwhocodes/object-schema": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "deprecated": "Use @eslint/object-schema instead", + "dev": true + }, + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, "dependencies": { - "@ethersproject/bignumber": "^5.7.0", - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/logger": "^5.7.0", - "@ethersproject/networks": "^5.7.0", - "@ethersproject/properties": "^5.7.0", - "@ethersproject/transactions": "^5.7.0", - "@ethersproject/web": "^5.7.0" + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" } }, - "node_modules/@ethersproject/abstract-signer": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/abstract-signer/-/abstract-signer-5.7.0.tgz", - "integrity": "sha512-a16V8bq1/Cz+TGCkE2OPMTOUDLS3grCpdjoJCYNnVBbdYEMSgKrU0+B90s8b6H+ByYTBZN7a3g76jdIJi7UfKQ==", - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "dependencies": { - "@ethersproject/abstract-provider": "^5.7.0", - "@ethersproject/bignumber": "^5.7.0", - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/logger": "^5.7.0", - "@ethersproject/properties": "^5.7.0" + "node_modules/@istanbuljs/load-nyc-config/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "engines": { + "node": ">=8" } }, - "node_modules/@ethersproject/address": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/address/-/address-5.7.0.tgz", - "integrity": "sha512-9wYhYt7aghVGo758POM5nqcOMaE168Q6aRLJZwUmiqSrAungkG74gSSeKEIR7ukixesdRZGPgVqme6vmxs1fkA==", - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "dependencies": { - "@ethersproject/bignumber": "^5.7.0", - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/keccak256": "^5.7.0", - "@ethersproject/logger": "^5.7.0", - "@ethersproject/rlp": "^5.7.0" + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "engines": { + "node": ">=8" } }, - "node_modules/@ethersproject/base64": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/base64/-/base64-5.7.0.tgz", - "integrity": "sha512-Dr8tcHt2mEbsZr/mwTPIQAf3Ai0Bks/7gTw9dSqk1mQvhW3XvRlmDJr/4n+wg1JmCl16NZue17CDh8xb/vZ0sQ==", - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], + "node_modules/@jest/console": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz", + "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", + "dev": true, "dependencies": { - "@ethersproject/bytes": "^5.7.0" - } - }, - "node_modules/@ethersproject/bignumber": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/bignumber/-/bignumber-5.7.0.tgz", - "integrity": "sha512-n1CAdIHRWjSucQO3MC1zPSVgV/6dy/fjL9pMrPP9peL+QxEg9wOsVqwD4+818B6LUEtaXzVHQiuivzRoxPxUGw==", - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "dependencies": { - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/logger": "^5.7.0", - "bn.js": "^5.2.1" - } - }, - "node_modules/@ethersproject/bytes": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/bytes/-/bytes-5.7.0.tgz", - "integrity": "sha512-nsbxwgFXWh9NyYWo+U8atvmMsSdKJprTcICAkvbBffT75qDocbuggBU0SJiVK2MuTrp0q+xvLkTnGMPK1+uA9A==", - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "dependencies": { - "@ethersproject/logger": "^5.7.0" - } - }, - "node_modules/@ethersproject/constants": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/constants/-/constants-5.7.0.tgz", - "integrity": "sha512-DHI+y5dBNvkpYUMiRQyxRBYBefZkJfo70VUkUAsRjcPs47muV9evftfZ0PJVCXYbAiCgght0DtcF9srFQmIgWA==", - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "dependencies": { - "@ethersproject/bignumber": "^5.7.0" - } - }, - "node_modules/@ethersproject/hash": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/hash/-/hash-5.7.0.tgz", - "integrity": "sha512-qX5WrQfnah1EFnO5zJv1v46a8HW0+E5xuBBDTwMFZLuVTx0tbU2kkx15NqdjxecrLGatQN9FGQKpb1FKdHCt+g==", - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "dependencies": { - "@ethersproject/abstract-signer": "^5.7.0", - "@ethersproject/address": "^5.7.0", - "@ethersproject/base64": "^5.7.0", - "@ethersproject/bignumber": "^5.7.0", - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/keccak256": "^5.7.0", - "@ethersproject/logger": "^5.7.0", - "@ethersproject/properties": "^5.7.0", - "@ethersproject/strings": "^5.7.0" - } - }, - "node_modules/@ethersproject/keccak256": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/keccak256/-/keccak256-5.7.0.tgz", - "integrity": "sha512-2UcPboeL/iW+pSg6vZ6ydF8tCnv3Iu/8tUmLLzWWGzxWKFFqOBQFLo6uLUv6BDrLgCDfN28RJ/wtByx+jZ4KBg==", - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "dependencies": { - "@ethersproject/bytes": "^5.7.0", - "js-sha3": "0.8.0" - } - }, - "node_modules/@ethersproject/logger": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/logger/-/logger-5.7.0.tgz", - "integrity": "sha512-0odtFdXu/XHtjQXJYA3u9G0G8btm0ND5Cu8M7i5vhEcE8/HmF4Lbdqanwyv4uQTr2tx6b7fQRmgLrsnpQlmnig==", - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ] - }, - "node_modules/@ethersproject/networks": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/@ethersproject/networks/-/networks-5.7.1.tgz", - "integrity": "sha512-n/MufjFYv3yFcUyfhnXotyDlNdFb7onmkSy8aQERi2PjNcnWQ66xXxa3XlS8nCcA8aJKJjIIMNJTC7tu80GwpQ==", - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "dependencies": { - "@ethersproject/logger": "^5.7.0" - } - }, - "node_modules/@ethersproject/properties": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/properties/-/properties-5.7.0.tgz", - "integrity": "sha512-J87jy8suntrAkIZtecpxEPxY//szqr1mlBaYlQ0r4RCaiD2hjheqF9s1LVE8vVuJCXisjIP+JgtK/Do54ej4Sw==", - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "dependencies": { - "@ethersproject/logger": "^5.7.0" - } - }, - "node_modules/@ethersproject/rlp": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/rlp/-/rlp-5.7.0.tgz", - "integrity": "sha512-rBxzX2vK8mVF7b0Tol44t5Tb8gomOHkj5guL+HhzQ1yBh/ydjGnpw6at+X6Iw0Kp3OzzzkcKp8N9r0W4kYSs9w==", - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "dependencies": { - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/logger": "^5.7.0" - } - }, - "node_modules/@ethersproject/signing-key": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/signing-key/-/signing-key-5.7.0.tgz", - "integrity": "sha512-MZdy2nL3wO0u7gkB4nA/pEf8lu1TlFswPNmy8AiYkfKTdO6eXBJyUdmHO/ehm/htHw9K/qF8ujnTyUAD+Ry54Q==", - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "dependencies": { - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/logger": "^5.7.0", - "@ethersproject/properties": "^5.7.0", - "bn.js": "^5.2.1", - "elliptic": "6.5.4", - "hash.js": "1.1.7" - } - }, - "node_modules/@ethersproject/strings": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/strings/-/strings-5.7.0.tgz", - "integrity": "sha512-/9nu+lj0YswRNSH0NXYqrh8775XNyEdUQAuf3f+SmOrnVewcJ5SBNAjF7lpgehKi4abvNNXyf+HX86czCdJ8Mg==", - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "dependencies": { - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/constants": "^5.7.0", - "@ethersproject/logger": "^5.7.0" - } - }, - "node_modules/@ethersproject/transactions": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/transactions/-/transactions-5.7.0.tgz", - "integrity": "sha512-kmcNicCp1lp8qanMTC3RIikGgoJ80ztTyvtsFvCYpSCfkjhD0jZ2LOrnbcuxuToLIUYYf+4XwD1rP+B/erDIhQ==", - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "dependencies": { - "@ethersproject/address": "^5.7.0", - "@ethersproject/bignumber": "^5.7.0", - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/constants": "^5.7.0", - "@ethersproject/keccak256": "^5.7.0", - "@ethersproject/logger": "^5.7.0", - "@ethersproject/properties": "^5.7.0", - "@ethersproject/rlp": "^5.7.0", - "@ethersproject/signing-key": "^5.7.0" - } - }, - "node_modules/@ethersproject/web": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/@ethersproject/web/-/web-5.7.1.tgz", - "integrity": "sha512-Gueu8lSvyjBWL4cYsWsjh6MtMwM0+H4HvqFPZfB6dV8ctbP9zFAO73VG1cMWae0FLPCtz0peKPpZY8/ugJJX2w==", - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "dependencies": { - "@ethersproject/base64": "^5.7.0", - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/logger": "^5.7.0", - "@ethersproject/properties": "^5.7.0", - "@ethersproject/strings": "^5.7.0" - } - }, - "node_modules/@fortawesome/fontawesome-free": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-6.5.2.tgz", - "integrity": "sha512-hRILoInAx8GNT5IMkrtIt9blOdrqHOnPBH+k70aWUAqPZPgopb9G5EQJFpaBx/S8zp2fC+mPW349Bziuk1o28Q==", - "hasInstallScript": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/@humanwhocodes/config-array": { - "version": "0.11.14", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", - "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", - "dev": true, - "dependencies": { - "@humanwhocodes/object-schema": "^2.0.2", - "debug": "^4.3.1", - "minimatch": "^3.0.5" - }, - "engines": { - "node": ">=10.10.0" - } - }, - "node_modules/@humanwhocodes/module-importer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", - "dev": true, - "engines": { - "node": ">=12.22" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@humanwhocodes/object-schema": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.2.tgz", - "integrity": "sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw==", - "dev": true - }, - "node_modules/@istanbuljs/load-nyc-config": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", - "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", - "dev": true, - "dependencies": { - "camelcase": "^5.3.1", - "find-up": "^4.1.0", - "get-package-type": "^0.1.0", - "js-yaml": "^3.13.1", - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/schema": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/console": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz", - "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", - "dev": true, - "dependencies": { - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0", - "slash": "^3.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/@jest/console/node_modules/ansi-styles": { @@ -3369,20 +2941,20 @@ "integrity": "sha512-/kSXhY692qiV1MXu6EeOZvg5nECLclxNXcKCxJ3cXQgYuRymRHpdx/t7JXfsK+JLjwA1e1c1/SBrlQYpusC29Q==" }, "node_modules/@noble/curves": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.3.0.tgz", - "integrity": "sha512-t01iSXPuN+Eqzb4eBX0S5oubSqXbK/xXa1Ne18Hj8f9pStxztHCE2gfboSp/dZRLSqfuLpRK2nDXDK+W9puocA==", + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.4.2.tgz", + "integrity": "sha512-TavHr8qycMChk8UwMld0ZDRvatedkzWfH8IiaeGCfymOP5i0hSCozz9vHOL0nkwk7HRMlFnAiKpS2jrUmSybcw==", "dependencies": { - "@noble/hashes": "1.3.3" + "@noble/hashes": "1.4.0" }, "funding": { "url": "https://paulmillr.com/funding/" } }, "node_modules/@noble/hashes": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.3.tgz", - "integrity": "sha512-V7/fPHgl+jsVPXqqeOzT8egNj2iBIVt+ECeMMG8TdcnTikP3oaBtUVqpT/gYCR68aEBJSF+XbYUxStjbFMqIIA==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz", + "integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==", "engines": { "node": ">= 16" }, @@ -3434,33 +3006,33 @@ } }, "node_modules/@scure/base": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.5.tgz", - "integrity": "sha512-Brj9FiG2W1MRQSTB212YVPRrcbjkv48FoZi/u4l/zds/ieRrqsh7aUf6CLwkAq61oKXr/ZlTzlY66gLIj3TFTQ==", + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.8.tgz", + "integrity": "sha512-6CyAclxj3Nb0XT7GHK6K4zK6k2xJm6E4Ft0Ohjt4WgegiFUHEtFb2CGzmPmGBwoIhrLsqNLYfLr04Y1GePrzZg==", "funding": { "url": "https://paulmillr.com/funding/" } }, "node_modules/@scure/bip32": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.3.3.tgz", - "integrity": "sha512-LJaN3HwRbfQK0X1xFSi0Q9amqOgzQnnDngIt+ZlsBC3Bm7/nE7K0kwshZHyaru79yIVRv/e1mQAjZyuZG6jOFQ==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.4.0.tgz", + "integrity": "sha512-sVUpc0Vq3tXCkDGYVWGIZTRfnvu8LoTDaev7vbwh0omSvVORONr960MQWdKqJDCReIEmTj3PAr73O3aoxz7OPg==", "dependencies": { - "@noble/curves": "~1.3.0", - "@noble/hashes": "~1.3.2", - "@scure/base": "~1.1.4" + "@noble/curves": "~1.4.0", + "@noble/hashes": "~1.4.0", + "@scure/base": "~1.1.6" }, "funding": { "url": "https://paulmillr.com/funding/" } }, "node_modules/@scure/bip39": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.2.2.tgz", - "integrity": "sha512-HYf9TUXG80beW+hGAt3TRM8wU6pQoYur9iNypTROm42dorCGmLnFe3eWjz3gOq6G62H2WRh0FCzAR1PI+29zIA==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.3.0.tgz", + "integrity": "sha512-disdg7gHuTDZtY+ZdkmLpPCk7fxZSu3gBiEGuoC1XYxv9cGx3Z6cpTggCgW6odSOOIXCiDjuGejW+aJKCY/pIQ==", "dependencies": { - "@noble/hashes": "~1.3.2", - "@scure/base": "~1.1.4" + "@noble/hashes": "~1.4.0", + "@scure/base": "~1.1.6" }, "funding": { "url": "https://paulmillr.com/funding/" @@ -3472,17 +3044,6 @@ "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", "dev": true }, - "node_modules/@sindresorhus/is": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", - "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", - "engines": { - "node": ">=10" - }, - "funding": { - "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", @@ -3513,17 +3074,6 @@ "@sinonjs/commons": "^2.0.0" } }, - "node_modules/@szmarczak/http-timer": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-5.0.1.tgz", - "integrity": "sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==", - "dependencies": { - "defer-to-connect": "^2.0.1" - }, - "engines": { - "node": ">=14.16" - } - }, "node_modules/@tarekraafat/autocomplete.js": { "version": "10.2.7", "resolved": "https://registry.npmjs.org/@tarekraafat/autocomplete.js/-/autocomplete.js-10.2.7.tgz", @@ -3601,42 +3151,11 @@ "@types/node": "*" } }, - "node_modules/@types/cacheable-request": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.3.tgz", - "integrity": "sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==", - "dependencies": { - "@types/http-cache-semantics": "*", - "@types/keyv": "^3.1.4", - "@types/node": "*", - "@types/responselike": "^1.0.0" - } - }, "node_modules/@types/css-font-loading-module": { "version": "0.0.7", "resolved": "https://registry.npmjs.org/@types/css-font-loading-module/-/css-font-loading-module-0.0.7.tgz", "integrity": "sha512-nl09VhutdjINdWyXxHWN/w9zlNCfr60JUqJbd24YXUuCwgeL0TpFSdElCwb6cxfB6ybE19Gjj4g0jsgkXxKv1Q==" }, - "node_modules/@types/eslint": { - "version": "8.4.1", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.1.tgz", - "integrity": "sha512-GE44+DNEyxxh2Kc6ro/VkIj+9ma0pO0bwv9+uHSyBrikYOHr8zYcdPvnBOp1aw8s+CjRvuSx7CyWqRrNFQ59mA==", - "dev": true, - "dependencies": { - "@types/estree": "*", - "@types/json-schema": "*" - } - }, - "node_modules/@types/eslint-scope": { - "version": "3.7.3", - "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.3.tgz", - "integrity": "sha512-PB3ldyrcnAicT35TWPs5IcwKD8S333HMaa2VVv4+wdvebJkjWuW/xESoB8IwRcog8HYVYamb1g/R31Qv5Bx03g==", - "dev": true, - "dependencies": { - "@types/eslint": "*", - "@types/estree": "*" - } - }, "node_modules/@types/estree": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", @@ -3652,11 +3171,6 @@ "@types/node": "*" } }, - "node_modules/@types/http-cache-semantics": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz", - "integrity": "sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==" - }, "node_modules/@types/istanbul-lib-coverage": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", @@ -3704,14 +3218,6 @@ "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", "dev": true }, - "node_modules/@types/keyv": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.4.tgz", - "integrity": "sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==", - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@types/node": { "version": "16.11.6", "resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.6.tgz", @@ -3725,14 +3231,6 @@ "@types/node": "*" } }, - "node_modules/@types/responselike": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.3.tgz", - "integrity": "sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==", - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@types/secp256k1": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/@types/secp256k1/-/secp256k1-4.0.3.tgz", @@ -3753,6 +3251,14 @@ "integrity": "sha512-Q5vtl1W5ue16D+nIaW8JWebSSraJVlK+EthKn7e7UcD4KWsaSJ8BqGPXNaPghgtcn/fhvrN17Tv8ksUsQpiplw==", "dev": true }, + "node_modules/@types/ws": { + "version": "8.5.3", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.3.tgz", + "integrity": "sha512-6YOoWjruKj1uLf3INHH7D3qTXwFfEsg1kf3c0uDdSBJwfa/llkwIjrAGV7j7mVgGNbzTQ3HiHKKDXl6bJPD97w==", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/yargs": { "version": "17.0.11", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.11.tgz", @@ -4232,10 +3738,19 @@ "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==", "dev": true }, - "node_modules/abortcontroller-polyfill": { - "version": "1.7.5", - "resolved": "https://registry.npmjs.org/abortcontroller-polyfill/-/abortcontroller-polyfill-1.7.5.tgz", - "integrity": "sha512-JMJ5soJWP18htbbxJjG7bG6yuI6pRhgJ0scHHTfkUjf6wjP912xZWvM+A4sJK3gqd9E8fcPbDnOefbA9Th/FIQ==" + "node_modules/abitype": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/abitype/-/abitype-0.7.1.tgz", + "integrity": "sha512-VBkRHTDZf9Myaek/dO3yMmOzB/y2s3Zo6nVU7yaw1G+TvCHAjwaJzNGN9yo4K5D8bU/VZXKP1EJpRhFr862PlQ==", + "peerDependencies": { + "typescript": ">=4.9.4", + "zod": "^3 >=3.19.1" + }, + "peerDependenciesMeta": { + "zod": { + "optional": true + } + } }, "node_modules/abstract-leveldown": { "version": "2.6.3", @@ -4245,18 +3760,6 @@ "xtend": "~4.0.0" } }, - "node_modules/accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", - "dependencies": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" - }, - "engines": { - "node": ">= 0.6" - } - }, "node_modules/acorn": { "version": "8.9.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.9.0.tgz", @@ -4468,11 +3971,6 @@ "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.7", "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.7.tgz", @@ -4660,9 +4158,9 @@ "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" }, "node_modules/autoprefixer": { - "version": "10.4.19", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.19.tgz", - "integrity": "sha512-BaENR2+zBZ8xXhM4pUaKUxlVdxZ0EZhjvbopwnXmxRUfqDmwSpC2lAi/QXvx7NRdPCo1WKEcEF6mV64si1z4Ew==", + "version": "10.4.20", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.20.tgz", + "integrity": "sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g==", "dev": true, "funding": [ { @@ -4679,11 +4177,11 @@ } ], "dependencies": { - "browserslist": "^4.23.0", - "caniuse-lite": "^1.0.30001599", + "browserslist": "^4.23.3", + "caniuse-lite": "^1.0.30001646", "fraction.js": "^4.3.7", "normalize-range": "^0.1.2", - "picocolors": "^1.0.0", + "picocolors": "^1.0.1", "postcss-value-parser": "^4.2.0" }, "bin": { @@ -4889,22 +4387,22 @@ } }, "node_modules/babel-plugin-polyfill-corejs3": { - "version": "0.10.4", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.10.4.tgz", - "integrity": "sha512-25J6I8NGfa5YkCDogHRID3fVCadIR8/pGl1/spvCkzb6lVn6SR3ojpx9nOn9iEBcUsjY24AmdKm5khcfKdylcg==", + "version": "0.10.6", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.10.6.tgz", + "integrity": "sha512-b37+KR2i/khY5sKmWNVQAnitvquQbNdWy6lJdsr0kmquCKEEUgMKK4SboVM3HtfnZilfjr4MMQ7vY58FVWDtIA==", "dev": true, "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.6.1", - "core-js-compat": "^3.36.1" + "@babel/helper-define-polyfill-provider": "^0.6.2", + "core-js-compat": "^3.38.0" }, "peerDependencies": { "@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.6.1", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.1.tgz", - "integrity": "sha512-o7SDgTJuvx5vLKD6SFvkydkSMBvahDKGiNJzG22IZYXhiqoe9efY7zocICBgzHV4IRg5wdgl2nEL/tulKIEIbA==", + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.2.tgz", + "integrity": "sha512-LV76g+C502biUK6AyZ3LK10vDpDyCzZnhZFXkH1L75zHPj68+qc8Zfpx2th+gzwA2MzyK+1g/3EPl62yFnVttQ==", "dev": true, "dependencies": { "@babel/helper-compilation-targets": "^7.22.6", @@ -5063,80 +4561,16 @@ "node": "*" } }, - "node_modules/binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/blakejs": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/blakejs/-/blakejs-1.1.1.tgz", "integrity": "sha512-bLG6PHOCZJKNshTjGRBvET0vTciwQE6zFKOKKXPDJfwFBd4Ac0yBfPZqcGvGJap50l7ktvlpFqc2jGVaUgbJgg==" }, - "node_modules/bluebird": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", - "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" - }, "node_modules/bn.js": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==" }, - "node_modules/body-parser": { - "version": "1.20.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", - "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", - "dependencies": { - "bytes": "3.1.2", - "content-type": "~1.0.5", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.11.0", - "raw-body": "2.5.2", - "type-is": "~1.6.18", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/body-parser/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/body-parser/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, - "node_modules/body-parser/node_modules/qs": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", - "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", - "dependencies": { - "side-channel": "^1.0.4" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/boolbase": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", @@ -5271,9 +4705,9 @@ ] }, "node_modules/browserslist": { - "version": "4.23.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.0.tgz", - "integrity": "sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==", + "version": "4.23.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.3.tgz", + "integrity": "sha512-btwCFJVjI4YWDNfau8RhZ+B1Q/VLoUITrm3RlP6y1tYGWIOa+InuYiRGXUBXo8nA1qKmHMyLB/iVQg5TT4eFoA==", "funding": [ { "type": "opencollective", @@ -5289,10 +4723,10 @@ } ], "dependencies": { - "caniuse-lite": "^1.0.30001587", - "electron-to-chromium": "^1.4.668", - "node-releases": "^2.0.14", - "update-browserslist-db": "^1.0.13" + "caniuse-lite": "^1.0.30001646", + "electron-to-chromium": "^1.5.4", + "node-releases": "^2.0.18", + "update-browserslist-db": "^1.1.0" }, "bin": { "browserslist": "cli.js" @@ -5386,11 +4820,6 @@ "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" }, - "node_modules/buffer-to-arraybuffer": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/buffer-to-arraybuffer/-/buffer-to-arraybuffer-0.0.5.tgz", - "integrity": "sha512-3dthu5CYiVB1DEJp61FtApNnNndTckcqe4pFcLdvHtrpG+kcyekCJKg4MRiDcFW7A6AODnXB9U4dwQiCW5kzJQ==" - }, "node_modules/buffer-xor": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", @@ -5401,6 +4830,8 @@ "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.5.tgz", "integrity": "sha512-HTm14iMQKK2FjFLRTM5lAVcyaUzOnqbPtesFIvREgXpJHdQm8bWS+GkQgIkfaBYRHuCnea7w8UVNfwiAQhlr9A==", "hasInstallScript": true, + "optional": true, + "peer": true, "dependencies": { "node-gyp-build": "^4.3.0" }, @@ -5439,69 +4870,19 @@ "node": ">=10" } }, - "node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/cacheable-lookup": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-6.1.0.tgz", - "integrity": "sha512-KJ/Dmo1lDDhmW2XDPMo+9oiy/CeqosPguPCrgcVzKyZrL6pM1gU2GmPY/xo6OQPTUaA/c0kwHuywB4E6nmT9ww==", - "engines": { - "node": ">=10.6.0" - } - }, - "node_modules/cacheable-request": { - "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", - "http-cache-semantics": "^4.0.0", - "keyv": "^4.0.0", - "lowercase-keys": "^2.0.0", - "normalize-url": "^6.0.1", - "responselike": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cacheable-request/node_modules/get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", - "dependencies": { - "pump": "^3.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/cacheable-request/node_modules/lowercase-keys": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", - "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", - "engines": { - "node": ">=8" - } - }, "node_modules/call-bind": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.5.tgz", - "integrity": "sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.1", - "set-function-length": "^1.1.1" + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -5542,9 +4923,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001605", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001605.tgz", - "integrity": "sha512-nXwGlFWo34uliI9z3n6Qc0wZaf7zaZWA1CPZ169La5mV3I/gem7bst0vr5XQH5TJXZIMfDeZyOrZnSlVzKxxHQ==", + "version": "1.0.30001655", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001655.tgz", + "integrity": "sha512-jRGVy3iSGO5Uutn2owlb5gR6qsGngTw9ZTb4ali9f3glshcNmJ2noam4Mo9zia5P9Dk3jNNydy7vQjuE5dQmfg==", "funding": [ { "type": "opencollective", @@ -5640,49 +5021,20 @@ } }, "node_modules/chokidar": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", - "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.1.tgz", + "integrity": "sha512-n8enUVCED/KVRQlab1hr3MVpcVMvxtZjmEa956u+4YijlmQED223XMSYj2tLuKvr4jcCTzNNMpQDUer72MMmzA==", "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ], "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" + "readdirp": "^4.0.1" }, "engines": { - "node": ">= 8.10.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/chokidar/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.1" + "node": ">= 14.16.0" }, - "engines": { - "node": ">= 6" + "funding": { + "url": "https://paulmillr.com/funding/" } }, - "node_modules/chownr": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" - }, "node_modules/chrome-trace-event": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", @@ -5698,33 +5050,6 @@ "integrity": "sha512-xmDt/QIAdeZ9+nfdPsaBCpMvHNLFiLdjj59qjqn+6iPe6YmHGQ35sBnQ8uslRBXFmXkiZQOJRjvQeoGppoTjjg==", "dev": true }, - "node_modules/cids": { - "version": "0.7.5", - "resolved": "https://registry.npmjs.org/cids/-/cids-0.7.5.tgz", - "integrity": "sha512-zT7mPeghoWAu+ppn8+BS1tQ5qGmbMfB4AregnQjA/qHY3GC1m1ptI9GkWNlgeu38r7CuRdXB47uY2XgAYt6QVA==", - "deprecated": "This module has been superseded by the multiformats module", - "dependencies": { - "buffer": "^5.5.0", - "class-is": "^1.1.0", - "multibase": "~0.6.0", - "multicodec": "^1.0.0", - "multihashes": "~0.4.15" - }, - "engines": { - "node": ">=4.0.0", - "npm": ">=3.0.0" - } - }, - "node_modules/cids/node_modules/multicodec": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/multicodec/-/multicodec-1.0.4.tgz", - "integrity": "sha512-NDd7FeS3QamVtbgfvu5h7fd1IlbaC4EQ0/pgU4zqE2vdHCmBGsUa0TiM8/TdSeG6BMPC92OOCf8F1ocE/Wkrrg==", - "deprecated": "This module has been superseded by the multiformats module", - "dependencies": { - "buffer": "^5.6.0", - "varint": "^5.0.0" - } - }, "node_modules/cipher-base": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", @@ -5740,11 +5065,6 @@ "integrity": "sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ==", "dev": true }, - "node_modules/class-is": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/class-is/-/class-is-1.1.0.tgz", - "integrity": "sha512-rhjH9AG1fvabIDoGRVH587413LPjTZgmDF9fOFCbFJQV4yuocX1mHxxvXI4g3cGwbVY9wAYIoKlg1N79frJKQw==" - }, "node_modules/clipboard": { "version": "2.0.11", "resolved": "https://registry.npmjs.org/clipboard/-/clipboard-2.0.11.tgz", @@ -5791,17 +5111,6 @@ "node": ">=6" } }, - "node_modules/clone-response": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.3.tgz", - "integrity": "sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==", - "dependencies": { - "mimic-response": "^1.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/co": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", @@ -5875,54 +5184,6 @@ "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", "dev": true }, - "node_modules/content-disposition": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", - "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", - "dependencies": { - "safe-buffer": "5.2.1" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/content-disposition/node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/content-hash": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/content-hash/-/content-hash-2.5.2.tgz", - "integrity": "sha512-FvIQKy0S1JaWV10sMsA7TRx8bpU+pqPkhbsfvOJAdjRXvYxEckAwQWGwtRjiaJfh+E0DvcWUGqcdjwMGFjsSdw==", - "dependencies": { - "cids": "^0.7.1", - "multicodec": "^0.5.5", - "multihashes": "^0.4.15" - } - }, - "node_modules/content-type": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", - "engines": { - "node": ">= 0.6" - } - }, "node_modules/convert-source-map": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz", @@ -5932,19 +5193,6 @@ "safe-buffer": "~5.1.1" } }, - "node_modules/cookie": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", - "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" - }, "node_modules/cookiejar": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.4.tgz", @@ -5983,9 +5231,9 @@ } }, "node_modules/core-js": { - "version": "3.37.1", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.37.1.tgz", - "integrity": "sha512-Xn6qmxrQZyB0FFY8E3bgRXei3lWDJHhvI+u0q9TKIYM49G8pAr0FgnnrFRAmsbptZL1yxRADVXn+x5AGsbBfyw==", + "version": "3.38.1", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.38.1.tgz", + "integrity": "sha512-OP35aUorbU3Zvlx7pjsFdu1rGNnD4pgw/CWoYzRY3t2EzoVT7shKHY1dlAy3f41cGIO7ZDPQimhGFTlEYkG/Hw==", "hasInstallScript": true, "funding": { "type": "opencollective", @@ -5993,11 +5241,11 @@ } }, "node_modules/core-js-compat": { - "version": "3.36.1", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.36.1.tgz", - "integrity": "sha512-Dk997v9ZCt3X/npqzyGdTlq6t7lDBhZwGvV94PKzDArjp7BTRm7WlDAXYd/OWdeFHO8OChQYRJNJvUCqCbrtKA==", + "version": "3.38.1", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.38.1.tgz", + "integrity": "sha512-JRH6gfXxGmrzF3tZ57lFx97YARxCXPaMzPo6jELZhv88pBH5VXpQ+y0znKGlFnzuaihqhLbefxSJxWJMPtfDzw==", "dependencies": { - "browserslist": "^4.23.0" + "browserslist": "^4.23.3" }, "funding": { "type": "opencollective", @@ -6009,18 +5257,6 @@ "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" }, - "node_modules/cors": { - "version": "2.8.5", - "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", - "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", - "dependencies": { - "object-assign": "^4", - "vary": "^1" - }, - "engines": { - "node": ">= 0.10" - } - }, "node_modules/cosmiconfig": { "version": "9.0.0", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", @@ -6604,15 +5840,6 @@ "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", "dev": true }, - "node_modules/d": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", - "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==", - "dependencies": { - "es5-ext": "^0.10.50", - "type": "^1.0.1" - } - }, "node_modules/dashdash": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", @@ -6676,31 +5903,6 @@ "node": ">=0.10" } }, - "node_modules/decompress-response": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", - "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", - "dependencies": { - "mimic-response": "^3.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/decompress-response/node_modules/mimic-response": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", - "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/dedent": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.1.tgz", @@ -6741,14 +5943,6 @@ "node": ">=0.10.0" } }, - "node_modules/defer-to-connect": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", - "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", - "engines": { - "node": ">=10" - } - }, "node_modules/deferred-leveldown": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/deferred-leveldown/-/deferred-leveldown-1.2.2.tgz", @@ -6758,16 +5952,19 @@ } }, "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==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", "dependencies": { - "get-intrinsic": "^1.2.1", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.0" + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" }, "engines": { "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/define-properties": { @@ -6798,14 +5995,6 @@ "resolved": "https://registry.npmjs.org/delegate/-/delegate-3.2.0.tgz", "integrity": "sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw==" }, - "node_modules/depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/des.js": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.1.tgz", @@ -6815,15 +6004,6 @@ "minimalistic-assert": "^1.0.0" } }, - "node_modules/destroy": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", - "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, "node_modules/detect-browser": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/detect-browser/-/detect-browser-5.2.1.tgz", @@ -6965,20 +6145,15 @@ "safer-buffer": "^2.1.0" } }, - "node_modules/ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" - }, "node_modules/electron-to-chromium": { - "version": "1.4.692", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.692.tgz", - "integrity": "sha512-d5rZRka9n2Y3MkWRN74IoAsxR0HK3yaAt7T50e3iT9VZmCCQDT3geXUO5ZRMhDToa1pkCeQXuNo+0g+NfDOVPA==" + "version": "1.5.13", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.13.tgz", + "integrity": "sha512-lbBcvtIJ4J6sS4tb5TLp1b4LyfCdMkwStzXPyAgVgTRAsep4bvrAGaBOP7ZJtQMNJpSQ9SqG4brWOroNaQtm7Q==" }, "node_modules/elliptic": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", - "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==", + "version": "6.5.7", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.7.tgz", + "integrity": "sha512-ESVCtTwiA+XhY3wyh24QqRGBoP3rEdDUl3EDUUo9tft074fi19IrdpH7hLCMMP3CIj7jb3W96rn8lt/BqIlt5Q==", "dependencies": { "bn.js": "^4.11.9", "brorand": "^1.1.0", @@ -7021,14 +6196,6 @@ "node": ">= 4" } }, - "node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/encoding": { "version": "0.1.13", "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", @@ -7052,18 +6219,10 @@ "node": ">=0.10.0" } }, - "node_modules/end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "dependencies": { - "once": "^1.4.0" - } - }, "node_modules/enhanced-resolve": { - "version": "5.17.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.0.tgz", - "integrity": "sha512-dwDPwZL0dmye8Txp2gzFmA6sxALaSvdRDjPH0viLcKrtlOL3tw62nWWweVD1SdILDTJrbrL6tdWVN58Wo6U3eA==", + "version": "5.17.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz", + "integrity": "sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==", "dev": true, "dependencies": { "graceful-fs": "^4.2.4", @@ -7179,6 +6338,25 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/es-module-lexer": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.2.1.tgz", @@ -7225,58 +6403,14 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/es5-ext": { - "version": "0.10.64", - "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.64.tgz", - "integrity": "sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==", - "hasInstallScript": true, - "dependencies": { - "es6-iterator": "^2.0.3", - "es6-symbol": "^3.1.3", - "esniff": "^2.0.1", - "next-tick": "^1.1.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/es6-iterator": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", - "integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==", - "dependencies": { - "d": "1", - "es5-ext": "^0.10.35", - "es6-symbol": "^3.1.1" - } - }, - "node_modules/es6-promise": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", - "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==" - }, - "node_modules/es6-symbol": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz", - "integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==", - "dependencies": { - "d": "^1.0.1", - "ext": "^1.1.2" - } - }, - "node_modules/escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", "engines": { "node": ">=6" } }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" - }, "node_modules/escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", @@ -7368,16 +6502,16 @@ } }, "node_modules/eslint": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", - "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", + "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", "@eslint/eslintrc": "^2.1.4", - "@eslint/js": "8.57.0", - "@humanwhocodes/config-array": "^0.11.14", + "@eslint/js": "8.57.1", + "@humanwhocodes/config-array": "^0.13.0", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", "@ungap/structured-clone": "^1.2.0", @@ -7950,25 +7084,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/esniff": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/esniff/-/esniff-2.0.1.tgz", - "integrity": "sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==", - "dependencies": { - "d": "^1.0.1", - "es5-ext": "^0.10.62", - "event-emitter": "^0.3.5", - "type": "^2.7.2" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/esniff/node_modules/type": { - "version": "2.7.2", - "resolved": "https://registry.npmjs.org/type/-/type-2.7.2.tgz", - "integrity": "sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw==" - }, "node_modules/espree": { "version": "9.6.1", "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", @@ -8059,14 +7174,6 @@ "node": ">=0.10.0" } }, - "node_modules/etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", - "engines": { - "node": ">= 0.6" - } - }, "node_modules/eth-block-tracker": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/eth-block-tracker/-/eth-block-tracker-4.4.3.tgz", @@ -8080,20 +7187,6 @@ "safe-event-emitter": "^1.0.1" } }, - "node_modules/eth-ens-namehash": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/eth-ens-namehash/-/eth-ens-namehash-2.0.8.tgz", - "integrity": "sha512-VWEI1+KJfz4Km//dadyvBBoBeSQ0MHTXPvr8UIXiLW6IanxvAV+DmlZAijZwAyggqGUfwQBeHf7tc9wzc1piSw==", - "dependencies": { - "idna-uts46-hx": "^2.3.1", - "js-sha3": "^0.5.7" - } - }, - "node_modules/eth-ens-namehash/node_modules/js-sha3": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.5.7.tgz", - "integrity": "sha512-GII20kjaPX0zJ8wzkTbNDYMY7msuZcTWk8S5UOh6806Jq/wz1J8/bnr8uGU0DAUmYDjj2Mr4X1cW8v/GLYnR+g==" - }, "node_modules/eth-json-rpc-filters": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/eth-json-rpc-filters/-/eth-json-rpc-filters-4.2.2.tgz", @@ -8184,34 +7277,6 @@ "safe-event-emitter": "^1.0.1" } }, - "node_modules/eth-lib": { - "version": "0.1.29", - "resolved": "https://registry.npmjs.org/eth-lib/-/eth-lib-0.1.29.tgz", - "integrity": "sha512-bfttrr3/7gG4E02HoWTDUcDDslN003OlOoBxk9virpAZQ1ja/jDgwkWB8QfJF7ojuEowrqy+lzp9VcJG7/k5bQ==", - "dependencies": { - "bn.js": "^4.11.6", - "elliptic": "^6.4.0", - "nano-json-stream-parser": "^0.1.2", - "servify": "^0.1.12", - "ws": "^3.0.0", - "xhr-request-promise": "^0.1.2" - } - }, - "node_modules/eth-lib/node_modules/bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" - }, - "node_modules/eth-lib/node_modules/ws": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-3.3.3.tgz", - "integrity": "sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA==", - "dependencies": { - "async-limiter": "~1.0.0", - "safe-buffer": "~5.1.0", - "ultron": "~1.1.0" - } - }, "node_modules/eth-net-props": { "version": "1.0.41", "resolved": "https://registry.npmjs.org/eth-net-props/-/eth-net-props-1.0.41.tgz", @@ -8266,14 +7331,6 @@ "safe-buffer": "^5.1.1" } }, - "node_modules/ethereum-bloom-filters": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/ethereum-bloom-filters/-/ethereum-bloom-filters-1.0.10.tgz", - "integrity": "sha512-rxJ5OFN3RwjQxDcFP2Z5+Q9ho4eIdEmSc2ht0fCu8Se9nbXjZ7/031uXoUYJ87KHCOdVeiUuwSnoS7hmYAGVHA==", - "dependencies": { - "js-sha3": "^0.8.0" - } - }, "node_modules/ethereum-common": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/ethereum-common/-/ethereum-common-0.2.0.tgz", @@ -8430,29 +7487,6 @@ "safe-buffer": "^5.1.1" } }, - "node_modules/ethereumjs-util": { - "version": "7.1.5", - "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-7.1.5.tgz", - "integrity": "sha512-SDl5kKrQAudFBUe5OJM9Ac6WmMyYmXX/6sTmLZ3ffG2eY6ZIGBes3pEDxNN6V72WyOw4CPD5RomKdsa8DAAwLg==", - "dependencies": { - "@types/bn.js": "^5.1.0", - "bn.js": "^5.1.2", - "create-hash": "^1.1.2", - "ethereum-cryptography": "^0.1.3", - "rlp": "^2.2.4" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/ethereumjs-util/node_modules/@types/bn.js": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-5.1.5.tgz", - "integrity": "sha512-V46N0zwKRF5Q00AZ6hWtN0T8gGmDUaUzLWQvHFo5yThtVwK/VCenFY3wXVbOvNfajEpsTfQM4IN9k/d6gUVX3A==", - "dependencies": { - "@types/node": "*" - } - }, "node_modules/ethereumjs-vm": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/ethereumjs-vm/-/ethereumjs-vm-2.6.0.tgz", @@ -8528,24 +7562,6 @@ "rlp": "^2.2.3" } }, - "node_modules/ethjs-unit": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/ethjs-unit/-/ethjs-unit-0.1.6.tgz", - "integrity": "sha512-/Sn9Y0oKl0uqQuvgFk/zQgR7aw1g36qX/jzSQ5lSwlO0GigPymk4eGQfeNTD03w1dPOqfz8V77Cy43jH56pagw==", - "dependencies": { - "bn.js": "4.11.6", - "number-to-bn": "1.7.0" - }, - "engines": { - "node": ">=6.5.0", - "npm": ">=3" - } - }, - "node_modules/ethjs-unit/node_modules/bn.js": { - "version": "4.11.6", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.6.tgz", - "integrity": "sha512-XWwnNNFCuuSQ0m3r3C4LE3EiORltHd9M05pq6FOlVeiophzRbMo50Sbz1ehl8K3Z+jw9+vmgnXefY1hz8X+2wA==" - }, "node_modules/ethjs-util": { "version": "0.1.6", "resolved": "https://registry.npmjs.org/ethjs-util/-/ethjs-util-0.1.6.tgz", @@ -8559,19 +7575,10 @@ "npm": ">=3" } }, - "node_modules/event-emitter": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", - "integrity": "sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==", - "dependencies": { - "d": "1", - "es5-ext": "~0.10.14" - } - }, "node_modules/eventemitter3": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.4.tgz", - "integrity": "sha512-rlaVLnVxtxvoyLsQQFBx53YmXHDxRIzzTLbdfxqi4yocpSjAxXwkU0cScM5JgSKMqEhrZpnvQ2D9gjylR0AimQ==" + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==" }, "node_modules/events": { "version": "3.3.0", @@ -8638,106 +7645,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/express": { - "version": "4.19.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", - "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", - "dependencies": { - "accepts": "~1.3.8", - "array-flatten": "1.1.1", - "body-parser": "1.20.2", - "content-disposition": "0.5.4", - "content-type": "~1.0.4", - "cookie": "0.6.0", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "2.0.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "1.2.0", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "merge-descriptors": "1.0.1", - "methods": "~1.1.2", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", - "proxy-addr": "~2.0.7", - "qs": "6.11.0", - "range-parser": "~1.2.1", - "safe-buffer": "5.2.1", - "send": "0.18.0", - "serve-static": "1.15.0", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" - }, - "engines": { - "node": ">= 0.10.0" - } - }, - "node_modules/express/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/express/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, - "node_modules/express/node_modules/qs": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", - "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", - "dependencies": { - "side-channel": "^1.0.4" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/express/node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/ext": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz", - "integrity": "sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==", - "dependencies": { - "type": "^2.7.2" - } - }, - "node_modules/ext/node_modules/type": { - "version": "2.7.2", - "resolved": "https://registry.npmjs.org/type/-/type-2.7.2.tgz", - "integrity": "sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw==" - }, "node_modules/extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", @@ -8899,36 +7806,6 @@ "node": ">=8" } }, - "node_modules/finalhandler": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", - "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", - "dependencies": { - "debug": "2.6.9", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "statuses": "2.0.1", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/finalhandler/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/finalhandler/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, "node_modules/find-cache-dir": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-4.0.0.tgz", @@ -9015,19 +7892,6 @@ "node": ">= 0.12" } }, - "node_modules/form-data-encoder": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-1.7.1.tgz", - "integrity": "sha512-EFRDrsMm/kyqbTQocNvRXMLjc7Es2Vk+IQFx/YW7hkUH1eBl4J1fqiP34l74Yt0pFLCNpc06fkbVk00008mzjg==" - }, - "node_modules/forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", - "engines": { - "node": ">= 0.6" - } - }, "node_modules/fraction.js": { "version": "4.3.7", "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", @@ -9041,32 +7905,6 @@ "url": "https://github.com/sponsors/rawify" } }, - "node_modules/fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fs-extra": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-4.0.3.tgz", - "integrity": "sha512-q6rbdDd1o2mAnQreO7YADIxf/Whx4AHBiRf6d+/cVT8h44ss+lHgxf1FemcqDnQt9X3ct4McHr+JMGlYSsK7Cg==", - "dependencies": { - "graceful-fs": "^4.1.2", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - } - }, - "node_modules/fs-minipass": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.7.tgz", - "integrity": "sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==", - "dependencies": { - "minipass": "^2.6.0" - } - }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -9152,15 +7990,19 @@ } }, "node_modules/get-intrinsic": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.2.tgz", - "integrity": "sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", "dependencies": { + "es-errors": "^1.3.0", "function-bind": "^1.1.2", "has-proto": "^1.0.1", "has-symbols": "^1.0.3", "hasown": "^2.0.0" }, + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -9178,6 +8020,7 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, "engines": { "node": ">=10" }, @@ -9342,36 +8185,11 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/got": { - "version": "12.1.0", - "resolved": "https://registry.npmjs.org/got/-/got-12.1.0.tgz", - "integrity": "sha512-hBv2ty9QN2RdbJJMK3hesmSkFTjVIHyIDDbssCKnSmq62edGgImJWD10Eb1k77TiV1bxloxqcFAVK8+9pkhOig==", - "dependencies": { - "@sindresorhus/is": "^4.6.0", - "@szmarczak/http-timer": "^5.0.1", - "@types/cacheable-request": "^6.0.2", - "@types/responselike": "^1.0.0", - "cacheable-lookup": "^6.0.4", - "cacheable-request": "^7.0.2", - "decompress-response": "^6.0.0", - "form-data-encoder": "1.7.1", - "get-stream": "^6.0.1", - "http2-wrapper": "^2.1.10", - "lowercase-keys": "^3.0.0", - "p-cancelable": "^3.0.0", - "responselike": "^2.0.0" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sindresorhus/got?sponsor=1" - } - }, "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true }, "node_modules/graphemer": { "version": "1.4.0", @@ -9418,11 +8236,11 @@ } }, "node_modules/has-property-descriptors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", - "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", "dependencies": { - "get-intrinsic": "^1.1.1" + "es-define-property": "^1.0.0" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -9560,31 +8378,6 @@ "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", "dev": true }, - "node_modules/http-cache-semantics": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", - "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==" - }, - "node_modules/http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", - "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/http-https": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/http-https/-/http-https-1.0.0.tgz", - "integrity": "sha512-o0PWwVCSp3O0wS6FvNr6xfBCHgt0m1tvPLFOCc2iFDKTRAXhB7m8klDf7ErowFH8POa6dVdGatKU5I1YYwzUyg==" - }, "node_modules/http-signature": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", @@ -9599,18 +8392,6 @@ "npm": ">=1.3.7" } }, - "node_modules/http2-wrapper": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-2.2.1.tgz", - "integrity": "sha512-V5nVw1PAOgfI3Lmeaj2Exmeg7fenjhRUgz1lPSezy1CuhPYbgQtbQj4jZfEAEMlaL+vupsvhjqCyjzob0yxsmQ==", - "dependencies": { - "quick-lru": "^5.1.1", - "resolve-alpn": "^1.2.0" - }, - "engines": { - "node": ">=10.19.0" - } - }, "node_modules/https-browserify": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz", @@ -9643,17 +8424,6 @@ "resolved": "https://registry.npmjs.org/humps/-/humps-2.0.1.tgz", "integrity": "sha1-3QLqYIG9BWjcXQcxhEY5V7qe+ao=" }, - "node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/icss-utils": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", @@ -9666,25 +8436,6 @@ "postcss": "^8.1.0" } }, - "node_modules/idna-uts46-hx": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/idna-uts46-hx/-/idna-uts46-hx-2.3.1.tgz", - "integrity": "sha512-PWoF9Keq6laYdIRwwCdhTPl60xRqAloYNMQLiyUnG42VjT53oW07BXIRM+NK7eQjzXjAk2gUvX9caRxlnF9TAA==", - "dependencies": { - "punycode": "2.1.0" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/idna-uts46-hx/node_modules/punycode": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.0.tgz", - "integrity": "sha512-Yxz2kRwT90aPiWEMHVYnEf4+rhwF1tBmmZ4KepCP+Wkium9JxtWnUm1nqGwpiAHr/tnTSeHqr3wb++jgSkXjhA==", - "engines": { - "node": ">=6" - } - }, "node_modules/ieee754": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", @@ -9815,14 +8566,6 @@ "node": ">=10.13.0" } }, - "node_modules/ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", - "engines": { - "node": ">= 0.10" - } - }, "node_modules/is-arguments": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", @@ -9870,18 +8613,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/is-boolean-object": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", @@ -10209,6 +8940,14 @@ "node": ">=0.10.0" } }, + "node_modules/isomorphic-ws": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-5.0.0.tgz", + "integrity": "sha512-muId7Zzn9ywDsyXgTIafTry2sV3nySZeUDe6YedVd1Hvuuep5AsIlqK+XefWpYTyJG5e503F2xIuT2lcU6rCSw==", + "peerDependencies": { + "ws": "*" + } + }, "node_modules/isstream": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", @@ -12129,11 +10868,6 @@ "node": ">=4" } }, - "node_modules/json-buffer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==" - }, "node_modules/json-parse-even-better-errors": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", @@ -12205,14 +10939,6 @@ "node": ">=6" } }, - "node_modules/jsonfile": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, "node_modules/jsonify": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", @@ -12249,14 +10975,6 @@ "node": ">=10.0.0" } }, - "node_modules/keyv": { - "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" - } - }, "node_modules/keyvaluestorage-interface": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/keyvaluestorage-interface/-/keyvaluestorage-interface-1.0.0.tgz", @@ -12586,17 +11304,6 @@ "loose-envify": "cli.js" } }, - "node_modules/lowercase-keys": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-3.0.0.tgz", - "integrity": "sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ==", - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", @@ -12615,9 +11322,9 @@ "integrity": "sha1-81ypHEk/e3PaDgdJUwTxezH4fuU=" }, "node_modules/luxon": { - "version": "3.4.4", - "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.4.4.tgz", - "integrity": "sha512-zobTr7akeGHnv7eBOXcRgMeCP6+uyYsczwmeRCauvpvaAltgNyTbLH/+VaEAPUeWBT+1GuNmz4wC/6jtQzbbVA==", + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.5.0.tgz", + "integrity": "sha512-rh+Zjr6DNfUYR3bPwJEnuwDdqMbxZW7LOQfUN4B54+Cl+0o5zaU9RJ6bcidfDtC1cWCZXQ+nvX8bf6bAji37QQ==", "engines": { "node": ">=12" } @@ -12685,14 +11392,6 @@ "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==", "dev": true }, - "node_modules/media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", - "engines": { - "node": ">= 0.6" - } - }, "node_modules/memdown": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/memdown/-/memdown-1.4.1.tgz", @@ -12714,11 +11413,6 @@ "xtend": "~4.0.0" } }, - "node_modules/merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" - }, "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", @@ -12795,19 +11489,6 @@ "safe-buffer": "~5.1.0" } }, - "node_modules/methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", - "engines": { - "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", @@ -12838,17 +11519,6 @@ "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" }, - "node_modules/mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/mime-db": { "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", @@ -12877,14 +11547,6 @@ "node": ">=6" } }, - "node_modules/mimic-response": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", - "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", - "engines": { - "node": ">=4" - } - }, "node_modules/min-document": { "version": "2.19.0", "resolved": "https://registry.npmjs.org/min-document/-/min-document-2.19.0.tgz", @@ -12894,9 +11556,9 @@ } }, "node_modules/mini-css-extract-plugin": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.9.0.tgz", - "integrity": "sha512-Zs1YsZVfemekSZG+44vBsYTLQORkPMwnlv+aehcxK/NLKC+EGhDB39/YePYYqx/sTk6NnYpuqikhSn7+JIevTA==", + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.9.1.tgz", + "integrity": "sha512-+Vyi+GCCOHnrJ2VPS+6aPoXN2k2jgUzDRhTFLjjTBn23qyXJXkjUWQgTL+mXpF5/A8ixLdCc6kWsoeOjKGejKQ==", "dev": true, "dependencies": { "schema-utils": "^4.0.0", @@ -12938,29 +11600,8 @@ "node_modules/minimist": { "version": "1.2.6", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", - "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==" - }, - "node_modules/minipass": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz", - "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==", - "dependencies": { - "safe-buffer": "^5.1.2", - "yallist": "^3.0.0" - } - }, - "node_modules/minipass/node_modules/yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" - }, - "node_modules/minizlib": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.3.3.tgz", - "integrity": "sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==", - "dependencies": { - "minipass": "^2.9.0" - } + "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", + "dev": true }, "node_modules/mitt": { "version": "3.0.1", @@ -12968,44 +11609,13 @@ "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==" }, "node_modules/mixpanel-browser": { - "version": "2.54.1", - "resolved": "https://registry.npmjs.org/mixpanel-browser/-/mixpanel-browser-2.54.1.tgz", - "integrity": "sha512-1FDs/E0UHPJLry/J0r3pBXrnEhJnz7H2CqzWWZgmdDAm3WiMva69X/qFIqcW3TOmHPaprJVfgwzqt6MJavMrHQ==", + "version": "2.55.1", + "resolved": "https://registry.npmjs.org/mixpanel-browser/-/mixpanel-browser-2.55.1.tgz", + "integrity": "sha512-NSEPdFSJxoR1OCKWKHbtqd3BeH1c9NjXbEt0tN5TgBEO1nSDji6niU9n4MopAXOP0POET9spjpQKxZtLZKTJwA==", "dependencies": { "rrweb": "2.0.0-alpha.13" } }, - "node_modules/mkdirp": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz", - "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==", - "bin": { - "mkdirp": "dist/cjs/src/bin.js" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/mkdirp-promise": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/mkdirp-promise/-/mkdirp-promise-5.0.1.tgz", - "integrity": "sha512-Hepn5kb1lJPtVW84RFT40YG1OddBNTOVUZR2bzQUHc+Z03en8/3uX0+060JDhcEzyO08HmipsN9DcnFMxhIL9w==", - "deprecated": "This package is broken and no longer maintained. 'mkdirp' itself supports promises now, please switch to that.", - "dependencies": { - "mkdirp": "*" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/mock-fs": { - "version": "4.14.0", - "resolved": "https://registry.npmjs.org/mock-fs/-/mock-fs-4.14.0.tgz", - "integrity": "sha512-qYvlv/exQ4+svI3UOvPUpLDF0OMX5euvUH0Ny4N5QyRyhNdgAgUrVH3iUINSzEPLvx0kbo/Bp28GJKIqvE7URw==" - }, "node_modules/moment": { "version": "2.30.1", "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", @@ -13019,50 +11629,6 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, - "node_modules/multibase": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/multibase/-/multibase-0.6.1.tgz", - "integrity": "sha512-pFfAwyTjbbQgNc3G7D48JkJxWtoJoBMaR4xQUOuB8RnCgRqaYmWNFeJTTvrJ2w51bjLq2zTby6Rqj9TQ9elSUw==", - "deprecated": "This module has been superseded by the multiformats module", - "dependencies": { - "base-x": "^3.0.8", - "buffer": "^5.5.0" - } - }, - "node_modules/multicodec": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/multicodec/-/multicodec-0.5.7.tgz", - "integrity": "sha512-PscoRxm3f+88fAtELwUnZxGDkduE2HD9Q6GHUOywQLjOGT/HAdhjLDYNZ1e7VR0s0TP0EwZ16LNUTFpoBGivOA==", - "deprecated": "This module has been superseded by the multiformats module", - "dependencies": { - "varint": "^5.0.0" - } - }, - "node_modules/multihashes": { - "version": "0.4.21", - "resolved": "https://registry.npmjs.org/multihashes/-/multihashes-0.4.21.tgz", - "integrity": "sha512-uVSvmeCWf36pU2nB4/1kzYZjsXD9vofZKpgudqkceYY5g2aZZXJ5r9lxuzoRLl1OAp28XljXsEJ/X/85ZsKmKw==", - "dependencies": { - "buffer": "^5.5.0", - "multibase": "^0.7.0", - "varint": "^5.0.0" - } - }, - "node_modules/multihashes/node_modules/multibase": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/multibase/-/multibase-0.7.0.tgz", - "integrity": "sha512-TW8q03O0f6PNFTQDvh3xxH03c8CjGaaYrjkl9UQPG6rz53TQzzxJVCIWVjzcbN/Q5Y53Zd0IBQBMVktVgNx4Fg==", - "deprecated": "This module has been superseded by the multiformats module", - "dependencies": { - "base-x": "^3.0.8", - "buffer": "^5.5.0" - } - }, - "node_modules/nano-json-stream-parser": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/nano-json-stream-parser/-/nano-json-stream-parser-0.1.2.tgz", - "integrity": "sha512-9MqxMH/BSJC7dnLsEMPyfN5Dvoo49IsPFYMcHw3Bcfc2kN0lpHRBSzlMSVx4HGyJ7s9B31CyBTVehWJoQ8Ctew==" - }, "node_modules/nanoassert": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/nanoassert/-/nanoassert-1.1.0.tgz", @@ -13100,25 +11666,12 @@ "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", "dev": true }, - "node_modules/negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", - "engines": { - "node": ">= 0.6" - } - }, "node_modules/neo-async": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", "dev": true }, - "node_modules/next-tick": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz", - "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==" - }, "node_modules/node-addon-api": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-2.0.2.tgz", @@ -13179,9 +11732,9 @@ "dev": true }, "node_modules/node-releases": { - "version": "2.0.14", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", - "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==" + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", + "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==" }, "node_modules/normalize-path": { "version": "3.0.0", @@ -13201,17 +11754,6 @@ "node": ">=0.10.0" } }, - "node_modules/normalize-url": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", - "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/npm-run-path": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", @@ -13236,24 +11778,6 @@ "url": "https://github.com/fb55/nth-check?sponsor=1" } }, - "node_modules/number-to-bn": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/number-to-bn/-/number-to-bn-1.7.0.tgz", - "integrity": "sha512-wsJ9gfSz1/s4ZsJN01lyonwuxA1tml6X1yBDnfpMglypcBRFZZkus26EdPSlqS5GJfYddVZa22p3VNb3z5m5Ig==", - "dependencies": { - "bn.js": "4.11.6", - "strip-hex-prefix": "1.0.0" - }, - "engines": { - "node": ">=6.5.0", - "npm": ">=3" - } - }, - "node_modules/number-to-bn/node_modules/bn.js": { - "version": "4.11.6", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.6.tgz", - "integrity": "sha512-XWwnNNFCuuSQ0m3r3C4LE3EiORltHd9M05pq6FOlVeiophzRbMo50Sbz1ehl8K3Z+jw9+vmgnXefY1hz8X+2wA==" - }, "node_modules/numeral": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/numeral/-/numeral-2.0.6.tgz", @@ -13378,29 +11902,11 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/oboe": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/oboe/-/oboe-2.1.5.tgz", - "integrity": "sha512-zRFWiF+FoicxEs3jNI/WYUrVEgA7DeET/InK0XQuudGHRg8iIob3cNPrJTKaz4004uaA9Pbe+Dwa8iluhjLZWA==", - "dependencies": { - "http-https": "^1.0.0" - } - }, - "node_modules/on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, "dependencies": { "wrappy": "1" } @@ -13442,14 +11948,6 @@ "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz", "integrity": "sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc=" }, - "node_modules/p-cancelable": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-3.0.0.tgz", - "integrity": "sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw==", - "engines": { - "node": ">=12.20" - } - }, "node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -13559,14 +12057,6 @@ "url": "https://github.com/inikulin/parse5?sponsor=1" } }, - "node_modules/parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/path-exists": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", @@ -13607,11 +12097,6 @@ "tslib": "^1.10.0" } }, - "node_modules/path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" - }, "node_modules/pathval": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", @@ -13657,9 +12142,9 @@ } }, "node_modules/picocolors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", - "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==" + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz", + "integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==" }, "node_modules/picomatch": { "version": "2.3.0", @@ -13811,9 +12296,9 @@ } }, "node_modules/postcss": { - "version": "8.4.39", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.39.tgz", - "integrity": "sha512-0vzE+lAiG7hZl1/9I8yzKLx3aR9Xbof3fBHKunvMfOCYAtMhrsnccJY2iTURb9EZd5+pLuiNV9/c/GZJOHsgIw==", + "version": "8.4.47", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz", + "integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==", "dev": true, "funding": [ { @@ -13831,8 +12316,8 @@ ], "dependencies": { "nanoid": "^3.3.7", - "picocolors": "^1.0.1", - "source-map-js": "^1.2.0" + "picocolors": "^1.1.0", + "source-map-js": "^1.2.1" }, "engines": { "node": "^10 || ^12 || >=14" @@ -14474,18 +12959,6 @@ "react-is": "^16.8.1" } }, - "node_modules/proxy-addr": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", - "dependencies": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" - }, - "engines": { - "node": ">= 0.10" - } - }, "node_modules/prr": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", @@ -14514,15 +12987,6 @@ "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" }, - "node_modules/pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, "node_modules/punycode": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", @@ -14722,19 +13186,6 @@ "node": ">=0.6" } }, - "node_modules/query-string": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/query-string/-/query-string-5.1.1.tgz", - "integrity": "sha512-gjWOsm2SoGlgLEdAGt7a6slVOk9mGiXmPFMqrEhLQ68rhQuBnpfs3+EmlvqKyxnCo9/PPlF+9MtY02S1aFg+Jw==", - "dependencies": { - "decode-uri-component": "^0.2.0", - "object-assign": "^4.1.0", - "strict-uri-encode": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/querystringify": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", @@ -14761,17 +13212,6 @@ } ] }, - "node_modules/quick-lru": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", - "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -14789,28 +13229,6 @@ "safe-buffer": "^5.1.0" } }, - "node_modules/range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/raw-body": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", - "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", - "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, "node_modules/react": { "version": "16.14.0", "resolved": "https://registry.npmjs.org/react/-/react-16.14.0.tgz", @@ -14857,15 +13275,16 @@ } }, "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.0.1.tgz", + "integrity": "sha512-GkMg9uOTpIWWKbSsgwb5fA4EavTR+SG/PMPoAY8hkhHfEEY0/vqljY+XHqtDf2cr2IJtoNRDbrrEpZUiZCkYRw==", "dev": true, - "dependencies": { - "picomatch": "^2.2.1" - }, "engines": { - "node": ">=8.10.0" + "node": ">= 14.16.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" } }, "node_modules/rechoir": { @@ -15076,11 +13495,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/resolve-alpn": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", - "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==" - }, "node_modules/resolve-cwd": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", @@ -15120,25 +13534,6 @@ "node": ">=10" } }, - "node_modules/responselike": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.1.tgz", - "integrity": "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==", - "dependencies": { - "lowercase-keys": "^2.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/responselike/node_modules/lowercase-keys": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", - "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", - "engines": { - "node": ">=8" - } - }, "node_modules/reusify": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", @@ -15298,12 +13693,12 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "node_modules/sass": { - "version": "1.77.8", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.77.8.tgz", - "integrity": "sha512-4UHg6prsrycW20fqLGPShtEvo/WyHRVRHwOP4DzkUrObWoWI05QBSfzU71TVB7PFaL104TwNaHpjlWXAZbQiNQ==", + "version": "1.79.4", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.79.4.tgz", + "integrity": "sha512-K0QDSNPXgyqO4GZq2HO5Q70TLxTH6cIT59RdoCHMivrC8rqzaTw5ab9prjz9KUN1El4FLXrBXJhik61JR4HcGg==", "dev": true, "dependencies": { - "chokidar": ">=3.0.0 <4.0.0", + "chokidar": "^4.0.0", "immutable": "^4.0.0", "source-map-js": ">=0.6.2 <2.0.0" }, @@ -15439,19 +13834,24 @@ "integrity": "sha512-8CYNl/bjkEhXWbDTU/K7c2jQtrnqEffIPyOLMqygW/7/b+ym8UtQumcAZjOfMLjZKR6AxK5tOr9fChbQZCzPqg==" }, "node_modules/secp256k1": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/secp256k1/-/secp256k1-4.0.2.tgz", - "integrity": "sha512-UDar4sKvWAksIlfX3xIaQReADn+WFnHvbVujpcbr+9Sf/69odMwy2MUsz5CKLQgX9nsIyrjuxL2imVyoNHa3fg==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/secp256k1/-/secp256k1-4.0.4.tgz", + "integrity": "sha512-6JfvwvjUOn8F/jUoBY2Q1v5WY5XS+rj8qSe0v8Y4ezH4InLgTEeOOPQsRll9OV429Pvo6BCHGavIyJfr3TAhsw==", "hasInstallScript": true, "dependencies": { - "elliptic": "^6.5.2", - "node-addon-api": "^2.0.0", + "elliptic": "^6.5.7", + "node-addon-api": "^5.0.0", "node-gyp-build": "^4.2.0" }, "engines": { - "node": ">=10.0.0" + "node": ">=18.0.0" } }, + "node_modules/secp256k1/node_modules/node-addon-api": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz", + "integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==" + }, "node_modules/select": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/select/-/select-1.1.2.tgz", @@ -15473,47 +13873,6 @@ "semver": "bin/semver.js" } }, - "node_modules/send": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", - "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", - "dependencies": { - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "2.4.1", - "range-parser": "~1.2.1", - "statuses": "2.0.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/send/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/send/node_modules/debug/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, - "node_modules/send/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" - }, "node_modules/serialize-javascript": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", @@ -15523,49 +13882,22 @@ "randombytes": "^2.1.0" } }, - "node_modules/serve-static": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", - "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", - "dependencies": { - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.18.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/servify": { - "version": "0.1.12", - "resolved": "https://registry.npmjs.org/servify/-/servify-0.1.12.tgz", - "integrity": "sha512-/xE6GvsKKqyo1BAY+KxOWXcLpPsUUyji7Qg3bVD7hh1eRze5bR1uYiuDA/k3Gof1s9BTzQZEJK8sNcNGFIzeWw==", - "dependencies": { - "body-parser": "^1.16.0", - "cors": "^2.8.1", - "express": "^4.14.0", - "request": "^2.79.0", - "xhr": "^2.3.3" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/set-blocking": { "version": "2.0.0", "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==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", "dependencies": { - "define-data-property": "^1.1.1", - "get-intrinsic": "^1.2.1", + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.0" + "has-property-descriptors": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -15598,11 +13930,6 @@ "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=" }, - "node_modules/setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" - }, "node_modules/sha.js": { "version": "2.4.11", "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", @@ -15654,13 +13981,17 @@ } }, "node_modules/side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", + "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", "dependencies": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -15672,46 +14003,6 @@ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "dev": true }, - "node_modules/simple-concat": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", - "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/simple-get": { - "version": "2.8.2", - "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-2.8.2.tgz", - "integrity": "sha512-Ijd/rV5o+mSBBs4F/x9oDPtTx9Zb6X9brmnXvMW4J7IR15ngi9q5xxqWBKU744jTZiaXtxaPL7uHG6vtN8kUkw==", - "dependencies": { - "decompress-response": "^3.3.0", - "once": "^1.3.1", - "simple-concat": "^1.0.0" - } - }, - "node_modules/simple-get/node_modules/decompress-response": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz", - "integrity": "sha512-BzRPQuY1ip+qDonAOz42gRm/pg9F768C+npV/4JOsxRC2sq+Rlk+Q4ZCAsOhnIaMrgarILY+RMUIvMmmX1qAEA==", - "dependencies": { - "mimic-response": "^1.0.0" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/sisteransi": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", @@ -15737,9 +14028,9 @@ } }, "node_modules/source-map-js": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", - "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", "dev": true, "engines": { "node": ">=0.10.0" @@ -15814,14 +14105,6 @@ "node": ">=8" } }, - "node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/stream-browserify": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-3.0.0.tgz", @@ -15842,14 +14125,6 @@ "xtend": "^4.0.2" } }, - "node_modules/strict-uri-encode": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz", - "integrity": "sha512-R3f198pcvnB+5IpnBlRkphuE9n46WyVl8I39W/ZUTZLz4nqSP/oLYUrcnJrw462Ds8he4YKMov2efsTIw1BDGQ==", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/string_decoder": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", @@ -16111,99 +14386,10 @@ "url": "https://opencollective.com/svgo" } }, - "node_modules/swarm-js": { - "version": "0.1.42", - "resolved": "https://registry.npmjs.org/swarm-js/-/swarm-js-0.1.42.tgz", - "integrity": "sha512-BV7c/dVlA3R6ya1lMlSSNPLYrntt0LUq4YMgy3iwpCIc6rZnS5W2wUoctarZ5pXlpKtxDDf9hNziEkcfrxdhqQ==", - "dependencies": { - "bluebird": "^3.5.0", - "buffer": "^5.0.5", - "eth-lib": "^0.1.26", - "fs-extra": "^4.0.2", - "got": "^11.8.5", - "mime-types": "^2.1.16", - "mkdirp-promise": "^5.0.1", - "mock-fs": "^4.1.0", - "setimmediate": "^1.0.5", - "tar": "^4.0.2", - "xhr-request": "^1.0.1" - } - }, - "node_modules/swarm-js/node_modules/@szmarczak/http-timer": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", - "integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==", - "dependencies": { - "defer-to-connect": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/swarm-js/node_modules/cacheable-lookup": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", - "integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==", - "engines": { - "node": ">=10.6.0" - } - }, - "node_modules/swarm-js/node_modules/got": { - "version": "11.8.6", - "resolved": "https://registry.npmjs.org/got/-/got-11.8.6.tgz", - "integrity": "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==", - "dependencies": { - "@sindresorhus/is": "^4.0.0", - "@szmarczak/http-timer": "^4.0.5", - "@types/cacheable-request": "^6.0.1", - "@types/responselike": "^1.0.0", - "cacheable-lookup": "^5.0.3", - "cacheable-request": "^7.0.2", - "decompress-response": "^6.0.0", - "http2-wrapper": "^1.0.0-beta.5.2", - "lowercase-keys": "^2.0.0", - "p-cancelable": "^2.0.0", - "responselike": "^2.0.0" - }, - "engines": { - "node": ">=10.19.0" - }, - "funding": { - "url": "https://github.com/sindresorhus/got?sponsor=1" - } - }, - "node_modules/swarm-js/node_modules/http2-wrapper": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz", - "integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==", - "dependencies": { - "quick-lru": "^5.1.1", - "resolve-alpn": "^1.0.0" - }, - "engines": { - "node": ">=10.19.0" - } - }, - "node_modules/swarm-js/node_modules/lowercase-keys": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", - "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", - "engines": { - "node": ">=8" - } - }, - "node_modules/swarm-js/node_modules/p-cancelable": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", - "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==", - "engines": { - "node": ">=8" - } - }, "node_modules/sweetalert2": { - "version": "11.12.4", - "resolved": "https://registry.npmjs.org/sweetalert2/-/sweetalert2-11.12.4.tgz", - "integrity": "sha512-ZSpyaLbAmn4b7xjnV9x9BFD1UOrCAhIzm1D8dZ443kGxtVKqbTIA5SgXs4xeEtmFfEXUyC3RBgpSlu1AXmCiHA==", + "version": "11.14.1", + "resolved": "https://registry.npmjs.org/sweetalert2/-/sweetalert2-11.14.1.tgz", + "integrity": "sha512-xadhfcA4STGMh8nC5zHFFWURhRpWc4zyI3GdMDFH/m3hGWZeQQNWhX9xcG4lI9gZYsi/IlazKbwvvje3juL3Xg==", "funding": { "type": "individual", "url": "https://github.com/sponsors/limonte" @@ -16224,58 +14410,6 @@ "node": ">=6" } }, - "node_modules/tar": { - "version": "4.4.19", - "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.19.tgz", - "integrity": "sha512-a20gEsvHnWe0ygBY8JbxoM4w3SJdhc7ZAuxkLqh+nvNQN2IOt0B5lLgM490X5Hl8FF0dl0tOf2ewFYAlIFgzVA==", - "dependencies": { - "chownr": "^1.1.4", - "fs-minipass": "^1.2.7", - "minipass": "^2.9.0", - "minizlib": "^1.3.3", - "mkdirp": "^0.5.5", - "safe-buffer": "^5.2.1", - "yallist": "^3.1.1" - }, - "engines": { - "node": ">=4.5" - } - }, - "node_modules/tar/node_modules/mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", - "dependencies": { - "minimist": "^1.2.6" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, - "node_modules/tar/node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/tar/node_modules/yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" - }, "node_modules/terser": { "version": "5.27.0", "resolved": "https://registry.npmjs.org/terser/-/terser-5.27.0.tgz", @@ -16372,14 +14506,6 @@ "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", "dev": true }, - "node_modules/timed-out": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/timed-out/-/timed-out-4.0.1.tgz", - "integrity": "sha512-G7r3AhovYtr5YKOWQkta8RKAPb+J9IsO4uVmzjl8AZwfhs8UcUwTiD6gcJYSgOtzyjvQKrKYn41syHbUWMkafA==", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/tiny-emitter": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz", @@ -16416,14 +14542,6 @@ "resolved": "https://registry.npmjs.org/toggle-selection/-/toggle-selection-1.0.6.tgz", "integrity": "sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==" }, - "node_modules/toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", - "engines": { - "node": ">=0.6" - } - }, "node_modules/tough-cookie": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.2.tgz", @@ -16514,11 +14632,6 @@ "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" }, - "node_modules/type": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz", - "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==" - }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -16551,18 +14664,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", - "dependencies": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" - }, - "engines": { - "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", @@ -16636,10 +14737,18 @@ "is-typedarray": "^1.0.0" } }, - "node_modules/ultron": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.1.1.tgz", - "integrity": "sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og==" + "node_modules/typescript": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.2.tgz", + "integrity": "sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw==", + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } }, "node_modules/unbox-primitive": { "version": "1.0.2", @@ -16708,26 +14817,10 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/universalify": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", - "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/update-browserslist-db": { - "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==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz", + "integrity": "sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ==", "funding": [ { "type": "opencollective", @@ -16743,8 +14836,8 @@ } ], "dependencies": { - "escalade": "^3.1.1", - "picocolors": "^1.0.0" + "escalade": "^3.1.2", + "picocolors": "^1.0.1" }, "bin": { "update-browserslist-db": "cli.js" @@ -16767,12 +14860,15 @@ "integrity": "sha512-HXgFDgDommxn5/bIv0cnQZsPhHDA90NPHD6+c/v21U5+Sx5hoP8+dP9IZXBU1gIfvdRfhG8cel9QNPeionfcCQ==" }, "node_modules/url": { - "version": "0.11.3", - "resolved": "https://registry.npmjs.org/url/-/url-0.11.3.tgz", - "integrity": "sha512-6hxOLGfZASQK/cijlZnZJTq8OXAkt/3YGfQX45vvMYXpZoo8NdWZcY73K108Jf759lS1Bv/8wXnHDTSz17dSRw==", + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/url/-/url-0.11.4.tgz", + "integrity": "sha512-oCwdVC7mTuWiPyjLUz/COz5TLk6wgp0RCsN+wHZ2Ekneac9w8uuV0njcbbie2ME+Vs+d6duwmYuR3HgQXs1fOg==", "dependencies": { "punycode": "^1.4.1", - "qs": "^6.11.2" + "qs": "^6.12.3" + }, + "engines": { + "node": ">= 0.4" } }, "node_modules/url-parse": { @@ -16785,22 +14881,17 @@ "requires-port": "^1.0.0" } }, - "node_modules/url-set-query": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/url-set-query/-/url-set-query-1.0.0.tgz", - "integrity": "sha512-3AChu4NiXquPfeckE5R5cGdiHCMWJx1dwCWOmWIL4KHAziJNOFIYJlpGFeKDvwLPHovZRCxK3cYlwzqI9Vp+Gg==" - }, "node_modules/url/node_modules/punycode": { "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==", + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", "dependencies": { - "side-channel": "^1.0.4" + "side-channel": "^1.0.6" }, "engines": { "node": ">=0.6" @@ -16814,6 +14905,8 @@ "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.7.tgz", "integrity": "sha512-vLt1O5Pp+flcArHGIyKEQq883nBt8nN8tVBcoL0qUXj2XT1n7p70yGIq2VK98I5FdZ1YHc0wk/koOnHjnXWk1Q==", "hasInstallScript": true, + "optional": true, + "peer": true, "dependencies": { "node-gyp-build": "^4.3.0" }, @@ -16821,11 +14914,6 @@ "node": ">=6.14.2" } }, - "node_modules/utf8": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/utf8/-/utf8-3.0.0.tgz", - "integrity": "sha512-E8VjFIQ/TyQgp+TZfS6l8yp/xWppSAHzidGiRrqe4bK4XP9pTRyKFgGJpO3SN7zdX4DeomTrwaseCHovfpFcqQ==" - }, "node_modules/util": { "version": "0.12.5", "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", @@ -16843,14 +14931,6 @@ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" }, - "node_modules/utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", - "engines": { - "node": ">= 0.4.0" - } - }, "node_modules/uuid": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", @@ -16874,19 +14954,6 @@ "node": ">=10.12.0" } }, - "node_modules/varint": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/varint/-/varint-5.0.2.tgz", - "integrity": "sha512-lKxKYG6H03yCZUpAGOPOsMcGxd1RHCu1iKvEHYDPmTyq2HueGhD73ssNBqqQWfvYs04G9iUFRvmAVLW20Jw6ow==" - }, - "node_modules/vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/verror": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", @@ -16949,306 +15016,229 @@ } }, "node_modules/web3": { - "version": "1.10.4", - "resolved": "https://registry.npmjs.org/web3/-/web3-1.10.4.tgz", - "integrity": "sha512-kgJvQZjkmjOEKimx/tJQsqWfRDPTTcBfYPa9XletxuHLpHcXdx67w8EFn5AW3eVxCutE9dTVHgGa9VYe8vgsEA==", - "hasInstallScript": true, - "dependencies": { - "web3-bzz": "1.10.4", - "web3-core": "1.10.4", - "web3-eth": "1.10.4", - "web3-eth-personal": "1.10.4", - "web3-net": "1.10.4", - "web3-shh": "1.10.4", - "web3-utils": "1.10.4" - }, - "engines": { - "node": ">=8.0.0" + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/web3/-/web3-4.12.1.tgz", + "integrity": "sha512-zIFUPdgo2uG5Vbl7C4KrTv8dmWKN3sGnY/GundbiJzcaJZDxaCyu3a5HXAcgUM1VvvsVb1zaUQAFPceq05/q/Q==", + "dependencies": { + "web3-core": "^4.5.1", + "web3-errors": "^1.3.0", + "web3-eth": "^4.8.2", + "web3-eth-abi": "^4.2.3", + "web3-eth-accounts": "^4.2.1", + "web3-eth-contract": "^4.7.0", + "web3-eth-ens": "^4.4.0", + "web3-eth-iban": "^4.0.7", + "web3-eth-personal": "^4.0.8", + "web3-net": "^4.1.0", + "web3-providers-http": "^4.2.0", + "web3-providers-ws": "^4.0.8", + "web3-rpc-methods": "^1.3.0", + "web3-rpc-providers": "^1.0.0-rc.2", + "web3-types": "^1.7.0", + "web3-utils": "^4.3.1", + "web3-validator": "^2.0.6" + }, + "engines": { + "node": ">=14.0.0", + "npm": ">=6.12.0" } }, - "node_modules/web3-bzz": { - "version": "1.10.4", - "resolved": "https://registry.npmjs.org/web3-bzz/-/web3-bzz-1.10.4.tgz", - "integrity": "sha512-ZZ/X4sJ0Uh2teU9lAGNS8EjveEppoHNQiKlOXAjedsrdWuaMErBPdLQjXfcrYvN6WM6Su9PMsAxf3FXXZ+HwQw==", - "hasInstallScript": true, - "dependencies": { - "@types/node": "^12.12.6", - "got": "12.1.0", - "swarm-js": "^0.1.40" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/web3-bzz/node_modules/@types/node": { - "version": "12.20.55", - "resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.55.tgz", - "integrity": "sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==" - }, "node_modules/web3-core": { - "version": "1.10.4", - "resolved": "https://registry.npmjs.org/web3-core/-/web3-core-1.10.4.tgz", - "integrity": "sha512-B6elffYm81MYZDTrat7aEhnhdtVE3lDBUZft16Z8awYMZYJDbnykEbJVS+l3mnA7AQTnSDr/1MjWofGDLBJPww==", + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/web3-core/-/web3-core-4.5.1.tgz", + "integrity": "sha512-mFMOO/IWdKsLL1o2whh3oJ0LCG9P3l5c4lpiMoVsVln3QXh/B0Gf8gW3aY8S+Ixm0OHyzFDXJVc2CodxqmI4Gw==", "dependencies": { - "@types/bn.js": "^5.1.1", - "@types/node": "^12.12.6", - "bignumber.js": "^9.0.0", - "web3-core-helpers": "1.10.4", - "web3-core-method": "1.10.4", - "web3-core-requestmanager": "1.10.4", - "web3-utils": "1.10.4" + "web3-errors": "^1.3.0", + "web3-eth-accounts": "^4.2.0", + "web3-eth-iban": "^4.0.7", + "web3-providers-http": "^4.2.0", + "web3-providers-ws": "^4.0.8", + "web3-types": "^1.7.0", + "web3-utils": "^4.3.1", + "web3-validator": "^2.0.6" }, "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/web3-core-helpers": { - "version": "1.10.4", - "resolved": "https://registry.npmjs.org/web3-core-helpers/-/web3-core-helpers-1.10.4.tgz", - "integrity": "sha512-r+L5ylA17JlD1vwS8rjhWr0qg7zVoVMDvWhajWA5r5+USdh91jRUYosp19Kd1m2vE034v7Dfqe1xYRoH2zvG0g==", - "dependencies": { - "web3-eth-iban": "1.10.4", - "web3-utils": "1.10.4" + "node": ">=14", + "npm": ">=6.12.0" }, - "engines": { - "node": ">=8.0.0" + "optionalDependencies": { + "web3-providers-ipc": "^4.0.7" } }, - "node_modules/web3-core-method": { - "version": "1.10.4", - "resolved": "https://registry.npmjs.org/web3-core-method/-/web3-core-method-1.10.4.tgz", - "integrity": "sha512-uZTb7flr+Xl6LaDsyTeE2L1TylokCJwTDrIVfIfnrGmnwLc6bmTWCCrm71sSrQ0hqs6vp/MKbQYIYqUN0J8WyA==", + "node_modules/web3-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/web3-errors/-/web3-errors-1.3.0.tgz", + "integrity": "sha512-j5JkAKCtuVMbY3F5PYXBqg1vWrtF4jcyyMY1rlw8a4PV67AkqlepjGgpzWJZd56Mt+TvHy6DA1F/3Id8LatDSQ==", "dependencies": { - "@ethersproject/transactions": "^5.6.2", - "web3-core-helpers": "1.10.4", - "web3-core-promievent": "1.10.4", - "web3-core-subscriptions": "1.10.4", - "web3-utils": "1.10.4" + "web3-types": "^1.7.0" }, "engines": { - "node": ">=8.0.0" + "node": ">=14", + "npm": ">=6.12.0" } }, - "node_modules/web3-core-promievent": { - "version": "1.10.4", - "resolved": "https://registry.npmjs.org/web3-core-promievent/-/web3-core-promievent-1.10.4.tgz", - "integrity": "sha512-2de5WnJQ72YcIhYwV/jHLc4/cWJnznuoGTJGD29ncFQHAfwW/MItHFSVKPPA5v8AhJe+r6y4Y12EKvZKjQVBvQ==", + "node_modules/web3-eth": { + "version": "4.8.2", + "resolved": "https://registry.npmjs.org/web3-eth/-/web3-eth-4.8.2.tgz", + "integrity": "sha512-DLV/fIMG6gBp/B0gv0+G4FzxZ4YCDQsY3lzqqv7avwh3uU7/O27aifCUcFd7Ye+3ixTqCjAvLEl9wYSeyG3zQw==", "dependencies": { - "eventemitter3": "4.0.4" + "setimmediate": "^1.0.5", + "web3-core": "^4.5.0", + "web3-errors": "^1.2.1", + "web3-eth-abi": "^4.2.3", + "web3-eth-accounts": "^4.1.3", + "web3-net": "^4.1.0", + "web3-providers-ws": "^4.0.8", + "web3-rpc-methods": "^1.3.0", + "web3-types": "^1.7.0", + "web3-utils": "^4.3.1", + "web3-validator": "^2.0.6" }, "engines": { - "node": ">=8.0.0" + "node": ">=14", + "npm": ">=6.12.0" } }, - "node_modules/web3-core-requestmanager": { - "version": "1.10.4", - "resolved": "https://registry.npmjs.org/web3-core-requestmanager/-/web3-core-requestmanager-1.10.4.tgz", - "integrity": "sha512-vqP6pKH8RrhT/2MoaU+DY/OsYK9h7HmEBNCdoMj+4ZwujQtw/Mq2JifjwsJ7gits7Q+HWJwx8q6WmQoVZAWugg==", + "node_modules/web3-eth-abi": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/web3-eth-abi/-/web3-eth-abi-4.2.3.tgz", + "integrity": "sha512-rPVwTn0O1CzbtfXwEfIjUP0W5Y7u1OFjugwKpSqJzPQE6+REBg6OELjomTGZBu+GThxHnv0rp15SOxvqp+tyXA==", "dependencies": { - "util": "^0.12.5", - "web3-core-helpers": "1.10.4", - "web3-providers-http": "1.10.4", - "web3-providers-ipc": "1.10.4", - "web3-providers-ws": "1.10.4" + "abitype": "0.7.1", + "web3-errors": "^1.2.0", + "web3-types": "^1.7.0", + "web3-utils": "^4.3.1", + "web3-validator": "^2.0.6" }, "engines": { - "node": ">=8.0.0" + "node": ">=14", + "npm": ">=6.12.0" } }, - "node_modules/web3-core-subscriptions": { - "version": "1.10.4", - "resolved": "https://registry.npmjs.org/web3-core-subscriptions/-/web3-core-subscriptions-1.10.4.tgz", - "integrity": "sha512-o0lSQo/N/f7/L76C0HV63+S54loXiE9fUPfHFcTtpJRQNDBVsSDdWRdePbWwR206XlsBqD5VHApck1//jEafTw==", + "node_modules/web3-eth-accounts": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/web3-eth-accounts/-/web3-eth-accounts-4.2.1.tgz", + "integrity": "sha512-aOlEZFzqAgKprKs7+DGArU4r9b+ILBjThpeq42aY7LAQcP+mSpsWcQgbIRK3r/n3OwTYZ3aLPk0Ih70O/LwnYA==", "dependencies": { - "eventemitter3": "4.0.4", - "web3-core-helpers": "1.10.4" + "@ethereumjs/rlp": "^4.0.1", + "crc-32": "^1.2.2", + "ethereum-cryptography": "^2.0.0", + "web3-errors": "^1.3.0", + "web3-types": "^1.7.0", + "web3-utils": "^4.3.1", + "web3-validator": "^2.0.6" }, "engines": { - "node": ">=8.0.0" + "node": ">=14", + "npm": ">=6.12.0" } }, - "node_modules/web3-core/node_modules/@types/bn.js": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-5.1.5.tgz", - "integrity": "sha512-V46N0zwKRF5Q00AZ6hWtN0T8gGmDUaUzLWQvHFo5yThtVwK/VCenFY3wXVbOvNfajEpsTfQM4IN9k/d6gUVX3A==", + "node_modules/web3-eth-accounts/node_modules/ethereum-cryptography": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-2.2.1.tgz", + "integrity": "sha512-r/W8lkHSiTLxUxW8Rf3u4HGB0xQweG2RyETjywylKZSzLWoWAijRz8WCuOtJ6wah+avllXBqZuk29HCCvhEIRg==", "dependencies": { - "@types/node": "*" + "@noble/curves": "1.4.2", + "@noble/hashes": "1.4.0", + "@scure/bip32": "1.4.0", + "@scure/bip39": "1.3.0" } }, - "node_modules/web3-core/node_modules/@types/node": { - "version": "12.20.55", - "resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.55.tgz", - "integrity": "sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==" - }, - "node_modules/web3-eth": { - "version": "1.10.4", - "resolved": "https://registry.npmjs.org/web3-eth/-/web3-eth-1.10.4.tgz", - "integrity": "sha512-Sql2kYKmgt+T/cgvg7b9ce24uLS7xbFrxE4kuuor1zSCGrjhTJ5rRNG8gTJUkAJGKJc7KgnWmgW+cOfMBPUDSA==", - "dependencies": { - "web3-core": "1.10.4", - "web3-core-helpers": "1.10.4", - "web3-core-method": "1.10.4", - "web3-core-subscriptions": "1.10.4", - "web3-eth-abi": "1.10.4", - "web3-eth-accounts": "1.10.4", - "web3-eth-contract": "1.10.4", - "web3-eth-ens": "1.10.4", - "web3-eth-iban": "1.10.4", - "web3-eth-personal": "1.10.4", - "web3-net": "1.10.4", - "web3-utils": "1.10.4" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/web3-eth-abi": { - "version": "1.10.4", - "resolved": "https://registry.npmjs.org/web3-eth-abi/-/web3-eth-abi-1.10.4.tgz", - "integrity": "sha512-cZ0q65eJIkd/jyOlQPDjr8X4fU6CRL1eWgdLwbWEpo++MPU/2P4PFk5ZLAdye9T5Sdp+MomePPJ/gHjLMj2VfQ==", + "node_modules/web3-eth-contract": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/web3-eth-contract/-/web3-eth-contract-4.7.0.tgz", + "integrity": "sha512-fdStoBOjFyMHwlyJmSUt/BTDL1ATwKGmG3zDXQ/zTKlkkW/F/074ut0Vry4GuwSBg9acMHc0ycOiZx9ZKjNHsw==", "dependencies": { - "@ethersproject/abi": "^5.6.3", - "web3-utils": "1.10.4" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/web3-eth-accounts": { - "version": "1.10.4", - "resolved": "https://registry.npmjs.org/web3-eth-accounts/-/web3-eth-accounts-1.10.4.tgz", - "integrity": "sha512-ysy5sVTg9snYS7tJjxVoQAH6DTOTkRGR8emEVCWNGLGiB9txj+qDvSeT0izjurS/g7D5xlMAgrEHLK1Vi6I3yg==", - "dependencies": { - "@ethereumjs/common": "2.6.5", - "@ethereumjs/tx": "3.5.2", - "@ethereumjs/util": "^8.1.0", - "eth-lib": "0.2.8", - "scrypt-js": "^3.0.1", - "uuid": "^9.0.0", - "web3-core": "1.10.4", - "web3-core-helpers": "1.10.4", - "web3-core-method": "1.10.4", - "web3-utils": "1.10.4" + "@ethereumjs/rlp": "^5.0.2", + "web3-core": "^4.5.1", + "web3-errors": "^1.3.0", + "web3-eth": "^4.8.2", + "web3-eth-abi": "^4.2.3", + "web3-types": "^1.7.0", + "web3-utils": "^4.3.1", + "web3-validator": "^2.0.6" }, "engines": { - "node": ">=8.0.0" + "node": ">=14", + "npm": ">=6.12.0" } }, - "node_modules/web3-eth-accounts/node_modules/bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" - }, - "node_modules/web3-eth-accounts/node_modules/eth-lib": { - "version": "0.2.8", - "resolved": "https://registry.npmjs.org/eth-lib/-/eth-lib-0.2.8.tgz", - "integrity": "sha512-ArJ7x1WcWOlSpzdoTBX8vkwlkSQ85CjjifSZtV4co64vWxSV8geWfPI9x4SVYu3DSxnX4yWFVTtGL+j9DUFLNw==", - "dependencies": { - "bn.js": "^4.11.6", - "elliptic": "^6.4.0", - "xhr-request-promise": "^0.1.2" - } - }, - "node_modules/web3-eth-accounts/node_modules/uuid": { - "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" - ], + "node_modules/web3-eth-contract/node_modules/@ethereumjs/rlp": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@ethereumjs/rlp/-/rlp-5.0.2.tgz", + "integrity": "sha512-DziebCdg4JpGlEqEdGgXmjqcFoJi+JGulUXwEjsZGAscAQ7MyD/7LE/GVCP29vEQxKc7AAwjT3A2ywHp2xfoCA==", "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/web3-eth-contract": { - "version": "1.10.4", - "resolved": "https://registry.npmjs.org/web3-eth-contract/-/web3-eth-contract-1.10.4.tgz", - "integrity": "sha512-Q8PfolOJ4eV9TvnTj1TGdZ4RarpSLmHnUnzVxZ/6/NiTfe4maJz99R0ISgwZkntLhLRtw0C7LRJuklzGYCNN3A==", - "dependencies": { - "@types/bn.js": "^5.1.1", - "web3-core": "1.10.4", - "web3-core-helpers": "1.10.4", - "web3-core-method": "1.10.4", - "web3-core-promievent": "1.10.4", - "web3-core-subscriptions": "1.10.4", - "web3-eth-abi": "1.10.4", - "web3-utils": "1.10.4" + "rlp": "bin/rlp.cjs" }, "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/web3-eth-contract/node_modules/@types/bn.js": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-5.1.5.tgz", - "integrity": "sha512-V46N0zwKRF5Q00AZ6hWtN0T8gGmDUaUzLWQvHFo5yThtVwK/VCenFY3wXVbOvNfajEpsTfQM4IN9k/d6gUVX3A==", - "dependencies": { - "@types/node": "*" + "node": ">=18" } }, "node_modules/web3-eth-ens": { - "version": "1.10.4", - "resolved": "https://registry.npmjs.org/web3-eth-ens/-/web3-eth-ens-1.10.4.tgz", - "integrity": "sha512-LLrvxuFeVooRVZ9e5T6OWKVflHPFgrVjJ/jtisRWcmI7KN/b64+D/wJzXqgmp6CNsMQcE7rpmf4CQmJCrTdsgg==", + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/web3-eth-ens/-/web3-eth-ens-4.4.0.tgz", + "integrity": "sha512-DeyVIS060hNV9g8dnTx92syqvgbvPricE3MerCxe/DquNZT3tD8aVgFfq65GATtpCgDDJffO2bVeHp3XBemnSQ==", "dependencies": { - "content-hash": "^2.5.2", - "eth-ens-namehash": "2.0.8", - "web3-core": "1.10.4", - "web3-core-helpers": "1.10.4", - "web3-core-promievent": "1.10.4", - "web3-eth-abi": "1.10.4", - "web3-eth-contract": "1.10.4", - "web3-utils": "1.10.4" + "@adraffy/ens-normalize": "^1.8.8", + "web3-core": "^4.5.0", + "web3-errors": "^1.2.0", + "web3-eth": "^4.8.0", + "web3-eth-contract": "^4.5.0", + "web3-net": "^4.1.0", + "web3-types": "^1.7.0", + "web3-utils": "^4.3.0", + "web3-validator": "^2.0.6" }, "engines": { - "node": ">=8.0.0" + "node": ">=14", + "npm": ">=6.12.0" } }, "node_modules/web3-eth-iban": { - "version": "1.10.4", - "resolved": "https://registry.npmjs.org/web3-eth-iban/-/web3-eth-iban-1.10.4.tgz", - "integrity": "sha512-0gE5iNmOkmtBmbKH2aTodeompnNE8jEyvwFJ6s/AF6jkw9ky9Op9cqfzS56AYAbrqEFuClsqB/AoRves7LDELw==", + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/web3-eth-iban/-/web3-eth-iban-4.0.7.tgz", + "integrity": "sha512-8weKLa9KuKRzibC87vNLdkinpUE30gn0IGY027F8doeJdcPUfsa4IlBgNC4k4HLBembBB2CTU0Kr/HAOqMeYVQ==", "dependencies": { - "bn.js": "^5.2.1", - "web3-utils": "1.10.4" + "web3-errors": "^1.1.3", + "web3-types": "^1.3.0", + "web3-utils": "^4.0.7", + "web3-validator": "^2.0.3" }, "engines": { - "node": ">=8.0.0" + "node": ">=14", + "npm": ">=6.12.0" } }, "node_modules/web3-eth-personal": { - "version": "1.10.4", - "resolved": "https://registry.npmjs.org/web3-eth-personal/-/web3-eth-personal-1.10.4.tgz", - "integrity": "sha512-BRa/hs6jU1hKHz+AC/YkM71RP3f0Yci1dPk4paOic53R4ZZG4MgwKRkJhgt3/GPuPliwS46f/i5A7fEGBT4F9w==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/web3-eth-personal/-/web3-eth-personal-4.0.8.tgz", + "integrity": "sha512-sXeyLKJ7ddQdMxz1BZkAwImjqh7OmKxhXoBNF3isDmD4QDpMIwv/t237S3q4Z0sZQamPa/pHebJRWVuvP8jZdw==", "dependencies": { - "@types/node": "^12.12.6", - "web3-core": "1.10.4", - "web3-core-helpers": "1.10.4", - "web3-core-method": "1.10.4", - "web3-net": "1.10.4", - "web3-utils": "1.10.4" + "web3-core": "^4.3.0", + "web3-eth": "^4.3.1", + "web3-rpc-methods": "^1.1.3", + "web3-types": "^1.3.0", + "web3-utils": "^4.0.7", + "web3-validator": "^2.0.3" }, "engines": { - "node": ">=8.0.0" + "node": ">=14", + "npm": ">=6.12.0" } }, - "node_modules/web3-eth-personal/node_modules/@types/node": { - "version": "12.20.55", - "resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.55.tgz", - "integrity": "sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==" - }, "node_modules/web3-net": { - "version": "1.10.4", - "resolved": "https://registry.npmjs.org/web3-net/-/web3-net-1.10.4.tgz", - "integrity": "sha512-mKINnhOOnZ4koA+yV2OT5s5ztVjIx7IY9a03w6s+yao/BUn+Luuty0/keNemZxTr1E8Ehvtn28vbOtW7Ids+Ow==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/web3-net/-/web3-net-4.1.0.tgz", + "integrity": "sha512-WWmfvHVIXWEoBDWdgKNYKN8rAy6SgluZ0abyRyXOL3ESr7ym7pKWbfP4fjApIHlYTh8tNqkrdPfM4Dyi6CA0SA==", "dependencies": { - "web3-core": "1.10.4", - "web3-core-method": "1.10.4", - "web3-utils": "1.10.4" + "web3-core": "^4.4.0", + "web3-rpc-methods": "^1.3.0", + "web3-types": "^1.6.0", + "web3-utils": "^4.3.0" }, "engines": { - "node": ">=8.0.0" + "node": ">=14", + "npm": ">=6.12.0" } }, "node_modules/web3-provider-engine": { @@ -17330,17 +15320,18 @@ } }, "node_modules/web3-providers-http": { - "version": "1.10.4", - "resolved": "https://registry.npmjs.org/web3-providers-http/-/web3-providers-http-1.10.4.tgz", - "integrity": "sha512-m2P5Idc8hdiO0l60O6DSCPw0kw64Zgi0pMjbEFRmxKIck2Py57RQMu4bxvkxJwkF06SlGaEQF8rFZBmuX7aagQ==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/web3-providers-http/-/web3-providers-http-4.2.0.tgz", + "integrity": "sha512-IPMnDtHB7dVwaB7/mMxAZzyq7d5ezfO1+Vw0bNfAeIi7gaDlJiggp85SdyAfOgov8AMUA/dyiY72kQ0KmjXKvQ==", "dependencies": { - "abortcontroller-polyfill": "^1.7.5", "cross-fetch": "^4.0.0", - "es6-promise": "^4.2.8", - "web3-core-helpers": "1.10.4" + "web3-errors": "^1.3.0", + "web3-types": "^1.7.0", + "web3-utils": "^4.3.1" }, "engines": { - "node": ">=8.0.0" + "node": ">=14", + "npm": ">=6.12.0" } }, "node_modules/web3-providers-http/node_modules/cross-fetch": { @@ -17352,72 +15343,129 @@ } }, "node_modules/web3-providers-ipc": { - "version": "1.10.4", - "resolved": "https://registry.npmjs.org/web3-providers-ipc/-/web3-providers-ipc-1.10.4.tgz", - "integrity": "sha512-YRF/bpQk9z3WwjT+A6FI/GmWRCASgd+gC0si7f9zbBWLXjwzYAKG73bQBaFRAHex1hl4CVcM5WUMaQXf3Opeuw==", + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/web3-providers-ipc/-/web3-providers-ipc-4.0.7.tgz", + "integrity": "sha512-YbNqY4zUvIaK2MHr1lQFE53/8t/ejHtJchrWn9zVbFMGXlTsOAbNoIoZWROrg1v+hCBvT2c9z8xt7e/+uz5p1g==", + "optional": true, "dependencies": { - "oboe": "2.1.5", - "web3-core-helpers": "1.10.4" + "web3-errors": "^1.1.3", + "web3-types": "^1.3.0", + "web3-utils": "^4.0.7" }, "engines": { - "node": ">=8.0.0" + "node": ">=14", + "npm": ">=6.12.0" } }, "node_modules/web3-providers-ws": { - "version": "1.10.4", - "resolved": "https://registry.npmjs.org/web3-providers-ws/-/web3-providers-ws-1.10.4.tgz", - "integrity": "sha512-j3FBMifyuFFmUIPVQR4pj+t5ILhAexAui0opgcpu9R5LxQrLRUZxHSnU+YO25UycSOa/NAX8A+qkqZNpcFAlxA==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/web3-providers-ws/-/web3-providers-ws-4.0.8.tgz", + "integrity": "sha512-goJdgata7v4pyzHRsg9fSegUG4gVnHZSHODhNnn6J93ykHkBI1nz4fjlGpcQLUMi4jAMz6SHl9Ibzs2jj9xqPw==", "dependencies": { - "eventemitter3": "4.0.4", - "web3-core-helpers": "1.10.4", - "websocket": "^1.0.32" + "@types/ws": "8.5.3", + "isomorphic-ws": "^5.0.0", + "web3-errors": "^1.2.0", + "web3-types": "^1.7.0", + "web3-utils": "^4.3.1", + "ws": "^8.17.1" }, "engines": { - "node": ">=8.0.0" + "node": ">=14", + "npm": ">=6.12.0" } }, - "node_modules/web3-shh": { - "version": "1.10.4", - "resolved": "https://registry.npmjs.org/web3-shh/-/web3-shh-1.10.4.tgz", - "integrity": "sha512-cOH6iFFM71lCNwSQrC3niqDXagMqrdfFW85hC9PFUrAr3PUrIem8TNstTc3xna2bwZeWG6OBy99xSIhBvyIACw==", - "hasInstallScript": true, + "node_modules/web3-rpc-methods": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/web3-rpc-methods/-/web3-rpc-methods-1.3.0.tgz", + "integrity": "sha512-/CHmzGN+IYgdBOme7PdqzF+FNeMleefzqs0LVOduncSaqsppeOEoskLXb2anSpzmQAP3xZJPaTrkQPWSJMORig==", "dependencies": { - "web3-core": "1.10.4", - "web3-core-method": "1.10.4", - "web3-core-subscriptions": "1.10.4", - "web3-net": "1.10.4" + "web3-core": "^4.4.0", + "web3-types": "^1.6.0", + "web3-validator": "^2.0.6" }, "engines": { - "node": ">=8.0.0" + "node": ">=14", + "npm": ">=6.12.0" + } + }, + "node_modules/web3-rpc-providers": { + "version": "1.0.0-rc.2", + "resolved": "https://registry.npmjs.org/web3-rpc-providers/-/web3-rpc-providers-1.0.0-rc.2.tgz", + "integrity": "sha512-ocFIEXcBx/DYQ90HhVepTBUVnL9pGsZw8wyPb1ZINSenwYus9SvcFkjU1Hfvd/fXjuhAv2bUVch9vxvMx1mXAQ==", + "dependencies": { + "web3-errors": "^1.3.0", + "web3-providers-http": "^4.2.0", + "web3-providers-ws": "^4.0.8", + "web3-types": "^1.7.0", + "web3-utils": "^4.3.1", + "web3-validator": "^2.0.6" + }, + "engines": { + "node": ">=14", + "npm": ">=6.12.0" + } + }, + "node_modules/web3-types": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/web3-types/-/web3-types-1.7.0.tgz", + "integrity": "sha512-nhXxDJ7a5FesRw9UG5SZdP/C/3Q2EzHGnB39hkAV+YGXDMgwxBXFWebQLfEzZzuArfHnvC0sQqkIHNwSKcVjdA==", + "engines": { + "node": ">=14", + "npm": ">=6.12.0" } }, "node_modules/web3-utils": { - "version": "1.10.4", - "resolved": "https://registry.npmjs.org/web3-utils/-/web3-utils-1.10.4.tgz", - "integrity": "sha512-tsu8FiKJLk2PzhDl9fXbGUWTkkVXYhtTA+SmEFkKft+9BgwLxfCRpU96sWv7ICC8zixBNd3JURVoiR3dUXgP8A==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/web3-utils/-/web3-utils-4.3.1.tgz", + "integrity": "sha512-kGwOk8FxOLJ9DQC68yqNQc7AzN+k9YDLaW+ZjlAXs3qORhf8zXk5SxWAAGLbLykMs3vTeB0FTb1Exut4JEYfFA==", "dependencies": { - "@ethereumjs/util": "^8.1.0", - "bn.js": "^5.2.1", - "ethereum-bloom-filters": "^1.0.6", - "ethereum-cryptography": "^2.1.2", - "ethjs-unit": "0.1.6", - "number-to-bn": "1.7.0", - "randombytes": "^2.1.0", - "utf8": "3.0.0" + "ethereum-cryptography": "^2.0.0", + "eventemitter3": "^5.0.1", + "web3-errors": "^1.2.0", + "web3-types": "^1.7.0", + "web3-validator": "^2.0.6" }, "engines": { - "node": ">=8.0.0" + "node": ">=14", + "npm": ">=6.12.0" } }, "node_modules/web3-utils/node_modules/ethereum-cryptography": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-2.1.3.tgz", - "integrity": "sha512-BlwbIL7/P45W8FGW2r7LGuvoEZ+7PWsniMvQ4p5s2xCyw9tmaDlpfsN9HjAucbF+t/qpVHwZUisgfK24TCW8aA==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-2.2.1.tgz", + "integrity": "sha512-r/W8lkHSiTLxUxW8Rf3u4HGB0xQweG2RyETjywylKZSzLWoWAijRz8WCuOtJ6wah+avllXBqZuk29HCCvhEIRg==", "dependencies": { - "@noble/curves": "1.3.0", - "@noble/hashes": "1.3.3", - "@scure/bip32": "1.3.3", - "@scure/bip39": "1.2.2" + "@noble/curves": "1.4.2", + "@noble/hashes": "1.4.0", + "@scure/bip32": "1.4.0", + "@scure/bip39": "1.3.0" + } + }, + "node_modules/web3-validator": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/web3-validator/-/web3-validator-2.0.6.tgz", + "integrity": "sha512-qn9id0/l1bWmvH4XfnG/JtGKKwut2Vokl6YXP5Kfg424npysmtRLe9DgiNBM9Op7QL/aSiaA0TVXibuIuWcizg==", + "dependencies": { + "ethereum-cryptography": "^2.0.0", + "util": "^0.12.5", + "web3-errors": "^1.2.0", + "web3-types": "^1.6.0", + "zod": "^3.21.4" + }, + "engines": { + "node": ">=14", + "npm": ">=6.12.0" + } + }, + "node_modules/web3-validator/node_modules/ethereum-cryptography": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-2.2.1.tgz", + "integrity": "sha512-r/W8lkHSiTLxUxW8Rf3u4HGB0xQweG2RyETjywylKZSzLWoWAijRz8WCuOtJ6wah+avllXBqZuk29HCCvhEIRg==", + "dependencies": { + "@noble/curves": "1.4.2", + "@noble/hashes": "1.4.0", + "@scure/bip32": "1.4.0", + "@scure/bip39": "1.3.0" } }, "node_modules/web3modal": { @@ -17443,12 +15491,11 @@ } }, "node_modules/webpack": { - "version": "5.93.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.93.0.tgz", - "integrity": "sha512-Y0m5oEY1LRuwly578VqluorkXbvXKh7U3rLoQCEO04M97ScRr44afGVkI0FQFsXzysk5OgFAxjZAb9rsGQVihA==", + "version": "5.95.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.95.0.tgz", + "integrity": "sha512-2t3XstrKULz41MNMBF+cJ97TyHdyQ8HCt//pqErqDvNjU9YQBnZxIHa11VXsi7F3mb5/aO2tuDxdeTPdU7xu9Q==", "dev": true, "dependencies": { - "@types/eslint-scope": "^3.7.3", "@types/estree": "^1.0.5", "@webassemblyjs/ast": "^1.12.1", "@webassemblyjs/wasm-edit": "^1.12.1", @@ -17457,7 +15504,7 @@ "acorn-import-attributes": "^1.9.5", "browserslist": "^4.21.10", "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.17.0", + "enhanced-resolve": "^5.17.1", "es-module-lexer": "^1.2.1", "eslint-scope": "5.1.1", "events": "^3.2.0", @@ -17578,40 +15625,11 @@ "version": "3.2.3", "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", - "dev": true, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/websocket": { - "version": "1.0.34", - "resolved": "https://registry.npmjs.org/websocket/-/websocket-1.0.34.tgz", - "integrity": "sha512-PRDso2sGwF6kM75QykIesBijKSVceR6jL2G8NGYyq2XrItNC2P5/qL5XeR056GhA+Ly7JMFvJb9I312mJfmqnQ==", - "dependencies": { - "bufferutil": "^4.0.1", - "debug": "^2.2.0", - "es5-ext": "^0.10.50", - "typedarray-to-buffer": "^3.1.5", - "utf-8-validate": "^5.0.2", - "yaeti": "^0.0.6" - }, + "dev": true, "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/websocket/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" + "node": ">=10.13.0" } }, - "node_modules/websocket/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, "node_modules/whatwg-encoding": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz", @@ -17785,7 +15803,8 @@ "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true }, "node_modules/write-file-atomic": { "version": "4.0.2", @@ -17801,16 +15820,15 @@ } }, "node_modules/ws": { - "version": "8.8.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.8.1.tgz", - "integrity": "sha512-bGy2JzvzkPowEJV++hF07hAD6niYSr0JzBNo/J29WsB57A2r7Wlc1UFcTR9IzrPvuNVO4B8LGqF8qcpsVOhJCA==", - "dev": true, + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", "engines": { "node": ">=10.0.0" }, "peerDependencies": { "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" + "utf-8-validate": ">=5.0.2" }, "peerDependenciesMeta": { "bufferutil": { @@ -17832,28 +15850,6 @@ "xtend": "^4.0.0" } }, - "node_modules/xhr-request": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/xhr-request/-/xhr-request-1.1.0.tgz", - "integrity": "sha512-Y7qzEaR3FDtL3fP30k9wO/e+FBnBByZeybKOhASsGP30NIkRAAkKD/sCnLvgEfAIEC1rcmK7YG8f4oEnIrrWzA==", - "dependencies": { - "buffer-to-arraybuffer": "^0.0.5", - "object-assign": "^4.1.1", - "query-string": "^5.0.1", - "simple-get": "^2.7.0", - "timed-out": "^4.0.1", - "url-set-query": "^1.0.0", - "xhr": "^2.0.4" - } - }, - "node_modules/xhr-request-promise": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/xhr-request-promise/-/xhr-request-promise-0.1.3.tgz", - "integrity": "sha512-YUBytBsuwgitWtdRzXDDkWAXzhdGB8bYm0sSzMPZT7Z2MBjMSTHFsyCT1yCRATY+XC69DUrQraRAEgcoCRaIPg==", - "dependencies": { - "xhr-request": "^1.1.0" - } - }, "node_modules/xhr2-cookies": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/xhr2-cookies/-/xhr2-cookies-1.1.0.tgz", @@ -17910,14 +15906,6 @@ "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==" }, - "node_modules/yaeti": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/yaeti/-/yaeti-0.0.6.tgz", - "integrity": "sha512-MvQa//+KcZCUkBTIC9blM+CU9J2GzuTytsOUwf2lidtvkx/6gnEp1QvJv34t9vdjhFmha/mUiNDbN0D0mJWdug==", - "engines": { - "node": ">=0.10.32" - } - }, "node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", @@ -17971,6 +15959,14 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/zod": { + "version": "3.23.8", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz", + "integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } } }, "dependencies": { @@ -17980,6 +15976,11 @@ "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", "dev": true }, + "@adraffy/ens-normalize": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.10.1.tgz", + "integrity": "sha512-96Z2IP3mYmF1Xg2cDm8f1gWGf/HUVedQ3FMifV4kG/PQ4yEP51xDtRAEfhVNt5f/uzpNkZHwWQuUcu6D6K+Ekw==" + }, "@amplitude/analytics-browser": { "version": "2.9.3", "resolved": "https://registry.npmjs.org/@amplitude/analytics-browser/-/analytics-browser-2.9.3.tgz", @@ -18090,25 +16091,25 @@ } }, "@babel/compat-data": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.24.7.tgz", - "integrity": "sha512-qJzAIcv03PyaWqxRgO4mSU3lihncDT296vnyuE2O8uA4w3UHWI4S3hgeZd1L8W1Bft40w9JxJ2b412iDUFFRhw==" + "version": "7.25.4", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.25.4.tgz", + "integrity": "sha512-+LGRog6RAsCJrrrg/IO6LGmpphNe5DiK30dGjCoxxeGv49B10/3XYGxPsAwrDlMFcFEvdAUavDT8r9k/hSyQqQ==" }, "@babel/core": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.7.tgz", - "integrity": "sha512-nykK+LEK86ahTkX/3TgauT0ikKoNCfKHEaZYTUVupJdTLzGNvrblu4u6fa7DhZONAltdf8e662t/abY8idrd/g==", + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.25.2.tgz", + "integrity": "sha512-BBt3opiCOxUr9euZ5/ro/Xv8/V7yJ5bjYMqG/C1YAo8MIKAnumZalCN+msbci3Pigy4lIQfPUpfMM27HMGaYEA==", "requires": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.24.7", - "@babel/generator": "^7.24.7", - "@babel/helper-compilation-targets": "^7.24.7", - "@babel/helper-module-transforms": "^7.24.7", - "@babel/helpers": "^7.24.7", - "@babel/parser": "^7.24.7", - "@babel/template": "^7.24.7", - "@babel/traverse": "^7.24.7", - "@babel/types": "^7.24.7", + "@babel/generator": "^7.25.0", + "@babel/helper-compilation-targets": "^7.25.2", + "@babel/helper-module-transforms": "^7.25.2", + "@babel/helpers": "^7.25.0", + "@babel/parser": "^7.25.0", + "@babel/template": "^7.25.0", + "@babel/traverse": "^7.25.2", + "@babel/types": "^7.25.2", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -18124,11 +16125,11 @@ } }, "@babel/generator": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.7.tgz", - "integrity": "sha512-oipXieGC3i45Y1A41t4tAqpnEZWgB/lC6Ehh6+rOviR5XWpTtMmLN+fGjz9vOiNRt0p6RtO6DtD0pdU3vpqdSA==", + "version": "7.25.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.25.6.tgz", + "integrity": "sha512-VPC82gr1seXOpkjAAKoLhP50vx4vGNlF4msF64dSFq1P8RfB+QAuJWGHPXXPc8QyfVWwwB/TNNU4+ayZmHNbZw==", "requires": { - "@babel/types": "^7.24.7", + "@babel/types": "^7.25.6", "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25", "jsesc": "^2.5.1" @@ -18153,13 +16154,13 @@ } }, "@babel/helper-compilation-targets": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.24.7.tgz", - "integrity": "sha512-ctSdRHBi20qWOfy27RUb4Fhp07KSJ3sXcuSvTrXrc4aG8NSYDo1ici3Vhg9bg69y5bj0Mr1lh0aeEgTvc12rMg==", + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.2.tgz", + "integrity": "sha512-U2U5LsSaZ7TAt3cfaymQ8WHh0pxvdHoEk6HVpaexxixjyEquMh0L0YNJNM6CTGKMXV1iksi0iZkGw4AcFkPaaw==", "requires": { - "@babel/compat-data": "^7.24.7", - "@babel/helper-validator-option": "^7.24.7", - "browserslist": "^4.22.2", + "@babel/compat-data": "^7.25.2", + "@babel/helper-validator-option": "^7.24.8", + "browserslist": "^4.23.1", "lru-cache": "^5.1.1", "semver": "^6.3.1" }, @@ -18180,26 +16181,24 @@ } }, "@babel/helper-create-class-features-plugin": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.24.7.tgz", - "integrity": "sha512-kTkaDl7c9vO80zeX1rJxnuRpEsD5tA81yh11X1gQo+PhSti3JS+7qeZo9U4RHobKRiFPKaGK3svUAeb8D0Q7eg==", + "version": "7.25.4", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.25.4.tgz", + "integrity": "sha512-ro/bFs3/84MDgDmMwbcHgDa8/E6J3QKNTk4xJJnVeFtGE+tL0K26E3pNxhYz2b67fJpt7Aphw5XcploKXuCvCQ==", "dev": true, "requires": { "@babel/helper-annotate-as-pure": "^7.24.7", - "@babel/helper-environment-visitor": "^7.24.7", - "@babel/helper-function-name": "^7.24.7", - "@babel/helper-member-expression-to-functions": "^7.24.7", + "@babel/helper-member-expression-to-functions": "^7.24.8", "@babel/helper-optimise-call-expression": "^7.24.7", - "@babel/helper-replace-supers": "^7.24.7", + "@babel/helper-replace-supers": "^7.25.0", "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7", - "@babel/helper-split-export-declaration": "^7.24.7", + "@babel/traverse": "^7.25.4", "semver": "^6.3.1" } }, "@babel/helper-create-regexp-features-plugin": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.24.7.tgz", - "integrity": "sha512-03TCmXy2FtXJEZfbXDTSqq1fRJArk7lX9DOFC/47VthYcxyIOx+eXQmdo6DOQvrbpIix+KfXwvuXdFDZHxt+rA==", + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.25.2.tgz", + "integrity": "sha512-+wqVGP+DFmqwFD3EH6TMTfUNeqDehV3E/dl+Sd54eaXqm17tEUNbEIn4sVivVowbvUpOtIGxdo3GoXyDH9N/9g==", "dev": true, "requires": { "@babel/helper-annotate-as-pure": "^7.24.7", @@ -18220,39 +16219,14 @@ "semver": "^6.1.2" } }, - "@babel/helper-environment-visitor": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.24.7.tgz", - "integrity": "sha512-DoiN84+4Gnd0ncbBOM9AZENV4a5ZiL39HYMyZJGZ/AZEykHYdJw0wW3kdcsh9/Kn+BRXHLkkklZ51ecPKmI1CQ==", - "requires": { - "@babel/types": "^7.24.7" - } - }, - "@babel/helper-function-name": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.24.7.tgz", - "integrity": "sha512-FyoJTsj/PEUWu1/TYRiXTIHc8lbw+TDYkZuoE43opPS5TrI7MyONBE1oNvfguEXAD9yhQRrVBnXdXzSLQl9XnA==", - "requires": { - "@babel/template": "^7.24.7", - "@babel/types": "^7.24.7" - } - }, - "@babel/helper-hoist-variables": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.24.7.tgz", - "integrity": "sha512-MJJwhkoGy5c4ehfoRyrJ/owKeMl19U54h27YYftT0o2teQ3FJ3nQUf/I3LlJsX4l3qlw7WRXUmiyajvHXoTubQ==", - "requires": { - "@babel/types": "^7.24.7" - } - }, "@babel/helper-member-expression-to-functions": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.24.7.tgz", - "integrity": "sha512-LGeMaf5JN4hAT471eJdBs/GK1DoYIJ5GCtZN/EsL6KUiiDZOvO/eKE11AMZJa2zP4zk4qe9V2O/hxAmkRc8p6w==", + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.24.8.tgz", + "integrity": "sha512-LABppdt+Lp/RlBxqrh4qgf1oEH/WxdzQNDJIu5gC/W1GyvPVrOBiItmmM8wan2fm4oYqFuFfkXmlGpLQhPY8CA==", "dev": true, "requires": { - "@babel/traverse": "^7.24.7", - "@babel/types": "^7.24.7" + "@babel/traverse": "^7.24.8", + "@babel/types": "^7.24.8" } }, "@babel/helper-module-imports": { @@ -18265,15 +16239,14 @@ } }, "@babel/helper-module-transforms": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.24.7.tgz", - "integrity": "sha512-1fuJEwIrp+97rM4RWdO+qrRsZlAeL1lQJoPqtCYWv0NL115XM93hIH4CSRln2w52SqvmY5hqdtauB6QFCDiZNQ==", + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.25.2.tgz", + "integrity": "sha512-BjyRAbix6j/wv83ftcVJmBt72QtHI56C7JXZoG2xATiLpmoC7dpd8WnkikExHDVPpi/3qCmO6WY1EaXOluiecQ==", "requires": { - "@babel/helper-environment-visitor": "^7.24.7", "@babel/helper-module-imports": "^7.24.7", "@babel/helper-simple-access": "^7.24.7", - "@babel/helper-split-export-declaration": "^7.24.7", - "@babel/helper-validator-identifier": "^7.24.7" + "@babel/helper-validator-identifier": "^7.24.7", + "@babel/traverse": "^7.25.2" } }, "@babel/helper-optimise-call-expression": { @@ -18286,30 +16259,30 @@ } }, "@babel/helper-plugin-utils": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.7.tgz", - "integrity": "sha512-Rq76wjt7yz9AAc1KnlRKNAi/dMSVWgDRx43FHoJEbcYU6xOWaE2dVPwcdTukJrjxS65GITyfbvEYHvkirZ6uEg==" + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.8.tgz", + "integrity": "sha512-FFWx5142D8h2Mgr/iPVGH5G7w6jDn4jUSpZTyDnQO0Yn7Ks2Kuz6Pci8H6MPCoUJegd/UZQ3tAvfLCxQSnWWwg==" }, "@babel/helper-remap-async-to-generator": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.24.7.tgz", - "integrity": "sha512-9pKLcTlZ92hNZMQfGCHImUpDOlAgkkpqalWEeftW5FBya75k8Li2ilerxkM/uBEj01iBZXcCIB/bwvDYgWyibA==", + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.25.0.tgz", + "integrity": "sha512-NhavI2eWEIz/H9dbrG0TuOicDhNexze43i5z7lEqwYm0WEZVTwnPpA0EafUTP7+6/W79HWIP2cTe3Z5NiSTVpw==", "dev": true, "requires": { "@babel/helper-annotate-as-pure": "^7.24.7", - "@babel/helper-environment-visitor": "^7.24.7", - "@babel/helper-wrap-function": "^7.24.7" + "@babel/helper-wrap-function": "^7.25.0", + "@babel/traverse": "^7.25.0" } }, "@babel/helper-replace-supers": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.24.7.tgz", - "integrity": "sha512-qTAxxBM81VEyoAY0TtLrx1oAEJc09ZK67Q9ljQToqCnA+55eNwCORaxlKyu+rNfX86o8OXRUSNUnrtsAZXM9sg==", + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.25.0.tgz", + "integrity": "sha512-q688zIvQVYtZu+i2PsdIu/uWGRpfxzr5WESsfpShfZECkO+d2o+WROWezCi/Q6kJ0tfPa5+pUGUlfx2HhrA3Bg==", "dev": true, "requires": { - "@babel/helper-environment-visitor": "^7.24.7", - "@babel/helper-member-expression-to-functions": "^7.24.7", - "@babel/helper-optimise-call-expression": "^7.24.7" + "@babel/helper-member-expression-to-functions": "^7.24.8", + "@babel/helper-optimise-call-expression": "^7.24.7", + "@babel/traverse": "^7.25.0" } }, "@babel/helper-simple-access": { @@ -18331,18 +16304,10 @@ "@babel/types": "^7.24.7" } }, - "@babel/helper-split-export-declaration": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.7.tgz", - "integrity": "sha512-oy5V7pD+UvfkEATUKvIjvIAH/xCzfsFVw7ygW2SI6NClZzquT+mwdTfgfdbUiceh6iQO0CHtCPsyze/MZ2YbAA==", - "requires": { - "@babel/types": "^7.24.7" - } - }, "@babel/helper-string-parser": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.7.tgz", - "integrity": "sha512-7MbVt6xrwFQbunH2DNQsAP5sTGxfqQtErvBIvIMi6EQnbgUOuVYanvREcmFrOPhoXBrTtjhhP+lW+o5UfK+tDg==" + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz", + "integrity": "sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==" }, "@babel/helper-validator-identifier": { "version": "7.24.7", @@ -18350,29 +16315,28 @@ "integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==" }, "@babel/helper-validator-option": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.24.7.tgz", - "integrity": "sha512-yy1/KvjhV/ZCL+SM7hBrvnZJ3ZuT9OuZgIJAGpPEToANvc3iM6iDvBnRjtElWibHU6n8/LPR/EjX9EtIEYO3pw==" + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.24.8.tgz", + "integrity": "sha512-xb8t9tD1MHLungh/AIoWYN+gVHaB9kwlu8gffXGSt3FFEIT7RjS+xWbc2vUD1UTZdIpKj/ab3rdqJ7ufngyi2Q==" }, "@babel/helper-wrap-function": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.24.7.tgz", - "integrity": "sha512-N9JIYk3TD+1vq/wn77YnJOqMtfWhNewNE+DJV4puD2X7Ew9J4JvrzrFDfTfyv5EgEXVy9/Wt8QiOErzEmv5Ifw==", + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.25.0.tgz", + "integrity": "sha512-s6Q1ebqutSiZnEjaofc/UKDyC4SbzV5n5SrA2Gq8UawLycr3i04f1dX4OzoQVnexm6aOCh37SQNYlJ/8Ku+PMQ==", "dev": true, "requires": { - "@babel/helper-function-name": "^7.24.7", - "@babel/template": "^7.24.7", - "@babel/traverse": "^7.24.7", - "@babel/types": "^7.24.7" + "@babel/template": "^7.25.0", + "@babel/traverse": "^7.25.0", + "@babel/types": "^7.25.0" } }, "@babel/helpers": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.7.tgz", - "integrity": "sha512-NlmJJtvcw72yRJRcnCmGvSi+3jDEg8qFu3z0AFoymmzLx5ERVWyzd9kVXr7Th9/8yIJi2Zc6av4Tqz3wFs8QWg==", + "version": "7.25.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.25.6.tgz", + "integrity": "sha512-Xg0tn4HcfTijTwfDwYlvVCl43V6h4KyVVX2aEm4qdO/PC6L2YvzLHFdmxhoeSA3eslcE6+ZVXHgWwopXYLNq4Q==", "requires": { - "@babel/template": "^7.24.7", - "@babel/types": "^7.24.7" + "@babel/template": "^7.25.0", + "@babel/types": "^7.25.6" } }, "@babel/highlight": { @@ -18387,27 +16351,39 @@ } }, "@babel/parser": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.7.tgz", - "integrity": "sha512-9uUYRm6OqQrCqQdG1iCBwBPZgN8ciDBro2nIOFaiRz1/BCxaI7CNvQbDHvsArAC7Tw9Hda/B3U+6ui9u4HWXPw==" + "version": "7.25.6", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.6.tgz", + "integrity": "sha512-trGdfBdbD0l1ZPmcJ83eNxB9rbEax4ALFTF7fN386TMYbeCQbyme5cOEXQhbGXKebwGaB/J52w1mrklMcbgy6Q==", + "requires": { + "@babel/types": "^7.25.6" + } }, "@babel/plugin-bugfix-firefox-class-in-computed-class-key": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.24.7.tgz", - "integrity": "sha512-TiT1ss81W80eQsN+722OaeQMY/G4yTb4G9JrqeiDADs3N8lbPMGldWi9x8tyqCW5NLx1Jh2AvkE6r6QvEltMMQ==", + "version": "7.25.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.25.3.tgz", + "integrity": "sha512-wUrcsxZg6rqBXG05HG1FPYgsP6EvwF4WpBbxIpWIIYnH8wG0gzx3yZY3dtEHas4sTAOGkbTsc9EGPxwff8lRoA==", "dev": true, "requires": { - "@babel/helper-environment-visitor": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-plugin-utils": "^7.24.8", + "@babel/traverse": "^7.25.3" + } + }, + "@babel/plugin-bugfix-safari-class-field-initializer-scope": { + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.25.0.tgz", + "integrity": "sha512-Bm4bH2qsX880b/3ziJ8KD711LT7z4u8CFudmjqle65AZj/HNUFhEf90dqYv6O86buWvSBmeQDjv0Tn2aF/bIBA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.24.8" } }, "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.24.7.tgz", - "integrity": "sha512-unaQgZ/iRu/By6tsjMZzpeBZjChYfLYry6HrEXPoz3KmfF0sVBQ1l8zKMQ4xRGLWVsjuvB8nQfjNP/DcfEOCsg==", + "version": "7.25.0", + "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.25.0.tgz", + "integrity": "sha512-lXwdNZtTmeVOOFtwM/WDe7yg1PL8sYhRk/XH0FzbR2HDQ0xC+EnQ/JHeoMYSavtU115tnUk0q9CDyq8si+LMAA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-plugin-utils": "^7.24.8" } }, "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { @@ -18422,13 +16398,13 @@ } }, "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.24.7.tgz", - "integrity": "sha512-utA4HuR6F4Vvcr+o4DnjL8fCOlgRFGbeeBEGNg3ZTrLFw6VWG5XmUrvcQ0FjIYMU2ST4XcR2Wsp7t9qOAPnxMg==", + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.25.0.tgz", + "integrity": "sha512-tggFrk1AIShG/RUQbEwt2Tr/E+ObkfwrPjR6BjbRvsx24+PSjK8zrq0GWPNCjo8qpRx4DuJzlcvWJqlm+0h3kw==", "dev": true, "requires": { - "@babel/helper-environment-visitor": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-plugin-utils": "^7.24.8", + "@babel/traverse": "^7.25.0" } }, "@babel/plugin-proposal-private-property-in-object": { @@ -18638,15 +16614,15 @@ } }, "@babel/plugin-transform-async-generator-functions": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.24.7.tgz", - "integrity": "sha512-o+iF77e3u7ZS4AoAuJvapz9Fm001PuD2V3Lp6OSE4FYQke+cSewYtnek+THqGRWyQloRCyvWL1OkyfNEl9vr/g==", + "version": "7.25.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.25.4.tgz", + "integrity": "sha512-jz8cV2XDDTqjKPwVPJBIjORVEmSGYhdRa8e5k5+vN+uwcjSrSxUaebBRa4ko1jqNF2uxyg8G6XYk30Jv285xzg==", "dev": true, "requires": { - "@babel/helper-environment-visitor": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/helper-remap-async-to-generator": "^7.24.7", - "@babel/plugin-syntax-async-generators": "^7.8.4" + "@babel/helper-plugin-utils": "^7.24.8", + "@babel/helper-remap-async-to-generator": "^7.25.0", + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/traverse": "^7.25.4" } }, "@babel/plugin-transform-async-to-generator": { @@ -18670,22 +16646,22 @@ } }, "@babel/plugin-transform-block-scoping": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.24.7.tgz", - "integrity": "sha512-Nd5CvgMbWc+oWzBsuaMcbwjJWAcp5qzrbg69SZdHSP7AMY0AbWFqFO0WTFCA1jxhMCwodRwvRec8k0QUbZk7RQ==", + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.25.0.tgz", + "integrity": "sha512-yBQjYoOjXlFv9nlXb3f1casSHOZkWr29NX+zChVanLg5Nc157CrbEX9D7hxxtTpuFy7Q0YzmmWfJxzvps4kXrQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-plugin-utils": "^7.24.8" } }, "@babel/plugin-transform-class-properties": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.24.7.tgz", - "integrity": "sha512-vKbfawVYayKcSeSR5YYzzyXvsDFWU2mD8U5TFeXtbCPLFUqe7GyCgvO6XDHzje862ODrOwy6WCPmKeWHbCFJ4w==", + "version": "7.25.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.25.4.tgz", + "integrity": "sha512-nZeZHyCWPfjkdU5pA/uHiTaDAFUEqkpzf1YoQT2NeSynCGYq9rxfyI3XpQbfx/a0hSnFH6TGlEXvae5Vi7GD8g==", "dev": true, "requires": { - "@babel/helper-create-class-features-plugin": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-create-class-features-plugin": "^7.25.4", + "@babel/helper-plugin-utils": "^7.24.8" } }, "@babel/plugin-transform-class-static-block": { @@ -18700,18 +16676,16 @@ } }, "@babel/plugin-transform-classes": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.24.7.tgz", - "integrity": "sha512-CFbbBigp8ln4FU6Bpy6g7sE8B/WmCmzvivzUC6xDAdWVsjYTXijpuuGJmYkAaoWAzcItGKT3IOAbxRItZ5HTjw==", + "version": "7.25.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.25.4.tgz", + "integrity": "sha512-oexUfaQle2pF/b6E0dwsxQtAol9TLSO88kQvym6HHBWFliV2lGdrPieX+WgMRLSJDVzdYywk7jXbLPuO2KLTLg==", "dev": true, "requires": { "@babel/helper-annotate-as-pure": "^7.24.7", - "@babel/helper-compilation-targets": "^7.24.7", - "@babel/helper-environment-visitor": "^7.24.7", - "@babel/helper-function-name": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/helper-replace-supers": "^7.24.7", - "@babel/helper-split-export-declaration": "^7.24.7", + "@babel/helper-compilation-targets": "^7.25.2", + "@babel/helper-plugin-utils": "^7.24.8", + "@babel/helper-replace-supers": "^7.25.0", + "@babel/traverse": "^7.25.4", "globals": "^11.1.0" } }, @@ -18726,12 +16700,12 @@ } }, "@babel/plugin-transform-destructuring": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.24.7.tgz", - "integrity": "sha512-19eJO/8kdCQ9zISOf+SEUJM/bAUIsvY3YDnXZTupUCQ8LgrWnsG/gFB9dvXqdXnRXMAM8fvt7b0CBKQHNGy1mw==", + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.24.8.tgz", + "integrity": "sha512-36e87mfY8TnRxc7yc6M9g9gOB7rKgSahqkIKwLpz4Ppk2+zC2Cy1is0uwtuSG6AE4zlTOUa+7JGz9jCJGLqQFQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-plugin-utils": "^7.24.8" } }, "@babel/plugin-transform-dotall-regex": { @@ -18753,6 +16727,16 @@ "@babel/helper-plugin-utils": "^7.24.7" } }, + "@babel/plugin-transform-duplicate-named-capturing-groups-regex": { + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.25.0.tgz", + "integrity": "sha512-YLpb4LlYSc3sCUa35un84poXoraOiQucUTTu8X1j18JV+gNa8E0nyUf/CjZ171IRGr4jEguF+vzJU66QZhn29g==", + "dev": true, + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.25.0", + "@babel/helper-plugin-utils": "^7.24.8" + } + }, "@babel/plugin-transform-dynamic-import": { "version": "7.24.7", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.24.7.tgz", @@ -18794,14 +16778,14 @@ } }, "@babel/plugin-transform-function-name": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.24.7.tgz", - "integrity": "sha512-U9FcnA821YoILngSmYkW6FjyQe2TyZD5pHt4EVIhmcTkrJw/3KqcrRSxuOo5tFZJi7TE19iDyI1u+weTI7bn2w==", + "version": "7.25.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.25.1.tgz", + "integrity": "sha512-TVVJVdW9RKMNgJJlLtHsKDTydjZAbwIsn6ySBPQaEAUU5+gVvlJt/9nRmqVbsV/IBanRjzWoaAQKLoamWVOUuA==", "dev": true, "requires": { - "@babel/helper-compilation-targets": "^7.24.7", - "@babel/helper-function-name": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-compilation-targets": "^7.24.8", + "@babel/helper-plugin-utils": "^7.24.8", + "@babel/traverse": "^7.25.1" } }, "@babel/plugin-transform-json-strings": { @@ -18815,12 +16799,12 @@ } }, "@babel/plugin-transform-literals": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.24.7.tgz", - "integrity": "sha512-vcwCbb4HDH+hWi8Pqenwnjy+UiklO4Kt1vfspcQYFhJdpthSnW8XvWGyDZWKNVrVbVViI/S7K9PDJZiUmP2fYQ==", + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.25.2.tgz", + "integrity": "sha512-HQI+HcTbm9ur3Z2DkO+jgESMAMcYLuN/A7NRw9juzxAezN9AvqvUTnpKP/9kkYANz6u7dFlAyOu44ejuGySlfw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-plugin-utils": "^7.24.8" } }, "@babel/plugin-transform-logical-assignment-operators": { @@ -18853,26 +16837,26 @@ } }, "@babel/plugin-transform-modules-commonjs": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.24.7.tgz", - "integrity": "sha512-iFI8GDxtevHJ/Z22J5xQpVqFLlMNstcLXh994xifFwxxGslr2ZXXLWgtBeLctOD63UFDArdvN6Tg8RFw+aEmjQ==", + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.24.8.tgz", + "integrity": "sha512-WHsk9H8XxRs3JXKWFiqtQebdh9b/pTk4EgueygFzYlTKAg0Ud985mSevdNjdXdFBATSKVJGQXP1tv6aGbssLKA==", "dev": true, "requires": { - "@babel/helper-module-transforms": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-module-transforms": "^7.24.8", + "@babel/helper-plugin-utils": "^7.24.8", "@babel/helper-simple-access": "^7.24.7" } }, "@babel/plugin-transform-modules-systemjs": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.24.7.tgz", - "integrity": "sha512-GYQE0tW7YoaN13qFh3O1NCY4MPkUiAH3fiF7UcV/I3ajmDKEdG3l+UOcbAm4zUE3gnvUU+Eni7XrVKo9eO9auw==", + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.25.0.tgz", + "integrity": "sha512-YPJfjQPDXxyQWg/0+jHKj1llnY5f/R6a0p/vP4lPymxLu7Lvl4k2WMitqi08yxwQcCVUUdG9LCUj4TNEgAp3Jw==", "dev": true, "requires": { - "@babel/helper-hoist-variables": "^7.24.7", - "@babel/helper-module-transforms": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/helper-validator-identifier": "^7.24.7" + "@babel/helper-module-transforms": "^7.25.0", + "@babel/helper-plugin-utils": "^7.24.8", + "@babel/helper-validator-identifier": "^7.24.7", + "@babel/traverse": "^7.25.0" } }, "@babel/plugin-transform-modules-umd": { @@ -18957,12 +16941,12 @@ } }, "@babel/plugin-transform-optional-chaining": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.24.7.tgz", - "integrity": "sha512-tK+0N9yd4j+x/4hxF3F0e0fu/VdcxU18y5SevtyM/PCFlQvXbR0Zmlo2eBrKtVipGNFzpq56o8WsIIKcJFUCRQ==", + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.24.8.tgz", + "integrity": "sha512-5cTOLSMs9eypEy8JUVvIKOu6NgvbJMnpG62VpIHrTmROdQ+L5mDAaI40g25k5vXti55JWNX5jCkq3HZxXBQANw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.8", "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7", "@babel/plugin-syntax-optional-chaining": "^7.8.3" } @@ -18977,13 +16961,13 @@ } }, "@babel/plugin-transform-private-methods": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.24.7.tgz", - "integrity": "sha512-COTCOkG2hn4JKGEKBADkA8WNb35TGkkRbI5iT845dB+NyqgO8Hn+ajPbSnIQznneJTa3d30scb6iz/DhH8GsJQ==", + "version": "7.25.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.25.4.tgz", + "integrity": "sha512-ao8BG7E2b/URaUQGqN3Tlsg+M3KlHY6rJ1O1gXAEUnZoyNQnvKyH87Kfg+FoxSeyWUB8ISZZsC91C44ZuBFytw==", "dev": true, "requires": { - "@babel/helper-create-class-features-plugin": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-create-class-features-plugin": "^7.25.4", + "@babel/helper-plugin-utils": "^7.24.8" } }, "@babel/plugin-transform-private-property-in-object": { @@ -19088,12 +17072,12 @@ } }, "@babel/plugin-transform-typeof-symbol": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.24.7.tgz", - "integrity": "sha512-VtR8hDy7YLB7+Pet9IarXjg/zgCMSF+1mNS/EQEiEaUPoFXCVsHG64SIxcaaI2zJgRiv+YmgaQESUfWAdbjzgg==", + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.24.8.tgz", + "integrity": "sha512-adNTUpDCVnmAE58VEqKlAA6ZBlNkMnWD0ZcW76lyNFN3MJniyGFZfNwERVk8Ap56MCnXztmDr19T4mPTztcuaw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-plugin-utils": "^7.24.8" } }, "@babel/plugin-transform-unicode-escapes": { @@ -19126,29 +17110,30 @@ } }, "@babel/plugin-transform-unicode-sets-regex": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.24.7.tgz", - "integrity": "sha512-2G8aAvF4wy1w/AGZkemprdGMRg5o6zPNhbHVImRz3lss55TYCBd6xStN19rt8XJHq20sqV0JbyWjOWwQRwV/wg==", + "version": "7.25.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.25.4.tgz", + "integrity": "sha512-qesBxiWkgN1Q+31xUE9RcMk79eOXXDCv6tfyGMRSs4RGlioSg2WVyQAm07k726cSE56pa+Kb0y9epX2qaXzTvA==", "dev": true, "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-create-regexp-features-plugin": "^7.25.2", + "@babel/helper-plugin-utils": "^7.24.8" } }, "@babel/preset-env": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.24.7.tgz", - "integrity": "sha512-1YZNsc+y6cTvWlDHidMBsQZrZfEFjRIo/BZCT906PMdzOyXtSLTgqGdrpcuTDCXyd11Am5uQULtDIcCfnTc8fQ==", - "dev": true, - "requires": { - "@babel/compat-data": "^7.24.7", - "@babel/helper-compilation-targets": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/helper-validator-option": "^7.24.7", - "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.24.7", - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.24.7", + "version": "7.25.4", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.25.4.tgz", + "integrity": "sha512-W9Gyo+KmcxjGahtt3t9fb14vFRWvPpu5pT6GBlovAK6BTBcxgjfVMSQCfJl4oi35ODrxP6xx2Wr8LNST57Mraw==", + "dev": true, + "requires": { + "@babel/compat-data": "^7.25.4", + "@babel/helper-compilation-targets": "^7.25.2", + "@babel/helper-plugin-utils": "^7.24.8", + "@babel/helper-validator-option": "^7.24.8", + "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.25.3", + "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.25.0", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.25.0", "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.24.7", - "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.24.7", + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.25.0", "@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", @@ -19169,29 +17154,30 @@ "@babel/plugin-syntax-top-level-await": "^7.14.5", "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", "@babel/plugin-transform-arrow-functions": "^7.24.7", - "@babel/plugin-transform-async-generator-functions": "^7.24.7", + "@babel/plugin-transform-async-generator-functions": "^7.25.4", "@babel/plugin-transform-async-to-generator": "^7.24.7", "@babel/plugin-transform-block-scoped-functions": "^7.24.7", - "@babel/plugin-transform-block-scoping": "^7.24.7", - "@babel/plugin-transform-class-properties": "^7.24.7", + "@babel/plugin-transform-block-scoping": "^7.25.0", + "@babel/plugin-transform-class-properties": "^7.25.4", "@babel/plugin-transform-class-static-block": "^7.24.7", - "@babel/plugin-transform-classes": "^7.24.7", + "@babel/plugin-transform-classes": "^7.25.4", "@babel/plugin-transform-computed-properties": "^7.24.7", - "@babel/plugin-transform-destructuring": "^7.24.7", + "@babel/plugin-transform-destructuring": "^7.24.8", "@babel/plugin-transform-dotall-regex": "^7.24.7", "@babel/plugin-transform-duplicate-keys": "^7.24.7", + "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.25.0", "@babel/plugin-transform-dynamic-import": "^7.24.7", "@babel/plugin-transform-exponentiation-operator": "^7.24.7", "@babel/plugin-transform-export-namespace-from": "^7.24.7", "@babel/plugin-transform-for-of": "^7.24.7", - "@babel/plugin-transform-function-name": "^7.24.7", + "@babel/plugin-transform-function-name": "^7.25.1", "@babel/plugin-transform-json-strings": "^7.24.7", - "@babel/plugin-transform-literals": "^7.24.7", + "@babel/plugin-transform-literals": "^7.25.2", "@babel/plugin-transform-logical-assignment-operators": "^7.24.7", "@babel/plugin-transform-member-expression-literals": "^7.24.7", "@babel/plugin-transform-modules-amd": "^7.24.7", - "@babel/plugin-transform-modules-commonjs": "^7.24.7", - "@babel/plugin-transform-modules-systemjs": "^7.24.7", + "@babel/plugin-transform-modules-commonjs": "^7.24.8", + "@babel/plugin-transform-modules-systemjs": "^7.25.0", "@babel/plugin-transform-modules-umd": "^7.24.7", "@babel/plugin-transform-named-capturing-groups-regex": "^7.24.7", "@babel/plugin-transform-new-target": "^7.24.7", @@ -19200,9 +17186,9 @@ "@babel/plugin-transform-object-rest-spread": "^7.24.7", "@babel/plugin-transform-object-super": "^7.24.7", "@babel/plugin-transform-optional-catch-binding": "^7.24.7", - "@babel/plugin-transform-optional-chaining": "^7.24.7", + "@babel/plugin-transform-optional-chaining": "^7.24.8", "@babel/plugin-transform-parameters": "^7.24.7", - "@babel/plugin-transform-private-methods": "^7.24.7", + "@babel/plugin-transform-private-methods": "^7.25.4", "@babel/plugin-transform-private-property-in-object": "^7.24.7", "@babel/plugin-transform-property-literals": "^7.24.7", "@babel/plugin-transform-regenerator": "^7.24.7", @@ -19211,16 +17197,16 @@ "@babel/plugin-transform-spread": "^7.24.7", "@babel/plugin-transform-sticky-regex": "^7.24.7", "@babel/plugin-transform-template-literals": "^7.24.7", - "@babel/plugin-transform-typeof-symbol": "^7.24.7", + "@babel/plugin-transform-typeof-symbol": "^7.24.8", "@babel/plugin-transform-unicode-escapes": "^7.24.7", "@babel/plugin-transform-unicode-property-regex": "^7.24.7", "@babel/plugin-transform-unicode-regex": "^7.24.7", - "@babel/plugin-transform-unicode-sets-regex": "^7.24.7", + "@babel/plugin-transform-unicode-sets-regex": "^7.25.4", "@babel/preset-modules": "0.1.6-no-external-plugins", "babel-plugin-polyfill-corejs2": "^0.4.10", - "babel-plugin-polyfill-corejs3": "^0.10.4", + "babel-plugin-polyfill-corejs3": "^0.10.6", "babel-plugin-polyfill-regenerator": "^0.6.1", - "core-js-compat": "^3.31.0", + "core-js-compat": "^3.37.1", "semver": "^6.3.1" }, "dependencies": { @@ -19285,38 +17271,35 @@ } }, "@babel/template": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.7.tgz", - "integrity": "sha512-jYqfPrU9JTF0PmPy1tLYHW4Mp4KlgxJD9l2nP9fD6yT/ICi554DmrWBAEYpIelzjHf1msDP3PxJIRt/nFNfBig==", + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.0.tgz", + "integrity": "sha512-aOOgh1/5XzKvg1jvVz7AVrx2piJ2XBi227DHmbY6y+bM9H2FlN+IfecYu4Xl0cNiiVejlsCri89LUsbj8vJD9Q==", "requires": { "@babel/code-frame": "^7.24.7", - "@babel/parser": "^7.24.7", - "@babel/types": "^7.24.7" + "@babel/parser": "^7.25.0", + "@babel/types": "^7.25.0" } }, "@babel/traverse": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.7.tgz", - "integrity": "sha512-yb65Ed5S/QAcewNPh0nZczy9JdYXkkAbIsEo+P7BE7yO3txAY30Y/oPa3QkQ5It3xVG2kpKMg9MsdxZaO31uKA==", + "version": "7.25.6", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.6.tgz", + "integrity": "sha512-9Vrcx5ZW6UwK5tvqsj0nGpp/XzqthkT0dqIc9g1AdtygFToNtTF67XzYS//dm+SAK9cp3B9R4ZO/46p63SCjlQ==", "requires": { "@babel/code-frame": "^7.24.7", - "@babel/generator": "^7.24.7", - "@babel/helper-environment-visitor": "^7.24.7", - "@babel/helper-function-name": "^7.24.7", - "@babel/helper-hoist-variables": "^7.24.7", - "@babel/helper-split-export-declaration": "^7.24.7", - "@babel/parser": "^7.24.7", - "@babel/types": "^7.24.7", + "@babel/generator": "^7.25.6", + "@babel/parser": "^7.25.6", + "@babel/template": "^7.25.0", + "@babel/types": "^7.25.6", "debug": "^4.3.1", "globals": "^11.1.0" } }, "@babel/types": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.7.tgz", - "integrity": "sha512-XEFXSlxiG5td2EJRe8vOmRbaXVgfcBlszKujvVmWIK/UpywWljQCfzAv3RQCGujWQ1RD4YYWEAqDXfuJiy8f5Q==", + "version": "7.25.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.6.tgz", + "integrity": "sha512-/l42B1qxpG6RdfYf343Uw1vmDjeNhneUXtzhojE7pDgfpEypmRhI6j1kr17XCVv4Cgl9HdAiQY2x0GwKm7rWCw==", "requires": { - "@babel/helper-string-parser": "^7.24.7", + "@babel/helper-string-parser": "^7.24.8", "@babel/helper-validator-identifier": "^7.24.7", "to-fast-properties": "^2.0.0" } @@ -19371,313 +17354,78 @@ "integrity": "sha512-pPTNuaAG3QMH+buKyBIGJs3g/S5y0caxw0ygM3YyE6yJFySwiGGSzA+mM3KJ8QQvzeLh3blwgSonkFjgQdxzMw==", "dev": true }, - "@eslint/eslintrc": { - "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.6.0", - "globals": "^13.19.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" - }, - "dependencies": { - "argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "globals": { - "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" - } - }, - "js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "requires": { - "argparse": "^2.0.1" - } - }, - "type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true - } - } - }, - "@eslint/js": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz", - "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==", - "dev": true - }, - "@ethereumjs/common": { - "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.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.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/rlp": "^4.0.1", - "ethereum-cryptography": "^2.0.0", - "micro-ftch": "^0.3.1" - }, - "dependencies": { - "ethereum-cryptography": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-2.1.3.tgz", - "integrity": "sha512-BlwbIL7/P45W8FGW2r7LGuvoEZ+7PWsniMvQ4p5s2xCyw9tmaDlpfsN9HjAucbF+t/qpVHwZUisgfK24TCW8aA==", - "requires": { - "@noble/curves": "1.3.0", - "@noble/hashes": "1.3.3", - "@scure/bip32": "1.3.3", - "@scure/bip39": "1.2.2" - } - } - } - }, - "@ethersproject/abi": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/abi/-/abi-5.7.0.tgz", - "integrity": "sha512-351ktp42TiRcYB3H1OP8yajPeAQstMW/yCFokj/AthP9bLHzQFPlOrxOcwYEDkUAICmOHljvN4K39OMTMUa9RA==", - "requires": { - "@ethersproject/address": "^5.7.0", - "@ethersproject/bignumber": "^5.7.0", - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/constants": "^5.7.0", - "@ethersproject/hash": "^5.7.0", - "@ethersproject/keccak256": "^5.7.0", - "@ethersproject/logger": "^5.7.0", - "@ethersproject/properties": "^5.7.0", - "@ethersproject/strings": "^5.7.0" - } - }, - "@ethersproject/abstract-provider": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/abstract-provider/-/abstract-provider-5.7.0.tgz", - "integrity": "sha512-R41c9UkchKCpAqStMYUpdunjo3pkEvZC3FAwZn5S5MGbXoMQOHIdHItezTETxAO5bevtMApSyEhn9+CHcDsWBw==", - "requires": { - "@ethersproject/bignumber": "^5.7.0", - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/logger": "^5.7.0", - "@ethersproject/networks": "^5.7.0", - "@ethersproject/properties": "^5.7.0", - "@ethersproject/transactions": "^5.7.0", - "@ethersproject/web": "^5.7.0" - } - }, - "@ethersproject/abstract-signer": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/abstract-signer/-/abstract-signer-5.7.0.tgz", - "integrity": "sha512-a16V8bq1/Cz+TGCkE2OPMTOUDLS3grCpdjoJCYNnVBbdYEMSgKrU0+B90s8b6H+ByYTBZN7a3g76jdIJi7UfKQ==", - "requires": { - "@ethersproject/abstract-provider": "^5.7.0", - "@ethersproject/bignumber": "^5.7.0", - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/logger": "^5.7.0", - "@ethersproject/properties": "^5.7.0" - } - }, - "@ethersproject/address": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/address/-/address-5.7.0.tgz", - "integrity": "sha512-9wYhYt7aghVGo758POM5nqcOMaE168Q6aRLJZwUmiqSrAungkG74gSSeKEIR7ukixesdRZGPgVqme6vmxs1fkA==", - "requires": { - "@ethersproject/bignumber": "^5.7.0", - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/keccak256": "^5.7.0", - "@ethersproject/logger": "^5.7.0", - "@ethersproject/rlp": "^5.7.0" - } - }, - "@ethersproject/base64": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/base64/-/base64-5.7.0.tgz", - "integrity": "sha512-Dr8tcHt2mEbsZr/mwTPIQAf3Ai0Bks/7gTw9dSqk1mQvhW3XvRlmDJr/4n+wg1JmCl16NZue17CDh8xb/vZ0sQ==", - "requires": { - "@ethersproject/bytes": "^5.7.0" - } - }, - "@ethersproject/bignumber": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/bignumber/-/bignumber-5.7.0.tgz", - "integrity": "sha512-n1CAdIHRWjSucQO3MC1zPSVgV/6dy/fjL9pMrPP9peL+QxEg9wOsVqwD4+818B6LUEtaXzVHQiuivzRoxPxUGw==", - "requires": { - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/logger": "^5.7.0", - "bn.js": "^5.2.1" - } - }, - "@ethersproject/bytes": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/bytes/-/bytes-5.7.0.tgz", - "integrity": "sha512-nsbxwgFXWh9NyYWo+U8atvmMsSdKJprTcICAkvbBffT75qDocbuggBU0SJiVK2MuTrp0q+xvLkTnGMPK1+uA9A==", - "requires": { - "@ethersproject/logger": "^5.7.0" - } - }, - "@ethersproject/constants": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/constants/-/constants-5.7.0.tgz", - "integrity": "sha512-DHI+y5dBNvkpYUMiRQyxRBYBefZkJfo70VUkUAsRjcPs47muV9evftfZ0PJVCXYbAiCgght0DtcF9srFQmIgWA==", - "requires": { - "@ethersproject/bignumber": "^5.7.0" - } - }, - "@ethersproject/hash": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/hash/-/hash-5.7.0.tgz", - "integrity": "sha512-qX5WrQfnah1EFnO5zJv1v46a8HW0+E5xuBBDTwMFZLuVTx0tbU2kkx15NqdjxecrLGatQN9FGQKpb1FKdHCt+g==", - "requires": { - "@ethersproject/abstract-signer": "^5.7.0", - "@ethersproject/address": "^5.7.0", - "@ethersproject/base64": "^5.7.0", - "@ethersproject/bignumber": "^5.7.0", - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/keccak256": "^5.7.0", - "@ethersproject/logger": "^5.7.0", - "@ethersproject/properties": "^5.7.0", - "@ethersproject/strings": "^5.7.0" - } - }, - "@ethersproject/keccak256": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/keccak256/-/keccak256-5.7.0.tgz", - "integrity": "sha512-2UcPboeL/iW+pSg6vZ6ydF8tCnv3Iu/8tUmLLzWWGzxWKFFqOBQFLo6uLUv6BDrLgCDfN28RJ/wtByx+jZ4KBg==", - "requires": { - "@ethersproject/bytes": "^5.7.0", - "js-sha3": "0.8.0" - } - }, - "@ethersproject/logger": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/logger/-/logger-5.7.0.tgz", - "integrity": "sha512-0odtFdXu/XHtjQXJYA3u9G0G8btm0ND5Cu8M7i5vhEcE8/HmF4Lbdqanwyv4uQTr2tx6b7fQRmgLrsnpQlmnig==" - }, - "@ethersproject/networks": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/@ethersproject/networks/-/networks-5.7.1.tgz", - "integrity": "sha512-n/MufjFYv3yFcUyfhnXotyDlNdFb7onmkSy8aQERi2PjNcnWQ66xXxa3XlS8nCcA8aJKJjIIMNJTC7tu80GwpQ==", - "requires": { - "@ethersproject/logger": "^5.7.0" - } - }, - "@ethersproject/properties": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/properties/-/properties-5.7.0.tgz", - "integrity": "sha512-J87jy8suntrAkIZtecpxEPxY//szqr1mlBaYlQ0r4RCaiD2hjheqF9s1LVE8vVuJCXisjIP+JgtK/Do54ej4Sw==", - "requires": { - "@ethersproject/logger": "^5.7.0" - } - }, - "@ethersproject/rlp": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/rlp/-/rlp-5.7.0.tgz", - "integrity": "sha512-rBxzX2vK8mVF7b0Tol44t5Tb8gomOHkj5guL+HhzQ1yBh/ydjGnpw6at+X6Iw0Kp3OzzzkcKp8N9r0W4kYSs9w==", - "requires": { - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/logger": "^5.7.0" - } - }, - "@ethersproject/signing-key": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/signing-key/-/signing-key-5.7.0.tgz", - "integrity": "sha512-MZdy2nL3wO0u7gkB4nA/pEf8lu1TlFswPNmy8AiYkfKTdO6eXBJyUdmHO/ehm/htHw9K/qF8ujnTyUAD+Ry54Q==", - "requires": { - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/logger": "^5.7.0", - "@ethersproject/properties": "^5.7.0", - "bn.js": "^5.2.1", - "elliptic": "6.5.4", - "hash.js": "1.1.7" - } - }, - "@ethersproject/strings": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/strings/-/strings-5.7.0.tgz", - "integrity": "sha512-/9nu+lj0YswRNSH0NXYqrh8775XNyEdUQAuf3f+SmOrnVewcJ5SBNAjF7lpgehKi4abvNNXyf+HX86czCdJ8Mg==", - "requires": { - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/constants": "^5.7.0", - "@ethersproject/logger": "^5.7.0" - } - }, - "@ethersproject/transactions": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/transactions/-/transactions-5.7.0.tgz", - "integrity": "sha512-kmcNicCp1lp8qanMTC3RIikGgoJ80ztTyvtsFvCYpSCfkjhD0jZ2LOrnbcuxuToLIUYYf+4XwD1rP+B/erDIhQ==", + "@eslint/eslintrc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "dev": true, "requires": { - "@ethersproject/address": "^5.7.0", - "@ethersproject/bignumber": "^5.7.0", - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/constants": "^5.7.0", - "@ethersproject/keccak256": "^5.7.0", - "@ethersproject/logger": "^5.7.0", - "@ethersproject/properties": "^5.7.0", - "@ethersproject/rlp": "^5.7.0", - "@ethersproject/signing-key": "^5.7.0" + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "dependencies": { + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "globals": { + "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" + } + }, + "js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "requires": { + "argparse": "^2.0.1" + } + }, + "type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true + } } }, - "@ethersproject/web": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/@ethersproject/web/-/web-5.7.1.tgz", - "integrity": "sha512-Gueu8lSvyjBWL4cYsWsjh6MtMwM0+H4HvqFPZfB6dV8ctbP9zFAO73VG1cMWae0FLPCtz0peKPpZY8/ugJJX2w==", - "requires": { - "@ethersproject/base64": "^5.7.0", - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/logger": "^5.7.0", - "@ethersproject/properties": "^5.7.0", - "@ethersproject/strings": "^5.7.0" - } + "@eslint/js": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", + "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", + "dev": true + }, + "@ethereumjs/rlp": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@ethereumjs/rlp/-/rlp-4.0.1.tgz", + "integrity": "sha512-tqsQiBQDQdmPWE1xkkBq4rlSW5QZpLOUJ5RJh2/9fug+q9tnUhuZoVLk7s0scUIKTOzEtR72DFBXI4WiZcMpvw==" }, "@fortawesome/fontawesome-free": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-6.5.2.tgz", - "integrity": "sha512-hRILoInAx8GNT5IMkrtIt9blOdrqHOnPBH+k70aWUAqPZPgopb9G5EQJFpaBx/S8zp2fC+mPW349Bziuk1o28Q==" + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-6.6.0.tgz", + "integrity": "sha512-60G28ke/sXdtS9KZCpZSHHkCbdsOGEhIUGlwq6yhY74UpTiToIh8np7A8yphhM4BWsvNFtIvLpi4co+h9Mr9Ow==" }, "@humanwhocodes/config-array": { - "version": "0.11.14", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", - "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", + "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", "dev": true, "requires": { - "@humanwhocodes/object-schema": "^2.0.2", + "@humanwhocodes/object-schema": "^2.0.3", "debug": "^4.3.1", "minimatch": "^3.0.5" } @@ -19689,9 +17437,9 @@ "dev": true }, "@humanwhocodes/object-schema": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.2.tgz", - "integrity": "sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", "dev": true }, "@istanbuljs/load-nyc-config": { @@ -20280,17 +18028,17 @@ "integrity": "sha512-/kSXhY692qiV1MXu6EeOZvg5nECLclxNXcKCxJ3cXQgYuRymRHpdx/t7JXfsK+JLjwA1e1c1/SBrlQYpusC29Q==" }, "@noble/curves": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.3.0.tgz", - "integrity": "sha512-t01iSXPuN+Eqzb4eBX0S5oubSqXbK/xXa1Ne18Hj8f9pStxztHCE2gfboSp/dZRLSqfuLpRK2nDXDK+W9puocA==", + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.4.2.tgz", + "integrity": "sha512-TavHr8qycMChk8UwMld0ZDRvatedkzWfH8IiaeGCfymOP5i0hSCozz9vHOL0nkwk7HRMlFnAiKpS2jrUmSybcw==", "requires": { - "@noble/hashes": "1.3.3" + "@noble/hashes": "1.4.0" } }, "@noble/hashes": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.3.tgz", - "integrity": "sha512-V7/fPHgl+jsVPXqqeOzT8egNj2iBIVt+ECeMMG8TdcnTikP3oaBtUVqpT/gYCR68aEBJSF+XbYUxStjbFMqIIA==" + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz", + "integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==" }, "@nodelib/fs.scandir": { "version": "2.1.5", @@ -20327,27 +18075,27 @@ } }, "@scure/base": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.5.tgz", - "integrity": "sha512-Brj9FiG2W1MRQSTB212YVPRrcbjkv48FoZi/u4l/zds/ieRrqsh7aUf6CLwkAq61oKXr/ZlTzlY66gLIj3TFTQ==" + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.8.tgz", + "integrity": "sha512-6CyAclxj3Nb0XT7GHK6K4zK6k2xJm6E4Ft0Ohjt4WgegiFUHEtFb2CGzmPmGBwoIhrLsqNLYfLr04Y1GePrzZg==" }, "@scure/bip32": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.3.3.tgz", - "integrity": "sha512-LJaN3HwRbfQK0X1xFSi0Q9amqOgzQnnDngIt+ZlsBC3Bm7/nE7K0kwshZHyaru79yIVRv/e1mQAjZyuZG6jOFQ==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.4.0.tgz", + "integrity": "sha512-sVUpc0Vq3tXCkDGYVWGIZTRfnvu8LoTDaev7vbwh0omSvVORONr960MQWdKqJDCReIEmTj3PAr73O3aoxz7OPg==", "requires": { - "@noble/curves": "~1.3.0", - "@noble/hashes": "~1.3.2", - "@scure/base": "~1.1.4" + "@noble/curves": "~1.4.0", + "@noble/hashes": "~1.4.0", + "@scure/base": "~1.1.6" } }, "@scure/bip39": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.2.2.tgz", - "integrity": "sha512-HYf9TUXG80beW+hGAt3TRM8wU6pQoYur9iNypTROm42dorCGmLnFe3eWjz3gOq6G62H2WRh0FCzAR1PI+29zIA==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.3.0.tgz", + "integrity": "sha512-disdg7gHuTDZtY+ZdkmLpPCk7fxZSu3gBiEGuoC1XYxv9cGx3Z6cpTggCgW6odSOOIXCiDjuGejW+aJKCY/pIQ==", "requires": { - "@noble/hashes": "~1.3.2", - "@scure/base": "~1.1.4" + "@noble/hashes": "~1.4.0", + "@scure/base": "~1.1.6" } }, "@sinclair/typebox": { @@ -20356,11 +18104,6 @@ "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", "dev": true }, - "@sindresorhus/is": { - "version": "4.6.0", - "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", @@ -20385,14 +18128,6 @@ "@sinonjs/commons": "^2.0.0" } }, - "@szmarczak/http-timer": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-5.0.1.tgz", - "integrity": "sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==", - "requires": { - "defer-to-connect": "^2.0.1" - } - }, "@tarekraafat/autocomplete.js": { "version": "10.2.7", "resolved": "https://registry.npmjs.org/@tarekraafat/autocomplete.js/-/autocomplete.js-10.2.7.tgz", @@ -20453,42 +18188,11 @@ "@types/node": "*" } }, - "@types/cacheable-request": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.3.tgz", - "integrity": "sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==", - "requires": { - "@types/http-cache-semantics": "*", - "@types/keyv": "^3.1.4", - "@types/node": "*", - "@types/responselike": "^1.0.0" - } - }, "@types/css-font-loading-module": { "version": "0.0.7", "resolved": "https://registry.npmjs.org/@types/css-font-loading-module/-/css-font-loading-module-0.0.7.tgz", "integrity": "sha512-nl09VhutdjINdWyXxHWN/w9zlNCfr60JUqJbd24YXUuCwgeL0TpFSdElCwb6cxfB6ybE19Gjj4g0jsgkXxKv1Q==" }, - "@types/eslint": { - "version": "8.4.1", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.1.tgz", - "integrity": "sha512-GE44+DNEyxxh2Kc6ro/VkIj+9ma0pO0bwv9+uHSyBrikYOHr8zYcdPvnBOp1aw8s+CjRvuSx7CyWqRrNFQ59mA==", - "dev": true, - "requires": { - "@types/estree": "*", - "@types/json-schema": "*" - } - }, - "@types/eslint-scope": { - "version": "3.7.3", - "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.3.tgz", - "integrity": "sha512-PB3ldyrcnAicT35TWPs5IcwKD8S333HMaa2VVv4+wdvebJkjWuW/xESoB8IwRcog8HYVYamb1g/R31Qv5Bx03g==", - "dev": true, - "requires": { - "@types/eslint": "*", - "@types/estree": "*" - } - }, "@types/estree": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", @@ -20504,11 +18208,6 @@ "@types/node": "*" } }, - "@types/http-cache-semantics": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz", - "integrity": "sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==" - }, "@types/istanbul-lib-coverage": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", @@ -20556,14 +18255,6 @@ "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", "dev": true }, - "@types/keyv": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.4.tgz", - "integrity": "sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==", - "requires": { - "@types/node": "*" - } - }, "@types/node": { "version": "16.11.6", "resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.6.tgz", @@ -20577,14 +18268,6 @@ "@types/node": "*" } }, - "@types/responselike": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.3.tgz", - "integrity": "sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==", - "requires": { - "@types/node": "*" - } - }, "@types/secp256k1": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/@types/secp256k1/-/secp256k1-4.0.3.tgz", @@ -20605,6 +18288,14 @@ "integrity": "sha512-Q5vtl1W5ue16D+nIaW8JWebSSraJVlK+EthKn7e7UcD4KWsaSJ8BqGPXNaPghgtcn/fhvrN17Tv8ksUsQpiplw==", "dev": true }, + "@types/ws": { + "version": "8.5.3", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.3.tgz", + "integrity": "sha512-6YOoWjruKj1uLf3INHH7D3qTXwFfEsg1kf3c0uDdSBJwfa/llkwIjrAGV7j7mVgGNbzTQ3HiHKKDXl6bJPD97w==", + "requires": { + "@types/node": "*" + } + }, "@types/yargs": { "version": "17.0.11", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.11.tgz", @@ -21045,10 +18736,11 @@ "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==", "dev": true }, - "abortcontroller-polyfill": { - "version": "1.7.5", - "resolved": "https://registry.npmjs.org/abortcontroller-polyfill/-/abortcontroller-polyfill-1.7.5.tgz", - "integrity": "sha512-JMJ5soJWP18htbbxJjG7bG6yuI6pRhgJ0scHHTfkUjf6wjP912xZWvM+A4sJK3gqd9E8fcPbDnOefbA9Th/FIQ==" + "abitype": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/abitype/-/abitype-0.7.1.tgz", + "integrity": "sha512-VBkRHTDZf9Myaek/dO3yMmOzB/y2s3Zo6nVU7yaw1G+TvCHAjwaJzNGN9yo4K5D8bU/VZXKP1EJpRhFr862PlQ==", + "requires": {} }, "abstract-leveldown": { "version": "2.6.3", @@ -21058,15 +18750,6 @@ "xtend": "~4.0.0" } }, - "accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", - "requires": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" - } - }, "acorn": { "version": "8.9.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.9.0.tgz", @@ -21224,11 +18907,6 @@ "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.7", "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.7.tgz", @@ -21384,16 +19062,16 @@ "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" }, "autoprefixer": { - "version": "10.4.19", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.19.tgz", - "integrity": "sha512-BaENR2+zBZ8xXhM4pUaKUxlVdxZ0EZhjvbopwnXmxRUfqDmwSpC2lAi/QXvx7NRdPCo1WKEcEF6mV64si1z4Ew==", + "version": "10.4.20", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.20.tgz", + "integrity": "sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g==", "dev": true, "requires": { - "browserslist": "^4.23.0", - "caniuse-lite": "^1.0.30001599", + "browserslist": "^4.23.3", + "caniuse-lite": "^1.0.30001646", "fraction.js": "^4.3.7", "normalize-range": "^0.1.2", - "picocolors": "^1.0.0", + "picocolors": "^1.0.1", "postcss-value-parser": "^4.2.0" } }, @@ -21539,19 +19217,19 @@ } }, "babel-plugin-polyfill-corejs3": { - "version": "0.10.4", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.10.4.tgz", - "integrity": "sha512-25J6I8NGfa5YkCDogHRID3fVCadIR8/pGl1/spvCkzb6lVn6SR3ojpx9nOn9iEBcUsjY24AmdKm5khcfKdylcg==", + "version": "0.10.6", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.10.6.tgz", + "integrity": "sha512-b37+KR2i/khY5sKmWNVQAnitvquQbNdWy6lJdsr0kmquCKEEUgMKK4SboVM3HtfnZilfjr4MMQ7vY58FVWDtIA==", "dev": true, "requires": { - "@babel/helper-define-polyfill-provider": "^0.6.1", - "core-js-compat": "^3.36.1" + "@babel/helper-define-polyfill-provider": "^0.6.2", + "core-js-compat": "^3.38.0" }, "dependencies": { "@babel/helper-define-polyfill-provider": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.1.tgz", - "integrity": "sha512-o7SDgTJuvx5vLKD6SFvkydkSMBvahDKGiNJzG22IZYXhiqoe9efY7zocICBgzHV4IRg5wdgl2nEL/tulKIEIbA==", + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.2.tgz", + "integrity": "sha512-LV76g+C502biUK6AyZ3LK10vDpDyCzZnhZFXkH1L75zHPj68+qc8Zfpx2th+gzwA2MzyK+1g/3EPl62yFnVttQ==", "dev": true, "requires": { "@babel/helper-compilation-targets": "^7.22.6", @@ -21668,69 +19346,16 @@ "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", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "dev": true - }, "blakejs": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/blakejs/-/blakejs-1.1.1.tgz", "integrity": "sha512-bLG6PHOCZJKNshTjGRBvET0vTciwQE6zFKOKKXPDJfwFBd4Ac0yBfPZqcGvGJap50l7ktvlpFqc2jGVaUgbJgg==" }, - "bluebird": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", - "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" - }, "bn.js": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==" }, - "body-parser": { - "version": "1.20.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", - "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", - "requires": { - "bytes": "3.1.2", - "content-type": "~1.0.5", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.11.0", - "raw-body": "2.5.2", - "type-is": "~1.6.18", - "unpipe": "1.0.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, - "qs": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", - "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", - "requires": { - "side-channel": "^1.0.4" - } - } - } - }, "boolbase": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", @@ -21840,14 +19465,14 @@ } }, "browserslist": { - "version": "4.23.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.0.tgz", - "integrity": "sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==", + "version": "4.23.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.3.tgz", + "integrity": "sha512-btwCFJVjI4YWDNfau8RhZ+B1Q/VLoUITrm3RlP6y1tYGWIOa+InuYiRGXUBXo8nA1qKmHMyLB/iVQg5TT4eFoA==", "requires": { - "caniuse-lite": "^1.0.30001587", - "electron-to-chromium": "^1.4.668", - "node-releases": "^2.0.14", - "update-browserslist-db": "^1.0.13" + "caniuse-lite": "^1.0.30001646", + "electron-to-chromium": "^1.5.4", + "node-releases": "^2.0.18", + "update-browserslist-db": "^1.1.0" } }, "bs58": { @@ -21915,11 +19540,6 @@ "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" }, - "buffer-to-arraybuffer": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/buffer-to-arraybuffer/-/buffer-to-arraybuffer-0.0.5.tgz", - "integrity": "sha512-3dthu5CYiVB1DEJp61FtApNnNndTckcqe4pFcLdvHtrpG+kcyekCJKg4MRiDcFW7A6AODnXB9U4dwQiCW5kzJQ==" - }, "buffer-xor": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", @@ -21929,6 +19549,8 @@ "version": "4.0.5", "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.5.tgz", "integrity": "sha512-HTm14iMQKK2FjFLRTM5lAVcyaUzOnqbPtesFIvREgXpJHdQm8bWS+GkQgIkfaBYRHuCnea7w8UVNfwiAQhlr9A==", + "optional": true, + "peer": true, "requires": { "node-gyp-build": "^4.3.0" } @@ -21960,53 +19582,16 @@ } } }, - "bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==" - }, - "cacheable-lookup": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-6.1.0.tgz", - "integrity": "sha512-KJ/Dmo1lDDhmW2XDPMo+9oiy/CeqosPguPCrgcVzKyZrL6pM1gU2GmPY/xo6OQPTUaA/c0kwHuywB4E6nmT9ww==" - }, - "cacheable-request": { - "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", - "http-cache-semantics": "^4.0.0", - "keyv": "^4.0.0", - "lowercase-keys": "^2.0.0", - "normalize-url": "^6.0.1", - "responselike": "^2.0.0" - }, - "dependencies": { - "get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", - "requires": { - "pump": "^3.0.0" - } - }, - "lowercase-keys": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", - "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==" - } - } - }, "call-bind": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.5.tgz", - "integrity": "sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", "requires": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.1", - "set-function-length": "^1.1.1" + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" } }, "callsites": { @@ -22038,9 +19623,9 @@ } }, "caniuse-lite": { - "version": "1.0.30001605", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001605.tgz", - "integrity": "sha512-nXwGlFWo34uliI9z3n6Qc0wZaf7zaZWA1CPZ169La5mV3I/gem7bst0vr5XQH5TJXZIMfDeZyOrZnSlVzKxxHQ==" + "version": "1.0.30001655", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001655.tgz", + "integrity": "sha512-jRGVy3iSGO5Uutn2owlb5gR6qsGngTw9ZTb4ali9f3glshcNmJ2noam4Mo9zia5P9Dk3jNNydy7vQjuE5dQmfg==" }, "caseless": { "version": "0.12.0", @@ -22104,37 +19689,14 @@ } }, "chokidar": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", - "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.1.tgz", + "integrity": "sha512-n8enUVCED/KVRQlab1hr3MVpcVMvxtZjmEa956u+4YijlmQED223XMSYj2tLuKvr4jcCTzNNMpQDUer72MMmzA==", "dev": true, "requires": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "fsevents": "~2.3.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "dependencies": { - "glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "requires": { - "is-glob": "^4.0.1" - } - } + "readdirp": "^4.0.1" } }, - "chownr": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" - }, "chrome-trace-event": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", @@ -22147,29 +19709,6 @@ "integrity": "sha512-xmDt/QIAdeZ9+nfdPsaBCpMvHNLFiLdjj59qjqn+6iPe6YmHGQ35sBnQ8uslRBXFmXkiZQOJRjvQeoGppoTjjg==", "dev": true }, - "cids": { - "version": "0.7.5", - "resolved": "https://registry.npmjs.org/cids/-/cids-0.7.5.tgz", - "integrity": "sha512-zT7mPeghoWAu+ppn8+BS1tQ5qGmbMfB4AregnQjA/qHY3GC1m1ptI9GkWNlgeu38r7CuRdXB47uY2XgAYt6QVA==", - "requires": { - "buffer": "^5.5.0", - "class-is": "^1.1.0", - "multibase": "~0.6.0", - "multicodec": "^1.0.0", - "multihashes": "~0.4.15" - }, - "dependencies": { - "multicodec": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/multicodec/-/multicodec-1.0.4.tgz", - "integrity": "sha512-NDd7FeS3QamVtbgfvu5h7fd1IlbaC4EQ0/pgU4zqE2vdHCmBGsUa0TiM8/TdSeG6BMPC92OOCf8F1ocE/Wkrrg==", - "requires": { - "buffer": "^5.6.0", - "varint": "^5.0.0" - } - } - } - }, "cipher-base": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", @@ -22185,11 +19724,6 @@ "integrity": "sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ==", "dev": true }, - "class-is": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/class-is/-/class-is-1.1.0.tgz", - "integrity": "sha512-rhjH9AG1fvabIDoGRVH587413LPjTZgmDF9fOFCbFJQV4yuocX1mHxxvXI4g3cGwbVY9wAYIoKlg1N79frJKQw==" - }, "clipboard": { "version": "2.0.11", "resolved": "https://registry.npmjs.org/clipboard/-/clipboard-2.0.11.tgz", @@ -22227,14 +19761,6 @@ "shallow-clone": "^3.0.0" } }, - "clone-response": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.3.tgz", - "integrity": "sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==", - "requires": { - "mimic-response": "^1.0.0" - } - }, "co": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", @@ -22298,36 +19824,6 @@ "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", "dev": true }, - "content-disposition": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", - "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", - "requires": { - "safe-buffer": "5.2.1" - }, - "dependencies": { - "safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" - } - } - }, - "content-hash": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/content-hash/-/content-hash-2.5.2.tgz", - "integrity": "sha512-FvIQKy0S1JaWV10sMsA7TRx8bpU+pqPkhbsfvOJAdjRXvYxEckAwQWGwtRjiaJfh+E0DvcWUGqcdjwMGFjsSdw==", - "requires": { - "cids": "^0.7.1", - "multicodec": "^0.5.5", - "multihashes": "^0.4.15" - } - }, - "content-type": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==" - }, "convert-source-map": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz", @@ -22337,16 +19833,6 @@ "safe-buffer": "~5.1.1" } }, - "cookie": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", - "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==" - }, - "cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" - }, "cookiejar": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.4.tgz", @@ -22375,16 +19861,16 @@ } }, "core-js": { - "version": "3.37.1", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.37.1.tgz", - "integrity": "sha512-Xn6qmxrQZyB0FFY8E3bgRXei3lWDJHhvI+u0q9TKIYM49G8pAr0FgnnrFRAmsbptZL1yxRADVXn+x5AGsbBfyw==" + "version": "3.38.1", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.38.1.tgz", + "integrity": "sha512-OP35aUorbU3Zvlx7pjsFdu1rGNnD4pgw/CWoYzRY3t2EzoVT7shKHY1dlAy3f41cGIO7ZDPQimhGFTlEYkG/Hw==" }, "core-js-compat": { - "version": "3.36.1", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.36.1.tgz", - "integrity": "sha512-Dk997v9ZCt3X/npqzyGdTlq6t7lDBhZwGvV94PKzDArjp7BTRm7WlDAXYd/OWdeFHO8OChQYRJNJvUCqCbrtKA==", + "version": "3.38.1", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.38.1.tgz", + "integrity": "sha512-JRH6gfXxGmrzF3tZ57lFx97YARxCXPaMzPo6jELZhv88pBH5VXpQ+y0znKGlFnzuaihqhLbefxSJxWJMPtfDzw==", "requires": { - "browserslist": "^4.23.0" + "browserslist": "^4.23.3" } }, "core-util-is": { @@ -22392,15 +19878,6 @@ "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" }, - "cors": { - "version": "2.8.5", - "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", - "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", - "requires": { - "object-assign": "^4", - "vary": "^1" - } - }, "cosmiconfig": { "version": "9.0.0", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", @@ -22817,15 +20294,6 @@ } } }, - "d": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", - "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==", - "requires": { - "es5-ext": "^0.10.50", - "type": "^1.0.1" - } - }, "dashdash": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", @@ -22869,21 +20337,6 @@ "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz", "integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==" }, - "decompress-response": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", - "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", - "requires": { - "mimic-response": "^3.1.0" - }, - "dependencies": { - "mimic-response": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", - "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==" - } - } - }, "dedent": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.1.tgz", @@ -22911,11 +20364,6 @@ "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", "dev": true }, - "defer-to-connect": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", - "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==" - }, "deferred-leveldown": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/deferred-leveldown/-/deferred-leveldown-1.2.2.tgz", @@ -22925,13 +20373,13 @@ } }, "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==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", "requires": { - "get-intrinsic": "^1.2.1", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.0" + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" } }, "define-properties": { @@ -22953,11 +20401,6 @@ "resolved": "https://registry.npmjs.org/delegate/-/delegate-3.2.0.tgz", "integrity": "sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw==" }, - "depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" - }, "des.js": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.1.tgz", @@ -22967,11 +20410,6 @@ "minimalistic-assert": "^1.0.0" } }, - "destroy": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", - "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==" - }, "detect-browser": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/detect-browser/-/detect-browser-5.2.1.tgz", @@ -23085,20 +20523,15 @@ "safer-buffer": "^2.1.0" } }, - "ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" - }, "electron-to-chromium": { - "version": "1.4.692", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.692.tgz", - "integrity": "sha512-d5rZRka9n2Y3MkWRN74IoAsxR0HK3yaAt7T50e3iT9VZmCCQDT3geXUO5ZRMhDToa1pkCeQXuNo+0g+NfDOVPA==" + "version": "1.5.13", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.13.tgz", + "integrity": "sha512-lbBcvtIJ4J6sS4tb5TLp1b4LyfCdMkwStzXPyAgVgTRAsep4bvrAGaBOP7ZJtQMNJpSQ9SqG4brWOroNaQtm7Q==" }, "elliptic": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", - "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==", + "version": "6.5.7", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.7.tgz", + "integrity": "sha512-ESVCtTwiA+XhY3wyh24QqRGBoP3rEdDUl3EDUUo9tft074fi19IrdpH7hLCMMP3CIj7jb3W96rn8lt/BqIlt5Q==", "requires": { "bn.js": "^4.11.9", "brorand": "^1.1.0", @@ -23134,11 +20567,6 @@ "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", "dev": true }, - "encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==" - }, "encoding": { "version": "0.1.13", "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", @@ -23161,18 +20589,10 @@ } } }, - "end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "requires": { - "once": "^1.4.0" - } - }, "enhanced-resolve": { - "version": "5.17.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.0.tgz", - "integrity": "sha512-dwDPwZL0dmye8Txp2gzFmA6sxALaSvdRDjPH0viLcKrtlOL3tw62nWWweVD1SdILDTJrbrL6tdWVN58Wo6U3eA==", + "version": "5.17.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz", + "integrity": "sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==", "dev": true, "requires": { "graceful-fs": "^4.2.4", @@ -23261,6 +20681,19 @@ "which-typed-array": "^1.1.13" } }, + "es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "requires": { + "get-intrinsic": "^1.2.4" + } + }, + "es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==" + }, "es-module-lexer": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.2.1.tgz", @@ -23298,50 +20731,10 @@ "is-symbol": "^1.0.2" } }, - "es5-ext": { - "version": "0.10.64", - "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.64.tgz", - "integrity": "sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==", - "requires": { - "es6-iterator": "^2.0.3", - "es6-symbol": "^3.1.3", - "esniff": "^2.0.1", - "next-tick": "^1.1.0" - } - }, - "es6-iterator": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", - "integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==", - "requires": { - "d": "1", - "es5-ext": "^0.10.35", - "es6-symbol": "^3.1.1" - } - }, - "es6-promise": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", - "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==" - }, - "es6-symbol": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz", - "integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==", - "requires": { - "d": "^1.0.1", - "ext": "^1.1.2" - } - }, - "escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==" - }, - "escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" + "escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==" }, "escape-string-regexp": { "version": "1.0.5", @@ -23409,16 +20802,16 @@ } }, "eslint": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", - "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", + "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", "dev": true, "requires": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", "@eslint/eslintrc": "^2.1.4", - "@eslint/js": "8.57.0", - "@humanwhocodes/config-array": "^0.11.14", + "@eslint/js": "8.57.1", + "@humanwhocodes/config-array": "^0.13.0", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", "@ungap/structured-clone": "^1.2.0", @@ -23808,24 +21201,6 @@ "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", "dev": true }, - "esniff": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/esniff/-/esniff-2.0.1.tgz", - "integrity": "sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==", - "requires": { - "d": "^1.0.1", - "es5-ext": "^0.10.62", - "event-emitter": "^0.3.5", - "type": "^2.7.2" - }, - "dependencies": { - "type": { - "version": "2.7.2", - "resolved": "https://registry.npmjs.org/type/-/type-2.7.2.tgz", - "integrity": "sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw==" - } - } - }, "espree": { "version": "9.6.1", "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", @@ -23889,11 +21264,6 @@ "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", "dev": true }, - "etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==" - }, "eth-block-tracker": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/eth-block-tracker/-/eth-block-tracker-4.4.3.tgz", @@ -23907,22 +21277,6 @@ "safe-event-emitter": "^1.0.1" } }, - "eth-ens-namehash": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/eth-ens-namehash/-/eth-ens-namehash-2.0.8.tgz", - "integrity": "sha512-VWEI1+KJfz4Km//dadyvBBoBeSQ0MHTXPvr8UIXiLW6IanxvAV+DmlZAijZwAyggqGUfwQBeHf7tc9wzc1piSw==", - "requires": { - "idna-uts46-hx": "^2.3.1", - "js-sha3": "^0.5.7" - }, - "dependencies": { - "js-sha3": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.5.7.tgz", - "integrity": "sha512-GII20kjaPX0zJ8wzkTbNDYMY7msuZcTWk8S5UOh6806Jq/wz1J8/bnr8uGU0DAUmYDjj2Mr4X1cW8v/GLYnR+g==" - } - } - }, "eth-json-rpc-filters": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/eth-json-rpc-filters/-/eth-json-rpc-filters-4.2.2.tgz", @@ -24013,36 +21367,6 @@ } } }, - "eth-lib": { - "version": "0.1.29", - "resolved": "https://registry.npmjs.org/eth-lib/-/eth-lib-0.1.29.tgz", - "integrity": "sha512-bfttrr3/7gG4E02HoWTDUcDDslN003OlOoBxk9virpAZQ1ja/jDgwkWB8QfJF7ojuEowrqy+lzp9VcJG7/k5bQ==", - "requires": { - "bn.js": "^4.11.6", - "elliptic": "^6.4.0", - "nano-json-stream-parser": "^0.1.2", - "servify": "^0.1.12", - "ws": "^3.0.0", - "xhr-request-promise": "^0.1.2" - }, - "dependencies": { - "bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" - }, - "ws": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-3.3.3.tgz", - "integrity": "sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA==", - "requires": { - "async-limiter": "~1.0.0", - "safe-buffer": "~5.1.0", - "ultron": "~1.1.0" - } - } - } - }, "eth-net-props": { "version": "1.0.41", "resolved": "https://registry.npmjs.org/eth-net-props/-/eth-net-props-1.0.41.tgz", @@ -24098,14 +21422,6 @@ } } }, - "ethereum-bloom-filters": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/ethereum-bloom-filters/-/ethereum-bloom-filters-1.0.10.tgz", - "integrity": "sha512-rxJ5OFN3RwjQxDcFP2Z5+Q9ho4eIdEmSc2ht0fCu8Se9nbXjZ7/031uXoUYJ87KHCOdVeiUuwSnoS7hmYAGVHA==", - "requires": { - "js-sha3": "^0.8.0" - } - }, "ethereum-common": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/ethereum-common/-/ethereum-common-0.2.0.tgz", @@ -24266,28 +21582,6 @@ } } }, - "ethereumjs-util": { - "version": "7.1.5", - "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-7.1.5.tgz", - "integrity": "sha512-SDl5kKrQAudFBUe5OJM9Ac6WmMyYmXX/6sTmLZ3ffG2eY6ZIGBes3pEDxNN6V72WyOw4CPD5RomKdsa8DAAwLg==", - "requires": { - "@types/bn.js": "^5.1.0", - "bn.js": "^5.1.2", - "create-hash": "^1.1.2", - "ethereum-cryptography": "^0.1.3", - "rlp": "^2.2.4" - }, - "dependencies": { - "@types/bn.js": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-5.1.5.tgz", - "integrity": "sha512-V46N0zwKRF5Q00AZ6hWtN0T8gGmDUaUzLWQvHFo5yThtVwK/VCenFY3wXVbOvNfajEpsTfQM4IN9k/d6gUVX3A==", - "requires": { - "@types/node": "*" - } - } - } - }, "ethereumjs-vm": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/ethereumjs-vm/-/ethereumjs-vm-2.6.0.tgz", @@ -24364,22 +21658,6 @@ } } }, - "ethjs-unit": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/ethjs-unit/-/ethjs-unit-0.1.6.tgz", - "integrity": "sha512-/Sn9Y0oKl0uqQuvgFk/zQgR7aw1g36qX/jzSQ5lSwlO0GigPymk4eGQfeNTD03w1dPOqfz8V77Cy43jH56pagw==", - "requires": { - "bn.js": "4.11.6", - "number-to-bn": "1.7.0" - }, - "dependencies": { - "bn.js": { - "version": "4.11.6", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.6.tgz", - "integrity": "sha512-XWwnNNFCuuSQ0m3r3C4LE3EiORltHd9M05pq6FOlVeiophzRbMo50Sbz1ehl8K3Z+jw9+vmgnXefY1hz8X+2wA==" - } - } - }, "ethjs-util": { "version": "0.1.6", "resolved": "https://registry.npmjs.org/ethjs-util/-/ethjs-util-0.1.6.tgz", @@ -24389,19 +21667,10 @@ "strip-hex-prefix": "1.0.0" } }, - "event-emitter": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", - "integrity": "sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==", - "requires": { - "d": "1", - "es5-ext": "~0.10.14" - } - }, "eventemitter3": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.4.tgz", - "integrity": "sha512-rlaVLnVxtxvoyLsQQFBx53YmXHDxRIzzTLbdfxqi4yocpSjAxXwkU0cScM5JgSKMqEhrZpnvQ2D9gjylR0AimQ==" + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==" }, "events": { "version": "3.3.0", @@ -24453,87 +21722,6 @@ "jest-util": "^29.7.0" } }, - "express": { - "version": "4.19.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", - "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", - "requires": { - "accepts": "~1.3.8", - "array-flatten": "1.1.1", - "body-parser": "1.20.2", - "content-disposition": "0.5.4", - "content-type": "~1.0.4", - "cookie": "0.6.0", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "2.0.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "1.2.0", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "merge-descriptors": "1.0.1", - "methods": "~1.1.2", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", - "proxy-addr": "~2.0.7", - "qs": "6.11.0", - "range-parser": "~1.2.1", - "safe-buffer": "5.2.1", - "send": "0.18.0", - "serve-static": "1.15.0", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, - "qs": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", - "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", - "requires": { - "side-channel": "^1.0.4" - } - }, - "safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" - } - } - }, - "ext": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz", - "integrity": "sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==", - "requires": { - "type": "^2.7.2" - }, - "dependencies": { - "type": { - "version": "2.7.2", - "resolved": "https://registry.npmjs.org/type/-/type-2.7.2.tgz", - "integrity": "sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw==" - } - } - }, "extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", @@ -24667,35 +21855,6 @@ "to-regex-range": "^5.0.1" } }, - "finalhandler": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", - "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", - "requires": { - "debug": "2.6.9", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "statuses": "2.0.1", - "unpipe": "~1.0.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - } - } - }, "find-cache-dir": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-4.0.0.tgz", @@ -24763,45 +21922,12 @@ "mime-types": "^2.1.12" } }, - "form-data-encoder": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-1.7.1.tgz", - "integrity": "sha512-EFRDrsMm/kyqbTQocNvRXMLjc7Es2Vk+IQFx/YW7hkUH1eBl4J1fqiP34l74Yt0pFLCNpc06fkbVk00008mzjg==" - }, - "forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==" - }, "fraction.js": { "version": "4.3.7", "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", "dev": true }, - "fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==" - }, - "fs-extra": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-4.0.3.tgz", - "integrity": "sha512-q6rbdDd1o2mAnQreO7YADIxf/Whx4AHBiRf6d+/cVT8h44ss+lHgxf1FemcqDnQt9X3ct4McHr+JMGlYSsK7Cg==", - "requires": { - "graceful-fs": "^4.1.2", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - } - }, - "fs-minipass": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.7.tgz", - "integrity": "sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==", - "requires": { - "minipass": "^2.6.0" - } - }, "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -24859,10 +21985,11 @@ "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==" }, "get-intrinsic": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.2.tgz", - "integrity": "sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", "requires": { + "es-errors": "^1.3.0", "function-bind": "^1.1.2", "has-proto": "^1.0.1", "has-symbols": "^1.0.3", @@ -24878,7 +22005,8 @@ "get-stream": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==" + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true }, "get-symbol-description": { "version": "1.0.0", @@ -24994,30 +22122,11 @@ "get-intrinsic": "^1.1.3" } }, - "got": { - "version": "12.1.0", - "resolved": "https://registry.npmjs.org/got/-/got-12.1.0.tgz", - "integrity": "sha512-hBv2ty9QN2RdbJJMK3hesmSkFTjVIHyIDDbssCKnSmq62edGgImJWD10Eb1k77TiV1bxloxqcFAVK8+9pkhOig==", - "requires": { - "@sindresorhus/is": "^4.6.0", - "@szmarczak/http-timer": "^5.0.1", - "@types/cacheable-request": "^6.0.2", - "@types/responselike": "^1.0.0", - "cacheable-lookup": "^6.0.4", - "cacheable-request": "^7.0.2", - "decompress-response": "^6.0.0", - "form-data-encoder": "1.7.1", - "get-stream": "^6.0.1", - "http2-wrapper": "^2.1.10", - "lowercase-keys": "^3.0.0", - "p-cancelable": "^3.0.0", - "responselike": "^2.0.0" - } - }, "graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true }, "graphemer": { "version": "1.4.0", @@ -25051,11 +22160,11 @@ "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" }, "has-property-descriptors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", - "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", "requires": { - "get-intrinsic": "^1.1.1" + "es-define-property": "^1.0.0" } }, "has-proto": { @@ -25148,28 +22257,6 @@ "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", "dev": true }, - "http-cache-semantics": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", - "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==" - }, - "http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", - "requires": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" - } - }, - "http-https": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/http-https/-/http-https-1.0.0.tgz", - "integrity": "sha512-o0PWwVCSp3O0wS6FvNr6xfBCHgt0m1tvPLFOCc2iFDKTRAXhB7m8klDf7ErowFH8POa6dVdGatKU5I1YYwzUyg==" - }, "http-signature": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", @@ -25180,15 +22267,6 @@ "sshpk": "^1.7.0" } }, - "http2-wrapper": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-2.2.1.tgz", - "integrity": "sha512-V5nVw1PAOgfI3Lmeaj2Exmeg7fenjhRUgz1lPSezy1CuhPYbgQtbQj4jZfEAEMlaL+vupsvhjqCyjzob0yxsmQ==", - "requires": { - "quick-lru": "^5.1.1", - "resolve-alpn": "^1.2.0" - } - }, "https-browserify": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz", @@ -25215,14 +22293,6 @@ "resolved": "https://registry.npmjs.org/humps/-/humps-2.0.1.tgz", "integrity": "sha1-3QLqYIG9BWjcXQcxhEY5V7qe+ao=" }, - "iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - }, "icss-utils": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", @@ -25230,21 +22300,6 @@ "dev": true, "requires": {} }, - "idna-uts46-hx": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/idna-uts46-hx/-/idna-uts46-hx-2.3.1.tgz", - "integrity": "sha512-PWoF9Keq6laYdIRwwCdhTPl60xRqAloYNMQLiyUnG42VjT53oW07BXIRM+NK7eQjzXjAk2gUvX9caRxlnF9TAA==", - "requires": { - "punycode": "2.1.0" - }, - "dependencies": { - "punycode": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.0.tgz", - "integrity": "sha512-Yxz2kRwT90aPiWEMHVYnEf4+rhwF1tBmmZ4KepCP+Wkium9JxtWnUm1nqGwpiAHr/tnTSeHqr3wb++jgSkXjhA==" - } - } - }, "ieee754": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", @@ -25336,11 +22391,6 @@ "integrity": "sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ==", "dev": true }, - "ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" - }, "is-arguments": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", @@ -25376,15 +22426,6 @@ "has-bigints": "^1.0.1" } }, - "is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "requires": { - "binary-extensions": "^2.0.0" - } - }, "is-boolean-object": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", @@ -25600,6 +22641,12 @@ "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", "dev": true }, + "isomorphic-ws": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-5.0.0.tgz", + "integrity": "sha512-muId7Zzn9ywDsyXgTIafTry2sV3nySZeUDe6YedVd1Hvuuep5AsIlqK+XefWpYTyJG5e503F2xIuT2lcU6rCSw==", + "requires": {} + }, "isstream": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", @@ -27030,11 +24077,6 @@ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==" }, - "json-buffer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==" - }, "json-parse-even-better-errors": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", @@ -27099,14 +24141,6 @@ "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==" }, - "jsonfile": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", - "requires": { - "graceful-fs": "^4.1.6" - } - }, "jsonify": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", @@ -27133,14 +24167,6 @@ "readable-stream": "^3.6.0" } }, - "keyv": { - "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" - } - }, "keyvaluestorage-interface": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/keyvaluestorage-interface/-/keyvaluestorage-interface-1.0.0.tgz", @@ -27440,11 +24466,6 @@ "js-tokens": "^3.0.0 || ^4.0.0" } }, - "lowercase-keys": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-3.0.0.tgz", - "integrity": "sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ==" - }, "lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", @@ -27460,9 +24481,9 @@ "integrity": "sha1-81ypHEk/e3PaDgdJUwTxezH4fuU=" }, "luxon": { - "version": "3.4.4", - "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.4.4.tgz", - "integrity": "sha512-zobTr7akeGHnv7eBOXcRgMeCP6+uyYsczwmeRCauvpvaAltgNyTbLH/+VaEAPUeWBT+1GuNmz4wC/6jtQzbbVA==" + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.5.0.tgz", + "integrity": "sha512-rh+Zjr6DNfUYR3bPwJEnuwDdqMbxZW7LOQfUN4B54+Cl+0o5zaU9RJ6bcidfDtC1cWCZXQ+nvX8bf6bAji37QQ==" }, "make-dir": { "version": "4.0.0", @@ -27517,11 +24538,6 @@ "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==", "dev": true }, - "media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==" - }, "memdown": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/memdown/-/memdown-1.4.1.tgz", @@ -27545,11 +24561,6 @@ } } }, - "merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" - }, "merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", @@ -27625,16 +24636,6 @@ } } }, - "methods": { - "version": "1.1.2", - "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", @@ -27661,11 +24662,6 @@ } } }, - "mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" - }, "mime-db": { "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", @@ -27685,11 +24681,6 @@ "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", "dev": true }, - "mimic-response": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", - "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==" - }, "min-document": { "version": "2.19.0", "resolved": "https://registry.npmjs.org/min-document/-/min-document-2.19.0.tgz", @@ -27699,9 +24690,9 @@ } }, "mini-css-extract-plugin": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.9.0.tgz", - "integrity": "sha512-Zs1YsZVfemekSZG+44vBsYTLQORkPMwnlv+aehcxK/NLKC+EGhDB39/YePYYqx/sTk6NnYpuqikhSn7+JIevTA==", + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.9.1.tgz", + "integrity": "sha512-+Vyi+GCCOHnrJ2VPS+6aPoXN2k2jgUzDRhTFLjjTBn23qyXJXkjUWQgTL+mXpF5/A8ixLdCc6kWsoeOjKGejKQ==", "dev": true, "requires": { "schema-utils": "^4.0.0", @@ -27730,31 +24721,8 @@ "minimist": { "version": "1.2.6", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", - "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==" - }, - "minipass": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz", - "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==", - "requires": { - "safe-buffer": "^5.1.2", - "yallist": "^3.0.0" - }, - "dependencies": { - "yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" - } - } - }, - "minizlib": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.3.3.tgz", - "integrity": "sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==", - "requires": { - "minipass": "^2.9.0" - } + "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", + "dev": true }, "mitt": { "version": "3.0.1", @@ -27762,31 +24730,13 @@ "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==" }, "mixpanel-browser": { - "version": "2.54.1", - "resolved": "https://registry.npmjs.org/mixpanel-browser/-/mixpanel-browser-2.54.1.tgz", - "integrity": "sha512-1FDs/E0UHPJLry/J0r3pBXrnEhJnz7H2CqzWWZgmdDAm3WiMva69X/qFIqcW3TOmHPaprJVfgwzqt6MJavMrHQ==", + "version": "2.55.1", + "resolved": "https://registry.npmjs.org/mixpanel-browser/-/mixpanel-browser-2.55.1.tgz", + "integrity": "sha512-NSEPdFSJxoR1OCKWKHbtqd3BeH1c9NjXbEt0tN5TgBEO1nSDji6niU9n4MopAXOP0POET9spjpQKxZtLZKTJwA==", "requires": { "rrweb": "2.0.0-alpha.13" } }, - "mkdirp": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz", - "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==" - }, - "mkdirp-promise": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/mkdirp-promise/-/mkdirp-promise-5.0.1.tgz", - "integrity": "sha512-Hepn5kb1lJPtVW84RFT40YG1OddBNTOVUZR2bzQUHc+Z03en8/3uX0+060JDhcEzyO08HmipsN9DcnFMxhIL9w==", - "requires": { - "mkdirp": "*" - } - }, - "mock-fs": { - "version": "4.14.0", - "resolved": "https://registry.npmjs.org/mock-fs/-/mock-fs-4.14.0.tgz", - "integrity": "sha512-qYvlv/exQ4+svI3UOvPUpLDF0OMX5euvUH0Ny4N5QyRyhNdgAgUrVH3iUINSzEPLvx0kbo/Bp28GJKIqvE7URw==" - }, "moment": { "version": "2.30.1", "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", @@ -27797,49 +24747,6 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, - "multibase": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/multibase/-/multibase-0.6.1.tgz", - "integrity": "sha512-pFfAwyTjbbQgNc3G7D48JkJxWtoJoBMaR4xQUOuB8RnCgRqaYmWNFeJTTvrJ2w51bjLq2zTby6Rqj9TQ9elSUw==", - "requires": { - "base-x": "^3.0.8", - "buffer": "^5.5.0" - } - }, - "multicodec": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/multicodec/-/multicodec-0.5.7.tgz", - "integrity": "sha512-PscoRxm3f+88fAtELwUnZxGDkduE2HD9Q6GHUOywQLjOGT/HAdhjLDYNZ1e7VR0s0TP0EwZ16LNUTFpoBGivOA==", - "requires": { - "varint": "^5.0.0" - } - }, - "multihashes": { - "version": "0.4.21", - "resolved": "https://registry.npmjs.org/multihashes/-/multihashes-0.4.21.tgz", - "integrity": "sha512-uVSvmeCWf36pU2nB4/1kzYZjsXD9vofZKpgudqkceYY5g2aZZXJ5r9lxuzoRLl1OAp28XljXsEJ/X/85ZsKmKw==", - "requires": { - "buffer": "^5.5.0", - "multibase": "^0.7.0", - "varint": "^5.0.0" - }, - "dependencies": { - "multibase": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/multibase/-/multibase-0.7.0.tgz", - "integrity": "sha512-TW8q03O0f6PNFTQDvh3xxH03c8CjGaaYrjkl9UQPG6rz53TQzzxJVCIWVjzcbN/Q5Y53Zd0IBQBMVktVgNx4Fg==", - "requires": { - "base-x": "^3.0.8", - "buffer": "^5.5.0" - } - } - } - }, - "nano-json-stream-parser": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/nano-json-stream-parser/-/nano-json-stream-parser-0.1.2.tgz", - "integrity": "sha512-9MqxMH/BSJC7dnLsEMPyfN5Dvoo49IsPFYMcHw3Bcfc2kN0lpHRBSzlMSVx4HGyJ7s9B31CyBTVehWJoQ8Ctew==" - }, "nanoassert": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/nanoassert/-/nanoassert-1.1.0.tgz", @@ -27865,22 +24772,12 @@ "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", "dev": true }, - "negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==" - }, "neo-async": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", "dev": true }, - "next-tick": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz", - "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==" - }, "node-addon-api": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-2.0.2.tgz", @@ -27927,9 +24824,9 @@ "dev": true }, "node-releases": { - "version": "2.0.14", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", - "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==" + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", + "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==" }, "normalize-path": { "version": "3.0.0", @@ -27943,11 +24840,6 @@ "integrity": "sha1-LRDAa9/TEuqXd2laTShDlFa3WUI=", "dev": true }, - "normalize-url": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", - "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==" - }, "npm-run-path": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", @@ -27966,22 +24858,6 @@ "boolbase": "^1.0.0" } }, - "number-to-bn": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/number-to-bn/-/number-to-bn-1.7.0.tgz", - "integrity": "sha512-wsJ9gfSz1/s4ZsJN01lyonwuxA1tml6X1yBDnfpMglypcBRFZZkus26EdPSlqS5GJfYddVZa22p3VNb3z5m5Ig==", - "requires": { - "bn.js": "4.11.6", - "strip-hex-prefix": "1.0.0" - }, - "dependencies": { - "bn.js": { - "version": "4.11.6", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.6.tgz", - "integrity": "sha512-XWwnNNFCuuSQ0m3r3C4LE3EiORltHd9M05pq6FOlVeiophzRbMo50Sbz1ehl8K3Z+jw9+vmgnXefY1hz8X+2wA==" - } - } - }, "numeral": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/numeral/-/numeral-2.0.6.tgz", @@ -28067,26 +24943,11 @@ "es-abstract": "^1.22.1" } }, - "oboe": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/oboe/-/oboe-2.1.5.tgz", - "integrity": "sha512-zRFWiF+FoicxEs3jNI/WYUrVEgA7DeET/InK0XQuudGHRg8iIob3cNPrJTKaz4004uaA9Pbe+Dwa8iluhjLZWA==", - "requires": { - "http-https": "^1.0.0" - } - }, - "on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "requires": { - "ee-first": "1.1.1" - } - }, "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, "requires": { "wrappy": "1" } @@ -28119,11 +24980,6 @@ "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz", "integrity": "sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc=" }, - "p-cancelable": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-3.0.0.tgz", - "integrity": "sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw==" - }, "p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -28205,11 +25061,6 @@ "entities": "^4.3.0" } }, - "parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" - }, "path-exists": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", @@ -28241,11 +25092,6 @@ "tslib": "^1.10.0" } }, - "path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" - }, "pathval": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", @@ -28280,9 +25126,9 @@ "integrity": "sha512-WNFHoKrkZNnvFFhbHL93WDkW3ifwVOXSW3w1UuZZelSmgXpIGiZSNlZJq37rR8YejqME2rHs9EhH9ZvlvFH2NA==" }, "picocolors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", - "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==" + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz", + "integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==" }, "picomatch": { "version": "2.3.0", @@ -28377,14 +25223,14 @@ "integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==" }, "postcss": { - "version": "8.4.39", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.39.tgz", - "integrity": "sha512-0vzE+lAiG7hZl1/9I8yzKLx3aR9Xbof3fBHKunvMfOCYAtMhrsnccJY2iTURb9EZd5+pLuiNV9/c/GZJOHsgIw==", + "version": "8.4.47", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz", + "integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==", "dev": true, "requires": { "nanoid": "^3.3.7", - "picocolors": "^1.0.1", - "source-map-js": "^1.2.0" + "picocolors": "^1.1.0", + "source-map-js": "^1.2.1" } }, "postcss-calc": { @@ -28790,15 +25636,6 @@ "react-is": "^16.8.1" } }, - "proxy-addr": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", - "requires": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" - } - }, "prr": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", @@ -28829,15 +25666,6 @@ } } }, - "pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, "punycode": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", @@ -28987,16 +25815,6 @@ "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz", "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==" }, - "query-string": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/query-string/-/query-string-5.1.1.tgz", - "integrity": "sha512-gjWOsm2SoGlgLEdAGt7a6slVOk9mGiXmPFMqrEhLQ68rhQuBnpfs3+EmlvqKyxnCo9/PPlF+9MtY02S1aFg+Jw==", - "requires": { - "decode-uri-component": "^0.2.0", - "object-assign": "^4.1.0", - "strict-uri-encode": "^1.0.0" - } - }, "querystringify": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", @@ -29009,11 +25827,6 @@ "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", "dev": true }, - "quick-lru": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", - "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==" - }, "randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -29031,22 +25844,6 @@ "safe-buffer": "^5.1.0" } }, - "range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" - }, - "raw-body": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", - "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", - "requires": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - } - }, "react": { "version": "16.14.0", "resolved": "https://registry.npmjs.org/react/-/react-16.14.0.tgz", @@ -29084,13 +25881,10 @@ } }, "readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "requires": { - "picomatch": "^2.2.1" - } + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.0.1.tgz", + "integrity": "sha512-GkMg9uOTpIWWKbSsgwb5fA4EavTR+SG/PMPoAY8hkhHfEEY0/vqljY+XHqtDf2cr2IJtoNRDbrrEpZUiZCkYRw==", + "dev": true }, "rechoir": { "version": "0.8.0", @@ -29258,11 +26052,6 @@ "supports-preserve-symlinks-flag": "^1.0.0" } }, - "resolve-alpn": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", - "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==" - }, "resolve-cwd": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", @@ -29292,21 +26081,6 @@ "integrity": "sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==", "dev": true }, - "responselike": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.1.tgz", - "integrity": "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==", - "requires": { - "lowercase-keys": "^2.0.0" - }, - "dependencies": { - "lowercase-keys": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", - "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==" - } - } - }, "reusify": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", @@ -29431,12 +26205,12 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "sass": { - "version": "1.77.8", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.77.8.tgz", - "integrity": "sha512-4UHg6prsrycW20fqLGPShtEvo/WyHRVRHwOP4DzkUrObWoWI05QBSfzU71TVB7PFaL104TwNaHpjlWXAZbQiNQ==", + "version": "1.79.4", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.79.4.tgz", + "integrity": "sha512-K0QDSNPXgyqO4GZq2HO5Q70TLxTH6cIT59RdoCHMivrC8rqzaTw5ab9prjz9KUN1El4FLXrBXJhik61JR4HcGg==", "dev": true, "requires": { - "chokidar": ">=3.0.0 <4.0.0", + "chokidar": "^4.0.0", "immutable": "^4.0.0", "source-map-js": ">=0.6.2 <2.0.0" } @@ -29520,13 +26294,20 @@ "integrity": "sha512-8CYNl/bjkEhXWbDTU/K7c2jQtrnqEffIPyOLMqygW/7/b+ym8UtQumcAZjOfMLjZKR6AxK5tOr9fChbQZCzPqg==" }, "secp256k1": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/secp256k1/-/secp256k1-4.0.2.tgz", - "integrity": "sha512-UDar4sKvWAksIlfX3xIaQReADn+WFnHvbVujpcbr+9Sf/69odMwy2MUsz5CKLQgX9nsIyrjuxL2imVyoNHa3fg==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/secp256k1/-/secp256k1-4.0.4.tgz", + "integrity": "sha512-6JfvwvjUOn8F/jUoBY2Q1v5WY5XS+rj8qSe0v8Y4ezH4InLgTEeOOPQsRll9OV429Pvo6BCHGavIyJfr3TAhsw==", "requires": { - "elliptic": "^6.5.2", - "node-addon-api": "^2.0.0", + "elliptic": "^6.5.7", + "node-addon-api": "^5.0.0", "node-gyp-build": "^4.2.0" + }, + "dependencies": { + "node-addon-api": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz", + "integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==" + } } }, "select": { @@ -29544,48 +26325,6 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==" }, - "send": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", - "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", - "requires": { - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "2.4.1", - "range-parser": "~1.2.1", - "statuses": "2.0.1" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - }, - "dependencies": { - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - } - } - }, - "ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" - } - } - }, "serialize-javascript": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", @@ -29595,43 +26334,22 @@ "randombytes": "^2.1.0" } }, - "serve-static": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", - "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", - "requires": { - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.18.0" - } - }, - "servify": { - "version": "0.1.12", - "resolved": "https://registry.npmjs.org/servify/-/servify-0.1.12.tgz", - "integrity": "sha512-/xE6GvsKKqyo1BAY+KxOWXcLpPsUUyji7Qg3bVD7hh1eRze5bR1uYiuDA/k3Gof1s9BTzQZEJK8sNcNGFIzeWw==", - "requires": { - "body-parser": "^1.16.0", - "cors": "^2.8.1", - "express": "^4.14.0", - "request": "^2.79.0", - "xhr": "^2.3.3" - } - }, "set-blocking": { "version": "2.0.0", "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==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", "requires": { - "define-data-property": "^1.1.1", - "get-intrinsic": "^1.2.1", + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.0" + "has-property-descriptors": "^1.0.2" } }, "set-function-name": { @@ -29655,11 +26373,6 @@ "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=" }, - "setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" - }, "sha.js": { "version": "2.4.11", "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", @@ -29699,13 +26412,14 @@ "dev": true }, "side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", + "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", "requires": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" } }, "signal-exit": { @@ -29714,31 +26428,6 @@ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "dev": true }, - "simple-concat": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", - "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==" - }, - "simple-get": { - "version": "2.8.2", - "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-2.8.2.tgz", - "integrity": "sha512-Ijd/rV5o+mSBBs4F/x9oDPtTx9Zb6X9brmnXvMW4J7IR15ngi9q5xxqWBKU744jTZiaXtxaPL7uHG6vtN8kUkw==", - "requires": { - "decompress-response": "^3.3.0", - "once": "^1.3.1", - "simple-concat": "^1.0.0" - }, - "dependencies": { - "decompress-response": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz", - "integrity": "sha512-BzRPQuY1ip+qDonAOz42gRm/pg9F768C+npV/4JOsxRC2sq+Rlk+Q4ZCAsOhnIaMrgarILY+RMUIvMmmX1qAEA==", - "requires": { - "mimic-response": "^1.0.0" - } - } - } - }, "sisteransi": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", @@ -29758,9 +26447,9 @@ "dev": true }, "source-map-js": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", - "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", "dev": true }, "source-map-support": { @@ -29817,11 +26506,6 @@ } } }, - "statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==" - }, "stream-browserify": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-3.0.0.tgz", @@ -29842,11 +26526,6 @@ "xtend": "^4.0.2" } }, - "strict-uri-encode": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz", - "integrity": "sha512-R3f198pcvnB+5IpnBlRkphuE9n46WyVl8I39W/ZUTZLz4nqSP/oLYUrcnJrw462Ds8he4YKMov2efsTIw1BDGQ==" - }, "string_decoder": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", @@ -30013,80 +26692,10 @@ "picocolors": "^1.0.0" } }, - "swarm-js": { - "version": "0.1.42", - "resolved": "https://registry.npmjs.org/swarm-js/-/swarm-js-0.1.42.tgz", - "integrity": "sha512-BV7c/dVlA3R6ya1lMlSSNPLYrntt0LUq4YMgy3iwpCIc6rZnS5W2wUoctarZ5pXlpKtxDDf9hNziEkcfrxdhqQ==", - "requires": { - "bluebird": "^3.5.0", - "buffer": "^5.0.5", - "eth-lib": "^0.1.26", - "fs-extra": "^4.0.2", - "got": "^11.8.5", - "mime-types": "^2.1.16", - "mkdirp-promise": "^5.0.1", - "mock-fs": "^4.1.0", - "setimmediate": "^1.0.5", - "tar": "^4.0.2", - "xhr-request": "^1.0.1" - }, - "dependencies": { - "@szmarczak/http-timer": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", - "integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==", - "requires": { - "defer-to-connect": "^2.0.0" - } - }, - "cacheable-lookup": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", - "integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==" - }, - "got": { - "version": "11.8.6", - "resolved": "https://registry.npmjs.org/got/-/got-11.8.6.tgz", - "integrity": "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==", - "requires": { - "@sindresorhus/is": "^4.0.0", - "@szmarczak/http-timer": "^4.0.5", - "@types/cacheable-request": "^6.0.1", - "@types/responselike": "^1.0.0", - "cacheable-lookup": "^5.0.3", - "cacheable-request": "^7.0.2", - "decompress-response": "^6.0.0", - "http2-wrapper": "^1.0.0-beta.5.2", - "lowercase-keys": "^2.0.0", - "p-cancelable": "^2.0.0", - "responselike": "^2.0.0" - } - }, - "http2-wrapper": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz", - "integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==", - "requires": { - "quick-lru": "^5.1.1", - "resolve-alpn": "^1.0.0" - } - }, - "lowercase-keys": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", - "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==" - }, - "p-cancelable": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", - "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==" - } - } - }, "sweetalert2": { - "version": "11.12.4", - "resolved": "https://registry.npmjs.org/sweetalert2/-/sweetalert2-11.12.4.tgz", - "integrity": "sha512-ZSpyaLbAmn4b7xjnV9x9BFD1UOrCAhIzm1D8dZ443kGxtVKqbTIA5SgXs4xeEtmFfEXUyC3RBgpSlu1AXmCiHA==" + "version": "11.14.1", + "resolved": "https://registry.npmjs.org/sweetalert2/-/sweetalert2-11.14.1.tgz", + "integrity": "sha512-xadhfcA4STGMh8nC5zHFFWURhRpWc4zyI3GdMDFH/m3hGWZeQQNWhX9xcG4lI9gZYsi/IlazKbwvvje3juL3Xg==" }, "symbol-tree": { "version": "3.2.4", @@ -30100,40 +26709,6 @@ "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", "dev": true }, - "tar": { - "version": "4.4.19", - "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.19.tgz", - "integrity": "sha512-a20gEsvHnWe0ygBY8JbxoM4w3SJdhc7ZAuxkLqh+nvNQN2IOt0B5lLgM490X5Hl8FF0dl0tOf2ewFYAlIFgzVA==", - "requires": { - "chownr": "^1.1.4", - "fs-minipass": "^1.2.7", - "minipass": "^2.9.0", - "minizlib": "^1.3.3", - "mkdirp": "^0.5.5", - "safe-buffer": "^5.2.1", - "yallist": "^3.1.1" - }, - "dependencies": { - "mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", - "requires": { - "minimist": "^1.2.6" - } - }, - "safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" - }, - "yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" - } - } - }, "terser": { "version": "5.27.0", "resolved": "https://registry.npmjs.org/terser/-/terser-5.27.0.tgz", @@ -30197,11 +26772,6 @@ "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", "dev": true }, - "timed-out": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/timed-out/-/timed-out-4.0.1.tgz", - "integrity": "sha512-G7r3AhovYtr5YKOWQkta8RKAPb+J9IsO4uVmzjl8AZwfhs8UcUwTiD6gcJYSgOtzyjvQKrKYn41syHbUWMkafA==" - }, "tiny-emitter": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz", @@ -30232,11 +26802,6 @@ "resolved": "https://registry.npmjs.org/toggle-selection/-/toggle-selection-1.0.6.tgz", "integrity": "sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==" }, - "toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==" - }, "tough-cookie": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.2.tgz", @@ -30313,11 +26878,6 @@ "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" }, - "type": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz", - "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==" - }, "type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -30338,15 +26898,6 @@ "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", "dev": true }, - "type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", - "requires": { - "media-typer": "0.3.0", - "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", @@ -30402,10 +26953,11 @@ "is-typedarray": "^1.0.0" } }, - "ultron": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.1.1.tgz", - "integrity": "sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og==" + "typescript": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.2.tgz", + "integrity": "sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw==", + "peer": true }, "unbox-primitive": { "version": "1.0.2", @@ -30453,23 +27005,13 @@ "integrity": "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==", "dev": true }, - "universalify": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==" - }, - "unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==" - }, "update-browserslist-db": { - "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==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz", + "integrity": "sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ==", "requires": { - "escalade": "^3.1.1", - "picocolors": "^1.0.0" + "escalade": "^3.1.2", + "picocolors": "^1.0.1" } }, "uri-js": { @@ -30486,12 +27028,12 @@ "integrity": "sha512-HXgFDgDommxn5/bIv0cnQZsPhHDA90NPHD6+c/v21U5+Sx5hoP8+dP9IZXBU1gIfvdRfhG8cel9QNPeionfcCQ==" }, "url": { - "version": "0.11.3", - "resolved": "https://registry.npmjs.org/url/-/url-0.11.3.tgz", - "integrity": "sha512-6hxOLGfZASQK/cijlZnZJTq8OXAkt/3YGfQX45vvMYXpZoo8NdWZcY73K108Jf759lS1Bv/8wXnHDTSz17dSRw==", + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/url/-/url-0.11.4.tgz", + "integrity": "sha512-oCwdVC7mTuWiPyjLUz/COz5TLk6wgp0RCsN+wHZ2Ekneac9w8uuV0njcbbie2ME+Vs+d6duwmYuR3HgQXs1fOg==", "requires": { "punycode": "^1.4.1", - "qs": "^6.11.2" + "qs": "^6.12.3" }, "dependencies": { "punycode": { @@ -30500,11 +27042,11 @@ "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==", + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", "requires": { - "side-channel": "^1.0.4" + "side-channel": "^1.0.6" } } } @@ -30519,24 +27061,16 @@ "requires-port": "^1.0.0" } }, - "url-set-query": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/url-set-query/-/url-set-query-1.0.0.tgz", - "integrity": "sha512-3AChu4NiXquPfeckE5R5cGdiHCMWJx1dwCWOmWIL4KHAziJNOFIYJlpGFeKDvwLPHovZRCxK3cYlwzqI9Vp+Gg==" - }, "utf-8-validate": { "version": "5.0.7", "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.7.tgz", "integrity": "sha512-vLt1O5Pp+flcArHGIyKEQq883nBt8nN8tVBcoL0qUXj2XT1n7p70yGIq2VK98I5FdZ1YHc0wk/koOnHjnXWk1Q==", + "optional": true, + "peer": true, "requires": { "node-gyp-build": "^4.3.0" } }, - "utf8": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/utf8/-/utf8-3.0.0.tgz", - "integrity": "sha512-E8VjFIQ/TyQgp+TZfS6l8yp/xWppSAHzidGiRrqe4bK4XP9pTRyKFgGJpO3SN7zdX4DeomTrwaseCHovfpFcqQ==" - }, "util": { "version": "0.12.5", "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", @@ -30554,11 +27088,6 @@ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" }, - "utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==" - }, "uuid": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", @@ -30575,16 +27104,6 @@ "convert-source-map": "^1.6.0" } }, - "varint": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/varint/-/varint-5.0.2.tgz", - "integrity": "sha512-lKxKYG6H03yCZUpAGOPOsMcGxd1RHCu1iKvEHYDPmTyq2HueGhD73ssNBqqQWfvYs04G9iUFRvmAVLW20Jw6ow==" - }, - "vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==" - }, "verror": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", @@ -30638,259 +27157,181 @@ } }, "web3": { - "version": "1.10.4", - "resolved": "https://registry.npmjs.org/web3/-/web3-1.10.4.tgz", - "integrity": "sha512-kgJvQZjkmjOEKimx/tJQsqWfRDPTTcBfYPa9XletxuHLpHcXdx67w8EFn5AW3eVxCutE9dTVHgGa9VYe8vgsEA==", - "requires": { - "web3-bzz": "1.10.4", - "web3-core": "1.10.4", - "web3-eth": "1.10.4", - "web3-eth-personal": "1.10.4", - "web3-net": "1.10.4", - "web3-shh": "1.10.4", - "web3-utils": "1.10.4" - } - }, - "web3-bzz": { - "version": "1.10.4", - "resolved": "https://registry.npmjs.org/web3-bzz/-/web3-bzz-1.10.4.tgz", - "integrity": "sha512-ZZ/X4sJ0Uh2teU9lAGNS8EjveEppoHNQiKlOXAjedsrdWuaMErBPdLQjXfcrYvN6WM6Su9PMsAxf3FXXZ+HwQw==", - "requires": { - "@types/node": "^12.12.6", - "got": "12.1.0", - "swarm-js": "^0.1.40" - }, - "dependencies": { - "@types/node": { - "version": "12.20.55", - "resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.55.tgz", - "integrity": "sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==" - } + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/web3/-/web3-4.12.1.tgz", + "integrity": "sha512-zIFUPdgo2uG5Vbl7C4KrTv8dmWKN3sGnY/GundbiJzcaJZDxaCyu3a5HXAcgUM1VvvsVb1zaUQAFPceq05/q/Q==", + "requires": { + "web3-core": "^4.5.1", + "web3-errors": "^1.3.0", + "web3-eth": "^4.8.2", + "web3-eth-abi": "^4.2.3", + "web3-eth-accounts": "^4.2.1", + "web3-eth-contract": "^4.7.0", + "web3-eth-ens": "^4.4.0", + "web3-eth-iban": "^4.0.7", + "web3-eth-personal": "^4.0.8", + "web3-net": "^4.1.0", + "web3-providers-http": "^4.2.0", + "web3-providers-ws": "^4.0.8", + "web3-rpc-methods": "^1.3.0", + "web3-rpc-providers": "^1.0.0-rc.2", + "web3-types": "^1.7.0", + "web3-utils": "^4.3.1", + "web3-validator": "^2.0.6" } }, "web3-core": { - "version": "1.10.4", - "resolved": "https://registry.npmjs.org/web3-core/-/web3-core-1.10.4.tgz", - "integrity": "sha512-B6elffYm81MYZDTrat7aEhnhdtVE3lDBUZft16Z8awYMZYJDbnykEbJVS+l3mnA7AQTnSDr/1MjWofGDLBJPww==", - "requires": { - "@types/bn.js": "^5.1.1", - "@types/node": "^12.12.6", - "bignumber.js": "^9.0.0", - "web3-core-helpers": "1.10.4", - "web3-core-method": "1.10.4", - "web3-core-requestmanager": "1.10.4", - "web3-utils": "1.10.4" - }, - "dependencies": { - "@types/bn.js": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-5.1.5.tgz", - "integrity": "sha512-V46N0zwKRF5Q00AZ6hWtN0T8gGmDUaUzLWQvHFo5yThtVwK/VCenFY3wXVbOvNfajEpsTfQM4IN9k/d6gUVX3A==", - "requires": { - "@types/node": "*" - } - }, - "@types/node": { - "version": "12.20.55", - "resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.55.tgz", - "integrity": "sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==" - } - } - }, - "web3-core-helpers": { - "version": "1.10.4", - "resolved": "https://registry.npmjs.org/web3-core-helpers/-/web3-core-helpers-1.10.4.tgz", - "integrity": "sha512-r+L5ylA17JlD1vwS8rjhWr0qg7zVoVMDvWhajWA5r5+USdh91jRUYosp19Kd1m2vE034v7Dfqe1xYRoH2zvG0g==", - "requires": { - "web3-eth-iban": "1.10.4", - "web3-utils": "1.10.4" - } - }, - "web3-core-method": { - "version": "1.10.4", - "resolved": "https://registry.npmjs.org/web3-core-method/-/web3-core-method-1.10.4.tgz", - "integrity": "sha512-uZTb7flr+Xl6LaDsyTeE2L1TylokCJwTDrIVfIfnrGmnwLc6bmTWCCrm71sSrQ0hqs6vp/MKbQYIYqUN0J8WyA==", - "requires": { - "@ethersproject/transactions": "^5.6.2", - "web3-core-helpers": "1.10.4", - "web3-core-promievent": "1.10.4", - "web3-core-subscriptions": "1.10.4", - "web3-utils": "1.10.4" - } - }, - "web3-core-promievent": { - "version": "1.10.4", - "resolved": "https://registry.npmjs.org/web3-core-promievent/-/web3-core-promievent-1.10.4.tgz", - "integrity": "sha512-2de5WnJQ72YcIhYwV/jHLc4/cWJnznuoGTJGD29ncFQHAfwW/MItHFSVKPPA5v8AhJe+r6y4Y12EKvZKjQVBvQ==", - "requires": { - "eventemitter3": "4.0.4" - } - }, - "web3-core-requestmanager": { - "version": "1.10.4", - "resolved": "https://registry.npmjs.org/web3-core-requestmanager/-/web3-core-requestmanager-1.10.4.tgz", - "integrity": "sha512-vqP6pKH8RrhT/2MoaU+DY/OsYK9h7HmEBNCdoMj+4ZwujQtw/Mq2JifjwsJ7gits7Q+HWJwx8q6WmQoVZAWugg==", - "requires": { - "util": "^0.12.5", - "web3-core-helpers": "1.10.4", - "web3-providers-http": "1.10.4", - "web3-providers-ipc": "1.10.4", - "web3-providers-ws": "1.10.4" - } - }, - "web3-core-subscriptions": { - "version": "1.10.4", - "resolved": "https://registry.npmjs.org/web3-core-subscriptions/-/web3-core-subscriptions-1.10.4.tgz", - "integrity": "sha512-o0lSQo/N/f7/L76C0HV63+S54loXiE9fUPfHFcTtpJRQNDBVsSDdWRdePbWwR206XlsBqD5VHApck1//jEafTw==", + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/web3-core/-/web3-core-4.5.1.tgz", + "integrity": "sha512-mFMOO/IWdKsLL1o2whh3oJ0LCG9P3l5c4lpiMoVsVln3QXh/B0Gf8gW3aY8S+Ixm0OHyzFDXJVc2CodxqmI4Gw==", + "requires": { + "web3-errors": "^1.3.0", + "web3-eth-accounts": "^4.2.0", + "web3-eth-iban": "^4.0.7", + "web3-providers-http": "^4.2.0", + "web3-providers-ipc": "^4.0.7", + "web3-providers-ws": "^4.0.8", + "web3-types": "^1.7.0", + "web3-utils": "^4.3.1", + "web3-validator": "^2.0.6" + } + }, + "web3-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/web3-errors/-/web3-errors-1.3.0.tgz", + "integrity": "sha512-j5JkAKCtuVMbY3F5PYXBqg1vWrtF4jcyyMY1rlw8a4PV67AkqlepjGgpzWJZd56Mt+TvHy6DA1F/3Id8LatDSQ==", "requires": { - "eventemitter3": "4.0.4", - "web3-core-helpers": "1.10.4" + "web3-types": "^1.7.0" } }, "web3-eth": { - "version": "1.10.4", - "resolved": "https://registry.npmjs.org/web3-eth/-/web3-eth-1.10.4.tgz", - "integrity": "sha512-Sql2kYKmgt+T/cgvg7b9ce24uLS7xbFrxE4kuuor1zSCGrjhTJ5rRNG8gTJUkAJGKJc7KgnWmgW+cOfMBPUDSA==", - "requires": { - "web3-core": "1.10.4", - "web3-core-helpers": "1.10.4", - "web3-core-method": "1.10.4", - "web3-core-subscriptions": "1.10.4", - "web3-eth-abi": "1.10.4", - "web3-eth-accounts": "1.10.4", - "web3-eth-contract": "1.10.4", - "web3-eth-ens": "1.10.4", - "web3-eth-iban": "1.10.4", - "web3-eth-personal": "1.10.4", - "web3-net": "1.10.4", - "web3-utils": "1.10.4" + "version": "4.8.2", + "resolved": "https://registry.npmjs.org/web3-eth/-/web3-eth-4.8.2.tgz", + "integrity": "sha512-DLV/fIMG6gBp/B0gv0+G4FzxZ4YCDQsY3lzqqv7avwh3uU7/O27aifCUcFd7Ye+3ixTqCjAvLEl9wYSeyG3zQw==", + "requires": { + "setimmediate": "^1.0.5", + "web3-core": "^4.5.0", + "web3-errors": "^1.2.1", + "web3-eth-abi": "^4.2.3", + "web3-eth-accounts": "^4.1.3", + "web3-net": "^4.1.0", + "web3-providers-ws": "^4.0.8", + "web3-rpc-methods": "^1.3.0", + "web3-types": "^1.7.0", + "web3-utils": "^4.3.1", + "web3-validator": "^2.0.6" } }, "web3-eth-abi": { - "version": "1.10.4", - "resolved": "https://registry.npmjs.org/web3-eth-abi/-/web3-eth-abi-1.10.4.tgz", - "integrity": "sha512-cZ0q65eJIkd/jyOlQPDjr8X4fU6CRL1eWgdLwbWEpo++MPU/2P4PFk5ZLAdye9T5Sdp+MomePPJ/gHjLMj2VfQ==", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/web3-eth-abi/-/web3-eth-abi-4.2.3.tgz", + "integrity": "sha512-rPVwTn0O1CzbtfXwEfIjUP0W5Y7u1OFjugwKpSqJzPQE6+REBg6OELjomTGZBu+GThxHnv0rp15SOxvqp+tyXA==", "requires": { - "@ethersproject/abi": "^5.6.3", - "web3-utils": "1.10.4" + "abitype": "0.7.1", + "web3-errors": "^1.2.0", + "web3-types": "^1.7.0", + "web3-utils": "^4.3.1", + "web3-validator": "^2.0.6" } }, "web3-eth-accounts": { - "version": "1.10.4", - "resolved": "https://registry.npmjs.org/web3-eth-accounts/-/web3-eth-accounts-1.10.4.tgz", - "integrity": "sha512-ysy5sVTg9snYS7tJjxVoQAH6DTOTkRGR8emEVCWNGLGiB9txj+qDvSeT0izjurS/g7D5xlMAgrEHLK1Vi6I3yg==", - "requires": { - "@ethereumjs/common": "2.6.5", - "@ethereumjs/tx": "3.5.2", - "@ethereumjs/util": "^8.1.0", - "eth-lib": "0.2.8", - "scrypt-js": "^3.0.1", - "uuid": "^9.0.0", - "web3-core": "1.10.4", - "web3-core-helpers": "1.10.4", - "web3-core-method": "1.10.4", - "web3-utils": "1.10.4" + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/web3-eth-accounts/-/web3-eth-accounts-4.2.1.tgz", + "integrity": "sha512-aOlEZFzqAgKprKs7+DGArU4r9b+ILBjThpeq42aY7LAQcP+mSpsWcQgbIRK3r/n3OwTYZ3aLPk0Ih70O/LwnYA==", + "requires": { + "@ethereumjs/rlp": "^4.0.1", + "crc-32": "^1.2.2", + "ethereum-cryptography": "^2.0.0", + "web3-errors": "^1.3.0", + "web3-types": "^1.7.0", + "web3-utils": "^4.3.1", + "web3-validator": "^2.0.6" }, "dependencies": { - "bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" - }, - "eth-lib": { - "version": "0.2.8", - "resolved": "https://registry.npmjs.org/eth-lib/-/eth-lib-0.2.8.tgz", - "integrity": "sha512-ArJ7x1WcWOlSpzdoTBX8vkwlkSQ85CjjifSZtV4co64vWxSV8geWfPI9x4SVYu3DSxnX4yWFVTtGL+j9DUFLNw==", + "ethereum-cryptography": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-2.2.1.tgz", + "integrity": "sha512-r/W8lkHSiTLxUxW8Rf3u4HGB0xQweG2RyETjywylKZSzLWoWAijRz8WCuOtJ6wah+avllXBqZuk29HCCvhEIRg==", "requires": { - "bn.js": "^4.11.6", - "elliptic": "^6.4.0", - "xhr-request-promise": "^0.1.2" + "@noble/curves": "1.4.2", + "@noble/hashes": "1.4.0", + "@scure/bip32": "1.4.0", + "@scure/bip39": "1.3.0" } - }, - "uuid": { - "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.4", - "resolved": "https://registry.npmjs.org/web3-eth-contract/-/web3-eth-contract-1.10.4.tgz", - "integrity": "sha512-Q8PfolOJ4eV9TvnTj1TGdZ4RarpSLmHnUnzVxZ/6/NiTfe4maJz99R0ISgwZkntLhLRtw0C7LRJuklzGYCNN3A==", - "requires": { - "@types/bn.js": "^5.1.1", - "web3-core": "1.10.4", - "web3-core-helpers": "1.10.4", - "web3-core-method": "1.10.4", - "web3-core-promievent": "1.10.4", - "web3-core-subscriptions": "1.10.4", - "web3-eth-abi": "1.10.4", - "web3-utils": "1.10.4" - }, - "dependencies": { - "@types/bn.js": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-5.1.5.tgz", - "integrity": "sha512-V46N0zwKRF5Q00AZ6hWtN0T8gGmDUaUzLWQvHFo5yThtVwK/VCenFY3wXVbOvNfajEpsTfQM4IN9k/d6gUVX3A==", - "requires": { - "@types/node": "*" - } + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/web3-eth-contract/-/web3-eth-contract-4.7.0.tgz", + "integrity": "sha512-fdStoBOjFyMHwlyJmSUt/BTDL1ATwKGmG3zDXQ/zTKlkkW/F/074ut0Vry4GuwSBg9acMHc0ycOiZx9ZKjNHsw==", + "requires": { + "@ethereumjs/rlp": "^5.0.2", + "web3-core": "^4.5.1", + "web3-errors": "^1.3.0", + "web3-eth": "^4.8.2", + "web3-eth-abi": "^4.2.3", + "web3-types": "^1.7.0", + "web3-utils": "^4.3.1", + "web3-validator": "^2.0.6" + }, + "dependencies": { + "@ethereumjs/rlp": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@ethereumjs/rlp/-/rlp-5.0.2.tgz", + "integrity": "sha512-DziebCdg4JpGlEqEdGgXmjqcFoJi+JGulUXwEjsZGAscAQ7MyD/7LE/GVCP29vEQxKc7AAwjT3A2ywHp2xfoCA==" } } }, "web3-eth-ens": { - "version": "1.10.4", - "resolved": "https://registry.npmjs.org/web3-eth-ens/-/web3-eth-ens-1.10.4.tgz", - "integrity": "sha512-LLrvxuFeVooRVZ9e5T6OWKVflHPFgrVjJ/jtisRWcmI7KN/b64+D/wJzXqgmp6CNsMQcE7rpmf4CQmJCrTdsgg==", + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/web3-eth-ens/-/web3-eth-ens-4.4.0.tgz", + "integrity": "sha512-DeyVIS060hNV9g8dnTx92syqvgbvPricE3MerCxe/DquNZT3tD8aVgFfq65GATtpCgDDJffO2bVeHp3XBemnSQ==", "requires": { - "content-hash": "^2.5.2", - "eth-ens-namehash": "2.0.8", - "web3-core": "1.10.4", - "web3-core-helpers": "1.10.4", - "web3-core-promievent": "1.10.4", - "web3-eth-abi": "1.10.4", - "web3-eth-contract": "1.10.4", - "web3-utils": "1.10.4" + "@adraffy/ens-normalize": "^1.8.8", + "web3-core": "^4.5.0", + "web3-errors": "^1.2.0", + "web3-eth": "^4.8.0", + "web3-eth-contract": "^4.5.0", + "web3-net": "^4.1.0", + "web3-types": "^1.7.0", + "web3-utils": "^4.3.0", + "web3-validator": "^2.0.6" } }, "web3-eth-iban": { - "version": "1.10.4", - "resolved": "https://registry.npmjs.org/web3-eth-iban/-/web3-eth-iban-1.10.4.tgz", - "integrity": "sha512-0gE5iNmOkmtBmbKH2aTodeompnNE8jEyvwFJ6s/AF6jkw9ky9Op9cqfzS56AYAbrqEFuClsqB/AoRves7LDELw==", + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/web3-eth-iban/-/web3-eth-iban-4.0.7.tgz", + "integrity": "sha512-8weKLa9KuKRzibC87vNLdkinpUE30gn0IGY027F8doeJdcPUfsa4IlBgNC4k4HLBembBB2CTU0Kr/HAOqMeYVQ==", "requires": { - "bn.js": "^5.2.1", - "web3-utils": "1.10.4" + "web3-errors": "^1.1.3", + "web3-types": "^1.3.0", + "web3-utils": "^4.0.7", + "web3-validator": "^2.0.3" } }, "web3-eth-personal": { - "version": "1.10.4", - "resolved": "https://registry.npmjs.org/web3-eth-personal/-/web3-eth-personal-1.10.4.tgz", - "integrity": "sha512-BRa/hs6jU1hKHz+AC/YkM71RP3f0Yci1dPk4paOic53R4ZZG4MgwKRkJhgt3/GPuPliwS46f/i5A7fEGBT4F9w==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/web3-eth-personal/-/web3-eth-personal-4.0.8.tgz", + "integrity": "sha512-sXeyLKJ7ddQdMxz1BZkAwImjqh7OmKxhXoBNF3isDmD4QDpMIwv/t237S3q4Z0sZQamPa/pHebJRWVuvP8jZdw==", "requires": { - "@types/node": "^12.12.6", - "web3-core": "1.10.4", - "web3-core-helpers": "1.10.4", - "web3-core-method": "1.10.4", - "web3-net": "1.10.4", - "web3-utils": "1.10.4" - }, - "dependencies": { - "@types/node": { - "version": "12.20.55", - "resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.55.tgz", - "integrity": "sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==" - } + "web3-core": "^4.3.0", + "web3-eth": "^4.3.1", + "web3-rpc-methods": "^1.1.3", + "web3-types": "^1.3.0", + "web3-utils": "^4.0.7", + "web3-validator": "^2.0.3" } }, "web3-net": { - "version": "1.10.4", - "resolved": "https://registry.npmjs.org/web3-net/-/web3-net-1.10.4.tgz", - "integrity": "sha512-mKINnhOOnZ4koA+yV2OT5s5ztVjIx7IY9a03w6s+yao/BUn+Luuty0/keNemZxTr1E8Ehvtn28vbOtW7Ids+Ow==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/web3-net/-/web3-net-4.1.0.tgz", + "integrity": "sha512-WWmfvHVIXWEoBDWdgKNYKN8rAy6SgluZ0abyRyXOL3ESr7ym7pKWbfP4fjApIHlYTh8tNqkrdPfM4Dyi6CA0SA==", "requires": { - "web3-core": "1.10.4", - "web3-core-method": "1.10.4", - "web3-utils": "1.10.4" + "web3-core": "^4.4.0", + "web3-rpc-methods": "^1.3.0", + "web3-types": "^1.6.0", + "web3-utils": "^4.3.0" } }, "web3-provider-engine": { @@ -30974,14 +27415,14 @@ } }, "web3-providers-http": { - "version": "1.10.4", - "resolved": "https://registry.npmjs.org/web3-providers-http/-/web3-providers-http-1.10.4.tgz", - "integrity": "sha512-m2P5Idc8hdiO0l60O6DSCPw0kw64Zgi0pMjbEFRmxKIck2Py57RQMu4bxvkxJwkF06SlGaEQF8rFZBmuX7aagQ==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/web3-providers-http/-/web3-providers-http-4.2.0.tgz", + "integrity": "sha512-IPMnDtHB7dVwaB7/mMxAZzyq7d5ezfO1+Vw0bNfAeIi7gaDlJiggp85SdyAfOgov8AMUA/dyiY72kQ0KmjXKvQ==", "requires": { - "abortcontroller-polyfill": "^1.7.5", "cross-fetch": "^4.0.0", - "es6-promise": "^4.2.8", - "web3-core-helpers": "1.10.4" + "web3-errors": "^1.3.0", + "web3-types": "^1.7.0", + "web3-utils": "^4.3.1" }, "dependencies": { "cross-fetch": { @@ -30995,59 +27436,103 @@ } }, "web3-providers-ipc": { - "version": "1.10.4", - "resolved": "https://registry.npmjs.org/web3-providers-ipc/-/web3-providers-ipc-1.10.4.tgz", - "integrity": "sha512-YRF/bpQk9z3WwjT+A6FI/GmWRCASgd+gC0si7f9zbBWLXjwzYAKG73bQBaFRAHex1hl4CVcM5WUMaQXf3Opeuw==", + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/web3-providers-ipc/-/web3-providers-ipc-4.0.7.tgz", + "integrity": "sha512-YbNqY4zUvIaK2MHr1lQFE53/8t/ejHtJchrWn9zVbFMGXlTsOAbNoIoZWROrg1v+hCBvT2c9z8xt7e/+uz5p1g==", + "optional": true, "requires": { - "oboe": "2.1.5", - "web3-core-helpers": "1.10.4" + "web3-errors": "^1.1.3", + "web3-types": "^1.3.0", + "web3-utils": "^4.0.7" } }, "web3-providers-ws": { - "version": "1.10.4", - "resolved": "https://registry.npmjs.org/web3-providers-ws/-/web3-providers-ws-1.10.4.tgz", - "integrity": "sha512-j3FBMifyuFFmUIPVQR4pj+t5ILhAexAui0opgcpu9R5LxQrLRUZxHSnU+YO25UycSOa/NAX8A+qkqZNpcFAlxA==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/web3-providers-ws/-/web3-providers-ws-4.0.8.tgz", + "integrity": "sha512-goJdgata7v4pyzHRsg9fSegUG4gVnHZSHODhNnn6J93ykHkBI1nz4fjlGpcQLUMi4jAMz6SHl9Ibzs2jj9xqPw==", + "requires": { + "@types/ws": "8.5.3", + "isomorphic-ws": "^5.0.0", + "web3-errors": "^1.2.0", + "web3-types": "^1.7.0", + "web3-utils": "^4.3.1", + "ws": "^8.17.1" + } + }, + "web3-rpc-methods": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/web3-rpc-methods/-/web3-rpc-methods-1.3.0.tgz", + "integrity": "sha512-/CHmzGN+IYgdBOme7PdqzF+FNeMleefzqs0LVOduncSaqsppeOEoskLXb2anSpzmQAP3xZJPaTrkQPWSJMORig==", "requires": { - "eventemitter3": "4.0.4", - "web3-core-helpers": "1.10.4", - "websocket": "^1.0.32" + "web3-core": "^4.4.0", + "web3-types": "^1.6.0", + "web3-validator": "^2.0.6" } }, - "web3-shh": { - "version": "1.10.4", - "resolved": "https://registry.npmjs.org/web3-shh/-/web3-shh-1.10.4.tgz", - "integrity": "sha512-cOH6iFFM71lCNwSQrC3niqDXagMqrdfFW85hC9PFUrAr3PUrIem8TNstTc3xna2bwZeWG6OBy99xSIhBvyIACw==", + "web3-rpc-providers": { + "version": "1.0.0-rc.2", + "resolved": "https://registry.npmjs.org/web3-rpc-providers/-/web3-rpc-providers-1.0.0-rc.2.tgz", + "integrity": "sha512-ocFIEXcBx/DYQ90HhVepTBUVnL9pGsZw8wyPb1ZINSenwYus9SvcFkjU1Hfvd/fXjuhAv2bUVch9vxvMx1mXAQ==", "requires": { - "web3-core": "1.10.4", - "web3-core-method": "1.10.4", - "web3-core-subscriptions": "1.10.4", - "web3-net": "1.10.4" + "web3-errors": "^1.3.0", + "web3-providers-http": "^4.2.0", + "web3-providers-ws": "^4.0.8", + "web3-types": "^1.7.0", + "web3-utils": "^4.3.1", + "web3-validator": "^2.0.6" } }, + "web3-types": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/web3-types/-/web3-types-1.7.0.tgz", + "integrity": "sha512-nhXxDJ7a5FesRw9UG5SZdP/C/3Q2EzHGnB39hkAV+YGXDMgwxBXFWebQLfEzZzuArfHnvC0sQqkIHNwSKcVjdA==" + }, "web3-utils": { - "version": "1.10.4", - "resolved": "https://registry.npmjs.org/web3-utils/-/web3-utils-1.10.4.tgz", - "integrity": "sha512-tsu8FiKJLk2PzhDl9fXbGUWTkkVXYhtTA+SmEFkKft+9BgwLxfCRpU96sWv7ICC8zixBNd3JURVoiR3dUXgP8A==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/web3-utils/-/web3-utils-4.3.1.tgz", + "integrity": "sha512-kGwOk8FxOLJ9DQC68yqNQc7AzN+k9YDLaW+ZjlAXs3qORhf8zXk5SxWAAGLbLykMs3vTeB0FTb1Exut4JEYfFA==", "requires": { - "@ethereumjs/util": "^8.1.0", - "bn.js": "^5.2.1", - "ethereum-bloom-filters": "^1.0.6", - "ethereum-cryptography": "^2.1.2", - "ethjs-unit": "0.1.6", - "number-to-bn": "1.7.0", - "randombytes": "^2.1.0", - "utf8": "3.0.0" + "ethereum-cryptography": "^2.0.0", + "eventemitter3": "^5.0.1", + "web3-errors": "^1.2.0", + "web3-types": "^1.7.0", + "web3-validator": "^2.0.6" + }, + "dependencies": { + "ethereum-cryptography": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-2.2.1.tgz", + "integrity": "sha512-r/W8lkHSiTLxUxW8Rf3u4HGB0xQweG2RyETjywylKZSzLWoWAijRz8WCuOtJ6wah+avllXBqZuk29HCCvhEIRg==", + "requires": { + "@noble/curves": "1.4.2", + "@noble/hashes": "1.4.0", + "@scure/bip32": "1.4.0", + "@scure/bip39": "1.3.0" + } + } + } + }, + "web3-validator": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/web3-validator/-/web3-validator-2.0.6.tgz", + "integrity": "sha512-qn9id0/l1bWmvH4XfnG/JtGKKwut2Vokl6YXP5Kfg424npysmtRLe9DgiNBM9Op7QL/aSiaA0TVXibuIuWcizg==", + "requires": { + "ethereum-cryptography": "^2.0.0", + "util": "^0.12.5", + "web3-errors": "^1.2.0", + "web3-types": "^1.6.0", + "zod": "^3.21.4" }, "dependencies": { "ethereum-cryptography": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-2.1.3.tgz", - "integrity": "sha512-BlwbIL7/P45W8FGW2r7LGuvoEZ+7PWsniMvQ4p5s2xCyw9tmaDlpfsN9HjAucbF+t/qpVHwZUisgfK24TCW8aA==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-2.2.1.tgz", + "integrity": "sha512-r/W8lkHSiTLxUxW8Rf3u4HGB0xQweG2RyETjywylKZSzLWoWAijRz8WCuOtJ6wah+avllXBqZuk29HCCvhEIRg==", "requires": { - "@noble/curves": "1.3.0", - "@noble/hashes": "1.3.3", - "@scure/bip32": "1.3.3", - "@scure/bip39": "1.2.2" + "@noble/curves": "1.4.2", + "@noble/hashes": "1.4.0", + "@scure/bip32": "1.4.0", + "@scure/bip39": "1.3.0" } } } @@ -31072,12 +27557,11 @@ "dev": true }, "webpack": { - "version": "5.93.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.93.0.tgz", - "integrity": "sha512-Y0m5oEY1LRuwly578VqluorkXbvXKh7U3rLoQCEO04M97ScRr44afGVkI0FQFsXzysk5OgFAxjZAb9rsGQVihA==", + "version": "5.95.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.95.0.tgz", + "integrity": "sha512-2t3XstrKULz41MNMBF+cJ97TyHdyQ8HCt//pqErqDvNjU9YQBnZxIHa11VXsi7F3mb5/aO2tuDxdeTPdU7xu9Q==", "dev": true, "requires": { - "@types/eslint-scope": "^3.7.3", "@types/estree": "^1.0.5", "@webassemblyjs/ast": "^1.12.1", "@webassemblyjs/wasm-edit": "^1.12.1", @@ -31086,7 +27570,7 @@ "acorn-import-attributes": "^1.9.5", "browserslist": "^4.21.10", "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.17.0", + "enhanced-resolve": "^5.17.1", "es-module-lexer": "^1.2.1", "eslint-scope": "5.1.1", "events": "^3.2.0", @@ -31161,34 +27645,6 @@ "wildcard": "^2.0.0" } }, - "websocket": { - "version": "1.0.34", - "resolved": "https://registry.npmjs.org/websocket/-/websocket-1.0.34.tgz", - "integrity": "sha512-PRDso2sGwF6kM75QykIesBijKSVceR6jL2G8NGYyq2XrItNC2P5/qL5XeR056GhA+Ly7JMFvJb9I312mJfmqnQ==", - "requires": { - "bufferutil": "^4.0.1", - "debug": "^2.2.0", - "es5-ext": "^0.10.50", - "typedarray-to-buffer": "^3.1.5", - "utf-8-validate": "^5.0.2", - "yaeti": "^0.0.6" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - } - } - }, "whatwg-encoding": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz", @@ -31321,7 +27777,8 @@ "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true }, "write-file-atomic": { "version": "4.0.2", @@ -31334,10 +27791,9 @@ } }, "ws": { - "version": "8.8.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.8.1.tgz", - "integrity": "sha512-bGy2JzvzkPowEJV++hF07hAD6niYSr0JzBNo/J29WsB57A2r7Wlc1UFcTR9IzrPvuNVO4B8LGqF8qcpsVOhJCA==", - "dev": true, + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", "requires": {} }, "xhr": { @@ -31351,28 +27807,6 @@ "xtend": "^4.0.0" } }, - "xhr-request": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/xhr-request/-/xhr-request-1.1.0.tgz", - "integrity": "sha512-Y7qzEaR3FDtL3fP30k9wO/e+FBnBByZeybKOhASsGP30NIkRAAkKD/sCnLvgEfAIEC1rcmK7YG8f4oEnIrrWzA==", - "requires": { - "buffer-to-arraybuffer": "^0.0.5", - "object-assign": "^4.1.1", - "query-string": "^5.0.1", - "simple-get": "^2.7.0", - "timed-out": "^4.0.1", - "url-set-query": "^1.0.0", - "xhr": "^2.0.4" - } - }, - "xhr-request-promise": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/xhr-request-promise/-/xhr-request-promise-0.1.3.tgz", - "integrity": "sha512-YUBytBsuwgitWtdRzXDDkWAXzhdGB8bYm0sSzMPZT7Z2MBjMSTHFsyCT1yCRATY+XC69DUrQraRAEgcoCRaIPg==", - "requires": { - "xhr-request": "^1.1.0" - } - }, "xhr2-cookies": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/xhr2-cookies/-/xhr2-cookies-1.1.0.tgz", @@ -31419,11 +27853,6 @@ "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==" }, - "yaeti": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/yaeti/-/yaeti-0.0.6.tgz", - "integrity": "sha512-MvQa//+KcZCUkBTIC9blM+CU9J2GzuTytsOUwf2lidtvkx/6gnEp1QvJv34t9vdjhFmha/mUiNDbN0D0mJWdug==" - }, "yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", @@ -31464,6 +27893,11 @@ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", "dev": true + }, + "zod": { + "version": "3.23.8", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz", + "integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==" } } } diff --git a/apps/block_scout_web/assets/package.json b/apps/block_scout_web/assets/package.json index 37bd523..8bdc164 100644 --- a/apps/block_scout_web/assets/package.json +++ b/apps/block_scout_web/assets/package.json @@ -19,7 +19,7 @@ "eslint": "eslint js/**" }, "dependencies": { - "@fortawesome/fontawesome-free": "^6.5.2", + "@fortawesome/fontawesome-free": "^6.6.0", "@amplitude/analytics-browser": "^2.9.3", "@tarekraafat/autocomplete.js": "^10.2.7", "@walletconnect/web3-provider": "^1.8.0", @@ -29,7 +29,7 @@ "chart.js": "^4.4.3", "chartjs-adapter-luxon": "^1.3.1", "clipboard": "^2.0.11", - "core-js": "^3.37.1", + "core-js": "^3.38.1", "crypto-browserify": "^3.12.0", "dropzone": "^5.9.3", "eth-net-props": "^1.0.41", @@ -56,9 +56,9 @@ "lodash.omit": "^4.5.0", "lodash.rangeright": "^4.2.0", "lodash.reduce": "^4.6.0", - "luxon": "^3.4.4", + "luxon": "^3.5.0", "malihu-custom-scrollbar-plugin": "3.1.5", - "mixpanel-browser": "^2.54.1", + "mixpanel-browser": "^2.55.1", "moment": "^2.30.1", "nanomorph": "^5.4.0", "numeral": "^2.0.6", @@ -73,24 +73,24 @@ "redux": "^5.0.1", "stream-browserify": "^3.0.0", "stream-http": "^3.1.1", - "sweetalert2": "^11.12.4", + "sweetalert2": "^11.14.1", "urijs": "^1.19.11", - "url": "^0.11.3", + "url": "^0.11.4", "util": "^0.12.5", "viewerjs": "^1.11.6", - "web3": "^1.10.4", + "web3": "^4.12.1", "web3modal": "^1.9.12", "xss": "^1.0.15" }, "devDependencies": { - "@babel/core": "^7.24.7", - "@babel/preset-env": "^7.24.7", - "autoprefixer": "^10.4.19", + "@babel/core": "^7.25.2", + "@babel/preset-env": "^7.25.4", + "autoprefixer": "^10.4.20", "babel-loader": "^9.1.3", "copy-webpack-plugin": "^12.0.2", "css-loader": "^7.1.2", "css-minimizer-webpack-plugin": "^7.0.0", - "eslint": "^8.57.0", + "eslint": "^8.57.1", "eslint-config-standard": "^17.1.0", "eslint-plugin-import": "^2.29.1", "eslint-plugin-node": "^11.1.0", @@ -98,13 +98,13 @@ "file-loader": "^6.2.0", "jest": "^29.7.0", "jest-environment-jsdom": "^29.7.0", - "mini-css-extract-plugin": "^2.9.0", - "postcss": "^8.4.39", + "mini-css-extract-plugin": "^2.9.1", + "postcss": "^8.4.47", "postcss-loader": "^8.1.1", - "sass": "^1.77.8", + "sass": "^1.79.4", "sass-loader": "^14.2.1", "style-loader": "^4.0.0", - "webpack": "^5.93.0", + "webpack": "^5.95.0", "webpack-cli": "^5.1.4" }, "jest": { diff --git a/apps/block_scout_web/config/config.exs b/apps/block_scout_web/config/config.exs index 7467a51..1c0c83b 100644 --- a/apps/block_scout_web/config/config.exs +++ b/apps/block_scout_web/config/config.exs @@ -17,7 +17,8 @@ config :block_scout_web, # 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" + api_v2_temp_token_key: "api_v2_temp_token", + http_adapter: HTTPoison config :block_scout_web, admin_panel_enabled: ConfigHelper.parse_bool_env_var("ADMIN_PANEL_ENABLED") diff --git a/apps/block_scout_web/config/dev.exs b/apps/block_scout_web/config/dev.exs index 3f2bb8a..d69502e 100644 --- a/apps/block_scout_web/config/dev.exs +++ b/apps/block_scout_web/config/dev.exs @@ -67,5 +67,3 @@ config :logger, :api_v2, # Set a higher stacktrace during development. Avoid configuring such # in production as building large stacktraces may be expensive. config :phoenix, :stacktrace_depth, 20 - -config :block_scout_web, :captcha_helper, BlockScoutWeb.CaptchaHelper diff --git a/apps/block_scout_web/config/prod.exs b/apps/block_scout_web/config/prod.exs index d94e56e..2efcfd7 100644 --- a/apps/block_scout_web/config/prod.exs +++ b/apps/block_scout_web/config/prod.exs @@ -35,5 +35,3 @@ config :logger, :api_v2, path: Path.absname("logs/prod/api_v2.log"), metadata_filter: [application: :api_v2], rotate: %{max_bytes: 52_428_800, keep: 19} - -config :block_scout_web, :captcha_helper, BlockScoutWeb.CaptchaHelper diff --git a/apps/block_scout_web/config/test.exs b/apps/block_scout_web/config/test.exs index 9c3414f..033fc74 100644 --- a/apps/block_scout_web/config/test.exs +++ b/apps/block_scout_web/config/test.exs @@ -24,8 +24,6 @@ config :block_scout_web, BlockScoutWeb.Counters.BlocksIndexedCounter, enabled: f config :block_scout_web, BlockScoutWeb.Counters.InternalTransactionsIndexedCounter, enabled: false -config :block_scout_web, :captcha_helper, BlockScoutWeb.TestCaptchaHelper - config :ueberauth, Ueberauth, providers: [ auth0: { diff --git a/apps/block_scout_web/lib/block_scout_web.ex b/apps/block_scout_web/lib/block_scout_web.ex index 7cdc6b3..1ce9cca 100644 --- a/apps/block_scout_web/lib/block_scout_web.ex +++ b/apps/block_scout_web/lib/block_scout_web.ex @@ -25,7 +25,7 @@ defmodule BlockScoutWeb do import BlockScoutWeb.Controller import BlockScoutWeb.Router.Helpers import BlockScoutWeb.Routers.WebRouter.Helpers, except: [static_path: 2] - import BlockScoutWeb.Gettext + use Gettext, backend: BlockScoutWeb.Gettext import BlockScoutWeb.ErrorHelper import BlockScoutWeb.Routers.AccountRouter.Helpers, except: [static_path: 2] import Plug.Conn @@ -49,7 +49,6 @@ defmodule BlockScoutWeb do import BlockScoutWeb.{ CurrencyHelper, ErrorHelper, - Gettext, Router.Helpers, TabHelper, Tokens.Helper, @@ -57,6 +56,8 @@ defmodule BlockScoutWeb do WeiHelper } + use Gettext, backend: BlockScoutWeb.Gettext + import BlockScoutWeb.Routers.AccountRouter.Helpers, except: [static_path: 2] import Explorer.Chain.CurrencyHelper, only: [divide_decimals: 2] @@ -78,7 +79,7 @@ defmodule BlockScoutWeb do quote do use Phoenix.Channel - import BlockScoutWeb.Gettext + use Gettext, backend: BlockScoutWeb.Gettext end end diff --git a/apps/block_scout_web/lib/block_scout_web/captcha_helper.ex b/apps/block_scout_web/lib/block_scout_web/captcha_helper.ex index 78194bd..810652e 100644 --- a/apps/block_scout_web/lib/block_scout_web/captcha_helper.ex +++ b/apps/block_scout_web/lib/block_scout_web/captcha_helper.ex @@ -2,42 +2,101 @@ defmodule BlockScoutWeb.CaptchaHelper do @moduledoc """ A helper for CAPTCHA """ + require Logger - @callback recaptcha_passed?(String.t() | nil) :: bool - @spec recaptcha_passed?(String.t() | nil) :: bool - def recaptcha_passed?(nil), do: false + alias Explorer.Helper - def recaptcha_passed?(recaptcha_response) do - re_captcha_v2_secret_key = Application.get_env(:block_scout_web, :recaptcha)[:v2_secret_key] + @doc """ + Verifies if the CAPTCHA challenge has been passed based on the provided parameters. + + This function handles both reCAPTCHA v3 and v2 responses, as well as cases where + CAPTCHA is disabled. + + ## Parameters + - `params`: A map containing CAPTCHA response parameters or nil. + + ## Returns + - `true` if the CAPTCHA challenge is passed or disabled. + - `false` if the CAPTCHA challenge fails or an error occurs during verification. + """ + @spec recaptcha_passed?(%{String.t() => String.t()} | nil) :: bool + def recaptcha_passed?(%{"recaptcha_v3_response" => recaptcha_response}) do re_captcha_v3_secret_key = Application.get_env(:block_scout_web, :recaptcha)[:v3_secret_key] - re_captcha_secret_key = re_captcha_v2_secret_key || re_captcha_v3_secret_key - body = "secret=#{re_captcha_secret_key}&response=#{recaptcha_response}" + do_recaptcha_passed?(re_captcha_v3_secret_key, recaptcha_response) + end + + def recaptcha_passed?(%{"recaptcha_response" => recaptcha_response}) do + re_captcha_v2_secret_key = Application.get_env(:block_scout_web, :recaptcha)[:v2_secret_key] + do_recaptcha_passed?(re_captcha_v2_secret_key, recaptcha_response) + end + + def recaptcha_passed?(_), do: Application.get_env(:block_scout_web, :recaptcha)[:is_disabled] + + defp do_recaptcha_passed?(recaptcha_secret_key, recaptcha_response) do + body = "secret=#{recaptcha_secret_key}&response=#{recaptcha_response}" headers = [{"Content-type", "application/x-www-form-urlencoded"}] - case HTTPoison.post("https://www.google.com/recaptcha/api/siteverify", body, headers, []) do + case !Application.get_env(:block_scout_web, :recaptcha)[:is_disabled] && + Application.get_env(:block_scout_web, :http_adapter).post( + "https://www.google.com/recaptcha/api/siteverify", + body, + headers, + [] + ) do {:ok, %HTTPoison.Response{status_code: 200, body: body}} -> - case Jason.decode!(body) do - %{"success" => true} = resp -> success?(resp) - _ -> false - end + body |> Jason.decode!() |> success?() + + false -> + true - _ -> + error -> + Logger.error("Failed to verify reCAPTCHA: #{inspect(error)}") false end end - defp success?(%{"score" => score}) do - check_recaptcha_v3_score(score) + # v3 case + defp success?(%{"success" => true, "score" => score, "hostname" => hostname}) do + unless Helper.get_app_host() == hostname do + Logger.warning("reCAPTCHA v3 Hostname mismatch: #{inspect(hostname)} != #{inspect(Helper.get_app_host())}") + end + + if Helper.get_app_host() == hostname and not check_recaptcha_v3_score(score) do + Logger.warning("reCAPTCHA v3 low score: #{inspect(score)} < #{inspect(score_threshold())}") + end + + (!check_hostname?() || Helper.get_app_host() == hostname) && + check_recaptcha_v3_score(score) + end + + # v2 case + defp success?(%{"success" => true, "hostname" => hostname}) do + unless Helper.get_app_host() == hostname do + Logger.warning("reCAPTCHA v2 Hostname mismatch: #{inspect(hostname)} != #{inspect(Helper.get_app_host())}") + end + + !check_hostname?() || Helper.get_app_host() == hostname end - defp success?(_resp), do: true + defp success?(resp) do + Logger.error("Failed to verify reCAPTCHA, unexpected response: #{inspect(resp)}") + false + end defp check_recaptcha_v3_score(score) do - if score >= 0.5 do + if score >= score_threshold() do true else false end end + + defp check_hostname? do + Application.get_env(:block_scout_web, :recaptcha)[:check_hostname?] + end + + defp score_threshold do + Application.get_env(:block_scout_web, :recaptcha)[:score_threshold] + end end 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 b6b4ddd..6bb3212 100644 --- a/apps/block_scout_web/lib/block_scout_web/chain.ex +++ b/apps/block_scout_web/lib/block_scout_web/chain.ex @@ -51,8 +51,8 @@ defmodule BlockScoutWeb.Chain do alias Explorer.Chain.Optimism.Deposit, as: OptimismDeposit alias Explorer.Chain.Optimism.FrameSequence, as: OptimismFrameSequence alias Explorer.Chain.Optimism.OutputRoot, as: OptimismOutputRoot + alias Explorer.Chain.Scroll.Bridge, as: ScrollBridge - alias Explorer.Chain.PolygonZkevm.TransactionBatch alias Explorer.PagingOptions defimpl Poison.Encoder, for: Decimal do @@ -181,21 +181,21 @@ defmodule BlockScoutWeb.Chain do def paging_options(%{ "address_hash" => address_hash_string, - "tx_hash" => tx_hash_string, + "transaction_hash" => transaction_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 + when is_binary(address_hash_string) and is_binary(transaction_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_string, tx_hash_string, block_hash_string, holder_count_string, name_string, + {address_hash_string, transaction_hash_string, block_hash_string, holder_count_string, name_string, inserted_at_string, item_type_string} } ] @@ -376,7 +376,7 @@ defmodule BlockScoutWeb.Chain do 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}] + [paging_options: %{@default_paging_options | key: {inserted_at, hash}, is_pending_transaction: true}] else _ -> [paging_options: @default_paging_options] @@ -406,7 +406,7 @@ defmodule BlockScoutWeb.Chain do end def paging_options(%{"smart_contract_id" => id_str} = params) do - transactions_count = parse_integer(params["tx_count"]) + transactions_count = parse_integer(params["transaction_count"]) coin_balance = parse_integer(params["coin_balance"]) id = parse_integer(id_str) @@ -425,10 +425,10 @@ defmodule BlockScoutWeb.Chain do end end - def paging_options(%{"l1_block_number" => block_number, "tx_hash" => tx_hash}) do + def paging_options(%{"l1_block_number" => block_number, "transaction_hash" => transaction_hash}) do with {block_number, ""} <- Integer.parse(block_number), - {:ok, tx_hash} <- string_to_transaction_hash(tx_hash) do - [paging_options: %{@default_paging_options | key: {block_number, tx_hash}}] + {:ok, transaction_hash} <- string_to_transaction_hash(transaction_hash) do + [paging_options: %{@default_paging_options | key: {block_number, transaction_hash}}] else _ -> [paging_options: @default_paging_options] @@ -441,6 +441,7 @@ defmodule BlockScoutWeb.Chain do # - Polygon Edge Deposits # - Polygon Edge Withdrawals # - Arbitrum cross chain messages + # - Scroll cross chain messages def paging_options(%{"id" => id_string}) when is_binary(id_string) do case Integer.parse(id_string) do {id, ""} -> @@ -457,6 +458,7 @@ defmodule BlockScoutWeb.Chain do # - Polygon Edge Deposits # - Polygon Edge Withdrawals # - Arbitrum cross chain messages + # - Scroll cross chain messages def paging_options(%{"id" => id}) when is_integer(id) do [paging_options: %{@default_paging_options | key: {id}}] end @@ -508,6 +510,21 @@ defmodule BlockScoutWeb.Chain do [paging_options: %{@default_paging_options | key: %{block_index: index}}] end + # Clause for `Explorer.Chain.Blackfort.Validator`, + # returned by `BlockScoutWeb.API.V2.ValidatorController.blackfort_validators_list/2` (`/api/v2/validators/blackfort`) + def paging_options(%{ + "address_hash" => address_hash_string + }) do + [ + paging_options: %{ + @default_paging_options + | key: %{ + address_hash: parse_address_hash(address_hash_string) + } + } + ] + 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 @@ -692,8 +709,8 @@ defmodule BlockScoutWeb.Chain do %{"smart_contract_id" => smart_contract.id} end - defp paging_params(%OptimismDeposit{l1_block_number: l1_block_number, l2_transaction_hash: l2_tx_hash}) do - %{"l1_block_number" => l1_block_number, "tx_hash" => l2_tx_hash} + defp paging_params(%OptimismDeposit{l1_block_number: l1_block_number, l2_transaction_hash: l2_transaction_hash}) do + %{"l1_block_number" => l1_block_number, "transaction_hash" => l2_transaction_hash} end defp paging_params(%OptimismOutputRoot{l2_output_index: index}) do @@ -703,12 +720,16 @@ defmodule BlockScoutWeb.Chain do defp paging_params(%SmartContract{} = smart_contract) do %{ "smart_contract_id" => smart_contract.id, - "tx_count" => smart_contract.address.transactions_count, + "transaction_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(%ScrollBridge{index: id}) do + %{"id" => id} + end + defp paging_params(%{index: index}) do %{"index" => index} end @@ -721,15 +742,15 @@ defmodule BlockScoutWeb.Chain do %{"block_number" => block_number} end - # clause for zkEVM batches pagination - defp paging_params(%TransactionBatch{number: number}) do + # clause for zkEVM & Scroll batches pagination + defp paging_params(%{number: number}) do %{"number" => number} end # clause for search results pagination defp paging_params(%{ address_hash: address_hash, - tx_hash: tx_hash, + transaction_hash: transaction_hash, block_hash: block_hash, holder_count: holder_count, name: name, @@ -740,7 +761,7 @@ defmodule BlockScoutWeb.Chain do %{ "address_hash" => address_hash, - "tx_hash" => tx_hash, + "transaction_hash" => transaction_hash, "block_hash" => block_hash, "holder_count" => holder_count, "name" => name, 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 7fd6932..ebc45d0 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 @@ -49,9 +49,19 @@ defmodule BlockScoutWeb.AddressChannel do end @transaction_associations [ - from_address: [:names, :smart_contract, :proxy_implementations], - to_address: [:names, :smart_contract, :proxy_implementations], - created_contract_address: [:names, :smart_contract, :proxy_implementations] + from_address: [:scam_badge, :names, :smart_contract, :proxy_implementations], + to_address: [ + :scam_badge, + :names, + :smart_contract, + :proxy_implementations + ], + created_contract_address: [ + :scam_badge, + :names, + :smart_contract, + :proxy_implementations + ] ] ++ @chain_type_transaction_associations @@ -404,8 +414,8 @@ defmodule BlockScoutWeb.AddressChannel do token_transfers |> Repo.preload([ [ - from_address: [:names, :smart_contract, :proxy_implementations], - to_address: [:names, :smart_contract, :proxy_implementations] + from_address: [:scam_badge, :names, :smart_contract, :proxy_implementations], + to_address: [:scam_badge, :names, :smart_contract, :proxy_implementations] ] ]), conn: nil diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/account/api/v2/address_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/account/api/v2/address_controller.ex new file mode 100644 index 0000000..2034b8a --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/controllers/account/api/v2/address_controller.ex @@ -0,0 +1,41 @@ +defmodule BlockScoutWeb.Account.API.V2.AddressController do + use BlockScoutWeb, :controller + + import BlockScoutWeb.Account.AuthController, only: [current_user: 1] + + alias BlockScoutWeb.Account.API.V2.AuthenticateController + alias Explorer.ThirdPartyIntegrations.Auth0 + alias Plug.Conn + + action_fallback(BlockScoutWeb.Account.API.V2.FallbackController) + + @doc """ + Links an Ethereum address to the current user's account. + + This function attempts to link a provided Ethereum address to the currently + authenticated user's account. It verifies the provided message and signature, + then uses the Auth0 service to associate the address with the user's account. + + ## Parameters + - `conn`: The `Plug.Conn` struct representing the current connection. + - `params`: A map containing: + - `"message"`: The message that was signed. + - `"signature"`: The signature of the message. + + ## Returns + - `{:error, any()}`: Error and a description of the error. + - `:error`: In case of unexpected error. + - `Conn.t()`: A modified connection struct if the address is successfully + linked. The connection will have updated session information. + + ## Notes + - Errors are handled later in `BlockScoutWeb.Account.API.V2.FallbackController`. + """ + @spec link_address(Plug.Conn.t(), map()) :: :error | {:error, any()} | Conn.t() + def link_address(conn, %{"message" => message, "signature" => signature}) do + with %{uid: id} <- conn |> current_user(), + {:ok, auth} <- Auth0.link_address(id, message, signature) do + AuthenticateController.put_auth_to_session(conn, auth) + end + end +end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/account/api/v2/authenticate_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/account/api/v2/authenticate_controller.ex index b0b9454..13686c0 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/account/api/v2/authenticate_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/account/api/v2/authenticate_controller.ex @@ -1,12 +1,18 @@ -defmodule BlockScoutWeb.Account.Api.V2.AuthenticateController do +defmodule BlockScoutWeb.Account.API.V2.AuthenticateController do use BlockScoutWeb, :controller import BlockScoutWeb.Account.AuthController, only: [current_user: 1] - alias BlockScoutWeb.Models.UserFromAuth + alias BlockScoutWeb.{AccessHelper, CaptchaHelper} + alias BlockScoutWeb.Account.API.V2.UserView + alias BlockScoutWeb.API.V2.ApiView alias Explorer.Account.Identity + alias Explorer.Chain + alias Explorer.Chain.Address + alias Explorer.ThirdPartyIntegrations.Auth0 + alias Plug.Conn - action_fallback(BlockScoutWeb.Account.Api.V2.FallbackController) + action_fallback(BlockScoutWeb.Account.API.V2.FallbackController) def authenticate_get(conn, params) do authenticate(conn, params) @@ -21,10 +27,243 @@ defmodule BlockScoutWeb.Account.Api.V2.AuthenticateController do {:sensitive_endpoints_api_key, Application.get_env(:block_scout_web, :sensitive_endpoints_api_key)}, {:api_key, ^api_key} <- {:api_key, params["api_key"]}, {:auth, %{id: uid} = current_user} <- {:auth, current_user(conn)}, - {:identity, %Identity{}} <- {:identity, UserFromAuth.find_identity(uid)} do + {:identity, %Identity{}} <- {:identity, Identity.find_identity(uid)} do conn |> put_status(200) |> json(current_user) end end + + @doc """ + Sends a one-time password (OTP) to the specified email address. + + This function handles the process of sending an OTP to a given email address, + with different behaviors based on the current user's authentication status + and the relationship between the provided email and existing accounts. + + The function first verifies the reCAPTCHA response to prevent abuse. Then, + it checks the current user's status and proceeds accordingly: + + 1. If no user is logged in, it sends an OTP for a new account. + 2. If a user is logged in and the email matches their account, it returns an error. + 3. If a user is logged in but the email doesn't match, it checks if there is already + a user with such email and sends an OTP for linking if there is no such user. + + ## Parameters + - `conn`: The `Plug.Conn` struct representing the current connection. + - `params`: A map containing: + - `"email"`: The email address to which the OTP should be sent. + - `"recaptcha_v3_response"` or `"recaptcha_response"`: The reCAPTCHA response token. + + ## Returns + - `:error`: If there's an unexpected error during the process. + - `{:error, String.t()}`: If there's a specific error (e.g., email already linked). + - `{:interval, integer()}`: If an OTP was recently sent and the cooldown period hasn't elapsed. + - `{:recaptcha, false}`: If the reCAPTCHA verification fails. + - `Plug.Conn.t()`: A modified connection struct with a 200 status and success message + if the OTP is successfully sent. + + ## Notes + - Errors are handled later in `BlockScoutWeb.Account.API.V2.FallbackController`. + - The function uses the client's IP address for rate limiting and abuse prevention. + - It handles both logged-in and non-logged-in user scenarios. + """ + @spec send_otp(Conn.t(), map()) :: + :error + | {:error, String.t()} + | {:interval, integer()} + | {:recaptcha, false} + | Conn.t() + def send_otp(conn, %{"email" => email} = params) do + with {:recaptcha, true} <- {:recaptcha, CaptchaHelper.recaptcha_passed?(params)} do + case conn |> Conn.fetch_session() |> current_user() do + nil -> + with :ok <- Auth0.send_otp(email, AccessHelper.conn_to_ip_string(conn)) do + conn |> put_status(200) |> json(%{message: "Success"}) + end + + %{email: nil} -> + with :ok <- Auth0.send_otp_for_linking(email, AccessHelper.conn_to_ip_string(conn)) do + conn |> put_status(200) |> json(%{message: "Success"}) + end + + %{} -> + conn + |> put_status(500) + |> put_view(ApiView) + |> render(:message, %{message: "This account already has an email"}) + end + end + end + + @doc """ + Confirms a one-time password (OTP) for a given email and updates the session. + + This function verifies the OTP provided for a specific email address. If the + OTP is valid, it retrieves the authentication information and updates the + user's session accordingly. + + The function performs the following steps: + 1. Confirms the OTP with Auth0 and retrieves the authentication information. + 2. If successful, updates the session with the new authentication data. + + ## Parameters + - `conn`: The `Plug.Conn` struct representing the current connection. + - `params`: A map containing: + - `"email"`: The email address associated with the OTP. + - `"otp"`: The one-time password to be confirmed. + + ## Returns + - `:error`: If there's an unexpected error during the process. + - `{:error, any()}`: If there's a specific error during OTP confirmation or + session update. The error details are included. + - `Conn.t()`: A modified connection struct with updated session information + if the OTP is successfully confirmed. + + ## Notes + - Errors are handled later in `BlockScoutWeb.Account.API.V2.FallbackController`. + - This function relies on the Auth0 service to confirm the OTP and retrieve + the authentication information. + - The function handles both existing and newly created users. + - For newly created users, it may create a new authentication record if the + user is not immediately found in the search after OTP confirmation. + - The session update is handled by the `put_auth_to_session/2` function, which + perform additional operations such as setting cookies or rendering user + information. + """ + @spec confirm_otp(Conn.t(), map()) :: :error | {:error, any()} | Conn.t() + def confirm_otp(conn, %{"email" => email, "otp" => otp}) do + with {:ok, auth} <- Auth0.confirm_otp_and_get_auth(email, otp, AccessHelper.conn_to_ip_string(conn)) do + put_auth_to_session(conn, auth) + end + end + + @doc """ + Generates a Sign-In with Ethereum (SIWE) message for a given Ethereum address. + + This function takes an Ethereum address, validates its format, converts it to + its checksum representation, and then generates a SIWE message. The generated + message is returned as part of a JSON response. + + The function performs the following steps: + 1. Validates and converts the input address string to an address hash. + 2. Converts the address hash to its checksum representation. + 3. Generates a SIWE message using the checksum address. + 4. Returns the generated message in a JSON response. + + ## Parameters + - `conn`: The `Plug.Conn` struct representing the current connection. + - `params`: A map containing: + - `"address"`: The Ethereum address as a string, starting with "0x". + + ## Returns + - `{:error, String.t()}`: If there's an error during the SIWE message generation process. + - `{:format, :error}`: If the provided address string is not in a valid format. + - `Conn.t()`: A modified connection struct with a 200 status and a JSON body + containing the generated SIWE message if successful. + + ## Notes + - Errors are handled later in `BlockScoutWeb.Account.API.V2.FallbackController`. + - The address is converted to its checksum format before generating the SIWE message. + - The generated SIWE message includes: + - The domain and URI of the application. + - A statement for signing in. + - The chain ID of the current network. + - A nonce for security. + - Issuance and expiration timestamps. + - The nonce is cached for the address to prevent replay attacks. + - The SIWE message expires after 300 seconds from generation. + """ + @spec siwe_message(Conn.t(), map()) :: {:error, String.t()} | {:format, :error} | Conn.t() + def siwe_message(conn, %{"address" => address}) do + with {:format, {:ok, address_hash}} <- {:format, Chain.string_to_address_hash(address)}, + {:ok, message} <- Auth0.generate_siwe_message(Address.checksum(address_hash)) do + conn |> put_status(200) |> json(%{siwe_message: message}) + end + end + + @doc """ + Authenticates a user via their Ethereum wallet using a signed message. + + This function verifies a signed Ethereum message to authenticate a user. It uses + the Sign-In with Ethereum (SIWE) protocol to validate the signature and retrieve + or create the user's authentication information. + + The function performs the following steps: + 1. Verifies the provided message and signature. + 2. If successful, updates the session with the new authentication data. + + ## Parameters + - `conn`: The `Plug.Conn` struct representing the current connection. + - `params`: A map containing: + - `"message"`: The SIWE message that was signed. + - `"signature"`: The signature of the message. + + ## Returns + - `:error`: If there's an unexpected error during the process. + - `{:error, any()}`: If there's a specific error during authentication or + session update. The error details are included. + - `Conn.t()`: A modified connection struct with updated session information + if the authentication is successful. + + ## Notes + - Errors are handled later in `BlockScoutWeb.Account.API.V2.FallbackController`. + - The function verifies the nonce in the message to prevent replay attacks. + - If the user doesn't exist, a new Web3 user is created based on the Ethereum address. + - The nonce is deleted after successful verification to prevent reuse. + - The session update is handled by the `put_auth_to_session/2` function, which + perform additional operations such as setting cookies or rendering user + information. + """ + @spec authenticate_via_wallet(Conn.t(), map()) :: :error | {:error, any()} | Conn.t() + def authenticate_via_wallet(conn, %{"message" => message, "signature" => signature}) do + with {:ok, auth} <- Auth0.get_auth_with_web3(message, signature) do + put_auth_to_session(conn, auth) + end + end + + @doc """ + Updates the session with authentication information and renders user info. + + This function takes the authentication data, creates or retrieves the user's + identity, updates the session, and renders the user information. It performs + the following steps: + + 1. Finds or creates a user session based on the authentication data. + 2. Retrieves the user's identity using the session ID. + 3. Updates the connection's session with the current user information. + 4. Renders the user information view. + + ## Parameters + - `conn`: The `Plug.Conn` struct representing the current connection. + - `auth`: A `Ueberauth.Auth.t()` struct containing the authentication information. + + ## Returns + - `{:error, any()}`: If there's an error during the process of finding/creating + the user session or retrieving the user's identity. + - `Conn.t()`: A modified connection struct with updated session information + and rendered user info if successful. + + ## Notes + - Errors are handled later in `BlockScoutWeb.Account.API.V2.FallbackController`. + - This function relies on the `Identity` module to handle user identity operations. + - It updates the session with the current user information. + - The function sets the HTTP status to 200 on successful authentication. + - It uses the `UserView` to render the user information. + - The rendered user information includes session data (name, nickname, and + optionally address_hash) merged with the identity data. + """ + @spec put_auth_to_session(Conn.t(), Ueberauth.Auth.t()) :: {:error, any()} | Conn.t() + def put_auth_to_session(conn, auth) do + with {:ok, %{id: uid} = session} <- Identity.find_or_create(auth), + {:identity, %Identity{} = identity} <- {:identity, Identity.find_identity(uid)} do + conn + |> Conn.fetch_session() + |> put_session(:current_user, session) + |> delete_resp_cookie(Application.get_env(:block_scout_web, :invalid_session_key)) + |> put_status(200) + |> put_view(UserView) + |> render(:user_info, %{identity: identity |> Identity.put_session_info(session)}) + end + end end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/account/api/v2/email_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/account/api/v2/email_controller.ex index c9b7f32..829b914 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/account/api/v2/email_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/account/api/v2/email_controller.ex @@ -1,16 +1,19 @@ -defmodule BlockScoutWeb.Account.Api.V2.EmailController do +defmodule BlockScoutWeb.Account.API.V2.EmailController do use BlockScoutWeb, :controller - alias BlockScoutWeb.Models.UserFromAuth + import BlockScoutWeb.Account.AuthController, only: [current_user: 1] + + alias BlockScoutWeb.AccessHelper + alias BlockScoutWeb.Account.API.V2.AuthenticateController alias Explorer.Account.Identity - alias Explorer.Repo + alias Explorer.{Helper, Repo} alias Explorer.ThirdPartyIntegrations.Auth0 require Logger @invalid_session_key Application.compile_env(:block_scout_web, :invalid_session_key) - action_fallback(BlockScoutWeb.Account.Api.V2.FallbackController) + action_fallback(BlockScoutWeb.Account.API.V2.FallbackController) plug(:fetch_cookies, signed: [@invalid_session_key]) @@ -18,8 +21,13 @@ defmodule BlockScoutWeb.Account.Api.V2.EmailController do 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])}, - {:interval, true} <- {:interval, check_time_interval(identity.verification_email_sent_at)} do + {:identity, %Identity{} = identity} <- {:identity, Identity.find_identity(user[:id])}, + {:interval, true} <- + {:interval, + Helper.check_time_interval( + identity.verification_email_sent_at, + Application.get_env(:explorer, Explorer.Account)[:verification_email_resend_interval] + )} do domain = Application.get_env(:ueberauth, Ueberauth.Strategy.Auth0.OAuth)[:domain] api_key = Auth0.get_m2m_jwt() headers = [{"Authorization", "Bearer #{api_key}"}, {"Content-Type", "application/json"}] @@ -49,20 +57,49 @@ defmodule BlockScoutWeb.Account.Api.V2.EmailController do end end - def check_time_interval(nil), do: true + @doc """ + Links an email address to the current user's account using OTP verification. + + This function attempts to link a provided email address to the currently + authenticated user's account. It verifies the provided one-time password (OTP) + and uses the Auth0 service to associate the email with the user's account. + + The function performs the following steps: + 1. Retrieves the current user's information from the session. + 2. Attempts to link the email to the user's account using the Auth0 service. + 3. If successful, updates the session with the new authentication data. + + ## Parameters + - `conn`: The `Plug.Conn` struct representing the current connection. + - `params`: A map containing: + - `"email"`: The email address to be linked. + - `"otp"`: The one-time password for verification. - def check_time_interval(sent_at) do - interval = Application.get_env(:explorer, Explorer.Account)[:resend_interval] - now = DateTime.utc_now() + ## Returns + - `:error`: If there's an unexpected error during the process. + - `{:error, any()}`: If there's a specific error during email linking or + session update. The error details are included. + - `Conn.t()`: A modified connection struct with updated session information + if the email is successfully linked. - if sent_at - |> DateTime.add(interval, :millisecond) - |> DateTime.compare(now) != :gt do - true - else - sent_at - |> DateTime.add(interval, :millisecond) - |> DateTime.diff(now, :second) + ## Notes + - Errors are handled later in `BlockScoutWeb.Account.API.V2.FallbackController`. + - This function requires the user to be already authenticated (current user in session). + - The function will fail if the email is already associated with another account. + - The OTP must be valid and match the one sent to the provided email. + - If successful, the function updates the user's Auth0 profile and local session. + - The session update is handled by the `AuthenticateController.put_auth_to_session/2` + function, which perform additional operations such as setting cookies or + rendering user information. + """ + @spec link_email(Plug.Conn.t(), map()) :: + :error + | {:error, any()} + | Plug.Conn.t() + def link_email(conn, %{"email" => email, "otp" => otp}) do + with {:auth, %{} = user} <- {:auth, current_user(conn)}, + {:ok, auth} <- Auth0.link_email(user, email, otp, AccessHelper.conn_to_ip_string(conn)) do + AuthenticateController.put_auth_to_session(conn, auth) end end end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/account/api/v2/fallback_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/account/api/v2/fallback_controller.ex index a4821b2..bf4bd39 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/account/api/v2/fallback_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/account/api/v2/fallback_controller.ex @@ -1,7 +1,7 @@ -defmodule BlockScoutWeb.Account.Api.V2.FallbackController do +defmodule BlockScoutWeb.Account.API.V2.FallbackController do use Phoenix.Controller - alias BlockScoutWeb.Account.Api.V2.UserView + alias BlockScoutWeb.Account.API.V2.UserView alias Ecto.Changeset def call(conn, {:identity, _}) do @@ -32,6 +32,20 @@ defmodule BlockScoutWeb.Account.Api.V2.FallbackController do |> render(:changeset_errors, changeset: changeset) end + def call(conn, {:error, message}) do + conn + |> put_status(500) + |> put_view(UserView) + |> render(:message, %{message: message}) + end + + def call(conn, :error) do + conn + |> put_status(500) + |> put_view(UserView) + |> render(:message, %{message: "Unexpected error"}) + end + def call(conn, {:create_tag, {:error, message}}) do conn |> put_status(:unprocessable_entity) @@ -111,6 +125,20 @@ defmodule BlockScoutWeb.Account.Api.V2.FallbackController do |> json(%{message: "Email resend is available in #{remain} seconds.", seconds_before_next_resend: remain}) end + def call(conn, {:format, _params}) do + conn + |> put_status(:unprocessable_entity) + |> put_view(UserView) + |> render(:message, %{message: "Invalid parameter(s)"}) + end + + def call(conn, {:recaptcha, _}) do + conn + |> put_status(:forbidden) + |> put_view(UserView) + |> render(:message, %{message: "Invalid reCAPTCHA response"}) + end + defp unauthorized_error(%{email_verified: false, email: email}) do %{message: "Unverified email", email: email} end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/account/api/v2/tags_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/account/api/v2/tags_controller.ex index 13c37f4..47b9671 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/account/api/v2/tags_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/account/api/v2/tags_controller.ex @@ -1,14 +1,14 @@ -defmodule BlockScoutWeb.Account.Api.V2.TagsController do +defmodule BlockScoutWeb.Account.API.V2.TagsController do use BlockScoutWeb, :controller import BlockScoutWeb.Account.AuthController, only: [current_user: 1] - alias BlockScoutWeb.Models.{GetAddressTags, GetTransactionTags, UserFromAuth} + alias BlockScoutWeb.Models.{GetAddressTags, GetTransactionTags} alias Explorer.Account.Identity alias Explorer.{Chain, Repo} alias Explorer.Chain.Hash.{Address, Full} - action_fallback(BlockScoutWeb.Account.Api.V2.FallbackController) + action_fallback(BlockScoutWeb.Account.API.V2.FallbackController) def tags_address(conn, %{"address_hash" => address_hash}) do personal_tags = @@ -17,7 +17,7 @@ defmodule BlockScoutWeb.Account.Api.V2.TagsController do else uid = current_user(conn).id - with {:identity, %Identity{} = identity} <- {:identity, UserFromAuth.find_identity(uid)}, + with {:identity, %Identity{} = identity} <- {:identity, Identity.find_identity(uid)}, {:watchlist, %{watchlists: [watchlist | _]}} <- {:watchlist, Repo.account_repo().preload(identity, :watchlists)}, {:address_hash, {:ok, address_hash}} <- {:address_hash, Address.cast(address_hash)} do @@ -54,11 +54,11 @@ defmodule BlockScoutWeb.Account.Api.V2.TagsController do personal_tags = if is_nil(current_user(conn)) do - %{personal_tags: [], watchlist_names: [], personal_tx_tag: nil} + %{personal_tags: [], watchlist_names: [], personal_transaction_tag: nil} else uid = current_user(conn).id - with {:identity, %Identity{} = identity} <- {:identity, UserFromAuth.find_identity(uid)}, + with {:identity, %Identity{} = identity} <- {:identity, Identity.find_identity(uid)}, {:watchlist, %{watchlists: [watchlist | _]}} <- {:watchlist, Repo.account_repo().preload(identity, :watchlists)}, false <- is_nil(transaction) do @@ -68,7 +68,7 @@ defmodule BlockScoutWeb.Account.Api.V2.TagsController do }) else _ -> - %{personal_tags: [], watchlist_names: [], personal_tx_tag: nil} + %{personal_tags: [], watchlist_names: [], personal_transaction_tag: nil} end end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/account/api/v2/user_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/account/api/v2/user_controller.ex index 89e60d6..982265f 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/account/api/v2/user_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/account/api/v2/user_controller.ex @@ -1,4 +1,5 @@ -defmodule BlockScoutWeb.Account.Api.V2.UserController do +defmodule BlockScoutWeb.Account.API.V2.UserController do + alias Explorer.ThirdPartyIntegrations.Auth0 use BlockScoutWeb, :controller import BlockScoutWeb.Account.AuthController, only: [current_user: 1] @@ -12,30 +13,38 @@ defmodule BlockScoutWeb.Account.Api.V2.UserController do import BlockScoutWeb.PagingHelper, only: [delete_parameters_from_next_page_params: 1] - 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.{Chain, Market, PagingOptions, Repo} alias Plug.CSRFProtection - action_fallback(BlockScoutWeb.Account.Api.V2.FallbackController) + action_fallback(BlockScoutWeb.Account.API.V2.FallbackController) @ok_message "OK" @token_balances_amount 150 def info(conn, _params) do - with {:auth, %{id: uid}} <- {:auth, current_user(conn)}, - {:identity, %Identity{} = identity} <- {:identity, UserFromAuth.find_identity(uid)} do - conn - |> put_status(200) - |> render(:user_info, %{identity: identity}) + with {:auth, %{id: uid} = session} <- {:auth, current_user(conn)}, + {:identity, %Identity{} = identity} <- {:identity, Identity.find_identity(uid)} do + case Auth0.update_session_with_address_hash(session) do + {:old, session} -> + conn + |> put_status(200) + |> render(:user_info, %{identity: identity |> Identity.put_session_info(session)}) + + {:new, session} -> + conn + |> put_session(:current_user, session) + |> put_status(200) + |> render(:user_info, %{identity: identity |> Identity.put_session_info(session)}) + end end end def watchlist(conn, params) do with {:auth, %{id: uid}} <- {:auth, current_user(conn)}, - {:identity, %Identity{} = identity} <- {:identity, UserFromAuth.find_identity(uid)}, + {:identity, %Identity{} = identity} <- {:identity, Identity.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)) @@ -80,7 +89,7 @@ defmodule BlockScoutWeb.Account.Api.V2.UserController do def delete_watchlist(conn, %{"id" => watchlist_address_id}) do with {:auth, %{id: uid}} <- {:auth, current_user(conn)}, - {:identity, %Identity{} = identity} <- {:identity, UserFromAuth.find_identity(uid)}, + {:identity, %Identity{} = identity} <- {:identity, Identity.find_identity(uid)}, {:watchlist, %{watchlists: [watchlist | _]}} <- {:watchlist, Repo.account_repo().preload(identity, :watchlists)}, {count, _} <- WatchlistAddress.delete(watchlist_address_id, watchlist.id), @@ -137,7 +146,7 @@ defmodule BlockScoutWeb.Account.Api.V2.UserController do } with {:auth, %{id: uid}} <- {:auth, current_user(conn)}, - {:identity, %Identity{} = identity} <- {:identity, UserFromAuth.find_identity(uid)}, + {:identity, %Identity{} = identity} <- {:identity, Identity.find_identity(uid)}, {:watchlist, %{watchlists: [watchlist | _]}} <- {:watchlist, Repo.account_repo().preload(identity, :watchlists)}, {:ok, watchlist_address} <- @@ -199,7 +208,7 @@ defmodule BlockScoutWeb.Account.Api.V2.UserController do } with {:auth, %{id: uid}} <- {:auth, current_user(conn)}, - {:identity, %Identity{} = identity} <- {:identity, UserFromAuth.find_identity(uid)}, + {:identity, %Identity{} = identity} <- {:identity, Identity.find_identity(uid)}, {:watchlist, %{watchlists: [watchlist | _]}} <- {:watchlist, Repo.account_repo().preload(identity, :watchlists)}, {:ok, watchlist_address} <- @@ -215,7 +224,7 @@ defmodule BlockScoutWeb.Account.Api.V2.UserController do def tags_address(conn, params) do with {:auth, %{id: uid}} <- {:auth, current_user(conn)}, - {:identity, %Identity{} = identity} <- {:identity, UserFromAuth.find_identity(uid)} do + {:identity, %Identity{} = identity} <- {:identity, Identity.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) @@ -230,7 +239,7 @@ defmodule BlockScoutWeb.Account.Api.V2.UserController do 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)}, + {:identity, %Identity{} = identity} <- {:identity, Identity.find_identity(uid)}, {count, _} <- TagAddress.delete(tag_id, identity.id), {:tag_delete, true} <- {:tag_delete, count > 0} do conn @@ -241,7 +250,7 @@ defmodule BlockScoutWeb.Account.Api.V2.UserController do def create_tag_address(conn, %{"address_hash" => address_hash, "name" => name}) do with {:auth, %{id: uid}} <- {:auth, current_user(conn)}, - {:identity, %Identity{} = identity} <- {:identity, UserFromAuth.find_identity(uid)}, + {:identity, %Identity{} = identity} <- {:identity, Identity.find_identity(uid)}, {:ok, address_tag} <- TagAddress.create(%{ name: name, @@ -256,7 +265,7 @@ defmodule BlockScoutWeb.Account.Api.V2.UserController do def update_tag_address(conn, %{"id" => tag_id} = attrs) do with {:auth, %{id: uid}} <- {:auth, current_user(conn)}, - {:identity, %Identity{} = identity} <- {:identity, UserFromAuth.find_identity(uid)}, + {:identity, %Identity{} = identity} <- {:identity, Identity.find_identity(uid)}, {:ok, address_tag} <- TagAddress.update( reject_nil_map_values(%{ @@ -274,7 +283,7 @@ defmodule BlockScoutWeb.Account.Api.V2.UserController do def tags_transaction(conn, params) do with {:auth, %{id: uid}} <- {:auth, current_user(conn)}, - {:identity, %Identity{} = identity} <- {:identity, UserFromAuth.find_identity(uid)} do + {:identity, %Identity{} = identity} <- {:identity, Identity.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) @@ -289,7 +298,7 @@ defmodule BlockScoutWeb.Account.Api.V2.UserController do 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)}, + {:identity, %Identity{} = identity} <- {:identity, Identity.find_identity(uid)}, {count, _} <- TagTransaction.delete(tag_id, identity.id), {:tag_delete, true} <- {:tag_delete, count > 0} do conn @@ -298,13 +307,13 @@ defmodule BlockScoutWeb.Account.Api.V2.UserController do end end - def create_tag_transaction(conn, %{"transaction_hash" => tx_hash, "name" => name}) do + def create_tag_transaction(conn, %{"transaction_hash" => transaction_hash, "name" => name}) do with {:auth, %{id: uid}} <- {:auth, current_user(conn)}, - {:identity, %Identity{} = identity} <- {:identity, UserFromAuth.find_identity(uid)}, + {:identity, %Identity{} = identity} <- {:identity, Identity.find_identity(uid)}, {:ok, transaction_tag} <- TagTransaction.create(%{ name: name, - tx_hash: tx_hash, + transaction_hash: transaction_hash, identity_id: identity.id }) do conn @@ -315,13 +324,13 @@ defmodule BlockScoutWeb.Account.Api.V2.UserController do def update_tag_transaction(conn, %{"id" => tag_id} = attrs) do with {:auth, %{id: uid}} <- {:auth, current_user(conn)}, - {:identity, %Identity{} = identity} <- {:identity, UserFromAuth.find_identity(uid)}, + {:identity, %Identity{} = identity} <- {:identity, Identity.find_identity(uid)}, {:ok, transaction_tag} <- TagTransaction.update( reject_nil_map_values(%{ id: tag_id, name: attrs["name"], - tx_hash: attrs["transaction_hash"], + transaction_hash: attrs["transaction_hash"], identity_id: identity.id }) ) do @@ -333,7 +342,7 @@ defmodule BlockScoutWeb.Account.Api.V2.UserController do def api_keys(conn, _params) do with {:auth, %{id: uid}} <- {:auth, current_user(conn)}, - {:identity, %Identity{} = identity} <- {:identity, UserFromAuth.find_identity(uid)}, + {:identity, %Identity{} = identity} <- {:identity, Identity.find_identity(uid)}, api_keys <- ApiKey.get_api_keys_by_identity_id(identity.id) do conn |> put_status(200) @@ -343,7 +352,7 @@ defmodule BlockScoutWeb.Account.Api.V2.UserController do def delete_api_key(conn, %{"api_key" => api_key_uuid}) do with {:auth, %{id: uid}} <- {:auth, current_user(conn)}, - {:identity, %Identity{} = identity} <- {:identity, UserFromAuth.find_identity(uid)}, + {:identity, %Identity{} = identity} <- {:identity, Identity.find_identity(uid)}, {count, _} <- ApiKey.delete(api_key_uuid, identity.id), {:api_key_delete, true} <- {:api_key_delete, count > 0} do conn @@ -354,7 +363,7 @@ defmodule BlockScoutWeb.Account.Api.V2.UserController do def create_api_key(conn, %{"name" => api_key_name}) do with {:auth, %{id: uid}} <- {:auth, current_user(conn)}, - {:identity, %Identity{} = identity} <- {:identity, UserFromAuth.find_identity(uid)}, + {:identity, %Identity{} = identity} <- {:identity, Identity.find_identity(uid)}, {:ok, api_key} <- ApiKey.create(%{name: api_key_name, identity_id: identity.id}) do conn @@ -365,7 +374,7 @@ defmodule BlockScoutWeb.Account.Api.V2.UserController do def update_api_key(conn, %{"name" => api_key_name, "api_key" => api_key_value}) do with {:auth, %{id: uid}} <- {:auth, current_user(conn)}, - {:identity, %Identity{} = identity} <- {:identity, UserFromAuth.find_identity(uid)}, + {:identity, %Identity{} = identity} <- {:identity, Identity.find_identity(uid)}, {:ok, api_key} <- ApiKey.update(%{value: api_key_value, name: api_key_name, identity_id: identity.id}) do conn @@ -376,7 +385,7 @@ defmodule BlockScoutWeb.Account.Api.V2.UserController do def custom_abis(conn, _params) do with {:auth, %{id: uid}} <- {:auth, current_user(conn)}, - {:identity, %Identity{} = identity} <- {:identity, UserFromAuth.find_identity(uid)}, + {:identity, %Identity{} = identity} <- {:identity, Identity.find_identity(uid)}, custom_abis <- CustomABI.get_custom_abis_by_identity_id(identity.id) do conn |> put_status(200) @@ -386,7 +395,7 @@ defmodule BlockScoutWeb.Account.Api.V2.UserController do def delete_custom_abi(conn, %{"id" => id}) do with {:auth, %{id: uid}} <- {:auth, current_user(conn)}, - {:identity, %Identity{} = identity} <- {:identity, UserFromAuth.find_identity(uid)}, + {:identity, %Identity{} = identity} <- {:identity, Identity.find_identity(uid)}, {count, _} <- CustomABI.delete(id, identity.id), {:custom_abi_delete, true} <- {:custom_abi_delete, count > 0} do conn @@ -397,7 +406,7 @@ defmodule BlockScoutWeb.Account.Api.V2.UserController do def create_custom_abi(conn, %{"contract_address_hash" => contract_address_hash, "name" => name, "abi" => abi}) do with {:auth, %{id: uid}} <- {:auth, current_user(conn)}, - {:identity, %Identity{} = identity} <- {:identity, UserFromAuth.find_identity(uid)}, + {:identity, %Identity{} = identity} <- {:identity, Identity.find_identity(uid)}, {:ok, custom_abi} <- CustomABI.create(%{ name: name, @@ -418,7 +427,7 @@ defmodule BlockScoutWeb.Account.Api.V2.UserController do } = params ) do with {:auth, %{id: uid}} <- {:auth, current_user(conn)}, - {:identity, %Identity{} = identity} <- {:identity, UserFromAuth.find_identity(uid)}, + {:identity, %Identity{} = identity} <- {:identity, Identity.find_identity(uid)}, {:ok, custom_abi} <- CustomABI.update( reject_nil_map_values(%{ @@ -437,7 +446,7 @@ defmodule BlockScoutWeb.Account.Api.V2.UserController do def public_tags_requests(conn, _params) do with {:auth, %{id: uid}} <- {:auth, current_user(conn)}, - {:identity, %Identity{} = identity} <- {:identity, UserFromAuth.find_identity(uid)}, + {:identity, %Identity{} = identity} <- {:identity, Identity.find_identity(uid)}, public_tags_requests <- PublicTagsRequest.get_public_tags_requests_by_identity_id(identity.id) do conn |> put_status(200) @@ -447,7 +456,7 @@ defmodule BlockScoutWeb.Account.Api.V2.UserController do def delete_public_tags_request(conn, %{"id" => id, "remove_reason" => remove_reason}) do with {:auth, %{id: uid}} <- {:auth, current_user(conn)}, - {:identity, %Identity{} = identity} <- {:identity, UserFromAuth.find_identity(uid)}, + {:identity, %Identity{} = identity} <- {:identity, Identity.find_identity(uid)}, {:public_tag_delete, true} <- {:public_tag_delete, PublicTagsRequest.mark_as_deleted_public_tags_request(%{ @@ -463,7 +472,7 @@ defmodule BlockScoutWeb.Account.Api.V2.UserController do def create_public_tags_request(conn, params) do with {:auth, %{id: uid}} <- {:auth, current_user(conn)}, - {:identity, %Identity{} = identity} <- {:identity, UserFromAuth.find_identity(uid)}, + {:identity, %Identity{} = identity} <- {:identity, Identity.find_identity(uid)}, {:ok, public_tags_request} <- PublicTagsRequest.create(%{ full_name: params["full_name"], @@ -489,7 +498,7 @@ defmodule BlockScoutWeb.Account.Api.V2.UserController do } = params ) do with {:auth, %{id: uid}} <- {:auth, current_user(conn)}, - {:identity, %Identity{} = identity} <- {:identity, UserFromAuth.find_identity(uid)}, + {:identity, %Identity{} = identity} <- {:identity, Identity.find_identity(uid)}, {:ok, public_tags_request} <- PublicTagsRequest.update( reject_nil_map_values(%{ 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 65f2916..2c4bbc0 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 @@ -1,8 +1,8 @@ defmodule BlockScoutWeb.Account.AuthController do use BlockScoutWeb, :controller - alias BlockScoutWeb.Models.UserFromAuth alias Explorer.Account + alias Explorer.Account.Identity alias Explorer.Repo.ConfigHelper alias Plug.CSRFProtection @@ -34,7 +34,7 @@ defmodule BlockScoutWeb.Account.AuthController do end def callback(%{assigns: %{ueberauth_auth: auth}} = conn, params) do - case UserFromAuth.find_or_create(auth) do + case Identity.find_or_create(auth) do {:ok, %{email_verified: false} = user} -> conn |> put_session(:current_user, user) diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/account/tag_transaction_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/account/tag_transaction_controller.ex index cba3dea..4a4c8fc 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/account/tag_transaction_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/account/tag_transaction_controller.ex @@ -8,7 +8,7 @@ defmodule BlockScoutWeb.Account.TagTransactionController do def index(conn, _params) do current_user = authenticate!(conn) - render(conn, "index.html", tx_tags: TagTransaction.get_tags_transaction_by_identity_id(current_user.id)) + render(conn, "index.html", transaction_tags: TagTransaction.get_tags_transaction_by_identity_id(current_user.id)) end def new(conn, _params) do @@ -22,7 +22,7 @@ defmodule BlockScoutWeb.Account.TagTransactionController do case TagTransaction.create(%{ name: tag_address["name"], - tx_hash: tag_address["tx_hash"], + transaction_hash: tag_address["transaction_hash"], identity_id: current_user.id }) do {:ok, _} -> 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 96556cc..ac50e41 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 @@ -58,7 +58,7 @@ defmodule BlockScoutWeb.AddressController do items = addresses_page |> Enum.with_index(1) - |> Enum.map(fn {{address, tx_count}, index} -> + |> Enum.map(fn {{address, transaction_count}, index} -> View.render_to_string( AddressView, "_tile.html", @@ -66,7 +66,7 @@ defmodule BlockScoutWeb.AddressController do index: items_count + index, exchange_rate: exchange_rate, total_supply: total_supply, - tx_count: tx_count + transaction_count: transaction_count ) end) 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 9e3710e..7317039 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 @@ -10,11 +10,10 @@ defmodule BlockScoutWeb.AddressTransactionController do 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 BlockScoutWeb.{AccessHelper, CaptchaHelper, Controller, TransactionView} alias BlockScoutWeb.API.V2.CSVExportController alias Explorer.{Chain, Market} alias Explorer.Chain.Address - alias Explorer.Chain.CSVExport.Helper, as: CSVHelper alias Explorer.Chain.CSVExport.{ AddressInternalTransactionCsvExporter, @@ -168,46 +167,6 @@ defmodule BlockScoutWeb.AddressTransactionController do end end - defp items_csv( - conn, - %{ - "address_id" => address_hash_string, - "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), - {:address_exists, true} <- {:address_exists, Address.address_exists?(address_hash)}, - {:recaptcha, true} <- {:recaptcha, CSVHelper.captcha_helper().recaptcha_passed?(recaptcha_response)} do - 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(CSVExportController.put_resp_params(conn), fn chunk, conn -> - case Conn.chunk(conn, chunk) do - {:ok, conn} -> - {:cont, conn} - - {:error, :closed} -> - {:halt, conn} - end - end) - else - :error -> - unprocessable_entity(conn) - - {:address_exists, false} -> - not_found(conn) - - {:recaptcha, false} -> - not_found(conn) - end - end - defp items_csv( conn, %{ @@ -220,7 +179,7 @@ defmodule BlockScoutWeb.AddressTransactionController do when is_binary(address_hash_string) do with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string), {:address_exists, true} <- {:address_exists, Address.address_exists?(address_hash)}, - true <- Application.get_env(:block_scout_web, :recaptcha)[:is_disabled] do + {:recaptcha, true} <- {:recaptcha, CaptchaHelper.recaptcha_passed?(params)} do filter_type = Map.get(params, "filter_type") filter_value = Map.get(params, "filter_value") @@ -242,7 +201,7 @@ defmodule BlockScoutWeb.AddressTransactionController do {:address_exists, false} -> not_found(conn) - false -> + {:recaptcha, false} -> not_found(conn) end end 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 48442d0..b0cec84 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 @@ -91,9 +91,6 @@ defmodule BlockScoutWeb.AddressValidationController do :error -> unprocessable_entity(conn) - - {:error, :not_found} -> - not_found(conn) end end end 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 e9d113a..a2629b1 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 @@ -101,17 +101,6 @@ defmodule BlockScoutWeb.API.RPC.ContractController do render(conn, :error, error: "#{@smth_went_wrong}: #{inspect(error.errors)}") - {:publish, error} -> - Logger.error(fn -> - [ - @smth_went_wrong, - ": ", - inspect(error) - ] - end) - - render(conn, :error, error: @smth_went_wrong) - {:format, :error} -> render(conn, :error, error: @invalid_address) @@ -142,9 +131,6 @@ defmodule BlockScoutWeb.API.RPC.ContractController do else {:error, error} -> render(conn, :error, error: error) - - _ -> - render(conn, :error, error: "Invalid body") end end end @@ -645,6 +631,10 @@ defmodule BlockScoutWeb.API.RPC.ContractController do |> required_param(params, "compilerversion", "compiler_version") |> optional_param(params, "constructorArguments", "constructor_arguments") |> optional_param(params, "licenseType", "license_type") + |> (&if(Application.get_env(:explorer, :chain_type) == :zksync, + do: optional_param(&1, params, "zksolcVersion", "zk_compiler_version"), + else: &1 + )).() end defp fetch_verifysourcecode_solidity_single_file_params(params) do 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 8b766c7..57782bc 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 @@ -5,11 +5,30 @@ defmodule BlockScoutWeb.API.RPC.StatsController do alias Explorer.Chain.Cache.{AddressSum, AddressSumMinusBurnt} alias Explorer.Chain.Wei + @cmc_token_supply_precision 9 + def tokensupply(conn, params) do with {:contractaddress_param, {:ok, contractaddress_param}} <- fetch_contractaddress(params), {:format, {:ok, address_hash}} <- to_address_hash(contractaddress_param), {:token, {:ok, token}} <- {:token, Chain.token_from_address_hash(address_hash)} do - render(conn, "tokensupply.json", total_supply: token.total_supply && Decimal.to_string(token.total_supply)) + if Map.get(params, "cmc") == "true" do + conn + |> put_resp_content_type("text/plain") + |> send_resp( + 200, + token.total_supply && + to_cmc_total_supply( + token.total_supply, + token.decimals + ) + ) + else + conn + |> render( + "tokensupply.json", + total_supply: token.total_supply && Decimal.to_string(token.total_supply) + ) + end else {:contractaddress_param, :error} -> render(conn, :error, error: "Query parameter contract address is required") @@ -77,6 +96,19 @@ defmodule BlockScoutWeb.API.RPC.StatsController do {:format, Chain.string_to_address_hash(address_hash_string)} end + @spec to_cmc_total_supply(Decimal.t(), Decimal.t() | nil) :: String.t() + defp to_cmc_total_supply(total_supply, decimals) do + divider = + 1 + |> Decimal.new(1, Decimal.to_integer(decimals || Decimal.new(0))) + |> Decimal.to_integer() + + total_supply + |> Decimal.div(divider) + |> Decimal.round(@cmc_token_supply_precision) + |> Decimal.to_string() + end + def totalfees(conn, params) do case Map.fetch(params, "date") do {:ok, date} -> 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 fbea15c..0d932e6 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 @@ -9,7 +9,7 @@ defmodule BlockScoutWeb.API.RPC.TransactionController do @api_true [api?: true] def gettxinfo(conn, params) do - with {:txhash_param, {:ok, txhash_param}} <- fetch_txhash(params), + with {:txhash_param, {:ok, txhash_param}} <- fetch_transaction_hash(params), {:format, {:ok, transaction_hash}} <- to_transaction_hash(txhash_param), {:transaction, {:ok, %Transaction{revert_reason: revert_reason, error: error} = transaction}} <- transaction_from_hash(transaction_hash), @@ -19,7 +19,7 @@ defmodule BlockScoutWeb.API.RPC.TransactionController do transaction_updated = if (error == "Reverted" || error == "execution reverted") && !revert_reason do - %Transaction{transaction | revert_reason: Chain.fetch_tx_revert_reason(transaction)} + %Transaction{transaction | revert_reason: Chain.fetch_transaction_revert_reason(transaction)} else transaction end @@ -43,7 +43,7 @@ defmodule BlockScoutWeb.API.RPC.TransactionController do end def gettxreceiptstatus(conn, params) do - with {:txhash_param, {:ok, txhash_param}} <- fetch_txhash(params), + with {:txhash_param, {:ok, txhash_param}} <- fetch_transaction_hash(params), {:format, {:ok, transaction_hash}} <- to_transaction_hash(txhash_param) do status = to_transaction_status(transaction_hash) render(conn, :gettxreceiptstatus, %{status: status}) @@ -57,7 +57,7 @@ defmodule BlockScoutWeb.API.RPC.TransactionController do end def getstatus(conn, params) do - with {:txhash_param, {:ok, txhash_param}} <- fetch_txhash(params), + with {:txhash_param, {:ok, txhash_param}} <- fetch_transaction_hash(params), {:format, {:ok, transaction_hash}} <- to_transaction_hash(txhash_param) do error = to_transaction_error(transaction_hash) render(conn, :getstatus, %{error: error}) @@ -70,7 +70,7 @@ defmodule BlockScoutWeb.API.RPC.TransactionController do end end - defp fetch_txhash(params) do + defp fetch_transaction_hash(params) do {:txhash_param, Map.fetch(params, "txhash")} end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/address_badge_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/address_badge_controller.ex new file mode 100644 index 0000000..5a3d58d --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/address_badge_controller.ex @@ -0,0 +1,90 @@ +defmodule BlockScoutWeb.API.V2.AddressBadgeController do + require Logger + use BlockScoutWeb, :controller + + alias Explorer.Chain + alias Explorer.Chain.Address.ScamBadgeToAddress + alias Plug.Conn + + @api_true [api?: true] + + action_fallback(BlockScoutWeb.API.V2.FallbackController) + + def assign_badge_to_address( + conn, + %{ + "address_hashes" => address_hashes + } = params + ) + when is_list(address_hashes) do + with :ok <- check_sensitive_endpoint_api_key(params["api_key"]), + valid_address_hashes = filter_address_hashes(address_hashes), + {_num_of_inserted, badge_to_address_list} <- ScamBadgeToAddress.add(valid_address_hashes) do + conn + |> put_status(200) + |> render(:badge_to_address, %{ + badge_to_address_list: badge_to_address_list, + status: if(Enum.empty?(badge_to_address_list), do: "update skipped", else: "added") + }) + end + end + + def assign_badge_to_address(_, _), do: {:error, :not_found} + + def unassign_badge_from_address( + conn, + %{ + "address_hashes" => address_hashes + } = params + ) + when is_list(address_hashes) do + with :ok <- check_sensitive_endpoint_api_key(params["api_key"]), + valid_address_hashes = filter_address_hashes(address_hashes), + {_num_of_deleted, badge_to_address_list} <- ScamBadgeToAddress.delete(valid_address_hashes) do + conn + |> put_status(200) + |> render(:badge_to_address, %{ + badge_to_address_list: badge_to_address_list, + status: if(Enum.empty?(badge_to_address_list), do: "update skipped", else: "removed") + }) + end + end + + def unassign_badge_from_address(_, _), do: {:error, :not_found} + + def show_badge_addresses(conn, _) do + with {:ok, body, _conn} <- Conn.read_body(conn, []), + {:ok, %{"api_key" => provided_api_key}} <- Jason.decode(body), + :ok <- check_sensitive_endpoint_api_key(provided_api_key) do + badge_to_address_list = ScamBadgeToAddress.get(@api_true) + + conn + |> put_status(200) + |> render(:badge_to_address, %{ + badge_to_address_list: badge_to_address_list + }) + else + _ -> + {:error, :not_found} + end + end + + defp check_sensitive_endpoint_api_key(provided_api_key) 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, provided_api_key} do + :ok + end + end + + defp filter_address_hashes(address_hashes) do + address_hashes + |> Enum.uniq() + |> Enum.filter(fn potential_address_hash -> + case Chain.string_to_address_hash(potential_address_hash) do + {:ok, _address_hash} -> true + _ -> false + end + end) + end +end 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 59afca8..b2a2319 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 @@ -52,9 +52,9 @@ defmodule BlockScoutWeb.API.V2.AddressController do @transaction_necessity_by_association [ necessity_by_association: %{ - [created_contract_address: [:names, :smart_contract, :proxy_implementations]] => :optional, - [from_address: [:names, :smart_contract, :proxy_implementations]] => :optional, - [to_address: [:names, :smart_contract, :proxy_implementations]] => :optional, + [created_contract_address: [:scam_badge, :names, :smart_contract, :proxy_implementations]] => :optional, + [from_address: [:scam_badge, :names, :smart_contract, :proxy_implementations]] => :optional, + [to_address: [:scam_badge, :names, :smart_contract, :proxy_implementations]] => :optional, :block => :optional } |> Map.merge(@chain_type_transaction_necessity_by_association), @@ -63,8 +63,8 @@ defmodule BlockScoutWeb.API.V2.AddressController do @token_transfer_necessity_by_association [ necessity_by_association: %{ - [to_address: [:names, :smart_contract, :proxy_implementations]] => :optional, - [from_address: [:names, :smart_contract, :proxy_implementations]] => :optional, + [to_address: [:scam_badge, :names, :smart_contract, :proxy_implementations]] => :optional, + [from_address: [:scam_badge, :names, :smart_contract, :proxy_implementations]] => :optional, :block => :optional, :transaction => :optional, :token => :optional @@ -75,18 +75,31 @@ defmodule BlockScoutWeb.API.V2.AddressController do @address_options [ necessity_by_association: %{ :names => :optional, + :scam_badge => :optional, :token => :optional, - :proxy_implementations => :optional + :proxy_implementations => :optional, + :signed_authorization => :optional }, api?: true ] - @contract_address_preloads [ - :smart_contract, - :contracts_creation_internal_transaction, - :contracts_creation_transaction, - :proxy_implementations - ] + case Application.compile_env(:explorer, :chain_type) do + :filecoin -> + @contract_address_preloads [ + :smart_contract, + [contracts_creation_internal_transaction: :from_address], + [contracts_creation_transaction: :from_address], + :proxy_implementations + ] + + _ -> + @contract_address_preloads [ + :smart_contract, + :contracts_creation_internal_transaction, + :contracts_creation_transaction, + :proxy_implementations + ] + end @nft_necessity_by_association [ necessity_by_association: %{ @@ -212,8 +225,8 @@ defmodule BlockScoutWeb.API.V2.AddressController do options = [ necessity_by_association: %{ - [to_address: [:names, :smart_contract, :proxy_implementations]] => :optional, - [from_address: [:names, :smart_contract, :proxy_implementations]] => :optional, + [to_address: [:scam_badge, :names, :smart_contract, :proxy_implementations]] => :optional, + [from_address: [:scam_badge, :names, :smart_contract, :proxy_implementations]] => :optional, :block => :optional, :token => :optional, :transaction => :optional @@ -284,9 +297,9 @@ defmodule BlockScoutWeb.API.V2.AddressController do full_options = [ necessity_by_association: %{ - [created_contract_address: [:names, :smart_contract, :proxy_implementations]] => :optional, - [from_address: [:names, :smart_contract, :proxy_implementations]] => :optional, - [to_address: [:names, :smart_contract, :proxy_implementations]] => :optional + [created_contract_address: [:scam_badge, :names, :smart_contract, :proxy_implementations]] => :optional, + [from_address: [:scam_badge, :names, :smart_contract, :proxy_implementations]] => :optional, + [to_address: [:scam_badge, :names, :smart_contract, :proxy_implementations]] => :optional } ] |> Keyword.merge(paging_options(params)) @@ -493,12 +506,14 @@ defmodule BlockScoutWeb.API.V2.AddressController do with {:ok, address_hash, _address} <- validate_address(address_hash_string, params) do counter_name_to_json_field_name = %{ validations: :validations_count, - txs: :transactions_count, + transactions: :transactions_count, token_transfers: :token_transfers_count, token_balances: :token_balances_count, logs: :logs_count, withdrawals: :withdrawals_count, - internal_txs: :internal_txs_count, + # todo: support of 2 props in API endpoint is for compatibility with the current version of frontend. + # It should be ultimately removed. + internal_transactions: [:internal_transactions_count, :internal_txs_count], celo_election_rewards: :celo_election_rewards_count } @@ -510,7 +525,15 @@ defmodule BlockScoutWeb.API.V2.AddressController do |> Map.fetch(counter_name) |> case do {:ok, json_field_name} -> - Map.put(acc, json_field_name, counter_value) + # todo: array-type value processing here is temporary. Please remove it with updating frontend to the new version. + if is_list(json_field_name) do + # credo:disable-for-next-line + Enum.reduce(json_field_name, acc, fn field_name, acc2 -> + Map.put(acc2, field_name, counter_value) + end) + else + Map.put(acc, json_field_name, counter_value) + end :error -> acc diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/advanced_filter_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/advanced_filter_controller.ex index 35975e9..cfb074a 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/advanced_filter_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/advanced_filter_controller.ex @@ -4,6 +4,7 @@ defmodule BlockScoutWeb.API.V2.AdvancedFilterController do import BlockScoutWeb.Chain, only: [split_list_by_page: 1, next_page_params: 4] import Explorer.PagingOptions, only: [default_paging_options: 0] + alias BlockScoutWeb.CaptchaHelper alias BlockScoutWeb.API.V2.{AdvancedFilterView, CSVExportController} alias Explorer.{Chain, PagingOptions} alias Explorer.Chain.{AdvancedFilter, ContractMethod, Data, Token, Transaction} @@ -57,7 +58,7 @@ defmodule BlockScoutWeb.API.V2.AdvancedFilterController do {advanced_filters, next_page} = split_list_by_page(advanced_filters_plus_one) - {decoded_transactions, _abi_acc, methods_acc} = + decoded_transactions = advanced_filters |> Enum.map(fn af -> %Transaction{to_address: af.to_address, input: af.input, hash: af.hash} end) |> Transaction.decode_transactions(true, @api_true) @@ -69,7 +70,7 @@ defmodule BlockScoutWeb.API.V2.AdvancedFilterController do advanced_filters: advanced_filters, decoded_transactions: decoded_transactions, search_params: %{ - method_ids: method_id_to_name_from_params(full_options[:methods] || [], methods_acc), + method_ids: method_id_to_name_from_params(full_options[:methods] || [], decoded_transactions), tokens: contract_address_hash_to_token_from_params(full_options[:token_contract_address_hashes]) }, next_page_params: next_page_params @@ -81,10 +82,7 @@ defmodule BlockScoutWeb.API.V2.AdvancedFilterController do """ @spec list_csv(Plug.Conn.t(), map()) :: Plug.Conn.t() def list_csv(conn, params) do - with {:recaptcha, true} <- - {:recaptcha, - Application.get_env(:block_scout_web, :recaptcha)[:is_disabled] || - CSVHelper.captcha_helper().recaptcha_passed?(params["recaptcha_response"])} do + with {:recaptcha, true} <- {:recaptcha, CaptchaHelper.recaptcha_passed?(params)} do full_options = params |> extract_filters() @@ -92,6 +90,7 @@ defmodule BlockScoutWeb.API.V2.AdvancedFilterController do |> Keyword.update(:paging_options, %PagingOptions{page_size: CSVHelper.limit()}, fn paging_options -> %PagingOptions{paging_options | page_size: CSVHelper.limit()} end) + |> Keyword.put(:timeout, :timer.minutes(5)) full_options |> AdvancedFilter.list() @@ -132,8 +131,12 @@ defmodule BlockScoutWeb.API.V2.AdvancedFilterController do ContractMethod.find_contract_method_by_name(query, @api_true) end - with {:method, %ContractMethod{abi: %{"name" => name}, identifier: identifier}} <- {:method, mb_contract_method} do - render(conn, :methods, methods: [%{method_id: "0x" <> Base.encode16(identifier, case: :lower), name: name}]) + case mb_contract_method do + %ContractMethod{abi: %{"name" => name}, identifier: identifier} -> + render(conn, :methods, methods: [%{method_id: "0x" <> Base.encode16(identifier, case: :lower), name: name}]) + + _ -> + render(conn, :methods, methods: []) end end end @@ -142,22 +145,19 @@ defmodule BlockScoutWeb.API.V2.AdvancedFilterController do render(conn, :methods, methods: @methods) end - defp method_id_to_name_from_params(prepared_method_ids, methods_acc) do + defp method_id_to_name_from_params(prepared_method_ids, decoded_transactions) do {decoded_method_ids, method_ids_to_find} = Enum.reduce(prepared_method_ids, {%{}, []}, fn method_id, {decoded, to_decode} -> {:ok, method_id_hash} = Data.cast(method_id) + trimmed_method_id = method_id_hash.bytes |> Base.encode16(case: :lower) case {Map.get(@methods_id_to_name_map, method_id), - methods_acc - |> Map.get(method_id_hash.bytes, []) - |> Enum.find( - &match?(%ContractMethod{abi: %{"type" => "function", "name" => name}} when is_binary(name), &1) - )} do + decoded_transactions |> Enum.find(&match?({:ok, ^trimmed_method_id, _, _}, &1))} do {name, _} when is_binary(name) -> {Map.put(decoded, method_id, name), to_decode} - {_, %ContractMethod{abi: %{"type" => "function", "name" => name}}} when is_binary(name) -> - {Map.put(decoded, method_id, name), to_decode} + {_, {:ok, _, function_signature, _}} when is_binary(function_signature) -> + {Map.put(decoded, method_id, function_signature |> String.split("(") |> Enum.at(0)), to_decode} {nil, nil} -> {decoded, [method_id_hash.bytes | to_decode]} @@ -194,7 +194,8 @@ defmodule BlockScoutWeb.API.V2.AdvancedFilterController do defp extract_filters(params) do [ - tx_types: prepare_tx_types(params["tx_types"]), + # TODO: remove when frontend is adopted to new naming + transaction_types: prepare_transaction_types(params["transaction_types"] || params["tx_types"]), methods: params["methods"] |> prepare_methods(), age: prepare_age(params["age_from"], params["age_to"]), from_address_hashes: @@ -224,16 +225,16 @@ defmodule BlockScoutWeb.API.V2.AdvancedFilterController do ] end - @allowed_tx_types ~w(COIN_TRANSFER ERC-20 ERC-404 ERC-721 ERC-1155) + @allowed_transaction_types ~w(COIN_TRANSFER ERC-20 ERC-404 ERC-721 ERC-1155) - defp prepare_tx_types(tx_types) when is_binary(tx_types) do - tx_types + defp prepare_transaction_types(transaction_types) when is_binary(transaction_types) do + transaction_types |> String.upcase() |> String.split(",") - |> Enum.filter(&(&1 in @allowed_tx_types)) + |> Enum.filter(&(&1 in @allowed_transaction_types)) end - defp prepare_tx_types(_), do: nil + defp prepare_transaction_types(_), do: nil defp prepare_methods(methods) when is_binary(methods) do methods @@ -315,14 +316,15 @@ defmodule BlockScoutWeb.API.V2.AdvancedFilterController do defp paging_options(%{ "block_number" => block_number_string, - "transaction_index" => tx_index_string, - "internal_transaction_index" => internal_tx_index_string, + "transaction_index" => transaction_index_string, + "internal_transaction_index" => internal_transaction_index_string, "token_transfer_index" => token_transfer_index_string, "token_transfer_batch_index" => token_transfer_batch_index_string }) do with {block_number, ""} <- block_number_string && Integer.parse(block_number_string), - {tx_index, ""} <- tx_index_string && Integer.parse(tx_index_string), - {:ok, internal_tx_index} <- parse_nullable_integer_paging_parameter(internal_tx_index_string), + {transaction_index, ""} <- transaction_index_string && Integer.parse(transaction_index_string), + {:ok, internal_transaction_index} <- + parse_nullable_integer_paging_parameter(internal_transaction_index_string), {:ok, token_transfer_index} <- parse_nullable_integer_paging_parameter(token_transfer_index_string), {:ok, token_transfer_batch_index} <- parse_nullable_integer_paging_parameter(token_transfer_batch_index_string) do [ @@ -330,8 +332,8 @@ defmodule BlockScoutWeb.API.V2.AdvancedFilterController do default_paging_options() | key: %{ block_number: block_number, - transaction_index: tx_index, - internal_transaction_index: internal_tx_index, + transaction_index: transaction_index, + internal_transaction_index: internal_transaction_index, token_transfer_index: token_transfer_index, token_transfer_batch_index: token_transfer_batch_index } @@ -357,15 +359,15 @@ defmodule BlockScoutWeb.API.V2.AdvancedFilterController do defp paging_params(%AdvancedFilter{ block_number: block_number, - transaction_index: tx_index, - internal_transaction_index: internal_tx_index, + transaction_index: transaction_index, + internal_transaction_index: internal_transaction_index, token_transfer_index: token_transfer_index, token_transfer_batch_index: token_transfer_batch_index }) do %{ block_number: block_number, - transaction_index: tx_index, - internal_transaction_index: internal_tx_index, + transaction_index: transaction_index, + internal_transaction_index: internal_transaction_index, token_transfer_index: token_transfer_index, token_transfer_batch_index: token_transfer_batch_index } 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 4135a6b..2ca1937 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 @@ -1,7 +1,7 @@ defmodule BlockScoutWeb.API.V2.APIKeyController do use BlockScoutWeb, :controller - alias BlockScoutWeb.AccessHelper + alias BlockScoutWeb.{AccessHelper, CaptchaHelper} @api_v2_temp_token_key Application.compile_env(:block_scout_web, :api_v2_temp_token_key) @@ -14,12 +14,9 @@ defmodule BlockScoutWeb.API.V2.APIKeyController do """ @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] - with recaptcha_response <- params["recaptcha_response"], - {:recaptcha, false} <- {:recaptcha, is_nil(recaptcha_response)}, - {:recaptcha, true} <- {:recaptcha, helper.recaptcha_passed?(recaptcha_response)} do + with {:recaptcha, true} <- {:recaptcha, CaptchaHelper.recaptcha_passed?(params)} do conn |> put_resp_cookie(@api_v2_temp_token_key, %{ip: AccessHelper.conn_to_ip_string(conn)}, max_age: ttl, diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/arbitrum_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/arbitrum_controller.ex index e6faabd..2c2a538 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/arbitrum_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/arbitrum_controller.ex @@ -81,7 +81,7 @@ defmodule BlockScoutWeb.API.V2.ArbitrumController do @doc """ Function to handle GET requests to `/api/v2/arbitrum/batches/da/:data_hash` or - `/api/v2/arbitrum/batches/da/:tx_commitment/:height` endpoints. + `/api/v2/arbitrum/batches/da/:transaction_commitment/:height` endpoints. """ @spec batch_by_data_availability_info(Plug.Conn.t(), map()) :: Plug.Conn.t() def batch_by_data_availability_info(conn, %{"data_hash" => data_hash} = _params) do @@ -95,10 +95,13 @@ defmodule BlockScoutWeb.API.V2.ArbitrumController do end end - def batch_by_data_availability_info(conn, %{"tx_commitment" => tx_commitment, "height" => height} = _params) do + def batch_by_data_availability_info( + conn, + %{"transaction_commitment" => transaction_commitment, "height" => height} = _params + ) do # In case of Celestia, `data_key` is the hash of the height and the commitment hash - with {:ok, :hash, tx_commitment_hash} <- parse_block_hash_or_number_param(tx_commitment), - key <- calculate_celestia_data_key(height, tx_commitment_hash) do + with {:ok, :hash, transaction_commitment_hash} <- parse_block_hash_or_number_param(transaction_commitment), + key <- calculate_celestia_data_key(height, transaction_commitment_hash) do case Reader.get_da_record_by_data_key(key, api?: true) do {:ok, {batch_number, _}} -> batch(conn, %{"batch_number" => batch_number}) 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 ab2fed6..28eeeea 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 @@ -35,7 +35,8 @@ defmodule BlockScoutWeb.API.V2.BlockController do alias Explorer.Chain.Celo.EpochReward, as: CeloEpochReward alias Explorer.Chain.Celo.Reader, as: CeloReader alias Explorer.Chain.InternalTransaction - alias Explorer.Chain.Optimism.TxnBatch, as: OptimismTxnBatch + alias Explorer.Chain.Optimism.TransactionBatch, as: OptimismTransactionBatch + alias Explorer.Chain.Scroll.Reader, as: ScrollReader case Application.compile_env(:explorer, :chain_type) do :ethereum -> @@ -83,9 +84,9 @@ defmodule BlockScoutWeb.API.V2.BlockController do @transaction_necessity_by_association [ necessity_by_association: %{ - [created_contract_address: [:names, :smart_contract, :proxy_implementations]] => :optional, - [from_address: [:names, :smart_contract, :proxy_implementations]] => :optional, - [to_address: [:names, :smart_contract, :proxy_implementations]] => :optional, + [created_contract_address: [:scam_badge, :names, :smart_contract, :proxy_implementations]] => :optional, + [from_address: [:scam_badge, :names, :smart_contract, :proxy_implementations]] => :optional, + [to_address: [:scam_badge, :names, :smart_contract, :proxy_implementations]] => :optional, :block => :optional } |> Map.merge(@chain_type_transaction_necessity_by_association) @@ -93,9 +94,9 @@ defmodule BlockScoutWeb.API.V2.BlockController do @internal_transaction_necessity_by_association [ necessity_by_association: %{ - [created_contract_address: [:names, :smart_contract, :proxy_implementations]] => :optional, - [from_address: [:names, :smart_contract, :proxy_implementations]] => :optional, - [to_address: [:names, :smart_contract, :proxy_implementations]] => :optional + [created_contract_address: [:scam_badge, :names, :smart_contract, :proxy_implementations]] => :optional, + [from_address: [:scam_badge, :names, :smart_contract, :proxy_implementations]] => :optional, + [to_address: [:scam_badge, :names, :smart_contract, :proxy_implementations]] => :optional } ] @@ -212,7 +213,34 @@ defmodule BlockScoutWeb.API.V2.BlockController do {blocks, next_page} = batch_number - |> OptimismTxnBatch.batch_blocks(full_options) + |> OptimismTransactionBatch.batch_blocks(full_options) + |> split_list_by_page() + + next_page_params = next_page |> next_page_params(blocks, delete_parameters_from_next_page_params(params)) + + conn + |> put_status(200) + |> render(:blocks, %{ + blocks: blocks |> maybe_preload_ens() |> maybe_preload_metadata(), + next_page_params: next_page_params + }) + end + + @doc """ + Function to handle GET requests to `/api/v2/blocks/scroll-batch/:batch_number` endpoint. + It renders the list of L2 blocks bound to the specified batch. + """ + @spec scroll_batch(Plug.Conn.t(), any()) :: Plug.Conn.t() + def scroll_batch(conn, %{"batch_number" => batch_number} = params) do + full_options = + params + |> select_block_type() + |> Keyword.merge(paging_options(params)) + |> Keyword.merge(@api_true) + + {blocks, next_page} = + batch_number + |> ScrollReader.batch_blocks(full_options) |> split_list_by_page() next_page_params = next_page |> next_page_params(blocks, delete_parameters_from_next_page_params(params)) diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/csv_export_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/csv_export_controller.ex index 30e1fa7..4788123 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/csv_export_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/csv_export_controller.ex @@ -1,7 +1,7 @@ defmodule BlockScoutWeb.API.V2.CSVExportController do use BlockScoutWeb, :controller - alias BlockScoutWeb.AccessHelper + alias BlockScoutWeb.{AccessHelper, CaptchaHelper} alias Explorer.Chain alias Explorer.Chain.Address.CurrentTokenBalance alias Explorer.Chain.CSVExport.Helper, as: CSVHelper @@ -19,10 +19,7 @@ defmodule BlockScoutWeb.API.V2.CSVExportController do def export_token_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), - {:recaptcha, true} <- - {:recaptcha, - Application.get_env(:block_scout_web, :recaptcha)[:is_disabled] || - CSVHelper.captcha_helper().recaptcha_passed?(params["recaptcha_response"])}, + {:recaptcha, true} <- {:recaptcha, CaptchaHelper.recaptcha_passed?(params)}, {:not_found, {:ok, token}} <- {:not_found, Chain.token_from_address_hash(address_hash, @api_true)} do token_holders = Chain.fetch_token_holders_from_token_hash_for_csv(address_hash, options()) 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 7f2544b..127bfe7 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 @@ -3,7 +3,7 @@ defmodule BlockScoutWeb.API.V2.FallbackController do require Logger - alias BlockScoutWeb.Account.Api.V2.UserView + alias BlockScoutWeb.Account.API.V2.UserView alias BlockScoutWeb.API.V2.ApiView alias Ecto.Changeset @@ -30,7 +30,7 @@ defmodule BlockScoutWeb.API.V2.FallbackController do @vyper_smart_contract_is_not_supported "Vyper smart-contracts are not supported by SolidityScan" @unverified_smart_contract "Smart-contract is unverified" @empty_response "Empty response" - @tx_interpreter_service_disabled "Transaction Interpretation Service is disabled" + @transaction_interpreter_service_disabled "Transaction Interpretation Service is disabled" @disabled "API endpoint is disabled" @service_disabled "Service is disabled" @@ -133,6 +133,13 @@ defmodule BlockScoutWeb.API.V2.FallbackController do |> render(:changeset_errors, changeset: changeset) end + def call(conn, {:error, :badge_creation_failed}) do + conn + |> put_status(:unprocessable_entity) + |> put_view(UserView) + |> render(:message, %{message: "Badge creation failed"}) + end + def call(conn, {:restricted_access, true}) do Logger.error(fn -> ["#{@restricted_access}"] @@ -275,13 +282,6 @@ defmodule BlockScoutWeb.API.V2.FallbackController do |> render(:message, %{message: @unverified_smart_contract}) end - def call(conn, {:method, _}) do - conn - |> put_status(:not_found) - |> put_view(ApiView) - |> render(:message, %{message: @not_found}) - end - def call(conn, {:is_empty_response, true}) do conn |> put_status(500) @@ -289,11 +289,11 @@ defmodule BlockScoutWeb.API.V2.FallbackController do |> render(:message, %{message: @empty_response}) end - def call(conn, {:tx_interpreter_enabled, false}) do + def call(conn, {:transaction_interpreter_enabled, false}) do conn |> put_status(:forbidden) |> put_view(ApiView) - |> render(:message, %{message: @tx_interpreter_service_disabled}) + |> render(:message, %{message: @transaction_interpreter_service_disabled}) end def call(conn, {:disabled, _}) do 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 e41abe0..d31c465 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 @@ -70,11 +70,11 @@ defmodule BlockScoutWeb.API.V2.ImportController do {: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(address_hash, @api_true)} do - creation_tx_input = contract_creation_input(address.hash) + creation_transaction_input = contract_creation_input(address.hash) with {:ok, %{"sourceType" => type} = source} <- %{} - |> prepare_bytecode_for_microservice(creation_tx_input, Data.to_string(address.contract_code)) + |> prepare_bytecode_for_microservice(creation_transaction_input, Data.to_string(address.contract_code)) |> EthBytecodeDBInterface.search_contract_in_eth_bytecode_internal_db( address_hash_string, params_to_contract_search_options(params) diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/internal_transaction_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/internal_transaction_controller.ex new file mode 100644 index 0000000..dcfde74 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/internal_transaction_controller.ex @@ -0,0 +1,58 @@ +defmodule BlockScoutWeb.API.V2.InternalTransactionController do + use BlockScoutWeb, :controller + alias Explorer.Chain.InternalTransaction + alias Explorer.{Helper, PagingOptions} + + import BlockScoutWeb.Chain, + only: [ + split_list_by_page: 1, + paging_options: 1, + next_page_params: 3 + ] + + import BlockScoutWeb.PagingHelper, + only: [ + delete_parameters_from_next_page_params: 1 + ] + + import Explorer.PagingOptions, only: [default_paging_options: 0] + + action_fallback(BlockScoutWeb.API.V2.FallbackController) + + @api_true [api?: true] + + @doc """ + Function to handle GET requests to `/api/v2/internal-transactions` endpoint. + """ + @spec internal_transactions(Plug.Conn.t(), map()) :: Plug.Conn.t() + def internal_transactions(conn, params) do + paging_options = paging_options(params) + + options = + paging_options + |> Keyword.update(:paging_options, default_paging_options(), fn %PagingOptions{ + page_size: page_size + } = paging_options -> + maybe_parsed_limit = Helper.parse_integer(params["limit"]) + %PagingOptions{paging_options | page_size: min(page_size, maybe_parsed_limit && abs(maybe_parsed_limit))} + end) + |> Keyword.merge(@api_true) + + result = + options + |> InternalTransaction.fetch() + |> split_list_by_page() + + {internal_transactions, next_page} = result + + next_page_params = + next_page |> next_page_params(internal_transactions, delete_parameters_from_next_page_params(params)) + + conn + |> put_status(200) + |> render(:internal_transactions, %{ + internal_transactions: internal_transactions, + next_page_params: next_page_params + }) + 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 ae44324..814b5fe 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 @@ -24,9 +24,9 @@ defmodule BlockScoutWeb.API.V2.MainPageController do necessity_by_association: %{ :block => :required, - [created_contract_address: [:names, :smart_contract, :proxy_implementations]] => :optional, - [from_address: [:names, :smart_contract, :proxy_implementations]] => :optional, - [to_address: [:names, :smart_contract, :proxy_implementations]] => :optional + [created_contract_address: [:scam_badge, :names, :smart_contract, :proxy_implementations]] => :optional, + [from_address: [:scam_badge, :names, :smart_contract, :proxy_implementations]] => :optional, + [to_address: [:scam_badge, :names, :smart_contract, :proxy_implementations]] => :optional } |> Map.merge(@chain_type_transaction_necessity_by_association), paging_options: %PagingOptions{page_size: 6}, diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/mud_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/mud_controller.ex index bf735ed..9f4b2f4 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/mud_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/mud_controller.ex @@ -16,6 +16,8 @@ defmodule BlockScoutWeb.API.V2.MudController do alias Explorer.Chain alias Explorer.Chain.{Address, Data, Hash, Mud, Mud.Schema.FieldSchema, Mud.Table} + @api_true [api?: true] + action_fallback(BlockScoutWeb.API.V2.FallbackController) @doc """ @@ -94,6 +96,34 @@ defmodule BlockScoutWeb.API.V2.MudController do end end + @doc """ + Function to handle GET requests to `/api/v2/mud/worlds/:world/systems` endpoint. + """ + @spec world_systems(Plug.Conn.t(), map()) :: Plug.Conn.t() + def world_systems(conn, %{"world" => world_param} = _params) do + with {:format, {:ok, world}} <- {:format, Hash.Address.cast(world_param)} do + systems = world |> Mud.world_systems() + + conn + |> put_status(200) + |> render(:systems, %{systems: systems}) + end + end + + @doc """ + Function to handle GET requests to `/api/v2/mud/worlds/:world/systems/:system` endpoint. + """ + @spec world_system(Plug.Conn.t(), map()) :: Plug.Conn.t() + def world_system(conn, %{"world" => world_param, "system" => system_param} = _params) do + with {:format, {:ok, world}} <- {:format, Hash.Address.cast(world_param)}, + {:format, {:ok, system}} <- {:format, Hash.Address.cast(system_param)}, + {:ok, system_id, abi} <- Mud.world_system(world, system, @api_true) do + conn + |> put_status(200) + |> render(:system, %{system_id: system_id, abi: abi}) + end + end + @doc """ Function to handle GET requests to `/api/v2/mud/worlds/:world/tables/count` endpoint. """ diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/optimism_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/optimism_controller.ex index ecd5d35..b74df7c 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/optimism_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/optimism_controller.ex @@ -22,7 +22,7 @@ defmodule BlockScoutWeb.API.V2.OptimismController do FrameSequence, FrameSequenceBlob, OutputRoot, - TxnBatch, + TransactionBatch, Withdrawal } @@ -32,22 +32,22 @@ defmodule BlockScoutWeb.API.V2.OptimismController do Function to handle GET requests to `/api/v2/optimism/txn-batches` and `/api/v2/optimism/txn-batches/:l2_block_range_start/:l2_block_range_end` endpoints. """ - @spec txn_batches(Plug.Conn.t(), map()) :: Plug.Conn.t() - def txn_batches(conn, params) do + @spec transaction_batches(Plug.Conn.t(), map()) :: Plug.Conn.t() + def transaction_batches(conn, params) do {batches, next_page} = params |> paging_options() |> Keyword.put(:api?, true) |> Keyword.put(:l2_block_range_start, Map.get(params, "l2_block_range_start")) |> Keyword.put(:l2_block_range_end, Map.get(params, "l2_block_range_end")) - |> TxnBatch.list() + |> TransactionBatch.list() |> split_list_by_page() next_page_params = next_page_params(next_page, batches, delete_parameters_from_next_page_params(params)) conn |> put_status(200) - |> render(:optimism_txn_batches, %{ + |> render(:optimism_transaction_batches, %{ batches: batches, next_page_params: next_page_params }) @@ -56,9 +56,9 @@ defmodule BlockScoutWeb.API.V2.OptimismController do @doc """ Function to handle GET requests to `/api/v2/optimism/txn-batches/count` endpoint. """ - @spec txn_batches_count(Plug.Conn.t(), map()) :: Plug.Conn.t() - def txn_batches_count(conn, _params) do - items_count(conn, TxnBatch) + @spec transaction_batches_count(Plug.Conn.t(), map()) :: Plug.Conn.t() + def transaction_batches_count(conn, _params) do + items_count(conn, TransactionBatch) end @doc """ @@ -80,14 +80,14 @@ defmodule BlockScoutWeb.API.V2.OptimismController do batches |> Enum.map(fn fs -> Task.async(fn -> - l2_block_number_from = TxnBatch.edge_l2_block_number(fs.id, :min) - l2_block_number_to = TxnBatch.edge_l2_block_number(fs.id, :max) - tx_count = Transaction.tx_count_for_block_range(l2_block_number_from..l2_block_number_to) + l2_block_number_from = TransactionBatch.edge_l2_block_number(fs.id, :min) + l2_block_number_to = TransactionBatch.edge_l2_block_number(fs.id, :max) + transaction_count = Transaction.transaction_count_for_block_range(l2_block_number_from..l2_block_number_to) {batch_data_container, _} = FrameSequenceBlob.list(fs.id, api?: true) fs |> Map.put(:l2_block_range, l2_block_number_from..l2_block_number_to) - |> Map.put(:tx_count, tx_count) + |> Map.put(:transaction_count, transaction_count) |> Map.put(:batch_data_container, batch_data_container) end) end) diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/proxy/account_abstraction_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/proxy/account_abstraction_controller.ex index 1b0bacf..9571069 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/proxy/account_abstraction_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/proxy/account_abstraction_controller.ex @@ -24,7 +24,7 @@ defmodule BlockScoutWeb.API.V2.Proxy.AccountAbstractionController do Function to handle GET requests to `/api/v2/proxy/account-abstraction/operations/:user_operation_hash_param/summary` endpoint. """ @spec summary(Plug.Conn.t(), map()) :: - {:error | :format | :tx_interpreter_enabled | non_neg_integer(), any()} | Plug.Conn.t() + {:error | :format | :transaction_interpreter_enabled | non_neg_integer(), any()} | Plug.Conn.t() def summary(conn, %{"operation_hash_param" => operation_hash_string, "just_request_body" => "true"}) do with {:format, {:ok, _operation_hash}} <- {:format, Chain.string_to_transaction_hash(operation_hash_string)}, {200, %{"hash" => _} = user_op} <- AccountAbstraction.get_user_ops_by_hash(operation_hash_string) do @@ -35,12 +35,13 @@ defmodule BlockScoutWeb.API.V2.Proxy.AccountAbstractionController do def summary(conn, %{"operation_hash_param" => operation_hash_string}) do with {:format, {:ok, _operation_hash}} <- {:format, Chain.string_to_transaction_hash(operation_hash_string)}, - {:tx_interpreter_enabled, true} <- {:tx_interpreter_enabled, TransactionInterpretationService.enabled?()}, + {:transaction_interpreter_enabled, true} <- + {:transaction_interpreter_enabled, TransactionInterpretationService.enabled?()}, {200, %{"hash" => _} = user_op} <- AccountAbstraction.get_user_ops_by_hash(operation_hash_string) do {response, code} = case TransactionInterpretationService.interpret_user_operation(user_op) do {:ok, response} -> {response, 200} - {:error, %Jason.DecodeError{}} -> {%{error: "Error while tx interpreter response decoding"}, 500} + {:error, %Jason.DecodeError{}} -> {%{error: "Error while transaction interpreter response decoding"}, 500} {{:error, error}, code} -> {%{error: error}, code} end @@ -233,10 +234,10 @@ defmodule BlockScoutWeb.API.V2.Proxy.AccountAbstractionController do defp try_to_decode_call_data(%{"call_data" => _call_data} = user_op) do user_op_hash = user_op["hash"] - {_mock_tx, _decoded_call_data, decoded_call_data_json} = + {_mock_transaction, _decoded_call_data, decoded_call_data_json} = TransactionInterpretationService.decode_user_op_calldata(user_op_hash, user_op["call_data"]) - {_mock_tx, _decoded_execute_call_data, decoded_execute_call_data_json} = + {_mock_transaction, _decoded_execute_call_data, decoded_execute_call_data_json} = TransactionInterpretationService.decode_user_op_calldata(user_op_hash, user_op["execute_call_data"]) user_op diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/proxy/noves_fi_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/proxy/noves_fi_controller.ex index c14d2bc..09f7523 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/proxy/noves_fi_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/proxy/noves_fi_controller.ex @@ -16,7 +16,7 @@ defmodule BlockScoutWeb.API.V2.Proxy.NovesFiController do necessity_by_association: %{}, api?: true ), - url = NovesFi.tx_url(transaction_hash_string), + url = NovesFi.transaction_url(transaction_hash_string), {response, status} <- NovesFi.api_request(url, conn), {:is_empty_response, false} <- {:is_empty_response, is_nil(response)} do conn @@ -31,7 +31,7 @@ defmodule BlockScoutWeb.API.V2.Proxy.NovesFiController do @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), + url = NovesFi.address_transactions_url(address_hash_string), {response, status} <- NovesFi.api_request(url, conn), {:is_empty_response, false} <- {:is_empty_response, is_nil(response)} do conn @@ -45,7 +45,7 @@ defmodule BlockScoutWeb.API.V2.Proxy.NovesFiController do """ @spec describe_transactions(Plug.Conn.t(), map()) :: Plug.Conn.t() | {atom(), any()} def describe_transactions(conn, _) do - url = NovesFi.describe_txs_url() + url = NovesFi.describe_transactions_url() with {response, status} <- NovesFi.api_request(url, conn, :post_transactions), {:is_empty_response, false} <- {:is_empty_response, is_nil(response)} do diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/proxy/xname_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/proxy/xname_controller.ex new file mode 100644 index 0000000..75426a4 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/proxy/xname_controller.ex @@ -0,0 +1,24 @@ +defmodule BlockScoutWeb.API.V2.Proxy.XnameController do + use BlockScoutWeb, :controller + + alias BlockScoutWeb.API.V2.AddressController + + alias Explorer.ThirdPartyIntegrations.Xname + + action_fallback(BlockScoutWeb.API.V2.FallbackController) + + @doc """ + Function to handle GET requests to `/api/v2/proxy/xname/address/:address_hash_param` endpoint. + """ + @spec address(Plug.Conn.t(), map()) :: Plug.Conn.t() | {atom(), any()} + def address(conn, %{"address_hash_param" => address_hash_string} = params) do + with {:ok, _address_hash, _address} <- AddressController.validate_address(address_hash_string, params), + url = Xname.address_url(address_hash_string), + {response, status} <- Xname.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/scroll_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/scroll_controller.ex new file mode 100644 index 0000000..1374369 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/scroll_controller.ex @@ -0,0 +1,154 @@ +defmodule BlockScoutWeb.API.V2.ScrollController do + use BlockScoutWeb, :controller + + 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 + ] + + alias Explorer.Chain.Scroll.Reader + + @api_true [api?: true] + + action_fallback(BlockScoutWeb.API.V2.FallbackController) + + @batch_necessity_by_association %{:bundle => :optional} + + @doc """ + Function to handle GET requests to `/api/v2/scroll/batches/:number` endpoint. + """ + @spec batch(Plug.Conn.t(), map()) :: Plug.Conn.t() + def batch(conn, %{"number" => number}) do + {number, ""} = Integer.parse(number) + + options = + [necessity_by_association: @batch_necessity_by_association] + |> Keyword.merge(@api_true) + + {_, batch} = Reader.batch(number, options) + + if batch == :not_found do + {:error, :not_found} + else + conn + |> put_status(200) + |> render(:scroll_batch, %{batch: batch}) + end + end + + @doc """ + Function to handle GET requests to `/api/v2/scroll/batches` endpoint. + """ + @spec batches(Plug.Conn.t(), map()) :: Plug.Conn.t() + def batches(conn, params) do + {batches, next_page} = + params + |> paging_options() + |> Keyword.merge(@api_true) + |> Reader.batches() + |> split_list_by_page() + + next_page_params = next_page_params(next_page, batches, delete_parameters_from_next_page_params(params)) + + conn + |> put_status(200) + |> render(:scroll_batches, %{ + batches: batches, + next_page_params: next_page_params + }) + end + + @doc """ + Function to handle GET requests to `/api/v2/scroll/batches/count` endpoint. + """ + @spec batches_count(Plug.Conn.t(), map()) :: Plug.Conn.t() + def batches_count(conn, _params) do + conn + |> put_status(200) + |> render(:scroll_batches_count, %{count: batch_latest_number() + 1}) + end + + @doc """ + Function to handle GET requests to `/api/v2/scroll/deposits` endpoint. + """ + @spec deposits(Plug.Conn.t(), map()) :: Plug.Conn.t() + def deposits(conn, params) do + {deposits, next_page} = + params + |> paging_options() + |> Keyword.merge(@api_true) + |> Reader.deposits() + |> split_list_by_page() + + next_page_params = next_page_params(next_page, deposits, params) + + conn + |> put_status(200) + |> render(:scroll_bridge_items, %{ + items: deposits, + next_page_params: next_page_params, + type: :deposits + }) + end + + @doc """ + Function to handle GET requests to `/api/v2/scroll/deposits/count` endpoint. + """ + @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(:scroll_bridge_items_count, %{count: count}) + end + + @doc """ + Function to handle GET requests to `/api/v2/scroll/withdrawals` endpoint. + """ + @spec withdrawals(Plug.Conn.t(), map()) :: Plug.Conn.t() + def withdrawals(conn, params) do + {withdrawals, next_page} = + params + |> paging_options() + |> Keyword.merge(@api_true) + |> Reader.withdrawals() + |> split_list_by_page() + + next_page_params = next_page_params(next_page, withdrawals, params) + + conn + |> put_status(200) + |> render(:scroll_bridge_items, %{ + items: withdrawals, + next_page_params: next_page_params, + type: :withdrawals + }) + end + + @doc """ + Function to handle GET requests to `/api/v2/scroll/withdrawals/count` endpoint. + """ + @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(:scroll_bridge_items_count, %{count: count}) + end + + defp batch_latest_number do + case Reader.batch(:latest, @api_true) do + {:ok, batch} -> batch.number + {:error, :not_found} -> -1 + 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 0a3c0f1..af3324a 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 @@ -8,6 +8,16 @@ defmodule BlockScoutWeb.API.V2.SearchController do alias Explorer.PagingOptions @api_true [api?: true] + @min_query_length 3 + + def search(conn, %{"q" => query}) when byte_size(query) < @min_query_length do + conn + |> put_status(200) + |> render(:search_results, %{ + search_results: [], + next_page_params: nil + }) + end def search(conn, %{"q" => query} = params) do [paging_options: paging_options] = paging_options(params) @@ -29,6 +39,12 @@ defmodule BlockScoutWeb.API.V2.SearchController do }) end + def check_redirect(conn, %{"q" => query}) when byte_size(query) < @min_query_length do + conn + |> put_status(200) + |> render(:search_results, %{result: {:error, :not_found}}) + end + def check_redirect(conn, %{"q" => query}) do result = query @@ -40,6 +56,12 @@ defmodule BlockScoutWeb.API.V2.SearchController do |> render(:search_results, %{result: result}) end + def quick_search(conn, %{"q" => query}) when byte_size(query) < @min_query_length do + conn + |> put_status(200) + |> render(:search_results, %{search_results: []}) + end + def quick_search(conn, %{"q" => query}) do search_results = Search.balanced_unpaginated_search(%PagingOptions{page_size: 50}, query, @api_true) 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 7d19376..10e176e 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 @@ -8,7 +8,7 @@ defmodule BlockScoutWeb.API.V2.SmartContractController do import Explorer.SmartContract.Solidity.Verifier, only: [parse_boolean: 1] - alias BlockScoutWeb.{AccessHelper, AddressView} + alias BlockScoutWeb.{AccessHelper, AddressView, CaptchaHelper} alias Ecto.Association.NotLoaded alias Explorer.Chain alias Explorer.Chain.{Address, SmartContract} @@ -275,11 +275,9 @@ defmodule BlockScoutWeb.API.V2.SmartContractController do | {:restricted_access, true} | Plug.Conn.t() def audit_report_submission(conn, %{"address_hash" => address_hash_string} = params) do - captcha_helper = Application.get_env(:block_scout_web, :captcha_helper) - with {:disabled, true} <- {:disabled, Application.get_env(:explorer, :air_table_audit_reports)[:enabled]}, {:ok, address_hash, _smart_contract} <- validate_smart_contract(params, address_hash_string), - {:recaptcha, _} <- {:recaptcha, captcha_helper.recaptcha_passed?(params["recaptcha_response"])}, + {:recaptcha, _} <- {:recaptcha, CaptchaHelper.recaptcha_passed?(params)}, audit_report_params <- %{ address_hash: address_hash, submitter_name: params["submitter_name"], 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 be905ed..84d4269 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 @@ -109,7 +109,8 @@ defmodule BlockScoutWeb.API.V2.StatsController do transaction_history_data = date_range |> Enum.map(fn row -> - %{date: row.date, tx_count: row.number_of_transactions} + # todo: keep `tx_count` for compatibility with frontend and remove when new frontend is bound to `transaction_count` property + %{date: row.date, transaction_count: row.number_of_transactions, tx_count: row.number_of_transactions} end) json(conn, %{ @@ -190,12 +191,18 @@ defmodule BlockScoutWeb.API.V2.StatsController do end end - "optimism" -> + :optimism -> defp add_chain_type_fields(response) do import Explorer.Counters.LastOutputRootSizeCounter, only: [fetch: 1] response |> Map.put("last_output_root_size", fetch(@api_true)) end + :celo -> + defp add_chain_type_fields(response) do + import Explorer.Chain.Celo.Reader, only: [last_block_epoch_number: 0] + response |> Map.put("celo", %{"epoch_number" => last_block_epoch_number()}) + end + _ -> defp add_chain_type_fields(response), do: response 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 934b4db..506db2a 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,11 +2,10 @@ defmodule BlockScoutWeb.API.V2.TokenController do alias Explorer.PagingOptions use BlockScoutWeb, :controller - alias BlockScoutWeb.AccessHelper + alias BlockScoutWeb.{AccessHelper, CaptchaHelper} alias BlockScoutWeb.API.V2.{AddressView, TransactionView} alias Explorer.{Chain, Helper} alias Explorer.Chain.{Address, BridgedToken, Token, Token.Instance} - alias Explorer.Chain.CSVExport.Helper, as: CSVHelper alias Indexer.Fetcher.OnDemand.TokenInstanceMetadataRefetch, as: TokenInstanceMetadataRefetchOnDemand alias Indexer.Fetcher.OnDemand.TokenTotalSupply, as: TokenTotalSupplyOnDemand @@ -298,8 +297,8 @@ defmodule BlockScoutWeb.API.V2.TokenController do |> Keyword.update(:paging_options, default_paging_options(), fn %PagingOptions{ page_size: page_size } = paging_options -> - mb_parsed_limit = Helper.parse_integer(params["limit"]) - %PagingOptions{paging_options | page_size: min(page_size, mb_parsed_limit && abs(mb_parsed_limit))} + maybe_parsed_limit = Helper.parse_integer(params["limit"]) + %PagingOptions{paging_options | page_size: min(page_size, maybe_parsed_limit && abs(maybe_parsed_limit))} end) |> Keyword.merge(token_transfers_types_options(params)) |> Keyword.merge(tokens_sorting(params)) @@ -339,11 +338,10 @@ defmodule BlockScoutWeb.API.V2.TokenController do ) do address_hash_string = params["address_hash_param"] token_id_string = params["token_id"] - recaptcha_response = params["recaptcha_response"] with {:format, {:ok, address_hash}} <- {:format, Chain.string_to_address_hash(address_hash_string)}, {:ok, false} <- AccessHelper.restricted_access?(address_hash_string, params), - {:recaptcha, true} <- {:recaptcha, CSVHelper.captcha_helper().recaptcha_passed?(recaptcha_response)}, + {:recaptcha, true} <- {:recaptcha, CaptchaHelper.recaptcha_passed?(params)}, {:not_found, {:ok, token}} <- {:not_found, Chain.token_from_address_hash(address_hash, @api_true)}, {:not_found, false} <- {:not_found, Chain.erc_20_token?(token)}, {:format, {token_id, ""}} <- {:format, Integer.parse(token_id_string)}, diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/token_transfer_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/token_transfer_controller.ex new file mode 100644 index 0000000..a917e80 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/token_transfer_controller.ex @@ -0,0 +1,79 @@ +defmodule BlockScoutWeb.API.V2.TokenTransferController do + use BlockScoutWeb, :controller + alias Explorer.{Chain, Helper, PagingOptions} + alias Explorer.Chain.{TokenTransfer, Transaction} + + import BlockScoutWeb.Chain, + only: [ + split_list_by_page: 1, + paging_options: 1, + token_transfers_next_page_params: 3 + ] + + import BlockScoutWeb.PagingHelper, + only: [ + delete_parameters_from_next_page_params: 1, + token_transfers_types_options: 1 + ] + + import Explorer.MicroserviceInterfaces.BENS, only: [maybe_preload_ens: 1] + import Explorer.MicroserviceInterfaces.Metadata, only: [maybe_preload_metadata: 1] + import Explorer.PagingOptions, only: [default_paging_options: 0] + + action_fallback(BlockScoutWeb.API.V2.FallbackController) + + @api_true [api?: true] + + @doc """ + Function to handle GET requests to `/api/v2/token-transfers` endpoint. + """ + @spec token_transfers(Plug.Conn.t(), map()) :: Plug.Conn.t() + def token_transfers(conn, params) do + paging_options = paging_options(params) + + options = + paging_options + |> Keyword.update(:paging_options, default_paging_options(), fn %PagingOptions{ + page_size: page_size + } = paging_options -> + maybe_parsed_limit = Helper.parse_integer(params["limit"]) + %PagingOptions{paging_options | page_size: min(page_size, maybe_parsed_limit && abs(maybe_parsed_limit))} + end) + |> Keyword.merge(token_transfers_types_options(params)) + |> Keyword.merge(@api_true) + + result = + options + |> TokenTransfer.fetch() + |> Chain.flat_1155_batch_token_transfers() + |> Chain.paginate_1155_batch_token_transfers(paging_options) + |> split_list_by_page() + + {token_transfers, next_page} = result + + transactions = + token_transfers + |> Enum.map(fn token_transfer -> + token_transfer.transaction + end) + |> Enum.uniq() + + decoded_transactions = Transaction.decode_transactions(transactions, true, @api_true) + + decoded_transactions_map = + transactions + |> Enum.zip(decoded_transactions) + |> Enum.into(%{}, fn {%{hash: hash}, decoded_input} -> {hash, decoded_input} end) + + next_page_params = + next_page |> 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 |> maybe_preload_ens() |> maybe_preload_metadata(), + decoded_transactions_map: decoded_transactions_map, + next_page_params: next_page_params + }) + 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 43179c2..1b92aa8 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 @@ -42,8 +42,9 @@ defmodule BlockScoutWeb.API.V2.TransactionController do alias Explorer.Chain.Arbitrum.Reader, as: ArbitrumReader alias Explorer.Chain.Beacon.Reader, as: BeaconReader alias Explorer.Chain.{Hash, InternalTransaction, Transaction} - alias Explorer.Chain.Optimism.TxnBatch, as: OptimismTxnBatch + alias Explorer.Chain.Optimism.TransactionBatch, as: OptimismTransactionBatch alias Explorer.Chain.PolygonZkevm.Reader, as: PolygonZkevmReader + alias Explorer.Chain.Scroll.Reader, as: ScrollReader alias Explorer.Chain.ZkSync.Reader, as: ZkSyncReader alias Explorer.Counters.{FreshPendingTransactionsCounter, Transactions24hStats} alias Indexer.Fetcher.OnDemand.FirstTrace, as: FirstTraceOnDemand @@ -70,34 +71,42 @@ defmodule BlockScoutWeb.API.V2.TransactionController do :block => :optional, [ created_contract_address: [ + :scam_badge, :names, :token, :smart_contract, :proxy_implementations ] ] => :optional, - [from_address: [:names, :smart_contract, :proxy_implementations]] => + [from_address: [:scam_badge, :names, :smart_contract, :proxy_implementations]] => :optional, - [to_address: [:names, :smart_contract, :proxy_implementations]] => :optional + [ + to_address: [ + :scam_badge, + :names, + :smart_contract, + :proxy_implementations + ] + ] => :optional } |> Map.merge(@chain_type_transaction_necessity_by_association) @token_transfers_necessity_by_association %{ - [from_address: [:names, :smart_contract, :proxy_implementations]] => :optional, - [to_address: [:names, :smart_contract, :proxy_implementations]] => :optional + [from_address: [:scam_badge, :names, :smart_contract, :proxy_implementations]] => :optional, + [to_address: [:scam_badge, :names, :smart_contract, :proxy_implementations]] => :optional } - @token_transfers_in_tx_necessity_by_association %{ - [from_address: [:names, :smart_contract, :proxy_implementations]] => :optional, - [to_address: [:names, :smart_contract, :proxy_implementations]] => :optional, + @token_transfers_in_transaction_necessity_by_association %{ + [from_address: [:scam_badge, :names, :smart_contract, :proxy_implementations]] => :optional, + [to_address: [:scam_badge, :names, :smart_contract, :proxy_implementations]] => :optional, token: :required } @internal_transaction_necessity_by_association [ necessity_by_association: %{ - [created_contract_address: [:names, :smart_contract, :proxy_implementations]] => :optional, - [from_address: [:names, :smart_contract, :proxy_implementations]] => :optional, - [to_address: [:names, :smart_contract, :proxy_implementations]] => :optional + [created_contract_address: [:scam_badge, :names, :smart_contract, :proxy_implementations]] => :optional, + [from_address: [:scam_badge, :names, :smart_contract, :proxy_implementations]] => :optional, + [to_address: [:scam_badge, :names, :smart_contract, :proxy_implementations]] => :optional } ] @@ -109,7 +118,9 @@ defmodule BlockScoutWeb.API.V2.TransactionController do @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) + @transaction_necessity_by_association + |> Map.put(:transaction_actions, :optional) + |> Map.put(:signed_authorizations, :optional) necessity_by_association = case Application.get_env(:explorer, :chain_type) do @@ -144,13 +155,18 @@ defmodule BlockScoutWeb.API.V2.TransactionController do 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 - ), + options = + [necessity_by_association: necessity_by_association] + |> Keyword.merge(@api_true) + + with {:ok, transaction, _transaction_hash} <- validate_transaction(transaction_hash_string, params, options), preloaded <- - Chain.preload_token_transfers(transaction, @token_transfers_in_tx_necessity_by_association, @api_true, false) do + Chain.preload_token_transfers( + transaction, + @token_transfers_in_transaction_necessity_by_association, + @api_true, + false + ) do conn |> put_status(200) |> render(:transaction, %{ @@ -195,11 +211,15 @@ defmodule BlockScoutWeb.API.V2.TransactionController do """ @spec polygon_zkevm_batch(Plug.Conn.t(), map()) :: Plug.Conn.t() def polygon_zkevm_batch(conn, %{"batch_number" => batch_number} = _params) do + options = + [necessity_by_association: @transaction_necessity_by_association] + |> Keyword.merge(@api_true) + transactions = batch_number - |> PolygonZkevmReader.batch_transactions(api?: true) - |> Enum.map(fn tx -> tx.hash end) - |> Chain.hashes_to_transactions(api?: true, necessity_by_association: @transaction_necessity_by_association) + |> PolygonZkevmReader.batch_transactions(@api_true) + |> Enum.map(fn transaction -> transaction.hash end) + |> Chain.hashes_to_transactions(options) conn |> put_status(200) @@ -235,9 +255,44 @@ defmodule BlockScoutWeb.API.V2.TransactionController do def optimism_batch(conn, %{"batch_number" => batch_number_string} = params) do {batch_number, ""} = Integer.parse(batch_number_string) - l2_block_number_from = OptimismTxnBatch.edge_l2_block_number(batch_number, :min) - l2_block_number_to = OptimismTxnBatch.edge_l2_block_number(batch_number, :max) + l2_block_number_from = OptimismTransactionBatch.edge_l2_block_number(batch_number, :min) + l2_block_number_to = OptimismTransactionBatch.edge_l2_block_number(batch_number, :max) + + handle_block_range_transactions(conn, params, l2_block_number_from, l2_block_number_to) + end + + @doc """ + Function to handle GET requests to `/api/v2/transactions/scroll-batch/:batch_number` endpoint. + It renders the list of L2 transactions bound to the specified batch. + """ + @spec scroll_batch(Plug.Conn.t(), map()) :: Plug.Conn.t() + def scroll_batch(conn, %{"batch_number" => batch_number_string} = params) do + {batch_number, ""} = Integer.parse(batch_number_string) + + {l2_block_number_from, l2_block_number_to} = + case ScrollReader.batch(batch_number, @api_true) do + {:ok, batch} -> {batch.l2_block_range.from, batch.l2_block_range.to} + _ -> {nil, nil} + end + + handle_block_range_transactions(conn, params, l2_block_number_from, l2_block_number_to) + end + # Processes and renders transactions for a specified L2 block range into an HTTP response. + # + # This function retrieves a list of transactions for a given L2 block range and formats + # these transactions into an HTTP response. + # + # ## Parameters + # - `conn`: The connection object. + # - `params`: Parameters from the request. + # - `l2_block_number_from`: Start L2 block number of the range. + # - `l2_block_number_to`: End L2 block number of the range. + # + # ## Returns + # - Updated connection object with the transactions data rendered. + @spec handle_block_range_transactions(Plug.Conn.t(), map(), non_neg_integer(), non_neg_integer()) :: Plug.Conn.t() + defp handle_block_range_transactions(conn, params, l2_block_number_from, l2_block_number_to) do transactions_plus_one = if is_nil(l2_block_number_from) or is_nil(l2_block_number_to) do [] @@ -246,8 +301,13 @@ defmodule BlockScoutWeb.API.V2.TransactionController do query = case paging_options do - %PagingOptions{key: {0, 0}, is_index_in_asc_order: false} -> [] - _ -> Transaction.fetch_transactions(paging_options, l2_block_number_from - 1, l2_block_number_to) + %PagingOptions{key: {0, 0}, is_index_in_asc_order: false} -> + [] + + _ -> + # here we need to subtract 1 because the block range inside the `fetch_transactions` function + # starts from the `from_block + 1` + Transaction.fetch_transactions(paging_options, l2_block_number_from - 1, l2_block_number_to) end query @@ -297,7 +357,7 @@ defmodule BlockScoutWeb.API.V2.TransactionController do transactions_plus_one = batch_number |> batch_transactions_fun.(@api_true) - |> Enum.map(fn tx -> tx.tx_hash end) + |> Enum.map(fn transaction -> transaction.transaction_hash end) |> Chain.hashes_to_transactions(full_options) {transactions, next_page} = split_list_by_page(transactions_plus_one) @@ -452,7 +512,7 @@ defmodule BlockScoutWeb.API.V2.TransactionController do conn |> put_status(200) |> render(:logs, %{ - tx_hash: transaction_hash, + transaction_hash: transaction_hash, logs: logs |> maybe_preload_ens() |> maybe_preload_metadata(), next_page_params: next_page_params }) @@ -464,9 +524,9 @@ defmodule BlockScoutWeb.API.V2.TransactionController do """ @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, api?: true) do + with {:ok, transaction, _transaction_hash} <- validate_transaction(transaction_hash_string, params) do state_changes_plus_next_page = - transaction |> TransactionStateHelper.state_changes(params |> paging_options() |> Keyword.merge(api?: true)) + transaction |> TransactionStateHelper.state_changes(params |> paging_options() |> Keyword.merge(@api_true)) {state_changes, next_page} = split_list_by_page(state_changes_plus_next_page) @@ -510,15 +570,18 @@ defmodule BlockScoutWeb.API.V2.TransactionController do 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, - necessity_by_association: %{ - [from_address: [:names, :smart_contract, :proxy_implementations]] => :optional, - [to_address: [:names, :smart_contract, :proxy_implementations]] => :optional - }, - api?: true - ) do + options = + [ + necessity_by_association: %{ + [from_address: [:scam_badge, :names, :smart_contract, :proxy_implementations]] => :optional, + [to_address: [:scam_badge, :names, :smart_contract, :proxy_implementations]] => :optional + } + ] + |> Keyword.merge(@api_true) + + with {:transaction_interpreter_enabled, true} <- + {:transaction_interpreter_enabled, TransactionInterpretationService.enabled?()}, + {:ok, transaction, _transaction_hash} <- validate_transaction(transaction_hash_string, params, options) do conn |> json(TransactionInterpretationService.get_request_body(transaction)) end @@ -531,22 +594,25 @@ defmodule BlockScoutWeb.API.V2.TransactionController do {:format, :error} | {:not_found, {:error, :not_found}} | {:restricted_access, true} - | {:tx_interpreter_enabled, boolean} + | {:transaction_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, - necessity_by_association: %{ - [from_address: [:names, :smart_contract, :proxy_implementations]] => :optional, - [to_address: [:names, :smart_contract, :proxy_implementations]] => :optional - }, - api?: true - ) do + options = + [ + necessity_by_association: %{ + [from_address: [:scam_badge, :names, :smart_contract, :proxy_implementations]] => :optional, + [to_address: [:scam_badge, :names, :smart_contract, :proxy_implementations]] => :optional + } + ] + |> Keyword.merge(@api_true) + + with {:transaction_interpreter_enabled, true} <- + {:transaction_interpreter_enabled, TransactionInterpretationService.enabled?()}, + {:ok, transaction, _transaction_hash} <- validate_transaction(transaction_hash_string, params, options) do {response, code} = case TransactionInterpretationService.interpret(transaction) do {:ok, response} -> {response, 200} - {:error, %Jason.DecodeError{}} -> {%{error: "Error while tx interpreter response decoding"}, 500} + {:error, %Jason.DecodeError{}} -> {%{error: "Error while transaction interpreter response decoding"}, 500} {{:error, error}, code} -> {%{error: error}, code} end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/utils_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/utils_controller.ex index 1826c67..233066a 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/utils_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/utils_controller.ex @@ -25,7 +25,7 @@ defmodule BlockScoutWeb.API.V2.UtilsController do updated_smart_contract end - {decoded_input, _abi_acc, _methods_acc} = + decoded_input = Transaction.decoded_input_data( %Transaction{ input: data, diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/validator_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/validator_controller.ex index 7873676..78a59a0 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/validator_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/validator_controller.ex @@ -1,13 +1,15 @@ defmodule BlockScoutWeb.API.V2.ValidatorController do use BlockScoutWeb, :controller - alias Explorer.Chain.Cache.StabilityValidatorsCounters + alias Explorer.Chain.Blackfort.Validator, as: ValidatorBlackfort + alias Explorer.Chain.Cache.{BlackfortValidatorsCounters, StabilityValidatorsCounters} alias Explorer.Chain.Stability.Validator, as: ValidatorStability import BlockScoutWeb.PagingHelper, only: [ delete_parameters_from_next_page_params: 1, stability_validators_state_options: 1, + validators_blackfort_sorting: 1, validators_stability_sorting: 1 ] @@ -71,6 +73,52 @@ defmodule BlockScoutWeb.API.V2.ValidatorController do }) end + @doc """ + Function to handle GET requests to `/api/v2/validators/blackfort` endpoint. + """ + @spec blackfort_validators_list(Plug.Conn.t(), map()) :: Plug.Conn.t() + def blackfort_validators_list(conn, params) do + options = + [ + necessity_by_association: %{ + [address: [:names, :smart_contract, :proxy_implementations]] => :optional + } + ] + |> Keyword.merge(@api_true) + |> Keyword.merge(paging_options(params)) + |> Keyword.merge(validators_blackfort_sorting(params)) + + {validators, next_page} = options |> ValidatorBlackfort.get_paginated_validators() |> split_list_by_page() + + next_page_params = + next_page + |> next_page_params( + validators, + delete_parameters_from_next_page_params(params), + &ValidatorBlackfort.next_page_params/1 + ) + + conn + |> render(:blackfort_validators, %{validators: validators, next_page_params: next_page_params}) + end + + @doc """ + Function to handle GET requests to `/api/v2/validators/blackfort/counters` endpoint. + """ + @spec blackfort_validators_counters(Plug.Conn.t(), map()) :: Plug.Conn.t() + def blackfort_validators_counters(conn, _params) do + %{ + validators_counter: validators_counter, + new_validators_counter: new_validators_counter + } = BlackfortValidatorsCounters.get_counters(@api_true) + + conn + |> json(%{ + validators_counter: validators_counter, + new_validators_counter_24h: new_validators_counter + }) + end + defp calculate_active_validators_percentage(active_validators_counter, validators_counter) do if Decimal.compare(validators_counter, Decimal.new(0)) == :gt do active_validators_counter 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 94cfb3b..3176bde 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 @@ -122,21 +122,13 @@ defmodule BlockScoutWeb.API.V2.VerificationController 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 - constructor_arguments = - if Application.get_env(:explorer, :chain_type) == :zksync do - zksync_get_constructor_arguments(address_hash_string) - else - Map.get(params, "constructor_args", "") - end - verification_params = %{ "address_hash" => String.downcase(address_hash_string), "compiler_version" => compiler_version } |> Map.put("autodetect_constructor_args", Map.get(params, "autodetect_constructor_args", true)) - # - |> Map.put("constructor_arguments", constructor_arguments) + |> Map.put("constructor_arguments", Map.get(params, "constructor_args", "")) |> Map.put("name", Map.get(params, "contract_name", "")) |> Map.put("license_type", Map.get(params, "license_type")) |> (&if(Application.get_env(:explorer, :chain_type) == :zksync, @@ -320,10 +312,6 @@ defmodule BlockScoutWeb.API.V2.VerificationController do end end - defp zksync_get_constructor_arguments(address_hash_string) do - Chain.contract_creation_input_data(address_hash_string) - end - # sobelow_skip ["Traversal.FileModule"] defp validate_params_standard_json_input(%{"files" => files} = params) do with :validated <- validate_address(params), 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 41bd04b..a867058 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 @@ -97,13 +97,13 @@ defmodule BlockScoutWeb.ChainController do encoded_results = results |> Enum.map(fn item -> - tx_hash_bytes = Map.get(item, :tx_hash) + transaction_hash_bytes = Map.get(item, :transaction_hash) block_hash_bytes = Map.get(item, :block_hash) item = - if tx_hash_bytes do + if transaction_hash_bytes do item - |> Map.replace(:tx_hash, "0x" <> Base.encode16(tx_hash_bytes, case: :lower)) + |> Map.replace(:transaction_hash, "0x" <> Base.encode16(transaction_hash_bytes, case: :lower)) else item 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 937c4b2..a797076 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 @@ -7,6 +7,18 @@ defmodule BlockScoutWeb.SearchController do alias Explorer.Chain.Search alias Phoenix.View + @min_query_length 3 + + def search_results(conn, %{"q" => query, "type" => "JSON"}) when byte_size(query) < @min_query_length do + json( + conn, + %{ + items: [], + next_page_path: nil + } + ) + end + def search_results(conn, %{"q" => query, "type" => "JSON"} = params) do [paging_options: paging_options] = paging_options(params) offset = (max(paging_options.page_number, 1) - 1) * paging_options.page_size 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 87eaf6b..ee2d59a 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 @@ -217,7 +217,7 @@ defmodule BlockScoutWeb.SmartContractController do :error -> unprocessable_entity(conn) - :not_found -> + {:error, :not_found} -> not_found(conn) _ -> 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 a31baf1..3f26d9a 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 @@ -167,19 +167,13 @@ defmodule BlockScoutWeb.TransactionController do transaction: transaction, 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: + transaction_tags: get_transaction_with_addresses_tags( transaction, current_user(conn) ) ) else - :not_found -> - set_not_found_view(conn, id) - - :error -> - unprocessable_entity(conn) - {:error, :not_found} -> set_not_found_view(conn, id) @@ -202,19 +196,13 @@ defmodule BlockScoutWeb.TransactionController do transaction: transaction, 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: + transaction_tags: get_transaction_with_addresses_tags( transaction, current_user(conn) ) ) else - :not_found -> - set_not_found_view(conn, id) - - :error -> - unprocessable_entity(conn) - {:error, :not_found} -> set_not_found_view(conn, id) 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 f3f9cc4..7af84eb 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 @@ -110,7 +110,7 @@ defmodule BlockScoutWeb.TransactionInternalTransactionController do transaction: transaction, 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: + transaction_tags: get_transaction_with_addresses_tags( transaction, current_user(conn) 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 53ba82e..fe0c93a 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 @@ -101,7 +101,7 @@ defmodule BlockScoutWeb.TransactionLogController do 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: + transaction_tags: get_transaction_with_addresses_tags( transaction, current_user(conn) 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 7482819..6beedaf 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 @@ -60,7 +60,7 @@ defmodule BlockScoutWeb.TransactionRawTraceController do transaction: transaction, 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: + transaction_tags: get_transaction_with_addresses_tags( transaction, 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 f978e76..15cc258 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 @@ -73,9 +73,6 @@ defmodule BlockScoutWeb.TransactionStateController do {:error, :not_found} -> TransactionController.set_not_found_view(conn, transaction_hash_string) - - :not_found -> - TransactionController.set_not_found_view(conn, transaction_hash_string) end end @@ -107,7 +104,7 @@ defmodule BlockScoutWeb.TransactionStateController do transaction: transaction, 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: + transaction_tags: get_transaction_with_addresses_tags( transaction, current_user(conn) @@ -115,9 +112,6 @@ defmodule BlockScoutWeb.TransactionStateController do current_user: current_user(conn) ) else - :not_found -> - TransactionController.set_not_found_view(conn, transaction_hash_string) - :error -> unprocessable_entity(conn) 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 061fde4..0db5840 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 @@ -113,16 +113,13 @@ defmodule BlockScoutWeb.TransactionTokenTransferController do transaction: transaction, 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: + transaction_tags: get_transaction_with_addresses_tags( transaction, current_user(conn) ) ) else - :not_found -> - TransactionController.set_not_found_view(conn, transaction_hash_string) - :error -> unprocessable_entity(conn) diff --git a/apps/block_scout_web/lib/block_scout_web/etherscan.ex b/apps/block_scout_web/lib/block_scout_web/etherscan.ex index b6be618..472bf82 100644 --- a/apps/block_scout_web/lib/block_scout_web/etherscan.ex +++ b/apps/block_scout_web/lib/block_scout_web/etherscan.ex @@ -1149,7 +1149,7 @@ defmodule BlockScoutWeb.Etherscan do confirmations: @confirmation_type, success: %{ type: "boolean", - definition: "Flag for success during tx execution", + definition: "Flag for success during transaction execution", example: ~s(true) }, from: @address_hash_type, diff --git a/apps/block_scout_web/lib/block_scout_web/gettext.ex b/apps/block_scout_web/lib/block_scout_web/gettext.ex index 3d54e80..bb3588f 100644 --- a/apps/block_scout_web/lib/block_scout_web/gettext.ex +++ b/apps/block_scout_web/lib/block_scout_web/gettext.ex @@ -20,5 +20,5 @@ defmodule BlockScoutWeb.Gettext do See the [Gettext Docs](https://hexdocs.pm/gettext) for detailed usage. """ - use Gettext, otp_app: :block_scout_web + use Gettext.Backend, otp_app: :block_scout_web end diff --git a/apps/block_scout_web/lib/block_scout_web/graphql/celo/resolvers/token_transfer.ex b/apps/block_scout_web/lib/block_scout_web/graphql/celo/resolvers/token_transfer.ex new file mode 100644 index 0000000..0eb2a5a --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/graphql/celo/resolvers/token_transfer.ex @@ -0,0 +1,21 @@ +defmodule BlockScoutWeb.GraphQL.Celo.Resolvers.TokenTransfer do + @moduledoc """ + Resolvers for token transfers, used in the CELO schema. + """ + + alias Absinthe.Relay.Connection + alias Explorer.GraphQL.Celo, as: GraphQL + alias Explorer.Repo + + def get_by(%{transaction_hash: hash}, args, _) do + hash + |> GraphQL.token_transaction_transfers_query_by_transaction_hash() + |> Connection.from_query(&Repo.all/1, args, options(args)) + end + + defp options(%{before: _}), do: [] + + defp options(%{count: count}), do: [count: count] + + defp options(_), do: [] +end diff --git a/apps/block_scout_web/lib/block_scout_web/graphql/celo/resolvers/token_transfer_transaction.ex b/apps/block_scout_web/lib/block_scout_web/graphql/celo/resolvers/token_transfer_transaction.ex new file mode 100644 index 0000000..4b39ec0 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/graphql/celo/resolvers/token_transfer_transaction.ex @@ -0,0 +1,27 @@ +defmodule BlockScoutWeb.GraphQL.Celo.Resolvers.TokenTransferTransaction do + @moduledoc false + + alias Absinthe.Relay.Connection + alias Explorer.GraphQL.Celo, as: GraphQL + alias Explorer.Repo + + def get_by(_, %{address_hash: address_hash, first: limit} = args, _) do + connection_args = Map.take(args, [:after, :before, :first, :last]) + + offset = + case Connection.offset(args) do + {:ok, offset} when is_integer(offset) -> offset + _ -> 0 + end + + address_hash + |> GraphQL.token_transaction_transfers_query_for_address(offset, limit) + |> Connection.from_query(&Repo.all/1, connection_args, options(args)) + end + + defp options(%{before: _}), do: [] + + defp options(%{count: count}), do: [count: count] + + defp options(_), do: [] +end diff --git a/apps/block_scout_web/lib/block_scout_web/graphql/celo/schema/query_fields.ex b/apps/block_scout_web/lib/block_scout_web/graphql/celo/schema/query_fields.ex new file mode 100644 index 0000000..8e2d277 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/graphql/celo/schema/query_fields.ex @@ -0,0 +1,28 @@ +defmodule BlockScoutWeb.GraphQL.Celo.QueryFields do + @moduledoc """ + Query fields for the CELO schema. + """ + + alias BlockScoutWeb.GraphQL.Celo.Resolvers.TokenTransferTransaction + + use Absinthe.Schema.Notation + use Absinthe.Relay.Schema, :modern + + defmacro generate do + quote do + @desc "Gets token transfer transactions." + connection field(:token_transfer_txs, node_type: :transfer_transaction) do + arg(:address_hash, :address_hash) + arg(:count, :integer) + + resolve(&TokenTransferTransaction.get_by/3) + + complexity(fn + %{first: first}, child_complexity -> first * child_complexity + %{last: last}, child_complexity -> last * child_complexity + %{}, _child_complexity -> 0 + end) + end + end + end +end diff --git a/apps/block_scout_web/lib/block_scout_web/graphql/celo/schema/types.ex b/apps/block_scout_web/lib/block_scout_web/graphql/celo/schema/types.ex new file mode 100644 index 0000000..61419d0 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/graphql/celo/schema/types.ex @@ -0,0 +1,87 @@ +defmodule BlockScoutWeb.GraphQL.Celo.Schema.Types do + @moduledoc false + + use Absinthe.Schema.Notation + use Absinthe.Relay.Schema.Notation, :modern + + alias BlockScoutWeb.GraphQL.Celo.Resolvers.TokenTransfer + + @desc """ + Represents a CELO or usd token transfer between addresses. + """ + node object(:celo_transfer, id_fetcher: &celo_transfer_id_fetcher/2) do + field(:value, :decimal) + field(:token, :string) + field(:token_address, :string) + field(:token_type, :string) + field(:token_id, :decimal) + field(:block_number, :integer) + field(:from_address_hash, :address_hash) + field(:to_address_hash, :address_hash) + field(:transaction_hash, :full_hash) + + field(:log_index, :integer) + + field(:gas_price, :wei) + field(:gas_used, :decimal) + field(:input, :string) + field(:timestamp, :datetime) + field(:comment, :string) + + field(:to_account_hash, :address_hash) + field(:from_account_hash, :address_hash) + end + + @desc """ + Represents a CELO token transfer between addresses. + """ + node object(:transfer_transaction, id_fetcher: &transfer_transaction_id_fetcher/2) do + field(:gateway_fee_recipient, :address_hash) + field(:gateway_fee, :address_hash) + field(:fee_currency, :address_hash) + field(:fee_token, :string) + field(:address_hash, :address_hash) + field(:transaction_hash, :full_hash) + field(:block_number, :integer) + field(:gas_price, :wei) + field(:gas_used, :decimal) + field(:input, :string) + field(:timestamp, :datetime) + + connection field(:token_transfer, node_type: :celo_transfer) do + arg(:count, :integer) + resolve(&TokenTransfer.get_by/3) + + complexity(fn + %{first: first}, child_complexity -> + first * child_complexity + + %{last: last}, child_complexity -> + last * child_complexity + end) + end + end + + connection(node_type: :transfer_transaction) + connection(node_type: :celo_transfer) + + defp transfer_transaction_id_fetcher( + %{transaction_hash: transaction_hash, address_hash: address_hash}, + _ + ) do + Jason.encode!(%{ + transaction_hash: to_string(transaction_hash), + address_hash: to_string(address_hash) + }) + end + + defp celo_transfer_id_fetcher( + %{transaction_hash: transaction_hash, log_index: log_index}, + _ + ) do + Jason.encode!(%{ + transaction_hash: to_string(transaction_hash), + log_index: log_index + }) + end +end diff --git a/apps/block_scout_web/lib/block_scout_web/graphql/middleware/api_enabled.ex b/apps/block_scout_web/lib/block_scout_web/graphql/middleware/api_enabled.ex new file mode 100644 index 0000000..dfe2bdb --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/graphql/middleware/api_enabled.ex @@ -0,0 +1,19 @@ +defmodule BlockScoutWeb.GraphQL.Middleware.ApiEnabled do + @moduledoc """ + Middleware to check if the GraphQL API is enabled. + """ + alias Absinthe.Resolution + + @behaviour Absinthe.Middleware + + @api_is_disabled "GraphQL API is disabled." + + def call(resolution, _config) do + if resolution.context.api_enabled do + resolution + else + resolution + |> Resolution.put_result({:error, @api_is_disabled}) + end + end +end diff --git a/apps/block_scout_web/lib/block_scout_web/graphql/resolvers/address.ex b/apps/block_scout_web/lib/block_scout_web/graphql/resolvers/address.ex index 4a94499..1cbc324 100644 --- a/apps/block_scout_web/lib/block_scout_web/graphql/resolvers/address.ex +++ b/apps/block_scout_web/lib/block_scout_web/graphql/resolvers/address.ex @@ -1,28 +1,19 @@ defmodule BlockScoutWeb.GraphQL.Resolvers.Address do @moduledoc false - alias BlockScoutWeb.GraphQL.Resolvers.Helper alias Explorer.Chain - def get_by(_, %{hashes: hashes}, resolution) do - if resolution.context.api_enabled do - case Chain.hashes_to_addresses(hashes) do - [] -> {:error, "Addresses not found."} - result -> {:ok, result} - end - else - {:error, Helper.api_is_disabled()} + def get_by(_, %{hashes: hashes}, _) do + case Chain.hashes_to_addresses(hashes) do + [] -> {:error, "Addresses not found."} + result -> {:ok, result} end end - def get_by(_, %{hash: hash}, resolution) do - if resolution.context.api_enabled do - case Chain.hash_to_address(hash) do - {:error, :not_found} -> {:error, "Address not found."} - {:ok, _} = result -> result - end - else - {:error, Helper.api_is_disabled()} + def get_by(_, %{hash: hash}, _) do + case Chain.hash_to_address(hash) do + {:error, :not_found} -> {:error, "Address not found."} + {:ok, _} = result -> result end end end diff --git a/apps/block_scout_web/lib/block_scout_web/graphql/resolvers/block.ex b/apps/block_scout_web/lib/block_scout_web/graphql/resolvers/block.ex index 913ae80..1b03f48 100644 --- a/apps/block_scout_web/lib/block_scout_web/graphql/resolvers/block.ex +++ b/apps/block_scout_web/lib/block_scout_web/graphql/resolvers/block.ex @@ -1,16 +1,32 @@ defmodule BlockScoutWeb.GraphQL.Resolvers.Block do @moduledoc false - alias BlockScoutWeb.GraphQL.Resolvers.Helper alias Explorer.Chain + alias Explorer.Chain.Transaction - def get_by(_, %{number: number}, resolution) do - with {:api_enabled, true} <- {:api_enabled, resolution.context.api_enabled}, - {:ok, _} = result <- Chain.number_to_block(number) do - result - else - {:api_enabled, false} -> {:error, Helper.api_is_disabled()} - {:error, :not_found} -> {:error, "Block number #{number} was not found."} + @api_true [api?: true] + + def get_by(_, %{number: number}, _) do + number + |> Chain.number_to_block(@api_true) + |> case do + {:ok, _} = result -> + result + + {:error, :not_found} -> + {:error, "Block number #{number} was not found."} + end + end + + def get_by(%Transaction{block_hash: hash}, _, _) do + hash + |> Chain.hash_to_block(@api_true) + |> case do + {:ok, _} = result -> + result + + {:error, :not_found} -> + {:error, "Block hash #{to_string(hash)} was not found."} end end end diff --git a/apps/block_scout_web/lib/block_scout_web/graphql/resolvers/helper.ex b/apps/block_scout_web/lib/block_scout_web/graphql/resolvers/helper.ex deleted file mode 100644 index 215e713..0000000 --- a/apps/block_scout_web/lib/block_scout_web/graphql/resolvers/helper.ex +++ /dev/null @@ -1,9 +0,0 @@ -defmodule BlockScoutWeb.GraphQL.Resolvers.Helper do - @moduledoc """ - Helper functions for BlockScoutWeb.GraphQL.Resolvers modules - """ - - @api_is_disabled "GraphQL API is disabled." - - def api_is_disabled, do: @api_is_disabled -end diff --git a/apps/block_scout_web/lib/block_scout_web/graphql/resolvers/internal_transaction.ex b/apps/block_scout_web/lib/block_scout_web/graphql/resolvers/internal_transaction.ex index 647ae0b..14e0093 100644 --- a/apps/block_scout_web/lib/block_scout_web/graphql/resolvers/internal_transaction.ex +++ b/apps/block_scout_web/lib/block_scout_web/graphql/resolvers/internal_transaction.ex @@ -2,26 +2,17 @@ defmodule BlockScoutWeb.GraphQL.Resolvers.InternalTransaction do @moduledoc false alias Absinthe.Relay.Connection - alias BlockScoutWeb.GraphQL.Resolvers.Helper alias Explorer.Chain.Transaction alias Explorer.{GraphQL, Repo} - def get_by(%{transaction_hash: _, index: _} = args, resolution) do - if resolution.context.api_enabled do - GraphQL.get_internal_transaction(args) - else - {:error, Helper.api_is_disabled()} - end + def get_by(%{transaction_hash: _, index: _} = args, _) do + GraphQL.get_internal_transaction(args) end - def get_by(%Transaction{} = transaction, args, resolution) do - if resolution.context.api_enabled do - transaction - |> GraphQL.transaction_to_internal_transactions_query() - |> Connection.from_query(&Repo.all/1, args, options(args)) - else - {:error, Helper.api_is_disabled()} - end + def get_by(%Transaction{} = transaction, args, _) do + transaction + |> GraphQL.transaction_to_internal_transactions_query() + |> Connection.from_query(&Repo.all/1, args, options(args)) end defp options(%{before: _}), do: [] diff --git a/apps/block_scout_web/lib/block_scout_web/graphql/resolvers/token.ex b/apps/block_scout_web/lib/block_scout_web/graphql/resolvers/token.ex new file mode 100644 index 0000000..55092ec --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/graphql/resolvers/token.ex @@ -0,0 +1,10 @@ +defmodule BlockScoutWeb.GraphQL.Resolvers.Token do + @moduledoc false + + alias Explorer.Chain.TokenTransfer + alias Explorer.GraphQL + + def get_by(%TokenTransfer{token_contract_address_hash: token_contract_address_hash}, _, _) do + GraphQL.get_token(%{contract_address_hash: token_contract_address_hash}) + end +end diff --git a/apps/block_scout_web/lib/block_scout_web/graphql/resolvers/token_transfer.ex b/apps/block_scout_web/lib/block_scout_web/graphql/resolvers/token_transfer.ex index 2fe2a05..74fb50b 100644 --- a/apps/block_scout_web/lib/block_scout_web/graphql/resolvers/token_transfer.ex +++ b/apps/block_scout_web/lib/block_scout_web/graphql/resolvers/token_transfer.ex @@ -2,27 +2,27 @@ defmodule BlockScoutWeb.GraphQL.Resolvers.TokenTransfer do @moduledoc false alias Absinthe.Relay.Connection - alias BlockScoutWeb.GraphQL.Resolvers.Helper + alias Explorer.Chain.{Address, TokenTransfer} alias Explorer.{GraphQL, Repo} - def get_by(%{transaction_hash: _, log_index: _} = args, resolution) do - if resolution.context.api_enabled do - GraphQL.get_token_transfer(args) - else - {:error, Helper.api_is_disabled()} - end + def get_by(%{transaction_hash: _, log_index: _} = args, _) do + GraphQL.get_token_transfer(args) end - def get_by(_, %{token_contract_address_hash: token_contract_address_hash} = args, resolution) do - if resolution.context.api_enabled do - connection_args = Map.take(args, [:after, :before, :first, :last]) + def get_by(_, %{token_contract_address_hash: token_contract_address_hash} = args, _) do + connection_args = Map.take(args, [:after, :before, :first, :last]) - token_contract_address_hash - |> GraphQL.list_token_transfers_query() - |> Connection.from_query(&Repo.all/1, connection_args, options(args)) - else - {:error, Helper.api_is_disabled()} - end + token_contract_address_hash + |> GraphQL.list_token_transfers_query() + |> Connection.from_query(&Repo.replica().all/1, connection_args, options(args)) + end + + def get_by(%Address{hash: address_hash}, args, _) do + connection_args = Map.take(args, [:after, :before, :first, :last]) + + address_hash + |> TokenTransfer.token_transfers_by_address_hash(nil, [], nil) + |> Connection.from_query(&Repo.replica().all/1, connection_args, options(args)) end defp options(%{before: _}), do: [] diff --git a/apps/block_scout_web/lib/block_scout_web/graphql/resolvers/transaction.ex b/apps/block_scout_web/lib/block_scout_web/graphql/resolvers/transaction.ex index 2dd299a..c1406ee 100644 --- a/apps/block_scout_web/lib/block_scout_web/graphql/resolvers/transaction.ex +++ b/apps/block_scout_web/lib/block_scout_web/graphql/resolvers/transaction.ex @@ -2,32 +2,23 @@ defmodule BlockScoutWeb.GraphQL.Resolvers.Transaction do @moduledoc false alias Absinthe.Relay.Connection - alias BlockScoutWeb.GraphQL.Resolvers.Helper - alias Explorer.{Chain, GraphQL, Repo} - alias Explorer.Chain.Address - - def get_by(_, %{hash: hash}, resolution) do - with {:api_enabled, true} <- {:api_enabled, resolution.context.api_enabled}, - {:ok, transaction} <- Chain.hash_to_transaction(hash) do - {:ok, transaction} - else - {:api_enabled, false} -> {:error, Helper.api_is_disabled()} - {:error, :not_found} -> {:error, "Transaction not found."} - end - end + alias Explorer.{GraphQL, Repo} + alias Explorer.Chain.{Address, TokenTransfer} + + def get_by(_, %{hash: hash}, _), + do: GraphQL.get_transaction_by_hash(hash) - def get_by(%Address{hash: address_hash}, args, resolution) do + def get_by(%Address{hash: address_hash}, args, _) do connection_args = Map.take(args, [:after, :before, :first, :last]) - if resolution.context.api_enabled do - address_hash - |> GraphQL.address_to_transactions_query(args.order) - |> Connection.from_query(&Repo.all/1, connection_args, options(args)) - else - {:error, Helper.api_is_disabled()} - end + address_hash + |> GraphQL.address_to_transactions_query(args.order) + |> Connection.from_query(&Repo.replica().all/1, connection_args, options(args)) end + def get_by(%TokenTransfer{transaction_hash: hash}, _, _), + do: GraphQL.get_transaction_by_hash(hash) + defp options(%{before: _}), do: [] defp options(%{count: count}), do: [count: count] diff --git a/apps/block_scout_web/lib/block_scout_web/graphql/schema.ex b/apps/block_scout_web/lib/block_scout_web/graphql/schema.ex index 91b11a6..2a01ac5 100644 --- a/apps/block_scout_web/lib/block_scout_web/graphql/schema.ex +++ b/apps/block_scout_web/lib/block_scout_web/graphql/schema.ex @@ -4,9 +4,11 @@ defmodule BlockScoutWeb.GraphQL.Schema do use Absinthe.Schema use Absinthe.Relay.Schema, :modern - alias Absinthe.Middleware.Dataloader, as: AbsintheMiddlewareDataloader + alias Absinthe.Middleware.Dataloader, as: AbsintheDataloaderMiddleware alias Absinthe.Plugin, as: AbsinthePlugin + alias BlockScoutWeb.GraphQL.Middleware.ApiEnabled, as: ApiEnabledMiddleware + alias BlockScoutWeb.GraphQL.Resolvers.{ Address, Block, @@ -22,6 +24,10 @@ defmodule BlockScoutWeb.GraphQL.Schema do import_types(BlockScoutWeb.GraphQL.Schema.Types) + if Application.compile_env(:explorer, :chain_type) == :celo do + import_types(BlockScoutWeb.GraphQL.Celo.Schema.Types) + end + node interface do resolve_type(fn %ExplorerChainInternalTransaction{}, _ -> @@ -100,6 +106,13 @@ defmodule BlockScoutWeb.GraphQL.Schema do arg(:hash, non_null(:full_hash)) resolve(&Transaction.get_by/3) end + + if Application.compile_env(:explorer, :chain_type) == :celo do + require BlockScoutWeb.GraphQL.Celo.QueryFields + alias BlockScoutWeb.GraphQL.Celo.QueryFields + + QueryFields.generate() + end end subscription do @@ -125,7 +138,11 @@ defmodule BlockScoutWeb.GraphQL.Schema do end end + def middleware(middleware, _field, _object) do + [ApiEnabledMiddleware | middleware] + end + def plugins do - [AbsintheMiddlewareDataloader] ++ AbsinthePlugin.defaults() + [AbsintheDataloaderMiddleware] ++ AbsinthePlugin.defaults() end end diff --git a/apps/block_scout_web/lib/block_scout_web/graphql/schema/types.ex b/apps/block_scout_web/lib/block_scout_web/graphql/schema/types.ex index 192d38d..f9fdee7 100644 --- a/apps/block_scout_web/lib/block_scout_web/graphql/schema/types.ex +++ b/apps/block_scout_web/lib/block_scout_web/graphql/schema/types.ex @@ -1,3 +1,66 @@ +defmodule BlockScoutWeb.GraphQL.Schema.Transaction do + @moduledoc false + + alias BlockScoutWeb.GraphQL.Resolvers.{Block, InternalTransaction} + + case Application.compile_env(:explorer, :chain_type) do + :celo -> + @chain_type_fields quote( + do: [ + field(:gas_token_contract_address_hash, :address_hash) + ] + ) + + _ -> + @chain_type_fields quote(do: []) + end + + defmacro generate do + quote do + node object(:transaction, id_fetcher: &transaction_id_fetcher/2) do + 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) + field(:r, :decimal) + field(:s, :decimal) + 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_transactions, :boolean) + + field :block, :block do + resolve(&Block.get_by/3) + end + + connection field(:internal_transactions, node_type: :internal_transaction) do + arg(:count, :integer) + resolve(&InternalTransaction.get_by/3) + + complexity(fn params, child_complexity -> process_complexity(params, child_complexity) end) + end + + unquote_splicing(@chain_type_fields) + end + end + end +end + defmodule BlockScoutWeb.GraphQL.Schema.SmartContracts do @moduledoc false case Application.compile_env(:explorer, :chain_type) do @@ -42,7 +105,7 @@ end defmodule BlockScoutWeb.GraphQL.Schema.Types do @moduledoc false - require BlockScoutWeb.GraphQL.Schema.SmartContracts + require BlockScoutWeb.GraphQL.Schema.{Transaction, SmartContracts} use Absinthe.Schema.Notation use Absinthe.Relay.Schema.Notation, :modern @@ -50,11 +113,13 @@ defmodule BlockScoutWeb.GraphQL.Schema.Types do import Absinthe.Resolution.Helpers alias BlockScoutWeb.GraphQL.Resolvers.{ - InternalTransaction, + Token, + TokenTransfer, Transaction } alias BlockScoutWeb.GraphQL.Schema.SmartContracts, as: SmartContractsSchema + alias BlockScoutWeb.GraphQL.Schema.Transaction, as: TransactionSchema import_types(Absinthe.Type.Custom) import_types(BlockScoutWeb.GraphQL.Schema.Scalars) @@ -87,6 +152,13 @@ defmodule BlockScoutWeb.GraphQL.Schema.Types do complexity(fn params, child_complexity -> process_complexity(params, child_complexity) end) end + + connection field(:token_transfers, node_type: :token_transfer) do + arg(:count, :integer) + resolve(&TokenTransfer.get_by/3) + + complexity(fn params, child_complexity -> process_complexity(params, child_complexity) end) + end end @desc """ @@ -160,46 +232,37 @@ defmodule BlockScoutWeb.GraphQL.Schema.Types do field(:to_address_hash, :address_hash) field(:token_contract_address_hash, :address_hash) field(:transaction_hash, :full_hash) + + field :transaction, :transaction do + resolve(&Transaction.get_by/3) + end + + field :token, :token do + resolve(&Token.get_by/3) + end end @desc """ - Models a Web3 transaction. + Represents a token. """ - node object(:transaction, id_fetcher: &transaction_id_fetcher/2) do - 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) - field(:r, :decimal) - field(:s, :decimal) - 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 params, child_complexity -> process_complexity(params, child_complexity) end) - end + object :token do + field(:name, :string) + field(:symbol, :string) + field(:total_supply, :decimal) + field(:decimals, :decimal) + field(:type, :string) + field(:holder_count, :integer) + field(:circulating_market_cap, :decimal) + field(:icon_url, :string) + field(:volume_24h, :decimal) + field(:contract_address_hash, :address_hash) end + @desc """ + Models a Web3 transaction. + """ + TransactionSchema.generate() + def token_transfer_id_fetcher(%{transaction_hash: transaction_hash, log_index: log_index}, _) do Jason.encode!(%{transaction_hash: to_string(transaction_hash), log_index: log_index}) 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 index 591598f..17d732b 100644 --- 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 @@ -3,12 +3,13 @@ defmodule BlockScoutWeb.MicroserviceInterfaces.TransactionInterpretation do Module to interact with Transaction Interpretation Service """ - alias BlockScoutWeb.API.V2.{Helper, TokenView, TransactionView} + alias BlockScoutWeb.API.V2.{Helper, InternalTransactionView, TokenTransferView, TokenView, TransactionView} alias Ecto.Association.NotLoaded alias Explorer.Chain alias Explorer.Chain.{Data, InternalTransaction, Log, TokenTransfer, Transaction} alias HTTPoison.Response + import Explorer.MicroserviceInterfaces.Metadata, only: [maybe_preload_metadata: 1] import Explorer.Utility.Microservice, only: [base_url: 2, check_enabled: 2] require Logger @@ -19,9 +20,9 @@ defmodule BlockScoutWeb.MicroserviceInterfaces.TransactionInterpretation do @items_limit 50 @internal_transaction_necessity_by_association [ necessity_by_association: %{ - [created_contract_address: [:names, :smart_contract, :proxy_implementations]] => :optional, - [from_address: [:names, :smart_contract, :proxy_implementations]] => :optional, - [to_address: [:names, :smart_contract, :proxy_implementations]] => :optional + [created_contract_address: [:scam_badge, :names, :smart_contract, :proxy_implementations]] => :optional, + [from_address: [:scam_badge, :names, :smart_contract, :proxy_implementations]] => :optional, + [to_address: [:scam_badge, :names, :smart_contract, :proxy_implementations]] => :optional } ] @@ -56,7 +57,7 @@ defmodule BlockScoutWeb.MicroserviceInterfaces.TransactionInterpretation do end @doc """ - Build the request body as for the tx interpreter POST request. + Build the request body as for the transaction interpreter POST request. """ @spec get_request_body(Transaction.t()) :: map() def get_request_body(transaction) do @@ -64,7 +65,7 @@ defmodule BlockScoutWeb.MicroserviceInterfaces.TransactionInterpretation do end @doc """ - Build the request body as for the tx interpreter POST request. + Build the request body as for the transaction interpreter POST request. """ @spec get_user_op_request_body(map()) :: map() def get_user_op_request_body(user_op) do @@ -106,50 +107,77 @@ defmodule BlockScoutWeb.MicroserviceInterfaces.TransactionInterpretation do defp prepare_request_body(transaction) do transaction = Chain.select_repo(@api_true).preload(transaction, [ - :transaction_actions, :block, - to_address: [:names, :smart_contract], - from_address: [:names, :smart_contract], - created_contract_address: [:names, :token, :smart_contract] + to_address: [:scam_badge, :names, :smart_contract], + from_address: [:scam_badge, :names, :smart_contract], + created_contract_address: [:scam_badge, :names, :token, :smart_contract] ]) + token_transfers = transaction |> fetch_token_transfers() |> Enum.reverse() + internal_transactions = transaction |> fetch_internal_transactions() |> Enum.reverse() + logs = transaction |> fetch_logs() |> Enum.reverse() + + [transaction_with_meta | other_elements] = + ([transaction | token_transfers] ++ internal_transactions ++ logs) + |> maybe_preload_metadata() + + %{ + logs: logs_with_meta, + token_transfers: token_transfers_with_meta, + internal_transactions: internal_transactions_with_meta + } = + Enum.reduce(other_elements, %{logs: [], token_transfers: [], internal_transactions: []}, fn element, acc -> + case element do + %InternalTransaction{} -> + Map.put(acc, :internal_transactions, [element | acc.internal_transactions]) + + %TokenTransfer{} -> + Map.put(acc, :token_transfers, [element | acc.token_transfers]) + + %Log{} -> + Map.put(acc, :logs, [element | acc.logs]) + end + end) + skip_sig_provider? = false - {decoded_input, _abi_acc, _methods_acc} = Transaction.decoded_input_data(transaction, skip_sig_provider?, @api_true) + decoded_input = Transaction.decoded_input_data(transaction, skip_sig_provider?, @api_true) decoded_input_data = decoded_input |> Transaction.format_decoded_input() |> TransactionView.decoded_input() %{ data: %{ - to: Helper.address_with_info(nil, transaction.to_address, transaction.to_address_hash, true), + to: + Helper.address_with_info(nil, transaction_with_meta.to_address, transaction_with_meta.to_address_hash, true), from: Helper.address_with_info( nil, - transaction.from_address, - transaction.from_address_hash, + transaction_with_meta.from_address, + transaction_with_meta.from_address_hash, true ), - hash: transaction.hash, - type: transaction.type, - value: transaction.value, - method: Transaction.method_name(transaction, Transaction.format_decoded_input(decoded_input)), - status: transaction.status, - actions: TransactionView.transaction_actions(transaction.transaction_actions), - tx_types: TransactionView.tx_types(transaction), - raw_input: transaction.input, + hash: transaction_with_meta.hash, + type: transaction_with_meta.type, + value: transaction_with_meta.value, + method: Transaction.method_name(transaction_with_meta, Transaction.format_decoded_input(decoded_input)), + status: transaction_with_meta.status, + # todo: keep `tx_types` for compatibility with interpreter and remove when new interpreter is bound to `transaction_types` property + tx_types: TransactionView.transaction_types(transaction_with_meta), + transaction_types: TransactionView.transaction_types(transaction_with_meta), + raw_input: transaction_with_meta.input, decoded_input: decoded_input_data, - token_transfers: prepare_token_transfers(transaction, decoded_input), - internal_transactions: prepare_internal_transactions(transaction) + token_transfers: prepare_token_transfers(token_transfers_with_meta, decoded_input), + internal_transactions: prepare_internal_transactions(internal_transactions_with_meta, transaction_with_meta) }, - logs_data: %{items: prepare_logs(transaction)} + logs_data: %{items: prepare_logs(logs_with_meta, transaction_with_meta)} } end - defp prepare_token_transfers(transaction, decoded_input) do + defp fetch_token_transfers(transaction) do full_options = [ necessity_by_association: %{ - [from_address: [:names, :smart_contract, :proxy_implementations]] => :optional, - [to_address: [:names, :smart_contract, :proxy_implementations]] => :optional + [from_address: [:scam_badge, :names, :smart_contract, :proxy_implementations]] => :optional, + [to_address: [:scam_badge, :names, :smart_contract, :proxy_implementations]] => :optional } ] |> Keyword.merge(@api_true) @@ -158,10 +186,14 @@ defmodule BlockScoutWeb.MicroserviceInterfaces.TransactionInterpretation do |> 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_internal_transactions(transaction) do + defp prepare_token_transfers(token_transfers, decoded_input) do + token_transfers + |> Enum.map(&TokenTransferView.prepare_token_transfer(&1, nil, decoded_input)) + end + + defp fetch_internal_transactions(transaction) do full_options = @internal_transaction_necessity_by_association |> Keyword.merge(@api_true) @@ -169,7 +201,33 @@ defmodule BlockScoutWeb.MicroserviceInterfaces.TransactionInterpretation do transaction.hash |> InternalTransaction.transaction_to_internal_transactions(full_options) |> Enum.take(@items_limit) - |> Enum.map(&TransactionView.prepare_internal_transaction(&1, transaction.block)) + end + + defp prepare_internal_transactions(internal_transactions, transaction) do + internal_transactions + |> Enum.map(&InternalTransactionView.prepare_internal_transaction(&1, transaction.block)) + end + + defp fetch_logs(transaction) do + full_options = + [ + necessity_by_association: %{ + [address: [:names, :smart_contract, :proxy_implementations]] => :optional + } + ] + |> Keyword.merge(@api_true) + + transaction.hash + |> Chain.transaction_to_logs(full_options) + |> Enum.take(@items_limit) + end + + defp prepare_logs(logs, transaction) do + decoded_logs = TransactionView.decode_logs(logs, false) + + logs + |> Enum.zip(decoded_logs) + |> Enum.map(fn {log, decoded_log} -> TransactionView.prepare_log(log, transaction.hash, decoded_log, true) end) end defp user_op_to_logs_and_token_transfers(user_op, decoded_input) do @@ -196,8 +254,8 @@ defmodule BlockScoutWeb.MicroserviceInterfaces.TransactionInterpretation do token_transfer_options = [ necessity_by_association: %{ - [from_address: [:names, :smart_contract, :proxy_implementations]] => :optional, - [to_address: [:names, :smart_contract, :proxy_implementations]] => :optional, + [from_address: [:scam_badge, :names, :smart_contract, :proxy_implementations]] => :optional, + [to_address: [:scam_badge, :names, :smart_contract, :proxy_implementations]] => :optional, :token => :optional } ] @@ -208,32 +266,11 @@ defmodule BlockScoutWeb.MicroserviceInterfaces.TransactionInterpretation do |> TokenTransfer.logs_to_token_transfers(token_transfer_options) |> Chain.flat_1155_batch_token_transfers() |> Enum.take(@items_limit) - |> Enum.map(&TransactionView.prepare_token_transfer(&1, nil, decoded_input)) + |> Enum.map(&TokenTransferView.prepare_token_transfer(&1, nil, decoded_input)) {prepared_logs, prepared_token_transfers} end - defp prepare_logs(transaction) do - full_options = - [ - necessity_by_association: %{ - [address: [:names, :smart_contract, :proxy_implementations]] => :optional - } - ] - |> Keyword.merge(@api_true) - - logs = - transaction.hash - |> Chain.transaction_to_logs(full_options) - |> Enum.take(@items_limit) - - decoded_logs = TransactionView.decode_logs(logs, false) - - 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 -> @@ -298,7 +335,7 @@ defmodule BlockScoutWeb.MicroserviceInterfaces.TransactionInterpretation do user_op_from = user_op["sender"] user_op_to = user_op["execute_target"] || user_op["sender"] - {mock_tx, decoded_input, decoded_input_json} = decode_user_op_calldata(user_op_hash, user_op_call_data) + {mock_transaction, decoded_input, decoded_input_json} = decode_user_op_calldata(user_op_hash, user_op_call_data) {prepared_logs, prepared_token_transfers} = user_op_to_logs_and_token_transfers(user_op, decoded_input) @@ -317,10 +354,10 @@ defmodule BlockScoutWeb.MicroserviceInterfaces.TransactionInterpretation do hash: user_op_hash, type: 0, value: "0", - method: Transaction.method_name(mock_tx, Transaction.format_decoded_input(decoded_input), true), + method: Transaction.method_name(mock_transaction, Transaction.format_decoded_input(decoded_input), true), status: user_op["status"], actions: [], - tx_types: [], + transaction_types: [], raw_input: user_op_call_data, decoded_input: decoded_input_json, token_transfers: prepared_token_transfers @@ -330,7 +367,7 @@ defmodule BlockScoutWeb.MicroserviceInterfaces.TransactionInterpretation do end @doc """ - Decodes user_op["call_data"] and return {mock_tx, decoded_input, decoded_input_json} + Decodes user_op["call_data"] and return {mock_transaction, decoded_input, decoded_input_json} """ @spec decode_user_op_calldata(binary(), binary() | nil) :: {Transaction.t(), tuple(), map()} | {nil, nil, nil} def decode_user_op_calldata(_user_op_hash, nil), do: {nil, nil, nil} @@ -340,7 +377,7 @@ defmodule BlockScoutWeb.MicroserviceInterfaces.TransactionInterpretation do {:ok, op_hash} = Chain.string_to_transaction_hash(user_op_hash) - mock_tx = %Transaction{ + mock_transaction = %Transaction{ to_address: %NotLoaded{}, input: input, hash: op_hash @@ -348,8 +385,9 @@ defmodule BlockScoutWeb.MicroserviceInterfaces.TransactionInterpretation do skip_sig_provider? = false - {decoded_input, _abi_acc, _methods_acc} = Transaction.decoded_input_data(mock_tx, skip_sig_provider?, @api_true) + decoded_input = Transaction.decoded_input_data(mock_transaction, skip_sig_provider?, @api_true) - {mock_tx, decoded_input, decoded_input |> Transaction.format_decoded_input() |> TransactionView.decoded_input()} + {mock_transaction, decoded_input, + decoded_input |> Transaction.format_decoded_input() |> TransactionView.decoded_input()} end end diff --git a/apps/block_scout_web/lib/block_scout_web/models/get_transaction_tags.ex b/apps/block_scout_web/lib/block_scout_web/models/get_transaction_tags.ex index 2f3ef3f..1aeed2d 100644 --- a/apps/block_scout_web/lib/block_scout_web/models/get_transaction_tags.ex +++ b/apps/block_scout_web/lib/block_scout_web/models/get_transaction_tags.ex @@ -7,15 +7,14 @@ defmodule BlockScoutWeb.Models.GetTransactionTags do alias Explorer.Account.TagTransaction alias Explorer.Chain.Transaction - alias Explorer.Repo def get_transaction_with_addresses_tags( %Transaction{} = transaction, %{id: identity_id, watchlist_id: watchlist_id} ) do - tx_tag = get_transaction_tags(transaction.hash, %{id: identity_id}) + transaction_tag = get_transaction_tags(transaction.hash, %{id: identity_id}) addresses_tags = get_addresses_tags_for_transaction(transaction, %{id: identity_id, watchlist_id: watchlist_id}) - Map.put(addresses_tags, :personal_tx_tag, tx_tag) + Map.put(addresses_tags, :personal_transaction_tag, transaction_tag) end def get_transaction_with_addresses_tags(%Transaction{} = transaction, _), @@ -23,11 +22,14 @@ defmodule BlockScoutWeb.Models.GetTransactionTags do common_tags: get_tags_on_address(transaction.to_address_hash), personal_tags: [], watchlist_names: [], - personal_tx_tag: nil + personal_transaction_tag: nil } def get_transaction_tags(transaction_hash, %{id: identity_id}) do - Repo.account_repo().get_by(TagTransaction, tx_hash_hash: transaction_hash, identity_id: identity_id) + case TagTransaction.get_tag_transaction_by_transaction_hash_and_identity_id(transaction_hash, identity_id) do + [tag | _] -> tag + _ -> nil + end end def get_transaction_tags(_, _), do: nil 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 3d96d69..4f1a6a2 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 @@ -8,7 +8,7 @@ defmodule BlockScoutWeb.Models.TransactionStateHelper do alias Explorer.Chain.Transaction.StateChange alias Explorer.{Chain, PagingOptions, Repo} - alias Explorer.Chain.{BlockNumberHelper, Transaction, Wei} + alias Explorer.Chain.{BlockNumberHelper, InternalTransaction, Transaction, Wei} alias Explorer.Chain.Cache.StateChanges alias Indexer.Fetcher.OnDemand.CoinBalance, as: CoinBalanceOnDemand alias Indexer.Fetcher.OnDemand.TokenBalance, as: TokenBalanceOnDemand @@ -55,7 +55,7 @@ defmodule BlockScoutWeb.Models.TransactionStateHelper do end defp do_state_changes(%Transaction{} = transaction, options) do - block_txs = + block_transactions = transaction.block_hash |> Chain.block_to_transactions( paging_options: %PagingOptions{key: nil, page_size: nil}, @@ -65,34 +65,35 @@ defmodule BlockScoutWeb.Models.TransactionStateHelper do |> Repo.preload([:token_transfers, :internal_transactions]) transaction = - block_txs + block_transactions |> Enum.find(&(&1.hash == transaction.hash)) |> Repo.preload( token_transfers: [ - from_address: [:names, :smart_contract, :proxy_implementations], - to_address: [:names, :smart_contract, :proxy_implementations] + from_address: [:scam_badge, :names, :smart_contract, :proxy_implementations], + to_address: [:scam_badge, :names, :smart_contract, :proxy_implementations] ], internal_transactions: [ - from_address: [:names, :smart_contract, :proxy_implementations], - to_address: [:names, :smart_contract, :proxy_implementations] + from_address: [:scam_badge, :names, :smart_contract, :proxy_implementations], + to_address: [:scam_badge, :names, :smart_contract, :proxy_implementations] ], block: [miner: [:names, :smart_contract, :proxy_implementations]], - from_address: [:names, :smart_contract, :proxy_implementations], - to_address: [:names, :smart_contract, :proxy_implementations] + from_address: [:scam_badge, :names, :smart_contract, :proxy_implementations], + to_address: [:scam_badge, :names, :smart_contract, :proxy_implementations] ) previous_block_number = BlockNumberHelper.previous_block_number(transaction.block_number) coin_balances_before_block = transaction_to_coin_balances(transaction, previous_block_number, options) - coin_balances_before_tx = StateChange.coin_balances_before(transaction, block_txs, coin_balances_before_block) + coin_balances_before_transaction = + StateChange.coin_balances_before(transaction, block_transactions, coin_balances_before_block) - native_coin_entries = StateChange.native_coin_entries(transaction, coin_balances_before_tx) + native_coin_entries = StateChange.native_coin_entries(transaction, coin_balances_before_transaction) token_balances_before = transaction.token_transfers |> Enum.reduce(%{}, &token_transfers_to_balances_reducer(&1, &2, previous_block_number, options)) - |> StateChange.token_balances_before(transaction, block_txs) + |> StateChange.token_balances_before(transaction, block_transactions) tokens_entries = StateChange.token_entries(transaction.token_transfers, token_balances_before) @@ -114,6 +115,8 @@ defmodule BlockScoutWeb.Models.TransactionStateHelper do ) end + defp internal_transaction_to_coin_balances(%InternalTransaction{call_type: :delegatecall}, _, _, acc), do: acc + defp internal_transaction_to_coin_balances(internal_transaction, previous_block_number, options, acc) do if internal_transaction.value |> Wei.to(:wei) |> Decimal.positive?() do acc diff --git a/apps/block_scout_web/lib/block_scout_web/models/user_from_auth.ex b/apps/block_scout_web/lib/block_scout_web/models/user_from_auth.ex deleted file mode 100644 index 2d09503..0000000 --- a/apps/block_scout_web/lib/block_scout_web/models/user_from_auth.ex +++ /dev/null @@ -1,143 +0,0 @@ -defmodule BlockScoutWeb.Models.UserFromAuth do - @moduledoc """ - Retrieve the user information from an auth request - """ - require Logger - require Poison - - alias Explorer.Account.Identity - alias Explorer.Repo - alias Ueberauth.Auth - - import Ecto.Query, only: [from: 2] - - def find_or_create(%Auth{} = auth) do - case find_identity(auth) do - nil -> - case create_identity(auth) do - %Identity{} = identity -> - {:ok, session_info(auth, identity)} - - {:error, changeset} -> - {:error, changeset} - end - - %{} = identity -> - update_identity(identity, update_identity_map(auth)) - {:ok, session_info(auth, identity)} - end - end - - defp create_identity(auth) do - with {:ok, %Identity{} = identity} <- Repo.account_repo().insert(new_identity(auth)), - {:ok, _watchlist} <- add_watchlist(identity) do - identity - end - end - - defp update_identity(identity, attrs) do - identity - |> Identity.changeset(attrs) - |> Repo.account_repo().update() - end - - defp new_identity(auth) do - %Identity{ - uid: auth.uid, - uid_hash: auth.uid, - email: email_from_auth(auth), - name: name_from_auth(auth), - nickname: nickname_from_auth(auth), - avatar: avatar_from_auth(auth) - } - end - - defp add_watchlist(identity) do - watchlist = Ecto.build_assoc(identity, :watchlists, %{}) - - with {:ok, _} <- Repo.account_repo().insert(watchlist), - do: {:ok, identity} - end - - def find_identity(auth_or_uid) do - Repo.account_repo().one(query_identity(auth_or_uid)) - end - - def query_identity(%Auth{} = auth) do - from(i in Identity, where: i.uid_hash == ^auth.uid) - end - - def query_identity(id) do - from(i in Identity, where: i.id == ^id) - end - - defp session_info( - %Auth{extra: %Ueberauth.Auth.Extra{raw_info: %{user: %{"email_verified" => false}}}} = auth, - identity - ) do - %{ - id: identity.id, - uid: auth.uid, - email: email_from_auth(auth), - nickname: nickname_from_auth(auth), - avatar: avatar_from_auth(auth), - email_verified: false - } - end - - defp session_info(auth, identity) do - %{watchlists: [watchlist | _]} = Repo.account_repo().preload(identity, :watchlists) - - %{ - id: identity.id, - uid: auth.uid, - email: email_from_auth(auth), - name: name_from_auth(auth), - nickname: nickname_from_auth(auth), - avatar: avatar_from_auth(auth), - watchlist_id: watchlist.id, - email_verified: true - } - end - - defp update_identity_map(auth) do - %{ - email: email_from_auth(auth), - name: name_from_auth(auth), - nickname: nickname_from_auth(auth), - avatar: avatar_from_auth(auth) - } - end - - # github does it this way - defp avatar_from_auth(%{info: %{urls: %{avatar_url: image}}}), do: image - - # facebook does it this way - defp avatar_from_auth(%{info: %{image: image}}), do: image - - # default case if nothing matches - defp avatar_from_auth(auth) do - Logger.warning(auth.provider <> " needs to find an avatar URL!") - Logger.debug(Poison.encode!(auth)) - nil - end - - defp email_from_auth(%{info: %{email: email}}), do: email - - defp nickname_from_auth(%{info: %{nickname: nickname}}), do: nickname - - defp name_from_auth(%{info: %{name: name}}) - when name != "" and not is_nil(name), - do: name - - defp name_from_auth(%{info: info}) do - [info.first_name, info.last_name, info.nickname] - |> Enum.map(&(&1 |> to_string() |> String.trim())) - |> case do - ["", "", nick] -> nick - ["", lastname, _] -> lastname - [name, "", _] -> name - [name, lastname, _] -> name <> " " <> lastname - end - 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 a25f812..8c353b6 100644 --- a/apps/block_scout_web/lib/block_scout_web/notifier.ex +++ b/apps/block_scout_web/lib/block_scout_web/notifier.ex @@ -163,7 +163,7 @@ defmodule BlockScoutWeb.Notifier do Endpoint.broadcast("transactions:#{transaction_hash}", "raw_trace", %{raw_trace_origin: transaction_hash}) end - # internal txs broadcast disabled on the indexer level, therefore it out of scope of the refactoring within https://github.com/blockscout/blockscout/pull/7474 + # internal transactions broadcast disabled on the indexer level, therefore it out of scope of the refactoring within https://github.com/blockscout/blockscout/pull/7474 def handle_event({:chain_event, :internal_transactions, :realtime, internal_transactions}) do internal_transactions |> Stream.map( @@ -181,8 +181,8 @@ defmodule BlockScoutWeb.Notifier do DenormalizationHelper.extend_transaction_preload([ :token, :transaction, - from_address: [:names, :smart_contract, :proxy_implementations], - to_address: [:names, :smart_contract, :proxy_implementations] + from_address: [:scam_badge, :names, :smart_contract, :proxy_implementations], + to_address: [:scam_badge, :names, :smart_contract, :proxy_implementations] ]) ) @@ -205,9 +205,9 @@ defmodule BlockScoutWeb.Notifier do def handle_event({:chain_event, :transactions, :realtime, transactions}) do base_preloads = [ :block, - created_contract_address: [:names, :smart_contract, :proxy_implementations], - from_address: [:names, :smart_contract, :proxy_implementations], - to_address: [:names, :smart_contract, :proxy_implementations] + created_contract_address: [:scam_badge, :names, :smart_contract, :proxy_implementations], + from_address: [:scam_badge, :names, :smart_contract, :proxy_implementations], + to_address: [:scam_badge, :names, :smart_contract, :proxy_implementations] ] preloads = if API_V2.enabled?(), do: [:token_transfers | base_preloads], else: base_preloads @@ -215,9 +215,9 @@ defmodule BlockScoutWeb.Notifier do transactions |> Repo.preload(preloads) |> broadcast_transactions_websocket_v2() - |> Enum.map(fn tx -> + |> Enum.map(fn transaction -> # Disable parsing of token transfers from websocket for transaction tab because we display token transfers at a separate tab - Map.put(tx, :token_transfers, []) + Map.put(transaction, :token_transfers, []) end) |> Enum.each(&broadcast_transaction/1) 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 25dec81..483e415 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 @@ -52,7 +52,7 @@ defmodule BlockScoutWeb.PagingHelper do def paging_options(%{"inserted_at" => inserted_at_string, "hash" => hash_string}, [:pending | _]) 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}] + [paging_options: %{@default_paging_options | key: {inserted_at, hash}, is_pending_transaction: true}] else _ -> [paging_options: @default_paging_options] @@ -274,8 +274,8 @@ defmodule BlockScoutWeb.PagingHelper 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("transactions_count", "asc"), do: [{:asc_nulls_first, :transactions_count, :address}] + defp do_smart_contracts_sorting("transactions_count", "desc"), do: [{:desc_nulls_last, :transactions_count, :address}] defp do_smart_contracts_sorting(_, _), do: [] @spec address_transactions_sorting(%{required(String.t()) => String.t()}) :: [ @@ -287,6 +287,8 @@ defmodule BlockScoutWeb.PagingHelper do def address_transactions_sorting(_), do: [] + defp do_address_transaction_sorting("block_number", "asc"), do: [asc: :block_number, asc: :index] + defp do_address_transaction_sorting("block_number", "desc"), do: [desc: :block_number, desc: :index] 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()}] @@ -334,4 +336,18 @@ defmodule BlockScoutWeb.PagingHelper do defp do_mud_records_sorting("key1", "asc"), do: [asc_nulls_first: :key1] defp do_mud_records_sorting("key1", "desc"), do: [desc_nulls_last: :key1] defp do_mud_records_sorting(_, _), do: [] + + @spec validators_blackfort_sorting(%{required(String.t()) => String.t()}) :: [ + {:sorting, SortingHelper.sorting_params()} + ] + def validators_blackfort_sorting(%{"sort" => sort_field, "order" => order}) do + [sorting: do_validators_blackfort_sorting(sort_field, order)] + end + + def validators_blackfort_sorting(_), do: [] + + defp do_validators_blackfort_sorting("address_hash", "asc"), do: [asc_nulls_first: :address_hash] + defp do_validators_blackfort_sorting("address_hash", "desc"), do: [desc_nulls_last: :address_hash] + + defp do_validators_blackfort_sorting(_, _), do: [] end diff --git a/apps/block_scout_web/lib/block_scout_web/routers/account_router.ex b/apps/block_scout_web/lib/block_scout_web/routers/account_router.ex index 6263aa6..2a915b9 100644 --- a/apps/block_scout_web/lib/block_scout_web/routers/account_router.ex +++ b/apps/block_scout_web/lib/block_scout_web/routers/account_router.ex @@ -4,7 +4,14 @@ defmodule BlockScoutWeb.Routers.AccountRouter do """ use BlockScoutWeb, :router - alias BlockScoutWeb.Account.Api.V2.{AuthenticateController, EmailController, TagsController, UserController} + alias BlockScoutWeb.Account.API.V2.{ + AddressController, + AuthenticateController, + EmailController, + TagsController, + UserController + } + alias BlockScoutWeb.Plug.{CheckAccountAPI, CheckAccountWeb} @max_query_string_length 5_000 @@ -117,8 +124,13 @@ defmodule BlockScoutWeb.Routers.AccountRouter do get("/get_csrf", UserController, :get_csrf) + scope "/address" do + post("/link", AddressController, :link_address) + end + scope "/email" do get("/resend", EmailController, :resend_email) + post("/link", EmailController, :link_email) end scope "/user" do @@ -170,4 +182,13 @@ defmodule BlockScoutWeb.Routers.AccountRouter do get("/transaction/:transaction_hash", TagsController, :tags_transaction) end end + + scope "/v2" do + pipe_through(:api) + + post("/authenticate_via_wallet", AuthenticateController, :authenticate_via_wallet) + post("/send_otp", AuthenticateController, :send_otp) + post("/confirm_otp", AuthenticateController, :confirm_otp) + get("/siwe_message", AuthenticateController, :siwe_message) + end end diff --git a/apps/block_scout_web/lib/block_scout_web/routers/address_badges_v2_router.ex b/apps/block_scout_web/lib/block_scout_web/routers/address_badges_v2_router.ex new file mode 100644 index 0000000..36edce1 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/routers/address_badges_v2_router.ex @@ -0,0 +1,58 @@ +# This file in ignore list of `sobelow`, be careful while adding new endpoints here +defmodule BlockScoutWeb.Routers.AddressBadgesApiV2Router do + @moduledoc """ + Router for /api/v2/scam-badge-addresses. This route has separate router in order to ignore sobelow's warning about missing CSRF protection + """ + use BlockScoutWeb, :router + alias BlockScoutWeb.API.V2 + alias BlockScoutWeb.Plug.{CheckApiV2, RateLimit} + + @max_query_string_length 5_000 + + pipeline :api_v2 do + plug( + Plug.Parsers, + parsers: [:urlencoded, :multipart, :json], + query_string_length: @max_query_string_length, + pass: ["*/*"], + json_decoder: Poison + ) + + plug(BlockScoutWeb.Plug.Logger, application: :api_v2) + plug(:accepts, ["json"]) + plug(CheckApiV2) + plug(:fetch_session) + plug(:protect_from_forgery) + plug(RateLimit) + end + + pipeline :api_v2_no_forgery_protect do + plug( + Plug.Parsers, + parsers: [:urlencoded, :multipart, :json], + length: 20_000_000, + query_string_length: 5_000, + pass: ["*/*"], + json_decoder: Poison + ) + + plug(BlockScoutWeb.Plug.Logger, application: :api_v2) + plug(:accepts, ["json"]) + plug(CheckApiV2) + plug(RateLimit) + plug(:fetch_session) + end + + scope "/", as: :api_v2 do + pipe_through(:api_v2_no_forgery_protect) + + post("/", V2.AddressBadgeController, :assign_badge_to_address) + delete("/", V2.AddressBadgeController, :unassign_badge_from_address) + end + + scope "/", as: :api_v2 do + pipe_through(:api_v2) + + get("/", V2.AddressBadgeController, :show_badge_addresses) + end +end diff --git a/apps/block_scout_web/lib/block_scout_web/routers/api_router.ex b/apps/block_scout_web/lib/block_scout_web/routers/api_router.ex index d0e0507..80038e0 100644 --- a/apps/block_scout_web/lib/block_scout_web/routers/api_router.ex +++ b/apps/block_scout_web/lib/block_scout_web/routers/api_router.ex @@ -14,7 +14,15 @@ defmodule BlockScoutWeb.Routers.ApiRouter do """ use BlockScoutWeb, :router alias BlockScoutWeb.AddressTransactionController - alias BlockScoutWeb.Routers.{APIKeyV2Router, SmartContractsApiV2Router, TokensApiV2Router, UtilsApiV2Router} + + alias BlockScoutWeb.Routers.{ + AddressBadgesApiV2Router, + APIKeyV2Router, + SmartContractsApiV2Router, + TokensApiV2Router, + UtilsApiV2Router + } + alias BlockScoutWeb.Plug.{CheckApiV2, RateLimit} alias BlockScoutWeb.Routers.AccountRouter @@ -25,6 +33,7 @@ defmodule BlockScoutWeb.Routers.ApiRouter do forward("/v2/key", APIKeyV2Router) forward("/v2/utils", UtilsApiV2Router) + forward("/v2/scam-badge-addresses", AddressBadgesApiV2Router) pipeline :api do plug( @@ -134,6 +143,10 @@ defmodule BlockScoutWeb.Routers.ApiRouter do get("/optimism-batch/:batch_number", V2.TransactionController, :optimism_batch) end + if Application.compile_env(:explorer, :chain_type) == :scroll do + get("/scroll-batch/:batch_number", V2.TransactionController, :scroll_batch) + end + if Application.compile_env(:explorer, :chain_type) == :suave do get("/execution-node/:execution_node_hash_param", V2.TransactionController, :execution_node) end @@ -151,6 +164,15 @@ defmodule BlockScoutWeb.Routers.ApiRouter do end end + scope "/token-transfers" do + get("/", V2.TokenTransferController, :token_transfers) + end + + # todo: enable this endpoint when DB index for underlying DB query will be installed. + # scope "/internal-transactions" do + # get("/", V2.InternalTransactionController, :internal_transactions) + # end + scope "/blocks" do get("/", V2.BlockController, :blocks) get("/:block_hash_or_number", V2.BlockController, :block) @@ -170,6 +192,10 @@ defmodule BlockScoutWeb.Routers.ApiRouter do if Application.compile_env(:explorer, :chain_type) == :optimism do get("/optimism-batch/:batch_number", V2.BlockController, :optimism_batch) end + + if Application.compile_env(:explorer, :chain_type) == :scroll do + get("/scroll-batch/:batch_number", V2.BlockController, :scroll_batch) + end end scope "/addresses" do @@ -234,9 +260,9 @@ defmodule BlockScoutWeb.Routers.ApiRouter do scope "/optimism" do if Application.compile_env(:explorer, :chain_type) == :optimism do - get("/txn-batches", V2.OptimismController, :txn_batches) - get("/txn-batches/count", V2.OptimismController, :txn_batches_count) - get("/txn-batches/:l2_block_range_start/:l2_block_range_end", V2.OptimismController, :txn_batches) + get("/txn-batches", V2.OptimismController, :transaction_batches) + get("/txn-batches/count", V2.OptimismController, :transaction_batches_count) + get("/txn-batches/:l2_block_range_start/:l2_block_range_end", V2.OptimismController, :transaction_batches) get("/batches", V2.OptimismController, :batches) get("/batches/count", V2.OptimismController, :batches_count) get("/batches/da/celestia/:height/:commitment", V2.OptimismController, :batch_by_celestia_blob) @@ -261,6 +287,18 @@ defmodule BlockScoutWeb.Routers.ApiRouter do end end + scope "/scroll" do + if Application.compile_env(:explorer, :chain_type) == :scroll do + get("/batches", V2.ScrollController, :batches) + get("/batches/count", V2.ScrollController, :batches_count) + get("/batches/:number", V2.ScrollController, :batch) + get("/deposits", V2.ScrollController, :deposits) + get("/deposits/count", V2.ScrollController, :deposits_count) + get("/withdrawals", V2.ScrollController, :withdrawals) + get("/withdrawals/count", V2.ScrollController, :withdrawals_count) + end + end + scope "/shibarium" do if Application.compile_env(:explorer, :chain_type) == :shibarium do get("/deposits", V2.ShibariumController, :deposits) @@ -315,6 +353,10 @@ defmodule BlockScoutWeb.Routers.ApiRouter do get("/wallets/:address_hash_param/portfolio", V2.Proxy.ZerionController, :wallet_portfolio) end + scope "/xname" do + get("/addresses/:address_hash_param", V2.Proxy.XnameController, :address) + end + scope "/metadata" do get("/addresses", V2.Proxy.MetadataController, :addresses) end @@ -327,11 +369,21 @@ defmodule BlockScoutWeb.Routers.ApiRouter do end scope "/validators" do - if Application.compile_env(:explorer, :chain_type) == :stability do - scope "/stability" do - get("/", V2.ValidatorController, :stability_validators_list) - get("/counters", V2.ValidatorController, :stability_validators_counters) - end + case Application.compile_env(:explorer, :chain_type) do + :stability -> + scope "/stability" do + get("/", V2.ValidatorController, :stability_validators_list) + get("/counters", V2.ValidatorController, :stability_validators_counters) + end + + :blackfort -> + scope "/blackfort" do + get("/", V2.ValidatorController, :blackfort_validators_list) + get("/counters", V2.ValidatorController, :blackfort_validators_counters) + end + + _ -> + nil end end @@ -348,6 +400,8 @@ defmodule BlockScoutWeb.Routers.ApiRouter do get("/worlds", V2.MudController, :worlds) get("/worlds/count", V2.MudController, :worlds_count) get("/worlds/:world/tables", V2.MudController, :world_tables) + get("/worlds/:world/systems", V2.MudController, :world_systems) + get("/worlds/:world/systems/:system", V2.MudController, :world_system) get("/worlds/:world/tables/count", V2.MudController, :world_tables_count) get("/worlds/:world/tables/:table_id/records", V2.MudController, :world_table_records) get("/worlds/:world/tables/:table_id/records/count", V2.MudController, :world_table_records_count) @@ -363,7 +417,12 @@ defmodule BlockScoutWeb.Routers.ApiRouter do get("/batches/count", V2.ArbitrumController, :batches_count) get("/batches/:batch_number", V2.ArbitrumController, :batch) get("/batches/da/anytrust/:data_hash", V2.ArbitrumController, :batch_by_data_availability_info) - get("/batches/da/celestia/:height/:tx_commitment", V2.ArbitrumController, :batch_by_data_availability_info) + + get( + "/batches/da/celestia/:height/:transaction_commitment", + V2.ArbitrumController, + :batch_by_data_availability_info + ) end end diff --git a/apps/block_scout_web/lib/block_scout_web/templates/account/tag_transaction/form.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/account/tag_transaction/form.html.eex index 1426bdd..6f6600f 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/account/tag_transaction/form.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/account/tag_transaction/form.html.eex @@ -8,9 +8,9 @@
<%= form_for @tag_transaction, tag_transaction_path(@conn, :create), fn f -> %>
- <%= label f, :tx_hash, gettext("Transaction"), class: "control-label", style: "font-size: 14px" %> - <%= text_input f, :tx_hash, class: "form-control", placeholder: "0x0000000000000000000000000000000000000000000000000000000000000000", maxlength: 66 %> - <%= error_tag f, :tx_hash, class: "text-danger form-error" %> + <%= label f, :transaction_hash, gettext("Transaction"), class: "control-label", style: "font-size: 14px" %> + <%= text_input f, :transaction_hash, class: "form-control", placeholder: "0x0000000000000000000000000000000000000000000000000000000000000000", maxlength: 66 %> + <%= error_tag f, :transaction_hash, class: "text-danger form-error" %> <%= error_tag f, :identity_id, class: "text-danger form-error" %>
diff --git a/apps/block_scout_web/lib/block_scout_web/templates/account/tag_transaction/index.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/account/tag_transaction/index.html.eex index 508d812..80d1f47 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/account/tag_transaction/index.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/account/tag_transaction/index.html.eex @@ -8,7 +8,7 @@
- <%= if @tx_tags == [] do %> + <%= if @transaction_tags == [] do %>
<%= gettext "You don't have transaction tags yet" %> @@ -25,15 +25,15 @@ - <%= Enum.map(@tx_tags, fn at -> - render("row.html", tx_tag: at, conn: @conn) + <%= Enum.map(@transaction_tags, fn at -> + render("row.html", transaction_tag: at, conn: @conn) end) %> <% end %>
- <%= if Enum.count(@tx_tags) < TagTransaction.get_max_tags_count() do %> + <%= if Enum.count(@transaction_tags) < TagTransaction.get_max_tags_count() do %> <%= gettext "Add transaction tag" %> <% end %>
diff --git a/apps/block_scout_web/lib/block_scout_web/templates/account/tag_transaction/row.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/account/tag_transaction/row.html.eex index 55151ae..0fad76a 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/account/tag_transaction/row.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/account/tag_transaction/row.html.eex @@ -1,18 +1,18 @@ -<%= if @tx_tag.tx_hash do %> +<%= if @transaction_tag.transaction_hash do %> - <%= @tx_tag.name %> + <%= @transaction_tag.name %>
- <%= link(@tx_tag.tx_hash, - to: transaction_path(BlockScoutWeb.Endpoint, :show, @tx_tag.tx_hash), + <%= link(@transaction_tag.transaction_hash, + to: transaction_path(BlockScoutWeb.Endpoint, :show, @transaction_tag.transaction_hash), "data-test": "transaction_hash_link", class: "text-truncate") %> <%= render BlockScoutWeb.CommonComponentsView, "_btn_copy.html", - additional_classes: ["btn-copy-icon-small", "btn-copy-icon-custom", "btn-copy-icon-no-borders"], clipboard_text: @tx_tag.tx_hash, aria_label: gettext("Copy Address"), title: gettext("Copy Address"), style: "display: inline-block; vertical-align: text-bottom; position: initial; margin-top: 1px;" %> + additional_classes: ["btn-copy-icon-small", "btn-copy-icon-custom", "btn-copy-icon-no-borders"], clipboard_text: @transaction_tag.transaction_hash, aria_label: gettext("Copy Address"), title: gettext("Copy Address"), style: "display: inline-block; vertical-align: text-bottom; position: initial; margin-top: 1px;" %>
- <%= link "Remove Tag", to: tag_transaction_path(@conn, :delete, @tx_tag.id), method: :delete %> + <%= link "Remove Tag", to: tag_transaction_path(@conn, :delete, @transaction_tag.id), method: :delete %> <% end %> 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 04cefd6..fa7c121 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 @@ -27,7 +27,7 @@ - <%= @tx_count %> + <%= @transaction_count %> <%= gettext "Transactions" %> diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address/_verify_other_explorer.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address/_verify_other_explorer.html.eex index 24a31bb..4f656c1 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/address/_verify_other_explorer.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/address/_verify_other_explorer.html.eex @@ -1,7 +1,7 @@ <%= if @type=="address" do %> <% else %> - + <% end %>
@@ -9,7 +9,7 @@ <%= if @type=="address" do %>
<%= address_link_to_other_explorer(@address_link, @hash ,true) %>
<% else %> -
<%= address_link_to_other_explorer(@tx_link, @hash ,true) %>
+
<%= address_link_to_other_explorer(@transaction_link, @hash ,true) %>
<% end %>
\ No newline at end of file diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address/_verify_other_explorer_modal.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address/_verify_other_explorer_modal.html.eex index 69ffb70..462ec8e 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/address/_verify_other_explorer_modal.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/address/_verify_other_explorer_modal.html.eex @@ -13,8 +13,8 @@ ) %> <% else %> <%= link( - address_link_to_other_explorer(@tx_link, @hash, false), - to: address_link_to_other_explorer(@tx_link, @hash ,true) + address_link_to_other_explorer(@transaction_link, @hash, false), + to: address_link_to_other_explorer(@transaction_link, @hash ,true) ) %> <% end %> diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address/_verify_other_explorers.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address/_verify_other_explorers.html.eex index 21afb57..ca0af97 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/address/_verify_other_explorers.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/address/_verify_other_explorers.html.eex @@ -3,9 +3,9 @@

Verify with other Explorers:

- <%= render "_verify_other_explorer.html", hash: @hash, type: @type, header: "Etherscan.io", class: "etherscan", address_link: "https://etherscan.io/address/", tx_link: "https://etherscan.io/tx/" %> - <%= render "_verify_other_explorer.html", hash: @hash, type: @type, header: "Blockchair.com", class: "blockchair", address_link: "https://blockchair.com/ethereum/address/", tx_link: "https://blockchair.com/ethereum/transaction/" %> - <%= render "_verify_other_explorer.html", hash: @hash, type: @type, header: "Etherchain.org", class: "etherchain", address_link: "https://www.etherchain.org/account/", tx_link: "https://www.etherchain.org/tx/" %> + <%= render "_verify_other_explorer.html", hash: @hash, type: @type, header: "Etherscan.io", class: "etherscan", address_link: "https://etherscan.io/address/", transaction_link: "https://etherscan.io/tx/" %> + <%= render "_verify_other_explorer.html", hash: @hash, type: @type, header: "Blockchair.com", class: "blockchair", address_link: "https://blockchair.com/ethereum/address/", transaction_link: "https://blockchair.com/ethereum/transaction/" %> + <%= render "_verify_other_explorer.html", hash: @hash, type: @type, header: "Etherchain.org", class: "etherchain", address_link: "https://www.etherchain.org/account/", transaction_link: "https://www.etherchain.org/tx/" %> @@ -26,9 +26,9 @@
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 807b11b..2096d38 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 @@ -43,7 +43,7 @@ :token_search, :name, class: "w-100 dropdown-search-field", - 'data-filter-dropdown-tokens': true, + "data-filter-dropdown-tokens": true, placeholder: gettext("Search tokens") ) %>
diff --git a/apps/block_scout_web/lib/block_scout_web/templates/layout/app.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/layout/app.html.eex index 57a85d7..e3ea973 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/layout/app.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/layout/app.html.eex @@ -94,7 +94,7 @@
<% end %> <% end %> - <% session = Explorer.Account.enabled?() && Plug.Conn.get_session(@conn, :current_user) %> + <% session = Explorer.Account.enabled?() && Map.get(@conn.private, :plug_session) && Plug.Conn.get_session(@conn, :current_user) %> <%= render BlockScoutWeb.LayoutView, "_topnav.html", current_user: session, conn: @conn %> <%= if session && !session[:email_verified] do %> diff --git a/apps/block_scout_web/lib/block_scout_web/templates/robots/sitemap.xml.eex b/apps/block_scout_web/lib/block_scout_web/templates/robots/sitemap.xml.eex index 55d8b1a..0e67d48 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/robots/sitemap.xml.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/robots/sitemap.xml.eex @@ -19,10 +19,10 @@ <% end %> - <% txs = Chain.recent_transactions(params, [:validated]) %> - <%= for tx <- txs do %> + <% transactions = Chain.recent_transactions(params, [:validated]) %> + <%= for transaction <- transactions do %> - <%= host %>/tx/<%= to_string(tx.hash) %> + <%= host %>/tx/<%= to_string(transaction.hash) %> <%= date %> <% end %> diff --git a/apps/block_scout_web/lib/block_scout_web/templates/search/_tile.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/search/_tile.html.eex index 85ccdff..981d1dd 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/search/_tile.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/search/_tile.html.eex @@ -1,4 +1,4 @@ --<%= if @result.block_hash, do: Base.encode16(@result.block_hash, case: :lower), else: "" %>"> +-<%= if @result.block_hash, do: Base.encode16(@result.block_hash, case: :lower), else: "" %>"> <%= render BlockScoutWeb.SearchView, "_empty_td.html" %> <%= case @result.type do %> @@ -79,7 +79,7 @@ <% "transaction" -> %> <%= render BlockScoutWeb.TransactionView, "_link.html", - transaction_hash: "0x" <> Base.encode16(@result.tx_hash, case: :lower) %> + transaction_hash: "0x" <> Base.encode16(@result.transaction_hash, case: :lower) %> <% "user_operation" -> %> <%= "0x" <> Base.encode16(@result.user_operation_hash, case: :lower) %> <% "blob" -> %> diff --git a/apps/block_scout_web/lib/block_scout_web/templates/transaction/_tile.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/transaction/_tile.html.eex index d7fa7e2..78118d1 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/transaction/_tile.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/transaction/_tile.html.eex @@ -1,13 +1,13 @@ <% status = transaction_status(@transaction) %> -<% error_in_internal_tx = @transaction.has_error_in_internal_txs %> +<% error_in_internal_transaction = @transaction.has_error_in_internal_transactions %> <% current_user = AuthController.current_user(@conn) %> -<% tx_tags = BlockScoutWeb.Models.GetTransactionTags.get_transaction_with_addresses_tags(@transaction, current_user) %> +<% transaction_tags = BlockScoutWeb.Models.GetTransactionTags.get_transaction_with_addresses_tags(@transaction, current_user) %>
- <%= if error_in_internal_tx do %> + <%= if error_in_internal_transaction do %> <%= render BlockScoutWeb.CommonComponentsView, "_i_tooltip_2.html", text: gettext("Error in internal transactions"), additional_classes: ["color-inherit"] %> <% end %> @@ -33,10 +33,10 @@ <%= if method_name do %> <%= render BlockScoutWeb.FormView, "_tag.html", text: method_name, additional_classes: ["method", "ml-1"] %> <% end %> - <%= if tx_tags.personal_tx_tag && tx_tags.personal_tx_tag.name !== :error do %> - <%= render BlockScoutWeb.FormView, "_tag.html", text: tx_tags.personal_tx_tag.name, additional_classes: [tag_name_to_label(tx_tags.personal_tx_tag.name), "ml-1"] %> + <%= if transaction_tags.personal_transaction_tag && transaction_tags.personal_transaction_tag.name !== :error do %> + <%= render BlockScoutWeb.FormView, "_tag.html", text: transaction_tags.personal_transaction_tag.name, additional_classes: [tag_name_to_label(transaction_tags.personal_transaction_tag.name), "ml-1"] %> <% end %> - <%= render BlockScoutWeb.AddressView, "_labels.html", tags: tx_tags %> + <%= render BlockScoutWeb.AddressView, "_labels.html", tags: transaction_tags %>
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 b529f94..611ed37 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 @@ -48,11 +48,11 @@

<%= gettext "Transaction Details" %> - <% personal_tx_tag = if assigns[:tx_tags], do: @tx_tags.personal_tx_tag, else: nil %> - <%= if personal_tx_tag && personal_tx_tag.name !== :error do %> - <%= render BlockScoutWeb.FormView, "_tag.html", text: personal_tx_tag.name, additional_classes: [tag_name_to_label(personal_tx_tag.name), "ml-1"] %> + <% personal_transaction_tag = if assigns[:transaction_tags], do: @transaction_tags.personal_transaction_tag, else: nil %> + <%= if personal_transaction_tag && personal_transaction_tag.name !== :error do %> + <%= render BlockScoutWeb.FormView, "_tag.html", text: personal_transaction_tag.name, additional_classes: [tag_name_to_label(personal_transaction_tag.name), "ml-1"] %> <% end %> - <%= render BlockScoutWeb.AddressView, "_labels.html", tags: @tx_tags %> + <%= render BlockScoutWeb.AddressView, "_labels.html", tags: @transaction_tags %>

<%= if status == :pending do %> 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 5fee550..12bde16 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 @@ -8,6 +8,7 @@ defmodule BlockScoutWeb.ABIEncodedValueView do use BlockScoutWeb, :view alias ABI.FunctionSelector + alias Explorer.Chain.{Address, Hash} alias Phoenix.HTML require Logger @@ -196,7 +197,10 @@ defmodule BlockScoutWeb.ABIEncodedValueView do end defp base_value_json(:address, value) do - hex_for_json(value) + case Hash.Address.cast(value) do + {:ok, address} -> Address.checksum(address) + :error -> "0x" + end end defp base_value_json(:bytes, value) do diff --git a/apps/block_scout_web/lib/block_scout_web/views/account/api/v2/account_view.ex b/apps/block_scout_web/lib/block_scout_web/views/account/api/v2/account_view.ex index 632b310..3a41871 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/account/api/v2/account_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/account/api/v2/account_view.ex @@ -1,4 +1,4 @@ -defmodule BlockScoutWeb.Account.Api.V2.AccountView do +defmodule BlockScoutWeb.Account.API.V2.AccountView do def render("message.json", %{message: message}) do %{ "message" => message diff --git a/apps/block_scout_web/lib/block_scout_web/views/account/api/v2/tags_view.ex b/apps/block_scout_web/lib/block_scout_web/views/account/api/v2/tags_view.ex index 8067000..06bb0ce 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/account/api/v2/tags_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/account/api/v2/tags_view.ex @@ -1,4 +1,4 @@ -defmodule BlockScoutWeb.Account.Api.V2.TagsView do +defmodule BlockScoutWeb.Account.API.V2.TagsView do def render("address_tags.json", %{tags_map: tags_map}) do tags_map end @@ -7,12 +7,14 @@ defmodule BlockScoutWeb.Account.Api.V2.TagsView do tags_map: %{ personal_tags: personal_tags, watchlist_names: watchlist_names, - personal_tx_tag: personal_tx_tag, + personal_transaction_tag: personal_transaction_tag, common_tags: common_tags } }) do %{ - personal_tx_tag: prepare_transaction_tag(personal_tx_tag), + personal_transaction_tag: prepare_transaction_tag(personal_transaction_tag), + # todo: keep next line for compatibility with frontend and remove when new frontend is bound to `personal_transaction_tag` property + personal_tx_tag: prepare_transaction_tag(personal_transaction_tag), personal_tags: personal_tags, watchlist_names: watchlist_names, common_tags: common_tags diff --git a/apps/block_scout_web/lib/block_scout_web/views/account/api/v2/user_view.ex b/apps/block_scout_web/lib/block_scout_web/views/account/api/v2/user_view.ex index bf6f869..f9b6a6a 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/account/api/v2/user_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/account/api/v2/user_view.ex @@ -1,5 +1,5 @@ -defmodule BlockScoutWeb.Account.Api.V2.UserView do - alias BlockScoutWeb.Account.Api.V2.AccountView +defmodule BlockScoutWeb.Account.API.V2.UserView do + alias BlockScoutWeb.Account.API.V2.AccountView alias BlockScoutWeb.API.V2.Helper alias Ecto.Changeset alias Explorer.Chain @@ -9,7 +9,13 @@ defmodule BlockScoutWeb.Account.Api.V2.UserView do end def render("user_info.json", %{identity: identity}) do - %{"name" => identity.name, "email" => identity.email, "avatar" => identity.avatar, "nickname" => identity.nickname} + %{ + "name" => identity.name, + "email" => identity.email, + "avatar" => identity.avatar, + "nickname" => identity.nickname, + "address_hash" => identity.address_hash + } end def render("watchlist_addresses.json", %{ @@ -161,7 +167,11 @@ defmodule BlockScoutWeb.Account.Api.V2.UserView do def prepare_transaction_tag(nil), do: nil def prepare_transaction_tag(transaction_tag) do - %{"id" => transaction_tag.id, "transaction_hash" => transaction_tag.tx_hash, "name" => transaction_tag.name} + %{ + "id" => transaction_tag.id, + "transaction_hash" => transaction_tag.transaction_hash, + "name" => transaction_tag.name + } end def prepare_public_tags_request(public_tags_request) do 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 efb3dd1..0b91ef2 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 @@ -118,14 +118,14 @@ defmodule BlockScoutWeb.AddressContractView do {:ok, contract_code} end - def creation_code(%Address{contracts_creation_internal_transaction: %InternalTransaction{}} = address) do - address.contracts_creation_internal_transaction.init - end - def creation_code(%Address{contracts_creation_transaction: %Transaction{}} = address) do address.contracts_creation_transaction.input end + def creation_code(%Address{contracts_creation_internal_transaction: %InternalTransaction{}} = address) do + address.contracts_creation_internal_transaction.init + end + def creation_code(%Address{contracts_creation_transaction: nil}) do nil end 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 a8016bd..fea2c32 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 @@ -9,7 +9,6 @@ defmodule BlockScoutWeb.AddressView do 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.Chain.SmartContract.Proxy.Models.Implementation alias Explorer.ExchangeRates.Token, as: TokenExchangeRate alias Explorer.SmartContract.{Helper, Writer} @@ -438,13 +437,6 @@ defmodule BlockScoutWeb.AddressView do end end - def smart_contract_is_gnosis_safe_proxy?(%Address{smart_contract: %SmartContract{}} = address) do - address.smart_contract.name == "GnosisSafeProxy" && - Proxy.gnosis_safe_contract?(address.smart_contract.abi) - end - - def smart_contract_is_gnosis_safe_proxy?(_address), do: false - def tag_name_to_label(tag_name) do tag_name |> String.replace(" ", "-") diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/eth_rpc/view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/eth_rpc/view.ex index d2b29c0..0e22ee5 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/api/eth_rpc/view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/api/eth_rpc/view.ex @@ -4,6 +4,8 @@ defmodule BlockScoutWeb.API.EthRPC.View do """ use BlockScoutWeb, :view + @jsonrpc_2_0 ~s("jsonrpc":"2.0") + defstruct [:result, :id, :error] def render("show.json", %{result: result, id: id}) do @@ -50,50 +52,64 @@ defmodule BlockScoutWeb.API.EthRPC.View do end) end - defimpl Poison.Encoder, for: BlockScoutWeb.API.EthRPC.View do - def encode(%BlockScoutWeb.API.EthRPC.View{result: result, id: id, error: error}, _options) when is_nil(error) do - result = Poison.encode!(result) + @doc """ + Encodes id into JSON string + """ + @spec sanitize_id(any()) :: non_neg_integer() | String.t() + def sanitize_id(id) do + if is_integer(id), do: id, else: "\"#{id}\"" + end - """ - {"jsonrpc":"2.0","result":#{result},"id":#{id}} - """ + @doc """ + Encodes error into JSON string + """ + @spec sanitize_error(any(), :jason | :poison) :: String.t() + def sanitize_error(error, json_encoder) do + case json_encoder do + :jason -> if is_map(error), do: Jason.encode!(error), else: "\"#{error}\"" + :poison -> if is_map(error), do: Poison.encode!(error), else: "\"#{error}\"" end + end + + @doc """ + Pass "jsonrpc":"2.0" to use in Poison.Encoder and Jason.Encoder below + """ + @spec jsonrpc_2_0() :: String.t() + def jsonrpc_2_0, do: @jsonrpc_2_0 - def encode(%BlockScoutWeb.API.EthRPC.View{id: id, error: error}, _options) when is_map(error) do - error = Poison.encode!(error) + defimpl Poison.Encoder, for: BlockScoutWeb.API.EthRPC.View do + alias BlockScoutWeb.API.EthRPC.View + + def encode(%View{result: result, id: id, error: error}, _options) when is_nil(error) do + result = Poison.encode!(result) """ - {"jsonrpc":"2.0","error": #{error},"id": #{id}} + {#{View.jsonrpc_2_0()},"result": #{result},"id": #{View.sanitize_id(id)}} """ end - def encode(%BlockScoutWeb.API.EthRPC.View{id: id, error: error}, _options) do + def encode(%View{id: id, error: error}, _options) do """ - {"jsonrpc":"2.0","error": "#{error}","id": #{id}} + {#{View.jsonrpc_2_0()},"error": #{View.sanitize_error(error, :poison)},"id": #{View.sanitize_id(id)}} """ end end defimpl Jason.Encoder, for: BlockScoutWeb.API.EthRPC.View do - def encode(%BlockScoutWeb.API.EthRPC.View{result: result, id: id, error: error}, _options) when is_nil(error) do - result = Jason.encode!(result) + # credo:disable-for-next-line + alias BlockScoutWeb.API.EthRPC.View - """ - {"jsonrpc":"2.0","result":#{result},"id":#{id}} - """ - end - - def encode(%BlockScoutWeb.API.EthRPC.View{id: id, error: error}, _options) when is_map(error) do - error = Jason.encode!(error) + def encode(%View{result: result, id: id, error: error}, _options) when is_nil(error) do + result = Jason.encode!(result) """ - {"jsonrpc":"2.0","error": #{error},"id": #{id}} + {#{View.jsonrpc_2_0()},"result": #{result},"id": #{View.sanitize_id(id)}} """ end - def encode(%BlockScoutWeb.API.EthRPC.View{id: id, error: error}, _options) do + def encode(%View{id: id, error: error}, _options) do """ - {"jsonrpc":"2.0","error": "#{error}","id": #{id}} + {#{View.jsonrpc_2_0()},"error": #{View.sanitize_error(error, :jason)},"id": #{View.sanitize_id(id)}} """ 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 eca2f51..5257899 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 @@ -3,6 +3,7 @@ defmodule BlockScoutWeb.API.RPC.AddressView do alias BlockScoutWeb.API.EthRPC.View, as: EthRPCView alias BlockScoutWeb.API.RPC.RPCView + alias Explorer.Chain.DenormalizationHelper def render("listaccounts.json", %{accounts: accounts}) do accounts = Enum.map(accounts, &prepare_account/1) @@ -210,9 +211,16 @@ defmodule BlockScoutWeb.API.RPC.AddressView do end defp prepare_nft_transfer(token_transfer, max_block_number) do + timestamp = + if DenormalizationHelper.tt_denormalization_finished?() do + to_string(DateTime.to_unix(token_transfer.transaction.block_timestamp)) + else + to_string(DateTime.to_unix(token_transfer.block.timestamp)) + end + %{ "blockNumber" => to_string(token_transfer.block_number), - "timeStamp" => to_string(DateTime.to_unix(token_transfer.block.timestamp)), + "timeStamp" => timestamp, "hash" => to_string(token_transfer.transaction_hash), "nonce" => to_string(token_transfer.transaction.nonce), "blockHash" => to_string(token_transfer.block_hash), 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 93a3413..8b8828b 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 @@ -267,13 +267,13 @@ defmodule BlockScoutWeb.API.RPC.ContractView do defp address_to_response(address) do creator_hash = AddressView.from_address_hash(address) - creation_tx = creator_hash && AddressView.transaction_hash(address) + creation_transaction = creator_hash && AddressView.transaction_hash(address) - creation_tx && + creation_transaction && %{ "contractAddress" => to_string(address.hash), "contractCreator" => to_string(creator_hash), - "txHash" => to_string(creation_tx) + "txHash" => to_string(creation_transaction) } end end 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 4a18643..74c8ba5 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 @@ -15,7 +15,7 @@ defmodule BlockScoutWeb.API.RPC.TransactionView do end def render("gettxreceiptstatus.json", %{status: status}) do - prepared_status = prepare_tx_receipt_status(status) + prepared_status = prepare_transaction_receipt_status(status) RPCView.render("show.json", data: %{"status" => prepared_status}) end @@ -27,13 +27,13 @@ defmodule BlockScoutWeb.API.RPC.TransactionView do RPCView.render("error.json", assigns) end - defp prepare_tx_receipt_status(""), do: "" + defp prepare_transaction_receipt_status(""), do: "" - defp prepare_tx_receipt_status(nil), do: "" + defp prepare_transaction_receipt_status(nil), do: "" - defp prepare_tx_receipt_status(:ok), do: "1" + defp prepare_transaction_receipt_status(:ok), do: "1" - defp prepare_tx_receipt_status(_), do: "0" + defp prepare_transaction_receipt_status(_), do: "0" defp prepare_error("") do %{ diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/v2/address_badge_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/v2/address_badge_view.ex new file mode 100644 index 0000000..d3eda45 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/views/api/v2/address_badge_view.ex @@ -0,0 +1,33 @@ +defmodule BlockScoutWeb.API.V2.AddressBadgeView do + use BlockScoutWeb, :view + + def render("badge_to_address.json", %{badge_to_address_list: badge_to_address_list, status: status}) do + prepare_badge_to_address(badge_to_address_list, status) + end + + def render("badge_to_address.json", %{badge_to_address_list: badge_to_address_list}) do + prepare_badge_to_address(badge_to_address_list) + end + + defp prepare_badge_to_address(badge_to_address_list) do + %{ + badge_to_address_list: format_badge_to_address_list(badge_to_address_list) + } + end + + defp prepare_badge_to_address(badge_to_address_list, status) do + %{ + badge_to_address_list: format_badge_to_address_list(badge_to_address_list), + status: status + } + end + + defp format_badge_to_address_list(badge_to_address_list) do + badge_to_address_list + |> Enum.map(fn badge_to_address -> + %{ + address_hash: "0x" <> Base.encode16(badge_to_address.address_hash.bytes) + } + end) + end +end 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 b39fe2f..90611f3 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 @@ -73,11 +73,17 @@ defmodule BlockScoutWeb.API.V2.AddressView do @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 + ) :: %{ + optional(:coin_balance) => any(), + optional(:transaction_count) => binary(), + optional(<<_::32, _::_*8>>) => any() + } + def prepare_address({address, transaction_count}) do nil |> Helper.address_with_info(address, address.hash, true) - |> Map.put(:tx_count, to_string(tx_count)) + # todo: keep `tx_count` for compatibility with frontend and remove when new frontend is bound to `transaction_count` property + |> Map.put(:tx_count, to_string(transaction_count)) + |> Map.put(:transaction_count, to_string(transaction_count)) |> Map.put(:coin_balance, if(address.fetched_coin_balance, do: address.fetched_coin_balance.value)) end @@ -91,14 +97,17 @@ defmodule BlockScoutWeb.API.V2.AddressView do balance = address.fetched_coin_balance && address.fetched_coin_balance.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) + creation_transaction = Address.creation_transaction(address) + creator_hash = creation_transaction && creation_transaction.from_address_hash + creation_transaction_hash = creator_hash && AddressView.transaction_hash(address) token = address.token && TokenView.render("token.json", %{token: address.token}) extended_info = Map.merge(base_info, %{ "creator_address_hash" => creator_hash && Address.checksum(creator_hash), - "creation_tx_hash" => creation_tx, + "creation_transaction_hash" => creation_transaction_hash, + # todo: keep next line for compatibility with frontend and remove when new frontend is bound to `creation_transaction_hash` property + "creation_tx_hash" => creation_transaction_hash, "token" => token, "coin_balance" => balance, "exchange_rate" => exchange_rate, @@ -112,14 +121,18 @@ defmodule BlockScoutWeb.API.V2.AddressView do "has_beacon_chain_withdrawals" => Counters.check_if_withdrawals_at_address(address.hash, @api_true) }) - if Enum.empty?(implementations) do - extended_info - else - Map.merge(extended_info, %{ - "proxy_type" => proxy_type, - "implementations" => implementations - }) - end + result = + if Enum.empty?(implementations) do + extended_info + else + Map.merge(extended_info, %{ + "proxy_type" => proxy_type, + "implementations" => implementations + }) + end + + result + |> chain_type_fields(%{address: creation_transaction && creation_transaction.from_address, field_prefix: "creator"}) end @spec prepare_token_balance(Chain.Address.TokenBalance.t(), boolean()) :: map() @@ -238,4 +251,17 @@ defmodule BlockScoutWeb.API.V2.AddressView do token: token }) end + + case Application.compile_env(:explorer, :chain_type) do + :filecoin -> + defp chain_type_fields(result, params) do + # credo:disable-for-next-line Credo.Check.Design.AliasUsage + BlockScoutWeb.API.V2.FilecoinView.put_filecoin_robust_address(result, params) + end + + _ -> + defp chain_type_fields(result, _params) do + result + end + end end diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/v2/advanced_filter_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/v2/advanced_filter_view.ex index eb602e3..cfa7c1f 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/api/v2/advanced_filter_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/api/v2/advanced_filter_view.ex @@ -1,7 +1,7 @@ defmodule BlockScoutWeb.API.V2.AdvancedFilterView do use BlockScoutWeb, :view - alias BlockScoutWeb.API.V2.{Helper, TokenView, TransactionView} + alias BlockScoutWeb.API.V2.{Helper, TokenTransferView, TokenView} alias Explorer.Chain.{Address, Data, Transaction} alias Explorer.Market alias Explorer.Market.MarketHistory @@ -152,7 +152,7 @@ defmodule BlockScoutWeb.API.V2.AdvancedFilterView do value: advanced_filter.value, total: if(advanced_filter.type != "coin_transfer", - do: TransactionView.prepare_token_transfer_total(advanced_filter.token_transfer), + do: TokenTransferView.prepare_token_transfer_total(advanced_filter.token_transfer), else: nil ), token: diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/v2/arbitrum_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/v2/arbitrum_view.ex index e41089b..bdce021 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/api/v2/arbitrum_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/api/v2/arbitrum_view.ex @@ -70,7 +70,7 @@ defmodule BlockScoutWeb.API.V2.ArbitrumView do "before_acc" => batch.before_acc, "after_acc" => batch.after_acc } - |> add_l1_tx_info(batch) + |> add_l1_transaction_info(batch) |> add_da_info(batch) end @@ -170,7 +170,7 @@ defmodule BlockScoutWeb.API.V2.ArbitrumView do "blocks_count" => batch.end_block - batch.start_block + 1, "batch_data_container" => batch.batch_container } - |> add_l1_tx_info(batch) + |> add_l1_transaction_info(batch) end @doc """ @@ -245,7 +245,7 @@ defmodule BlockScoutWeb.API.V2.ArbitrumView do }) :: map() defp extend_with_settlement_info(out_json, arbitrum_entity) do out_json - |> add_l1_txs_info_and_status(%{ + |> add_l1_transactions_info_and_status(%{ batch_number: get_batch_number(arbitrum_entity), commitment_transaction: arbitrum_entity.arbitrum_commitment_transaction, confirmation_transaction: arbitrum_entity.arbitrum_confirmation_transaction @@ -285,25 +285,25 @@ defmodule BlockScoutWeb.API.V2.ArbitrumView do end # Augments an output JSON with commit transaction details and its status. - @spec add_l1_tx_info(map(), %{ + @spec add_l1_transaction_info(map(), %{ :commitment_transaction => LifecycleTransaction.t() | LifecycleTransaction.to_import(), optional(any()) => any() }) :: map() - defp add_l1_tx_info(out_json, %L1Batch{} = batch) do - l1_tx = %{commitment_transaction: handle_associated_l1_txs_properly(batch.commitment_transaction)} + defp add_l1_transaction_info(out_json, %L1Batch{} = batch) do + l1_transaction = %{commitment_transaction: handle_associated_l1_transactions_properly(batch.commitment_transaction)} out_json |> Map.merge(%{ "commitment_transaction" => %{ - "hash" => APIV2Helper.get_2map_data(l1_tx, :commitment_transaction, :hash), - "block_number" => APIV2Helper.get_2map_data(l1_tx, :commitment_transaction, :block), - "timestamp" => APIV2Helper.get_2map_data(l1_tx, :commitment_transaction, :ts), - "status" => APIV2Helper.get_2map_data(l1_tx, :commitment_transaction, :status) + "hash" => APIV2Helper.get_2map_data(l1_transaction, :commitment_transaction, :hash), + "block_number" => APIV2Helper.get_2map_data(l1_transaction, :commitment_transaction, :block), + "timestamp" => APIV2Helper.get_2map_data(l1_transaction, :commitment_transaction, :ts), + "status" => APIV2Helper.get_2map_data(l1_transaction, :commitment_transaction, :status) } }) end - defp add_l1_tx_info(out_json, %{ + defp add_l1_transaction_info(out_json, %{ commitment_transaction: %{ hash: hash, block_number: block_number, @@ -419,34 +419,36 @@ defmodule BlockScoutWeb.API.V2.ArbitrumView do out |> Map.merge(%{ "height" => Map.get(da_info, "height"), - "tx_commitment" => Map.get(da_info, "tx_commitment") + # todo: keep next line for compatibility with frontend and remove when new frontend is bound to `transaction_count` property + "tx_commitment" => Map.get(da_info, "transaction_commitment"), + "transaction_commitment" => Map.get(da_info, "transaction_commitment") }) end # Augments an output JSON with commit and confirm transaction details and their statuses. - @spec add_l1_txs_info_and_status(map(), %{ + @spec add_l1_transactions_info_and_status(map(), %{ :commitment_transaction => any(), :confirmation_transaction => any(), optional(:batch_number) => any() }) :: map() - defp add_l1_txs_info_and_status(out_json, arbitrum_item) + defp add_l1_transactions_info_and_status(out_json, arbitrum_item) when is_map(arbitrum_item) and is_map_key(arbitrum_item, :commitment_transaction) and is_map_key(arbitrum_item, :confirmation_transaction) do - l1_txs = get_associated_l1_txs(arbitrum_item) + l1_transactions = get_associated_l1_transactions(arbitrum_item) out_json |> Map.merge(%{ "status" => block_or_transaction_status(arbitrum_item), "commitment_transaction" => %{ - "hash" => APIV2Helper.get_2map_data(l1_txs, :commitment_transaction, :hash), - "timestamp" => APIV2Helper.get_2map_data(l1_txs, :commitment_transaction, :ts), - "status" => APIV2Helper.get_2map_data(l1_txs, :commitment_transaction, :status) + "hash" => APIV2Helper.get_2map_data(l1_transactions, :commitment_transaction, :hash), + "timestamp" => APIV2Helper.get_2map_data(l1_transactions, :commitment_transaction, :ts), + "status" => APIV2Helper.get_2map_data(l1_transactions, :commitment_transaction, :status) }, "confirmation_transaction" => %{ - "hash" => APIV2Helper.get_2map_data(l1_txs, :confirmation_transaction, :hash), - "timestamp" => APIV2Helper.get_2map_data(l1_txs, :confirmation_transaction, :ts), - "status" => APIV2Helper.get_2map_data(l1_txs, :confirmation_transaction, :status) + "hash" => APIV2Helper.get_2map_data(l1_transactions, :confirmation_transaction, :hash), + "timestamp" => APIV2Helper.get_2map_data(l1_transactions, :confirmation_transaction, :ts), + "status" => APIV2Helper.get_2map_data(l1_transactions, :confirmation_transaction, :status) } }) end @@ -459,7 +461,7 @@ defmodule BlockScoutWeb.API.V2.ArbitrumView do # # ## Returns # A map containing nesting maps describing corresponding L1 transactions - @spec get_associated_l1_txs(%{ + @spec get_associated_l1_transactions(%{ :commitment_transaction => any(), :confirmation_transaction => any(), optional(any()) => any() @@ -481,15 +483,15 @@ defmodule BlockScoutWeb.API.V2.ArbitrumView do :status => nil | :finalized | :unfinalized } } - defp get_associated_l1_txs(arbitrum_item) do + defp get_associated_l1_transactions(arbitrum_item) do [:commitment_transaction, :confirmation_transaction] - |> Enum.reduce(%{}, fn key, l1_txs -> - Map.put(l1_txs, key, handle_associated_l1_txs_properly(Map.get(arbitrum_item, key))) + |> Enum.reduce(%{}, fn key, l1_transactions -> + Map.put(l1_transactions, key, handle_associated_l1_transactions_properly(Map.get(arbitrum_item, key))) end) end # Returns details of an associated L1 transaction or nil if not loaded or not available. - @spec handle_associated_l1_txs_properly(LifecycleTransaction | Ecto.Association.NotLoaded.t() | nil) :: + @spec handle_associated_l1_transactions_properly(LifecycleTransaction | Ecto.Association.NotLoaded.t() | nil) :: nil | %{ :hash => nil | binary(), @@ -497,8 +499,8 @@ defmodule BlockScoutWeb.API.V2.ArbitrumView do :ts => nil | DateTime.t(), :status => nil | :finalized | :unfinalized } - defp handle_associated_l1_txs_properly(associated_l1_tx) do - case associated_l1_tx do + defp handle_associated_l1_transactions_properly(associated_l1_transaction) do + case associated_l1_transaction do nil -> nil %Ecto.Association.NotLoaded{} -> nil value -> %{hash: value.hash, block: value.block_number, ts: value.timestamp, status: value.status} @@ -531,7 +533,7 @@ defmodule BlockScoutWeb.API.V2.ArbitrumView do # direction of the message, its status and the associated L1 transaction. # # ## Parameters - # - `arbitrum_tx`: An Arbitrum transaction. + # - `arbitrum_transaction`: An Arbitrum transaction. # # ## Returns # - A map extended with fields indicating the direction of the message, its status @@ -542,15 +544,15 @@ defmodule BlockScoutWeb.API.V2.ArbitrumView do :arbitrum_message_from_l2 => any(), optional(any()) => any() }) :: map() - defp extend_if_message(arbitrum_json, %Transaction{} = arbitrum_tx) do + defp extend_if_message(arbitrum_json, %Transaction{} = arbitrum_transaction) do {message_type, message_data} = - case {APIV2Helper.specified?(Map.get(arbitrum_tx, :arbitrum_message_to_l2)), - APIV2Helper.specified?(Map.get(arbitrum_tx, :arbitrum_message_from_l2))} do + case {APIV2Helper.specified?(Map.get(arbitrum_transaction, :arbitrum_message_to_l2)), + APIV2Helper.specified?(Map.get(arbitrum_transaction, :arbitrum_message_from_l2))} do {true, false} -> - {"incoming", l1_tx_and_status_for_message(arbitrum_tx, :incoming)} + {"incoming", l1_transaction_and_status_for_message(arbitrum_transaction, :incoming)} {false, true} -> - {"outcoming", l1_tx_and_status_for_message(arbitrum_tx, :outcoming)} + {"outcoming", l1_transaction_and_status_for_message(arbitrum_transaction, :outcoming)} _ -> {nil, %{}} @@ -562,7 +564,7 @@ defmodule BlockScoutWeb.API.V2.ArbitrumView do end # Determines the associated L1 transaction and its status for the given message direction. - @spec l1_tx_and_status_for_message( + @spec l1_transaction_and_status_for_message( %{ :__struct__ => Transaction, :arbitrum_message_to_l2 => any(), @@ -571,20 +573,21 @@ defmodule BlockScoutWeb.API.V2.ArbitrumView do }, :incoming | :outcoming ) :: map() - defp l1_tx_and_status_for_message(arbitrum_tx, message_direction) do - {l1_tx, status} = + defp l1_transaction_and_status_for_message(arbitrum_transaction, message_direction) do + {l1_transaction, status} = case message_direction do :incoming -> - l1_tx = APIV2Helper.get_2map_data(arbitrum_tx, :arbitrum_message_to_l2, :originating_transaction_hash) + l1_transaction = + APIV2Helper.get_2map_data(arbitrum_transaction, :arbitrum_message_to_l2, :originating_transaction_hash) - if is_nil(l1_tx) do + if is_nil(l1_transaction) do {nil, "Syncing with base layer"} else - {l1_tx, "Relayed"} + {l1_transaction, "Relayed"} end :outcoming -> - case APIV2Helper.get_2map_data(arbitrum_tx, :arbitrum_message_from_l2, :status) do + case APIV2Helper.get_2map_data(arbitrum_transaction, :arbitrum_message_from_l2, :status) do :initiated -> {nil, "Settlement pending"} @@ -595,12 +598,12 @@ defmodule BlockScoutWeb.API.V2.ArbitrumView do {nil, "Ready for relay"} :relayed -> - {APIV2Helper.get_2map_data(arbitrum_tx, :arbitrum_message_from_l2, :completion_transaction_hash), + {APIV2Helper.get_2map_data(arbitrum_transaction, :arbitrum_message_from_l2, :completion_transaction_hash), "Relayed"} end end - %{"associated_l1_transaction" => l1_tx, "message_status" => status} + %{"associated_l1_transaction" => l1_transaction, "message_status" => status} end # Extends the output JSON with information from Arbitrum-specific fields of the transaction. @@ -611,14 +614,14 @@ defmodule BlockScoutWeb.API.V2.ArbitrumView do :gas_price => Wei.t(), optional(any()) => any() }) :: map() - defp extend_with_transaction_info(out_json, %Transaction{} = arbitrum_tx) do + defp extend_with_transaction_info(out_json, %Transaction{} = arbitrum_transaction) do # Map.get is only needed for the case when the module is compiled with # chain_type different from "arbitrum", `|| 0` is used to avoid nil values # for the transaction prior to the migration to Arbitrum specific BS build. - gas_used_for_l1 = Map.get(arbitrum_tx, :gas_used_for_l1, 0) || 0 + gas_used_for_l1 = Map.get(arbitrum_transaction, :gas_used_for_l1, 0) || 0 - gas_used = Map.get(arbitrum_tx, :gas_used, 0) - gas_price = Map.get(arbitrum_tx, :gas_price, 0) + gas_used = Map.get(arbitrum_transaction, :gas_used, 0) || 0 + gas_price = Map.get(arbitrum_transaction, :gas_price, 0) || 0 gas_used_for_l2 = gas_used 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 75a2255..6f9b2cc 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 @@ -36,6 +36,8 @@ defmodule BlockScoutWeb.API.V2.BlockView do %{ "height" => block.number, "timestamp" => block.timestamp, + "transaction_count" => count_transactions(block), + # todo: keep next line for compatibility with frontend and remove when new frontend is bound to `transaction_count` property "tx_count" => count_transactions(block), "miner" => Helper.address_with_info(nil, block.miner, block.miner_hash, false), "size" => block.size, @@ -57,6 +59,8 @@ defmodule BlockScoutWeb.API.V2.BlockView do "gas_used_percentage" => Block.gas_used_percentage(block), "burnt_fees_percentage" => burnt_fees_percentage(burnt_fees, transaction_fees), "type" => block |> BlockView.block_type() |> String.downcase(), + "transaction_fees" => transaction_fees, + # todo: keep next line for compatibility with frontend and remove when new frontend is bound to `transaction_fees` property "tx_fees" => transaction_fees, "withdrawals_count" => count_withdrawals(block) } @@ -93,7 +97,7 @@ defmodule BlockScoutWeb.API.V2.BlockView do def burnt_fees_percentage(_, _), do: nil - def count_transactions(%Block{transactions: txs}) when is_list(txs), do: Enum.count(txs) + def count_transactions(%Block{transactions: transactions}) when is_list(transactions), do: Enum.count(transactions) def count_transactions(_), do: nil def count_withdrawals(%Block{withdrawals: withdrawals}) when is_list(withdrawals), do: Enum.count(withdrawals) diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/v2/celo_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/v2/celo_view.ex index eab95a7..b013d67 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/api/v2/celo_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/api/v2/celo_view.ex @@ -14,7 +14,7 @@ defmodule BlockScoutWeb.API.V2.CeloView do alias Explorer.Chain.Celo.Helper, as: CeloHelper alias Explorer.Chain.Celo.{ElectionReward, EpochReward} alias Explorer.Chain.Hash - alias Explorer.Chain.{Block, Transaction} + alias Explorer.Chain.{Block, Token, Transaction, Wei} @address_params [ necessity_by_association: %{ @@ -97,20 +97,26 @@ defmodule BlockScoutWeb.API.V2.CeloView do end def render("celo_base_fee.json", %Block{} = block) do - # For the blocks, where both FeeHandler and Governance contracts aren't - # deployed, the base fee is not burnt, but refunded to transaction sender, - # so we return nil in this case. - - base_fee = Block.burnt_fees(block.transactions, block.base_fee_per_gas) - - fee_handler_base_fee_breakdown( - base_fee, - block.number - ) || - governance_base_fee_breakdown( - base_fee, - block.number - ) + block.transactions + |> Block.burnt_fees(block.base_fee_per_gas) + |> Wei.cast() + |> case do + {:ok, base_fee} -> + # For the blocks, where both FeeHandler and Governance contracts aren't + # deployed, the base fee is not burnt, but refunded to transaction sender, + # so we return nil in this case. + fee_handler_base_fee_breakdown( + base_fee, + block.number + ) || + governance_base_fee_breakdown( + base_fee, + block.number + ) + + _ -> + nil + end end def render("celo_election_rewards.json", %{ @@ -215,11 +221,12 @@ defmodule BlockScoutWeb.API.V2.CeloView do } end - defp prepare_election_reward(%ElectionReward{} = reward) do + defp prepare_election_reward(%ElectionReward{token: %Token{}, block: %Block{}} = reward) do %{ amount: reward.amount, block_number: reward.block.number, block_hash: reward.block_hash, + block_timestamp: reward.block.timestamp, epoch_number: reward.block.number |> CeloHelper.block_number_to_epoch_number(), account: Helper.address_with_info( @@ -231,7 +238,12 @@ defmodule BlockScoutWeb.API.V2.CeloView do reward.associated_account_address, reward.associated_account_address_hash ), - type: reward.type + type: reward.type, + token: + TokenView.render("token.json", %{ + token: reward.token, + contract_address_hash: reward.token.contract_address_hash + }) } end @@ -246,7 +258,7 @@ defmodule BlockScoutWeb.API.V2.CeloView do # Get the breakdown of the base fee for the case when FeeHandler is a contract # that receives the base fee. - @spec fee_handler_base_fee_breakdown(Decimal.t(), Block.block_number()) :: + @spec fee_handler_base_fee_breakdown(Wei.t(), Block.block_number()) :: %{ :recipient => %{optional(String.t()) => any()}, :amount => float(), @@ -265,13 +277,14 @@ defmodule BlockScoutWeb.API.V2.CeloView do {:ok, %{"address" => fee_beneficiary_address_hash}} <- CeloCoreContracts.get_event(:fee_handler, :fee_beneficiary_set, block_number), {:ok, %{"value" => burn_fraction_fixidity_lib}} <- - CeloCoreContracts.get_event(:fee_handler, :burn_fraction_set, block_number) do + CeloCoreContracts.get_event(:fee_handler, :burn_fraction_set, block_number), + {:ok, celo_token_address_hash} <- CeloCoreContracts.get_address(:celo_token, block_number) do burn_fraction = burn_fraction_decimal(burn_fraction_fixidity_lib) - burnt_amount = Decimal.mult(base_fee, burn_fraction) + burnt_amount = Wei.mult(base_fee, burn_fraction) burnt_percentage = Decimal.mult(burn_fraction, 100) - carbon_offsetting_amount = Decimal.sub(base_fee, burnt_amount) + carbon_offsetting_amount = Wei.sub(base_fee, burnt_amount) carbon_offsetting_percentage = Decimal.sub(100, burnt_percentage) celo_burn_address_hash_string = dead_address_hash_string() @@ -311,18 +324,25 @@ defmodule BlockScoutWeb.API.V2.CeloView do } ) + celo_token = Token.get_by_contract_address_hash(celo_token_address_hash, api?: true) + %{ recipient: fee_handler_contract_address_info, amount: base_fee, + token: + TokenView.render("token.json", %{ + token: celo_token, + contract_address_hash: celo_token.contract_address_hash + }), breakdown: [ %{ address: burn_address_info, - amount: Decimal.to_float(burnt_amount), + amount: burnt_amount, percentage: Decimal.to_float(burnt_percentage) }, %{ address: fee_beneficiary_address_info, - amount: Decimal.to_float(carbon_offsetting_amount), + amount: carbon_offsetting_amount, percentage: Decimal.to_float(carbon_offsetting_percentage) } ] @@ -337,7 +357,7 @@ defmodule BlockScoutWeb.API.V2.CeloView do # # Note that the base fee is not burnt in this case, but simply kept on the # contract balance. - @spec governance_base_fee_breakdown(Decimal.t(), Block.block_number()) :: + @spec governance_base_fee_breakdown(Wei.t(), Block.block_number()) :: %{ :recipient => %{optional(String.t()) => any()}, :amount => float(), @@ -353,7 +373,8 @@ defmodule BlockScoutWeb.API.V2.CeloView do defp governance_base_fee_breakdown(base_fee, block_number) do with {:ok, address_hash_string} when not is_nil(address_hash_string) <- CeloCoreContracts.get_address(:governance, block_number), - {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string) do + {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string), + {:ok, celo_token_address_hash} <- CeloCoreContracts.get_address(:celo_token, block_number) do address = address_hash # todo: Querying database in the view is not a good practice. Consider @@ -370,9 +391,16 @@ defmodule BlockScoutWeb.API.V2.CeloView do address_hash ) + celo_token = Token.get_by_contract_address_hash(celo_token_address_hash, api?: true) + %{ recipient: address_with_info, amount: base_fee, + token: + TokenView.render("token.json", %{ + token: celo_token, + contract_address_hash: celo_token.contract_address_hash + }), breakdown: [] } else diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/v2/ethereum_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/v2/ethereum_view.ex index 6f7af66..4632242 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/api/v2/ethereum_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/api/v2/ethereum_view.ex @@ -1,9 +1,9 @@ defmodule BlockScoutWeb.API.V2.EthereumView do alias Explorer.Chain.{Block, Transaction} - defp count_blob_transactions(%Block{transactions: txs}) when is_list(txs), + defp count_blob_transactions(%Block{transactions: transactions}) when is_list(transactions), # EIP-2718 blob transaction type - do: Enum.count(txs, &(&1.type == 3)) + do: Enum.count(transactions, &(&1.type == 3)) defp count_blob_transactions(_), do: nil diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/v2/filecoin_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/v2/filecoin_view.ex new file mode 100644 index 0000000..398bc56 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/views/api/v2/filecoin_view.ex @@ -0,0 +1,105 @@ +if Application.compile_env(:explorer, :chain_type) == :filecoin do + defmodule BlockScoutWeb.API.V2.FilecoinView do + @moduledoc """ + View functions for rendering Filecoin-related data in JSON format. + """ + + alias Explorer.Chain + alias Explorer.Chain.Address + + @api_true [api?: true] + + @doc """ + Extends the json output with a sub-map containing information related to + Filecoin native addressing. + """ + @spec extend_address_json_response(map(), Address.t()) :: map() + def extend_address_json_response( + result, + %Address{filecoin_id: filecoin_id, filecoin_robust: filecoin_robust, filecoin_actor_type: filecoin_actor_type} + ) do + Map.put(result, :filecoin, %{ + id: filecoin_id, + robust: filecoin_robust, + actor_type: filecoin_actor_type + }) + end + + @spec preload_and_put_filecoin_robust_address(map(), %{ + address_hash: String.t() | nil, + field_prefix: String.t() | nil + }) :: + map() + def preload_and_put_filecoin_robust_address(result, %{address_hash: address_hash} = params) do + address = address_hash && Address.get(address_hash, @api_true) + + put_filecoin_robust_address(result, Map.put(params, :address, address)) + end + + @doc """ + Adds a Filecoin robust address to the given result. + + ## Parameters + + - result: The initial result to which the Filecoin robust address will be added. + - opts: A map containing the following keys: + - `:address` - A struct containing the `filecoin_robust` address. + - `:field_prefix` - A prefix to be used for the field name in the result. + + ## Returns + + The updated result with the Filecoin robust address added. + """ + @spec put_filecoin_robust_address(map(), %{ + required(:address) => Address.t(), + required(:field_prefix) => String.t() | nil, + optional(any) => any + }) :: map() + def put_filecoin_robust_address(result, %{ + address: %Address{filecoin_robust: filecoin_robust}, + field_prefix: field_prefix + }) do + put_filecoin_robust_address_internal(result, filecoin_robust, field_prefix) + end + + def put_filecoin_robust_address(result, %{field_prefix: field_prefix}) do + put_filecoin_robust_address_internal(result, nil, field_prefix) + end + + defp put_filecoin_robust_address_internal(result, filecoin_robust, field_prefix) do + field_name = (field_prefix && "#{field_prefix}_filecoin_robust_address") || "filecoin_robust_address" + Map.put(result, field_name, filecoin_robust) + end + + @doc """ + Preloads and inserts Filecoin robust addresses into the search results. + + ## Parameters + + - search_results: The search results that need to be enriched with Filecoin robust addresses. + + ## Returns + + - The search results with preloaded Filecoin robust addresses. + """ + @spec preload_and_put_filecoin_robust_address_to_search_results(list()) :: list() + def preload_and_put_filecoin_robust_address_to_search_results(search_results) do + addresses_map = + search_results + |> Enum.map(& &1["address"]) + |> Enum.reject(&is_nil/1) + |> Chain.hashes_to_addresses(@api_true) + |> Enum.group_by(&to_string(&1.hash)) + + search_results + |> Enum.map(fn + %{"address" => address_hash} = result when not is_nil(address_hash) -> + address = addresses_map[String.downcase(address_hash)] |> List.first() + put_filecoin_robust_address(result, %{address: address, field_prefix: nil}) + + other -> + other + end) + 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 5f483b1..935fdc0 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 @@ -84,12 +84,14 @@ defmodule BlockScoutWeb.API.V2.Helper do "hash" => Address.checksum(address), "is_contract" => smart_contract?, "name" => address_name(address), + "is_scam" => address_marked_as_scam?(address), "proxy_type" => proxy_type, "implementations" => Proxy.proxy_object_info(implementation_address_hashes, implementation_names), "is_verified" => verified?(address) || verified_minimal_proxy?(proxy_implementations), "ens_domain_name" => address.ens_domain_name, "metadata" => address.metadata } + |> address_chain_type_fields(address) end def address_with_info(%NotLoaded{}, address_hash) do @@ -120,6 +122,19 @@ defmodule BlockScoutWeb.API.V2.Helper do } end + case Application.compile_env(:explorer, :chain_type) do + :filecoin -> + defp address_chain_type_fields(result, address) do + # credo:disable-for-next-line Credo.Check.Design.AliasUsage + BlockScoutWeb.API.V2.FilecoinView.extend_address_json_response(result, address) + end + + _ -> + defp address_chain_type_fields(result, _address) do + result + end + end + defp minimal_proxy_pattern?(proxy_implementations) do proxy_implementations.proxy_type == :eip1167 end @@ -144,6 +159,16 @@ defmodule BlockScoutWeb.API.V2.Helper do def address_name(_), do: nil + def address_marked_as_scam?(%Address{scam_badge: %Ecto.Association.NotLoaded{}}) do + false + end + + def address_marked_as_scam?(%Address{scam_badge: scam_badge}) when not is_nil(scam_badge) do + true + end + + def address_marked_as_scam?(_), do: false + def verified?(%Address{smart_contract: nil}), do: false def verified?(%Address{smart_contract: %{metadata_from_verified_bytecode_twin: true}}), do: false def verified?(%Address{smart_contract: %NotLoaded{}}), do: nil diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/v2/internal_transaction_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/v2/internal_transaction_view.ex new file mode 100644 index 0000000..6ac34a0 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/views/api/v2/internal_transaction_view.ex @@ -0,0 +1,60 @@ +defmodule BlockScoutWeb.API.V2.InternalTransactionView do + use BlockScoutWeb, :view + + alias BlockScoutWeb.API.V2.Helper + alias Explorer.Chain.{Block, InternalTransaction} + + def render("internal_transaction.json", %{internal_transaction: nil}) do + nil + end + + def render("internal_transaction.json", %{ + internal_transaction: internal_transaction, + block: block + }) do + prepare_internal_transaction(internal_transaction, block) + end + + def render("internal_transactions.json", %{ + internal_transactions: internal_transactions, + next_page_params: next_page_params + }) do + %{ + "items" => Enum.map(internal_transactions, &prepare_internal_transaction(&1, &1.block)), + "next_page_params" => next_page_params + } + end + + @doc """ + Prepares internal transaction object to be returned in the API v2 endpoints. + """ + @spec prepare_internal_transaction(InternalTransaction.t(), Block.t() | nil) :: map() + def prepare_internal_transaction(internal_transaction, block \\ nil) do + %{ + "error" => internal_transaction.error, + "success" => is_nil(internal_transaction.error), + "type" => internal_transaction.call_type || internal_transaction.type, + "transaction_hash" => internal_transaction.transaction_hash, + "transaction_index" => internal_transaction.transaction_index, + "from" => + 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, + false + ), + "value" => internal_transaction.value, + # todo: keep next line for compatibility with frontend and remove when new frontend is bound to `block_number` property + "block" => internal_transaction.block_number, + "block_number" => internal_transaction.block_number, + "timestamp" => (block && block.timestamp) || internal_transaction.block.timestamp, + "index" => internal_transaction.index, + "gas_limit" => internal_transaction.gas, + "block_index" => internal_transaction.block_index + } + end +end diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/v2/mud_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/v2/mud_view.ex index 935646c..488425c 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/api/v2/mud_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/api/v2/mud_view.ex @@ -32,6 +32,25 @@ defmodule BlockScoutWeb.API.V2.MudView do } end + @doc """ + Function to render GET requests to `/api/v2/mud/worlds/:world/systems` endpoint. + """ + def render("systems.json", %{systems: systems}) do + %{ + items: systems |> Enum.map(&prepare_system_for_list/1) + } + end + + @doc """ + Function to render GET requests to `/api/v2/mud/worlds/:world/systems/:system` endpoint. + """ + def render("system.json", %{system_id: system_id, abi: abi}) do + %{ + name: system_id |> Table.from() |> Map.get(:table_full_name), + abi: abi + } + end + @doc """ Function to render GET requests to `/api/v2/mud/worlds/:world/tables/:table_id/records` endpoint. """ @@ -64,11 +83,20 @@ defmodule BlockScoutWeb.API.V2.MudView do defp prepare_world_for_list(%Address{} = address) do %{ "address" => Helper.address_with_info(address, address.hash), + # todo: keep next line for compatibility with frontend and remove when new frontend is bound to `transaction_count` property "tx_count" => address.transactions_count, + "transaction_count" => address.transactions_count, "coin_balance" => if(address.fetched_coin_balance, do: address.fetched_coin_balance.value) } end + defp prepare_system_for_list({system_id, system}) do + %{ + name: system_id |> Table.from() |> Map.get(:table_full_name), + address: system + } + end + defp format_record(nil, _schema, _blocks), do: nil defp format_record(record, schema, blocks) do diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/v2/optimism_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/v2/optimism_view.ex index 1b02171..62e4358 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/api/v2/optimism_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/api/v2/optimism_view.ex @@ -13,7 +13,7 @@ defmodule BlockScoutWeb.API.V2.OptimismView do Function to render GET requests to `/api/v2/optimism/txn-batches` endpoint. """ @spec render(binary(), map()) :: map() | list() | non_neg_integer() - def render("optimism_txn_batches.json", %{ + def render("optimism_transaction_batches.json", %{ batches: batches, next_page_params: next_page_params }) do @@ -21,7 +21,7 @@ defmodule BlockScoutWeb.API.V2.OptimismView do batches |> Enum.map(fn batch -> Task.async(fn -> - tx_count = + transaction_count = Repo.replica().aggregate( from( t in Transaction, @@ -35,7 +35,11 @@ defmodule BlockScoutWeb.API.V2.OptimismView do %{ "l2_block_number" => batch.l2_block_number, - "tx_count" => tx_count, + "transaction_count" => transaction_count, + # todo: keep next line for compatibility with frontend and remove when new frontend is bound to `transaction_count` property + "tx_count" => transaction_count, + "l1_transaction_hashes" => batch.frame_sequence.l1_transaction_hashes, + # todo: keep next line for compatibility with frontend and remove when new frontend is bound to `l1_transaction_hashes` property "l1_tx_hashes" => batch.frame_sequence.l1_transaction_hashes, "l1_timestamp" => batch.frame_sequence.l1_timestamp } @@ -60,14 +64,18 @@ defmodule BlockScoutWeb.API.V2.OptimismView do items = batches |> Enum.map(fn batch -> - from..to = batch.l2_block_range + from..to//_ = batch.l2_block_range %{ "internal_id" => batch.id, "l1_timestamp" => batch.l1_timestamp, "l2_block_start" => from, "l2_block_end" => to, - "tx_count" => batch.tx_count, + "transaction_count" => batch.transaction_count, + # todo: keep next line for compatibility with frontend and remove when new frontend is bound to `transaction_count` property + "tx_count" => batch.transaction_count, + "l1_transaction_hashes" => batch.l1_transaction_hashes, + # todo: keep next line for compatibility with frontend and remove when new frontend is bound to `l1_transaction_hashes` property "l1_tx_hashes" => batch.l1_transaction_hashes, "batch_data_container" => batch.batch_data_container } @@ -100,6 +108,8 @@ defmodule BlockScoutWeb.API.V2.OptimismView do %{ "l2_output_index" => r.l2_output_index, "l2_block_number" => r.l2_block_number, + "l1_transaction_hash" => r.l1_transaction_hash, + # todo: keep next line for compatibility with frontend and remove when new frontend is bound to `l1_transaction_hash` property "l1_tx_hash" => r.l1_transaction_hash, "l1_timestamp" => r.l1_timestamp, "l1_block_number" => r.l1_block_number, @@ -155,11 +165,19 @@ defmodule BlockScoutWeb.API.V2.OptimismView do Enum.map(deposits, fn deposit -> %{ "l1_block_number" => deposit.l1_block_number, + "l2_transaction_hash" => deposit.l2_transaction_hash, + # todo: keep next line for compatibility with frontend and remove when new frontend is bound to `l2_transaction_hash` property "l2_tx_hash" => deposit.l2_transaction_hash, "l1_block_timestamp" => deposit.l1_block_timestamp, + "l1_transaction_hash" => deposit.l1_transaction_hash, + # todo: keep next line for compatibility with frontend and remove when new frontend is bound to `l1_transaction_hash` property "l1_tx_hash" => deposit.l1_transaction_hash, + # todo: keep next line for compatibility with frontend and remove when new frontend is bound to `l1_transaction_origin` property "l1_tx_origin" => deposit.l1_transaction_origin, - "l2_tx_gas_limit" => deposit.l2_transaction.gas + # todo: keep next line for compatibility with frontend and remove when new frontend is bound to `l2_transaction_gas_limit` property + "l2_tx_gas_limit" => deposit.l2_transaction.gas, + "l1_transaction_origin" => deposit.l1_transaction_origin, + "l2_transaction_gas_limit" => deposit.l2_transaction.gas } end), next_page_params: next_page_params @@ -174,8 +192,12 @@ defmodule BlockScoutWeb.API.V2.OptimismView do %{ "l1_block_number" => deposit.l1_block_number, "l1_block_timestamp" => deposit.l1_block_timestamp, + # todo: keep next line for compatibility with frontend and remove when new frontend is bound to `l1_transaction_hash` property "l1_tx_hash" => deposit.l1_transaction_hash, - "l2_tx_hash" => deposit.l2_transaction_hash + # todo: keep next line for compatibility with frontend and remove when new frontend is bound to `l2_transaction_hash` property + "l2_tx_hash" => deposit.l2_transaction_hash, + "l1_transaction_hash" => deposit.l1_transaction_hash, + "l2_transaction_hash" => deposit.l2_transaction_hash } end) end @@ -228,9 +250,13 @@ defmodule BlockScoutWeb.API.V2.OptimismView do "msg_nonce" => msg_nonce, "msg_nonce_version" => msg_nonce_version, "from" => Helper.address_with_info(conn, from_address, from_address_hash, w.from), + "l2_transaction_hash" => w.l2_transaction_hash, + # todo: keep next line for compatibility with frontend and remove when new frontend is bound to `l2_transaction_hash` property "l2_tx_hash" => w.l2_transaction_hash, "l2_timestamp" => w.l2_timestamp, "status" => status, + "l1_transaction_hash" => w.l1_transaction_hash, + # todo: keep next line for compatibility with frontend and remove when new frontend is bound to `l1_transaction_hash` property "l1_tx_hash" => w.l1_transaction_hash, "challenge_period_end" => challenge_period_end } @@ -275,6 +301,8 @@ defmodule BlockScoutWeb.API.V2.OptimismView do %{ "internal_id" => frame_sequence.id, "l1_timestamp" => frame_sequence.l1_timestamp, + "l1_transaction_hashes" => frame_sequence.l1_transaction_hashes, + # todo: keep next line for compatibility with frontend and remove when new frontend is bound to `l1_transaction_hashes` property "l1_tx_hashes" => frame_sequence.l1_transaction_hashes, "batch_data_container" => batch_data_container } 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 index 497ef21..51b2515 100644 --- 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 @@ -52,10 +52,10 @@ defmodule BlockScoutWeb.API.V2.PolygonEdgeView do count end - def extend_transaction_json_response(out_json, tx_hash, connection) do + def extend_transaction_json_response(out_json, transaction_hash, connection) do out_json - |> Map.put("polygon_edge_deposit", polygon_edge_deposit(tx_hash, connection)) - |> Map.put("polygon_edge_withdrawal", polygon_edge_withdrawal(tx_hash, connection)) + |> Map.put("polygon_edge_deposit", polygon_edge_deposit(transaction_hash, connection)) + |> Map.put("polygon_edge_withdrawal", polygon_edge_withdrawal(transaction_hash, connection)) end defp polygon_edge_deposit(transaction_hash, conn) do diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/v2/polygon_zkevm_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/v2/polygon_zkevm_view.ex index ccf6e33..0960ca7 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/api/v2/polygon_zkevm_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/api/v2/polygon_zkevm_view.ex @@ -9,19 +9,19 @@ defmodule BlockScoutWeb.API.V2.PolygonZkevmView do """ @spec render(binary(), map()) :: map() | non_neg_integer() def render("zkevm_batch.json", %{batch: batch}) do - sequence_tx_hash = + sequence_transaction_hash = if Map.has_key?(batch, :sequence_transaction) and not is_nil(batch.sequence_transaction) do batch.sequence_transaction.hash end - verify_tx_hash = + verify_transaction_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) + Enum.map(batch.l2_transactions, fn transaction -> transaction.hash end) end %{ @@ -31,8 +31,12 @@ defmodule BlockScoutWeb.API.V2.PolygonZkevmView do "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, + "sequence_transaction_hash" => sequence_transaction_hash, + "verify_transaction_hash" => verify_transaction_hash, + # todo: keep next line for compatibility with frontend and remove when new frontend is bound to `sequence_transaction_hash` property + "sequence_tx_hash" => sequence_transaction_hash, + # todo: keep next line for compatibility with frontend and remove when new frontend is bound to `verify_transaction_hash` property + "verify_tx_hash" => verify_transaction_hash, "state_root" => batch.state_root } end @@ -141,12 +145,12 @@ defmodule BlockScoutWeb.API.V2.PolygonZkevmView do defp render_zkevm_batches(batches) do Enum.map(batches, fn batch -> - sequence_tx_hash = + sequence_transaction_hash = if not is_nil(batch.sequence_transaction) do batch.sequence_transaction.hash end - verify_tx_hash = + verify_transaction_hash = if not is_nil(batch.verify_transaction) do batch.verify_transaction.hash end @@ -155,9 +159,15 @@ defmodule BlockScoutWeb.API.V2.PolygonZkevmView do "number" => batch.number, "status" => batch_status(batch), "timestamp" => batch.timestamp, + "transaction_count" => batch.l2_transactions_count, + # todo: keep next line for compatibility with frontend and remove when new frontend is bound to `transaction_count` property "tx_count" => batch.l2_transactions_count, - "sequence_tx_hash" => sequence_tx_hash, - "verify_tx_hash" => verify_tx_hash + # todo: keep next line for compatibility with frontend and remove when new frontend is bound to `sequence_transaction_hash` property + "sequence_tx_hash" => sequence_transaction_hash, + # todo: keep next line for compatibility with frontend and remove when new frontend is bound to `verify_transaction_hash` property + "verify_tx_hash" => verify_transaction_hash, + "sequence_transaction_hash" => sequence_transaction_hash, + "verify_transaction_hash" => verify_transaction_hash } end) end diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/v2/proxy/metadata_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/v2/proxy/metadata_view.ex index 50b8cf2..301ae84 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/api/v2/proxy/metadata_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/api/v2/proxy/metadata_view.ex @@ -3,8 +3,8 @@ defmodule BlockScoutWeb.API.V2.Proxy.MetadataView do alias BlockScoutWeb.API.V2.AddressView - def render("addresses.json", %{result: {:ok, %{"addresses" => addresses} = body}}) do - Map.put(body, "addresses", Enum.map(addresses, &AddressView.prepare_address/1)) + def render("addresses.json", %{result: {:ok, %{"items" => addresses} = body}}) do + Map.put(body, "items", Enum.map(addresses, &AddressView.prepare_address/1)) end def render("addresses.json", %{result: :error}) do diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/v2/scroll_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/v2/scroll_view.ex new file mode 100644 index 0000000..f319f42 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/views/api/v2/scroll_view.ex @@ -0,0 +1,204 @@ +defmodule BlockScoutWeb.API.V2.ScrollView do + use BlockScoutWeb, :view + + alias BlockScoutWeb.API.V2.TransactionView + alias Explorer.Chain.Scroll.{Batch, L1FeeParam, Reader} + alias Explorer.Chain.{Data, Transaction} + + @api_true [api?: true] + + @doc """ + Function to render GET requests to `/api/v2/scroll/deposits` and `/api/v2/scroll/withdrawals` endpoints. + """ + @spec render(binary(), map()) :: map() | non_neg_integer() + def render("scroll_bridge_items.json", %{ + items: items, + next_page_params: next_page_params, + type: type + }) do + %{ + items: + Enum.map(items, fn item -> + {origination_transaction_hash, completion_transaction_hash} = + if type == :deposits do + {item.l1_transaction_hash, item.l2_transaction_hash} + else + {item.l2_transaction_hash, item.l1_transaction_hash} + end + + %{ + "id" => item.index, + "origination_transaction_hash" => origination_transaction_hash, + "origination_timestamp" => item.block_timestamp, + "origination_transaction_block_number" => item.block_number, + "completion_transaction_hash" => completion_transaction_hash, + "value" => item.amount + } + end), + next_page_params: next_page_params + } + end + + @doc """ + Function to render GET requests to `/api/v2/scroll/deposits/count` and `/api/v2/scroll/withdrawals/count` endpoints. + """ + def render("scroll_bridge_items_count.json", %{count: count}) do + count + end + + @doc """ + Function to render GET requests to `/api/v2/scroll/batches/:number` endpoint. + """ + def render("scroll_batch.json", %{batch: batch}) do + render_batch(batch) + end + + @doc """ + Function to render GET requests to `/api/v2/scroll/batches` endpoint. + """ + def render("scroll_batches.json", %{ + batches: batches, + next_page_params: next_page_params + }) do + items = + batches + |> Enum.map(fn batch -> + Task.async(fn -> render_batch(batch) end) + end) + |> Task.yield_many(:infinity) + |> Enum.map(fn {_task, {:ok, item}} -> item end) + + %{ + items: items, + next_page_params: next_page_params + } + end + + @doc """ + Function to render GET requests to `/api/v2/scroll/batches/count` endpoint. + """ + def render("scroll_batches_count.json", %{count: count}) do + count + end + + # Transforms the batch info into a map format for HTTP response. + # + # ## Parameters + # - `batch`: An instance of `Explorer.Chain.Scroll.Batch` entry. + # + # ## Returns + # - A map with detailed information about the batch formatted for use + # in JSON HTTP response. + @spec render_batch(Batch.t()) :: map() + defp render_batch(batch) do + {finalize_block_number, finalize_transaction_hash, finalize_timestamp} = + if is_nil(batch.bundle) do + {nil, nil, nil} + else + {batch.bundle.finalize_block_number, batch.bundle.finalize_transaction_hash, batch.bundle.finalize_timestamp} + end + + transaction_count = + Transaction.transaction_count_for_block_range(batch.l2_block_range.from..batch.l2_block_range.to) + + %{ + "number" => batch.number, + "commitment_transaction" => %{ + "block_number" => batch.commit_block_number, + "hash" => batch.commit_transaction_hash, + "timestamp" => batch.commit_timestamp + }, + "confirmation_transaction" => %{ + "block_number" => finalize_block_number, + "hash" => finalize_transaction_hash, + "timestamp" => finalize_timestamp + }, + "data_availability" => %{ + "batch_data_container" => batch.container + }, + "start_block" => batch.l2_block_range.from, + "end_block" => batch.l2_block_range.to, + "transaction_count" => transaction_count + } + end + + @doc """ + Extends the json output with a sub-map containing information related Scroll. + + ## Parameters + - `out_json`: A map defining output json which will be extended. + - `transaction`: Transaction structure containing Scroll related data + + ## Returns + - A map extended with the data related to Scroll rollup. + """ + @spec extend_transaction_json_response(map(), %{ + :__struct__ => Transaction, + :block_number => non_neg_integer(), + :index => non_neg_integer(), + :input => Data.t(), + optional(any()) => any() + }) :: map() + def extend_transaction_json_response(out_json, %Transaction{} = transaction) do + config = Application.get_all_env(:explorer)[L1FeeParam] + + l1_fee_scalar = get_param(:scalar, transaction, config) + l1_fee_commit_scalar = get_param(:commit_scalar, transaction, config) + l1_fee_blob_scalar = get_param(:blob_scalar, transaction, config) + l1_fee_overhead = get_param(:overhead, transaction, config) + l1_base_fee = get_param(:l1_base_fee, transaction, config) + l1_blob_base_fee = get_param(:l1_blob_base_fee, transaction, config) + + l1_gas_used = L1FeeParam.l1_gas_used(transaction, l1_fee_overhead) + + l2_fee = + transaction + |> Transaction.l2_fee(:wei) + |> TransactionView.format_fee() + + l2_block_status = l2_block_status(transaction.block_number) + + params = + %{} + |> add_optional_transaction_field(transaction, :l1_fee) + |> add_optional_transaction_field(transaction, :queue_index) + |> Map.put("l1_fee_scalar", l1_fee_scalar) + |> Map.put("l1_fee_commit_scalar", l1_fee_commit_scalar) + |> Map.put("l1_fee_blob_scalar", l1_fee_blob_scalar) + |> Map.put("l1_fee_overhead", l1_fee_overhead) + |> Map.put("l1_base_fee", l1_base_fee) + |> Map.put("l1_blob_base_fee", l1_blob_base_fee) + |> Map.put("l1_gas_used", l1_gas_used) + |> Map.put("l2_fee", l2_fee) + |> Map.put("l2_block_status", l2_block_status) + + Map.put(out_json, "scroll", params) + end + + defp add_optional_transaction_field(out_json, transaction, field) do + case Map.get(transaction, field) do + nil -> out_json + value -> Map.put(out_json, Atom.to_string(field), value) + end + end + + # sobelow_skip ["DOS.BinToAtom"] + defp get_param(name, transaction, config) + when name in [:scalar, :commit_scalar, :blob_scalar, :overhead, :l1_base_fee, :l1_blob_base_fee] do + name_init = :"#{name}#{:_init}" + + case Reader.get_l1_fee_param_for_transaction(name, transaction, @api_true) do + nil -> config[name_init] + value -> value + end + end + + @spec l2_block_status(non_neg_integer()) :: binary() + defp l2_block_status(block_number) do + case Reader.batch_by_l2_block_number(block_number, @api_true) do + {_batch_number, nil} -> "Committed" + {_batch_number, _bundle_id} -> "Finalized" + nil -> "Confirmed by Sequencer" + end + 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 026f4ba..1134914 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 @@ -6,11 +6,14 @@ defmodule BlockScoutWeb.API.V2.SearchView do alias Explorer.Chain.{Address, Beacon.Blob, Block, Hash, Transaction, UserOperation} 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} + %{ + "items" => search_results |> Enum.map(&prepare_search_result/1) |> chain_type_fields(), + "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) + search_results |> Enum.map(&prepare_search_result/1) |> chain_type_fields() end def render("search_results.json", %{result: {:ok, result}}) do @@ -92,12 +95,14 @@ defmodule BlockScoutWeb.API.V2.SearchView do end def prepare_search_result(%{type: "transaction"} = search_result) do - tx_hash = hash_to_string(search_result.tx_hash) + transaction_hash = hash_to_string(search_result.transaction_hash) %{ "type" => search_result.type, - "tx_hash" => tx_hash, - "url" => transaction_path(Endpoint, :show, tx_hash), + "transaction_hash" => transaction_hash, + # todo: keep next line for compatibility with frontend and remove when new frontend is bound to `transaction_hash` property + "tx_hash" => transaction_hash, + "url" => transaction_path(Endpoint, :show, transaction_hash), "timestamp" => search_result.timestamp, "priority" => search_result.priority } @@ -159,4 +164,17 @@ defmodule BlockScoutWeb.API.V2.SearchView do defp redirect_search_results(%Blob{} = item) do %{"type" => "blob", "parameter" => to_string(item.hash)} end + + case Application.compile_env(:explorer, :chain_type) do + :filecoin -> + defp chain_type_fields(result) do + # credo:disable-for-next-line Credo.Check.Design.AliasUsage + BlockScoutWeb.API.V2.FilecoinView.preload_and_put_filecoin_robust_address_to_search_results(result) + end + + _ -> + defp chain_type_fields(result) do + result + end + end 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 c653d40..f685fe2 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 @@ -162,7 +162,6 @@ defmodule BlockScoutWeb.API.V2.SmartContractView do conn ) do bytecode_twin = SmartContract.get_address_verified_bytecode_twin_contract(address.hash, @api_true) - minimal_proxy_address_hash = address.implementation bytecode_twin_contract = bytecode_twin.verified_contract smart_contract_verified = AddressView.smart_contract_verified?(address) fully_verified = SmartContract.verified_with_full_match?(address.hash, @api_true) @@ -185,10 +184,10 @@ defmodule BlockScoutWeb.API.V2.SmartContractView do target_contract = if smart_contract_verified, do: smart_contract, else: bytecode_twin_contract + verified_twin_address_hash = bytecode_twin_contract && Address.checksum(bytecode_twin_contract.address_hash) + %{ - "verified_twin_address_hash" => - bytecode_twin_contract && - Address.checksum(bytecode_twin_contract.address_hash), + "verified_twin_address_hash" => verified_twin_address_hash, "is_verified" => smart_contract_verified, "is_changed_bytecode" => smart_contract_verified && smart_contract.is_changed_bytecode, "is_partially_verified" => smart_contract.partially_verified && smart_contract_verified, @@ -203,9 +202,6 @@ defmodule BlockScoutWeb.API.V2.SmartContractView do "has_methods_write" => write_methods?, "has_methods_read_proxy" => is_proxy, "has_methods_write_proxy" => is_proxy && write_methods?, - # todo: remove this property once frontend is bound to "implementations" - "minimal_proxy_address_hash" => - minimal_proxy_address_hash && Address.checksum(minimal_proxy_address_hash.address_hash), "proxy_type" => proxy_type, "implementations" => implementations, "sourcify_repo_url" => @@ -241,6 +237,7 @@ defmodule BlockScoutWeb.API.V2.SmartContractView do } |> Map.merge(bytecode_info(address)) |> add_zksync_info(target_contract) + |> chain_type_fields(%{address_hash: verified_twin_address_hash, field_prefix: "verified_twin"}) end def prepare_smart_contract(address, implementations, proxy_type, conn) do @@ -257,18 +254,19 @@ defmodule BlockScoutWeb.API.V2.SmartContractView do end @doc """ - Returns additional sources of the smart-contract from bytecode twin + Returns additional sources of the smart-contract or from its bytecode twin """ @spec get_additional_sources(SmartContract.t(), boolean, SmartContract.t() | nil) :: [SmartContractAdditionalSource.t()] | nil def get_additional_sources(smart_contract, smart_contract_verified, bytecode_twin_contract) do cond do + smart_contract_verified && is_list(smart_contract.smart_contract_additional_sources) && + !Enum.empty?(smart_contract.smart_contract_additional_sources) -> + smart_contract.smart_contract_additional_sources + !is_nil(bytecode_twin_contract) -> bytecode_twin_contract.smart_contract_additional_sources - smart_contract_verified -> - smart_contract.smart_contract_additional_sources - true -> [] end @@ -360,6 +358,8 @@ defmodule BlockScoutWeb.API.V2.SmartContractView do ), "compiler_version" => smart_contract.compiler_version, "optimization_enabled" => smart_contract.optimization, + "transaction_count" => smart_contract.address.transactions_count, + # todo: keep next line for compatibility with frontend and remove when new frontend is bound to `transaction_count` property "tx_count" => smart_contract.address.transactions_count, "language" => smart_contract_language(smart_contract), "verified_at" => smart_contract.inserted_at, @@ -415,7 +415,7 @@ defmodule BlockScoutWeb.API.V2.SmartContractView do case type do "tuple[" <> rest -> # we need to convert tuple[...][] or tuple[...][n] into (...)[] or (...)[n] - # before sending it to the `FunctionSelector.decode_type/1. See https://github.com/poanetwork/ex_abi/issues/168. + # before sending it to the `FunctionSelector.decode_type/1`. See https://github.com/poanetwork/ex_abi/issues/168. tuple_item_types = rest |> String.split("]") @@ -450,4 +450,17 @@ defmodule BlockScoutWeb.API.V2.SmartContractView do def render_json(value, _type) do to_string(value) end + + case Application.compile_env(:explorer, :chain_type) do + :filecoin -> + defp chain_type_fields(result, params) do + # credo:disable-for-next-line Credo.Check.Design.AliasUsage + BlockScoutWeb.API.V2.FilecoinView.preload_and_put_filecoin_robust_address(result, params) + end + + _ -> + defp chain_type_fields(result, _address) do + result + end + end end diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/v2/stability_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/v2/stability_view.ex index f713428..3bd0c2a 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/api/v2/stability_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/api/v2/stability_view.ex @@ -1,6 +1,6 @@ defmodule BlockScoutWeb.API.V2.StabilityView do alias BlockScoutWeb.API.V2.{Helper, TokenView} - alias Explorer.Chain.{Hash, Log, Token, Transaction} + alias Explorer.Chain.{Log, Token, Transaction} @api_true [api?: true] @transaction_fee_event_signature "0x99e7b0ba56da2819c37c047f0511fd2bf6c9b4e27b4a979a19d6da0f74be8155" @@ -64,11 +64,12 @@ defmodule BlockScoutWeb.API.V2.StabilityView do "token" => TokenView.render("token.json", %{ token: transaction.transaction_fee_token, - contract_address_hash: bytes_to_address_hash(token_address_hash) + contract_address_hash: Transaction.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), + Helper.address_with_info(nil, nil, Transaction.bytes_to_address_hash(validator_address_hash), false), + "dapp_address" => + Helper.address_with_info(nil, nil, Transaction.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) @@ -89,13 +90,18 @@ defmodule BlockScoutWeb.API.V2.StabilityView do defp do_extend_with_stability_fees_info(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 + case Log.fetch_log_by_transaction_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) + {token, new_tokens_acc} = + check_tokens_acc(Transaction.bytes_to_address_hash(token_address_hash), tokens_acc) {%Transaction{transaction | transaction_fee_log: mapping, transaction_fee_token: token}, new_tokens_acc} @@ -121,6 +127,4 @@ defmodule BlockScoutWeb.API.V2.StabilityView do {token, Map.put(tokens_acc, token_address_hash, token)} end end - - defp bytes_to_address_hash(bytes), do: %Hash{byte_count: 20, bytes: bytes} end diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/v2/suave_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/v2/suave_view.ex index 5d334ee..732f371 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/api/v2/suave_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/api/v2/suave_view.ex @@ -9,7 +9,13 @@ defmodule BlockScoutWeb.API.V2.SuaveView do @suave_bid_event "0x83481d5b04dea534715acad673a8177a46fc93882760f36bdc16ccac439d504e" - def extend_transaction_json_response(%Transaction{} = transaction, out_json, single_tx?, conn, watchlist_names) do + def extend_transaction_json_response( + %Transaction{} = transaction, + out_json, + single_transaction?, + conn, + watchlist_names + ) do if is_nil(Map.get(transaction, :execution_node_hash)) do out_json else @@ -27,7 +33,7 @@ defmodule BlockScoutWeb.API.V2.SuaveView do wrapped_max_fee_per_gas = Map.get(transaction, :wrapped_max_fee_per_gas) wrapped_value = Map.get(transaction, :wrapped_value) - {[wrapped_decoded_input], _, _} = + [wrapped_decoded_input] = Transaction.decode_transactions( [ %Transaction{ @@ -48,7 +54,7 @@ defmodule BlockScoutWeb.API.V2.SuaveView do conn, execution_node, execution_node_hash, - single_tx?, + single_transaction?, watchlist_names ) ) @@ -60,7 +66,7 @@ defmodule BlockScoutWeb.API.V2.SuaveView do conn, wrapped_to_address, wrapped_to_address_hash, - single_tx?, + single_transaction?, watchlist_names ), "gas_limit" => wrapped_gas, diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/v2/token_transfer_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/v2/token_transfer_view.ex new file mode 100644 index 0000000..6d4ec1d --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/views/api/v2/token_transfer_view.ex @@ -0,0 +1,99 @@ +defmodule BlockScoutWeb.API.V2.TokenTransferView do + use BlockScoutWeb, :view + + alias BlockScoutWeb.API.V2.{Helper, TokenView, TransactionView} + alias BlockScoutWeb.Tokens.Helper, as: TokensHelper + alias Ecto.Association.NotLoaded + alias Explorer.Chain + alias Explorer.Chain.{TokenTransfer, Transaction} + + def render("token_transfer.json", %{token_transfer: nil}) do + nil + end + + def render("token_transfer.json", %{ + token_transfer: token_transfer, + decoded_transaction_input: decoded_transaction_input, + conn: conn + }) do + prepare_token_transfer(token_transfer, conn, decoded_transaction_input) + end + + def render("token_transfers.json", %{ + token_transfers: token_transfers, + decoded_transactions_map: decoded_transactions_map, + next_page_params: next_page_params, + conn: conn + }) do + %{ + "items" => + Enum.map( + token_transfers, + &render("token_transfer.json", %{ + token_transfer: &1, + decoded_transaction_input: decoded_transactions_map[&1.transaction.hash], + conn: conn + }) + ), + "next_page_params" => next_page_params + } + end + + @doc """ + Prepares token transfer object to be returned in the API v2 endpoints. + """ + @spec prepare_token_transfer(TokenTransfer.t(), Plug.Conn.t() | nil, any()) :: map() + def prepare_token_transfer(token_transfer, _conn, decoded_input) do + %{ + "transaction_hash" => token_transfer.transaction_hash, + # todo: keep next line for compatibility with frontend and remove when new frontend is bound to `transaction_hash` property + "tx_hash" => token_transfer.transaction_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), + "timestamp" => + if(match?(%NotLoaded{}, token_transfer.block), + do: TransactionView.block_timestamp(token_transfer.transaction), + else: TransactionView.block_timestamp(token_transfer.block) + ), + "method" => Transaction.method_name(token_transfer.transaction, decoded_input, true), + "block_hash" => to_string(token_transfer.block_hash), + "block_number" => token_transfer.block_number, + "log_index" => token_transfer.log_index + } + end + + @doc """ + Prepares token transfer total value/id transferred to be returned in the API v2 endpoints. + """ + @spec prepare_token_transfer_total(TokenTransfer.t()) :: map() + # credo:disable-for-next-line /Complexity/ + def prepare_token_transfer_total(token_transfer) do + case TokensHelper.token_transfer_amount_for_api(token_transfer) do + {:ok, :erc721_instance} -> + %{"token_id" => token_transfer.token_ids && List.first(token_transfer.token_ids)} + + {:ok, :erc1155_erc404_instance, value, decimals} -> + %{ + "token_id" => token_transfer.token_ids && List.first(token_transfer.token_ids), + "value" => value, + "decimals" => decimals + } + + {:ok, :erc1155_erc404_instance, values, token_ids, decimals} -> + %{ + "token_id" => token_ids && List.first(token_ids), + "value" => values && List.first(values), + "decimals" => decimals + } + + {:ok, value, decimals} -> + %{"value" => value, "decimals" => decimals} + + _ -> + nil + end + 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 f1ed2d9..935a06f 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 @@ -42,6 +42,7 @@ defmodule BlockScoutWeb.API.V2.TokenView do "circulating_market_cap" => token.circulating_market_cap } |> maybe_append_bridged_info(token) + |> chain_type_fields(%{address: token.contract_address, field_prefix: nil}) end def render("token_balances.json", %{ @@ -139,4 +140,17 @@ defmodule BlockScoutWeb.API.V2.TokenView do map end end + + case Application.compile_env(:explorer, :chain_type) do + :filecoin -> + defp chain_type_fields(result, params) do + # credo:disable-for-next-line Credo.Check.Design.AliasUsage + BlockScoutWeb.API.V2.FilecoinView.put_filecoin_robust_address(result, params) + end + + _ -> + defp chain_type_fields(result, _params) do + result + end + end 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 c950a69..36511e4 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 @@ -1,15 +1,14 @@ defmodule BlockScoutWeb.API.V2.TransactionView do use BlockScoutWeb, :view - alias BlockScoutWeb.API.V2.{ApiView, Helper, TokenView} + alias BlockScoutWeb.API.V2.{ApiView, Helper, InternalTransactionView, TokenTransferView, TokenView} alias BlockScoutWeb.{ABIEncodedValueView, TransactionView} alias BlockScoutWeb.Models.GetTransactionTags - alias BlockScoutWeb.Tokens.Helper, as: TokensHelper alias BlockScoutWeb.TransactionStateView alias Ecto.Association.NotLoaded alias Explorer.{Chain, Market} - alias Explorer.Chain.{Address, Block, Log, Token, Transaction, Wei} + alias Explorer.Chain.{Address, Block, Log, SignedAuthorization, Token, Transaction, Wei} alias Explorer.Chain.Block.Reward alias Explorer.Chain.Transaction.StateChange alias Explorer.Counters.AverageBlockTime @@ -30,15 +29,15 @@ defmodule BlockScoutWeb.API.V2.TransactionView do watchlist_names: watchlist_names }) do block_height = Chain.block_height(@api_true) - {decoded_transactions, _, _} = Transaction.decode_transactions(transactions, true, @api_true) + decoded_transactions = Transaction.decode_transactions(transactions, true, @api_true) %{ "items" => transactions |> chain_type_transformations() |> Enum.zip(decoded_transactions) - |> Enum.map(fn {tx, decoded_input} -> - prepare_transaction(tx, conn, false, block_height, watchlist_names, decoded_input) + |> Enum.map(fn {transaction, decoded_input} -> + prepare_transaction(transaction, conn, false, block_height, watchlist_names, decoded_input) end), "next_page_params" => next_page_params } @@ -50,26 +49,28 @@ defmodule BlockScoutWeb.API.V2.TransactionView do watchlist_names: watchlist_names }) do block_height = Chain.block_height(@api_true) - {decoded_transactions, _, _} = Transaction.decode_transactions(transactions, true, @api_true) + decoded_transactions = Transaction.decode_transactions(transactions, true, @api_true) transactions |> chain_type_transformations() |> Enum.zip(decoded_transactions) - |> Enum.map(fn {tx, decoded_input} -> - prepare_transaction(tx, conn, false, block_height, watchlist_names, decoded_input) + |> Enum.map(fn {transaction, decoded_input} -> + prepare_transaction(transaction, 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 block_height = Chain.block_height(@api_true) - {decoded_transactions, _, _} = Transaction.decode_transactions(transactions, true, @api_true) + decoded_transactions = Transaction.decode_transactions(transactions, true, @api_true) %{ "items" => transactions |> chain_type_transformations() |> Enum.zip(decoded_transactions) - |> Enum.map(fn {tx, decoded_input} -> prepare_transaction(tx, conn, false, block_height, decoded_input) end), + |> Enum.map(fn {transaction, decoded_input} -> + prepare_transaction(transaction, conn, false, block_height, decoded_input) + end), "next_page_params" => next_page_params } end @@ -82,17 +83,19 @@ defmodule BlockScoutWeb.API.V2.TransactionView do def render("transactions.json", %{transactions: transactions, conn: conn}) do block_height = Chain.block_height(@api_true) - {decoded_transactions, _, _} = Transaction.decode_transactions(transactions, true, @api_true) + decoded_transactions = Transaction.decode_transactions(transactions, true, @api_true) transactions |> chain_type_transformations() |> Enum.zip(decoded_transactions) - |> Enum.map(fn {tx, decoded_input} -> prepare_transaction(tx, conn, false, block_height, decoded_input) end) + |> Enum.map(fn {transaction, decoded_input} -> + prepare_transaction(transaction, conn, false, block_height, decoded_input) + end) end def render("transaction.json", %{transaction: transaction, conn: conn}) do block_height = Chain.block_height(@api_true) - {[decoded_input], _, _} = Transaction.decode_transactions([transaction], false, @api_true) + [decoded_input] = Transaction.decode_transactions([transaction], false, @api_true) transaction |> chain_type_transformations() @@ -116,30 +119,30 @@ 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 - {decoded_transactions, _, _} = + decoded_transactions = Transaction.decode_transactions(Enum.map(token_transfers, fn tt -> tt.transaction end), true, @api_true) %{ "items" => token_transfers |> Enum.zip(decoded_transactions) - |> Enum.map(fn {tt, decoded_input} -> prepare_token_transfer(tt, conn, decoded_input) end), + |> Enum.map(fn {tt, decoded_input} -> TokenTransferView.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 - {decoded_transactions, _, _} = + decoded_transactions = Transaction.decode_transactions(Enum.map(token_transfers, fn tt -> tt.transaction end), true, @api_true) token_transfers |> Enum.zip(decoded_transactions) - |> Enum.map(fn {tt, decoded_input} -> prepare_token_transfer(tt, conn, decoded_input) end) + |> Enum.map(fn {tt, decoded_input} -> TokenTransferView.prepare_token_transfer(tt, conn, decoded_input) end) end def render("token_transfer.json", %{token_transfer: token_transfer, conn: conn}) do - {[decoded_transaction], _, _} = Transaction.decode_transactions([token_transfer.transaction], true, @api_true) - prepare_token_transfer(token_transfer, conn, decoded_transaction) + [decoded_transaction] = Transaction.decode_transactions([token_transfer.transaction], true, @api_true) + TokenTransferView.prepare_token_transfer(token_transfer, conn, decoded_transaction) end def render("transaction_actions.json", %{actions: actions}) do @@ -152,7 +155,7 @@ defmodule BlockScoutWeb.API.V2.TransactionView do block: block }) do %{ - "items" => Enum.map(internal_transactions, &prepare_internal_transaction(&1, block)), + "items" => Enum.map(internal_transactions, &InternalTransactionView.prepare_internal_transaction(&1, block)), "next_page_params" => next_page_params } end @@ -162,17 +165,19 @@ defmodule BlockScoutWeb.API.V2.TransactionView do next_page_params: next_page_params }) do %{ - "items" => Enum.map(internal_transactions, &prepare_internal_transaction(&1)), + "items" => Enum.map(internal_transactions, &InternalTransactionView.prepare_internal_transaction(&1)), "next_page_params" => next_page_params } end - def render("logs.json", %{logs: logs, next_page_params: next_page_params, tx_hash: tx_hash}) do + def render("logs.json", %{logs: logs, next_page_params: next_page_params, transaction_hash: transaction_hash}) do 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), + logs + |> Enum.zip(decoded_logs) + |> Enum.map(fn {log, decoded_log} -> prepare_log(log, transaction_hash, decoded_log) end), "next_page_params" => next_page_params } end @@ -210,6 +215,12 @@ defmodule BlockScoutWeb.API.V2.TransactionView do } end + def render("authorization_list.json", %{signed_authorizations: signed_authorizations}) do + signed_authorizations + |> Enum.sort_by(& &1.index, :asc) + |> Enum.map(&prepare_signed_authorization/1) + end + @doc """ Decodes list of logs """ @@ -233,26 +244,6 @@ defmodule BlockScoutWeb.API.V2.TransactionView do Enum.reverse(result) 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, 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), - "timestamp" => - if(match?(%NotLoaded{}, token_transfer.block), - do: block_timestamp(token_transfer.transaction), - else: block_timestamp(token_transfer.block) - ), - "method" => Transaction.method_name(token_transfer.transaction, decoded_input, true), - "block_hash" => to_string(token_transfer.block_hash), - "block_number" => to_string(token_transfer.block_number), - "log_index" => to_string(token_transfer.log_index) - } - end - def prepare_transaction_action(action) do %{ "protocol" => action.protocol, @@ -261,65 +252,13 @@ defmodule BlockScoutWeb.API.V2.TransactionView do } end - # credo:disable-for-next-line /Complexity/ - def prepare_token_transfer_total(token_transfer) do - case TokensHelper.token_transfer_amount_for_api(token_transfer) do - {:ok, :erc721_instance} -> - %{"token_id" => token_transfer.token_ids && List.first(token_transfer.token_ids)} - - {:ok, :erc1155_erc404_instance, value, decimals} -> - %{ - "token_id" => token_transfer.token_ids && List.first(token_transfer.token_ids), - "value" => value, - "decimals" => decimals - } - - {:ok, :erc1155_erc404_instance, values, token_ids, decimals} -> - %{ - "token_id" => token_ids && List.first(token_ids), - "value" => values && List.first(values), - "decimals" => decimals - } - - {:ok, value, decimals} -> - %{"value" => value, "decimals" => decimals} - - _ -> - nil - end - end - - def prepare_internal_transaction(internal_transaction, block \\ nil) do - %{ - "error" => internal_transaction.error, - "success" => is_nil(internal_transaction.error), - "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, 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, - false - ), - "value" => internal_transaction.value, - "block" => internal_transaction.block_number, - "timestamp" => (block && block.timestamp) || internal_transaction.block.timestamp, - "index" => internal_transaction.index, - "gas_limit" => internal_transaction.gas, - "block_index" => internal_transaction.block_index - } - end - 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), + "transaction_hash" => get_transaction_hash(transaction_or_hash), + # todo: keep next line for compatibility with frontend and remove when new frontend is bound to `transaction_hash` property + "tx_hash" => get_transaction_hash(transaction_or_hash), "address" => Helper.address_with_info(nil, log.address, log.address_hash, tags_for_address_needed?), "topics" => [ log.first_topic, @@ -336,11 +275,33 @@ defmodule BlockScoutWeb.API.V2.TransactionView do } end - defp get_tx_hash(%Transaction{} = tx), do: to_string(tx.hash) - defp get_tx_hash(hash), do: to_string(hash) + @doc """ + Extracts the necessary fields from the signed authorization for the API response. + + ## Parameters + - `signed_authorization`: A `SignedAuthorization.t()` struct containing the signed authorization data. + + ## Returns + - A map with the necessary fields for the API response. + """ + @spec prepare_signed_authorization(SignedAuthorization.t()) :: map() + def prepare_signed_authorization(signed_authorization) do + %{ + "address" => Address.checksum(signed_authorization.address), + "chain_id" => signed_authorization.chain_id, + "nonce" => signed_authorization.nonce, + "r" => signed_authorization.r, + "s" => signed_authorization.s, + "v" => signed_authorization.v, + "authority" => Address.checksum(signed_authorization.authority) + } + end + + defp get_transaction_hash(%Transaction{} = transaction), do: to_string(transaction.hash) + defp get_transaction_hash(hash), do: to_string(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(%Transaction{} = transaction), + do: Helper.address_with_info(nil, transaction.to_address, transaction.to_address_hash, false) defp smart_contract_info(_), do: nil @@ -354,12 +315,12 @@ defmodule BlockScoutWeb.API.V2.TransactionView do end end - defp prepare_transaction(tx, conn, single_tx?, block_height, watchlist_names \\ nil, decoded_input) + defp prepare_transaction(transaction, conn, single_transaction?, block_height, watchlist_names \\ nil, decoded_input) defp prepare_transaction( {%Reward{} = emission_reward, %Reward{} = validator_reward}, conn, - single_tx?, + single_transaction?, _block_height, _watchlist_names, _decoded_input @@ -368,19 +329,31 @@ defmodule BlockScoutWeb.API.V2.TransactionView 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, single_tx?), + Helper.address_with_info( + single_transaction? && conn, + emission_reward.address, + emission_reward.address_hash, + single_transaction? + ), "to" => Helper.address_with_info( - single_tx? && conn, + single_transaction? && conn, validator_reward.address, validator_reward.address_hash, - single_tx? + single_transaction? ), "types" => [:reward] } end - defp prepare_transaction(%Transaction{} = transaction, conn, single_tx?, block_height, watchlist_names, decoded_input) do + defp prepare_transaction( + %Transaction{} = transaction, + conn, + single_transaction?, + 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 @@ -399,30 +372,32 @@ defmodule BlockScoutWeb.API.V2.TransactionView do "hash" => transaction.hash, "result" => status, "status" => transaction.status, + "block_number" => transaction.block_number, + # todo: keep next line for compatibility with frontend and remove when new frontend is bound to `block_number` property "block" => transaction.block_number, "timestamp" => block_timestamp(transaction), "from" => Helper.address_with_info( - single_tx? && conn, + single_transaction? && conn, transaction.from_address, transaction.from_address_hash, - single_tx?, + single_transaction?, watchlist_names ), "to" => Helper.address_with_info( - single_tx? && conn, + single_transaction? && conn, transaction.to_address, transaction.to_address_hash, - single_tx?, + single_transaction?, watchlist_names ), "created_contract" => Helper.address_with_info( - single_tx? && conn, + single_transaction? && conn, transaction.created_contract_address, transaction.created_contract_address_hash, - single_tx?, + single_transaction?, watchlist_names ), "confirmations" => transaction.block |> Chain.confirmations(block_height: block_height) |> format_confirmations(), @@ -443,18 +418,26 @@ defmodule BlockScoutWeb.API.V2.TransactionView do "revert_reason" => revert_reason, "raw_input" => transaction.input, "decoded_input" => decoded_input_data, - "token_transfers" => token_transfers(transaction.token_transfers, conn, single_tx?), - "token_transfers_overflow" => token_transfers_overflow(transaction.token_transfers, single_tx?), + "token_transfers" => token_transfers(transaction.token_transfers, conn, single_transaction?), + "token_transfers_overflow" => token_transfers_overflow(transaction.token_transfers, single_transaction?), "actions" => transaction_actions(transaction.transaction_actions), "exchange_rate" => Market.get_coin_exchange_rate().usd_value, "method" => Transaction.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 + "transaction_types" => transaction_types(transaction), + # todo: keep next line for compatibility with frontend and remove when new frontend is bound to `transaction_types` property + "tx_types" => transaction_types(transaction), + "transaction_tag" => + GetTransactionTags.get_transaction_tags(transaction.hash, current_user(single_transaction? && conn)), + # todo: keep next line for compatibility with frontend and remove when new frontend is bound to `transaction_tag` property + "tx_tag" => GetTransactionTags.get_transaction_tags(transaction.hash, current_user(single_transaction? && conn)), + # todo: keep next line for compatibility with frontend and remove when new frontend is bound to `has_error_in_internal_transactions` property + "has_error_in_internal_txs" => transaction.has_error_in_internal_transactions, + "has_error_in_internal_transactions" => transaction.has_error_in_internal_transactions, + "authorization_list" => authorization_list(transaction.signed_authorizations) } result - |> chain_type_fields(transaction, single_tx?, conn, watchlist_names) + |> chain_type_fields(transaction, single_transaction?, conn, watchlist_names) end def token_transfers(_, _conn, false), do: nil @@ -482,6 +465,23 @@ defmodule BlockScoutWeb.API.V2.TransactionView do render("transaction_actions.json", %{actions: actions}) end + @doc """ + Renders the authorization list for a transaction. + + ## Parameters + - `signed_authorizations`: A list of `SignedAuthorization.t()` structs. + + ## Returns + - A list of maps with the necessary fields for the API response. + """ + @spec authorization_list(nil | NotLoaded.t() | [SignedAuthorization.t()]) :: [map()] + def authorization_list(nil), do: [] + def authorization_list(%NotLoaded{}), do: [] + + def authorization_list(signed_authorizations) do + render("authorization_list.json", %{signed_authorizations: signed_authorizations}) + end + 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 @@ -515,7 +515,7 @@ defmodule BlockScoutWeb.API.V2.TransactionView do end @doc """ - Prepares decoded tx info + Prepares decoded transaction info """ @spec decoded_input(any()) :: map() | nil def decoded_input(decoded_input) do @@ -592,14 +592,14 @@ defmodule BlockScoutWeb.API.V2.TransactionView do end @doc """ - Returns array of token types for tx. + Returns array of token types for transaction. """ - @spec tx_types( + @spec transaction_types( Explorer.Chain.Transaction.t(), - [tx_type], - tx_type - ) :: [tx_type] - when tx_type: + [transaction_type], + transaction_type + ) :: [transaction_type] + when transaction_type: :coin_transfer | :contract_call | :contract_creation @@ -608,9 +608,22 @@ defmodule BlockScoutWeb.API.V2.TransactionView do | :token_creation | :token_transfer | :blob_transaction - def tx_types(tx, types \\ [], stage \\ :blob_transaction) + | :set_code_transaction + def transaction_types(transaction, types \\ [], stage \\ :set_code_transaction) - def tx_types(%Transaction{type: type} = tx, types, :blob_transaction) do + def transaction_types(%Transaction{type: type} = transaction, types, :set_code_transaction) do + # EIP-7702 set code transaction type + types = + if type == 4 do + [:set_code_transaction | types] + else + types + end + + transaction_types(transaction, types, :blob_transaction) + end + + def transaction_types(%Transaction{type: type} = transaction, types, :blob_transaction) do # EIP-2718 blob transaction type types = if type == 3 do @@ -619,22 +632,26 @@ defmodule BlockScoutWeb.API.V2.TransactionView do types end - tx_types(tx, types, :token_transfer) + transaction_types(transaction, types, :token_transfer) end - def tx_types(%Transaction{token_transfers: token_transfers} = tx, types, :token_transfer) do + def transaction_types(%Transaction{token_transfers: token_transfers} = transaction, types, :token_transfer) do types = if (!is_nil(token_transfers) && token_transfers != [] && !match?(%NotLoaded{}, token_transfers)) || - tx.has_token_transfers do + transaction.has_token_transfers do [:token_transfer | types] else types end - tx_types(tx, types, :token_creation) + transaction_types(transaction, types, :token_creation) end - def tx_types(%Transaction{created_contract_address: created_contract_address} = tx, types, :token_creation) do + def transaction_types( + %Transaction{created_contract_address: created_contract_address} = transaction, + types, + :token_creation + ) do types = if match?(%Address{}, created_contract_address) && match?(%Token{}, created_contract_address.token) do [:token_creation | types] @@ -642,11 +659,11 @@ defmodule BlockScoutWeb.API.V2.TransactionView do types end - tx_types(tx, types, :contract_creation) + transaction_types(transaction, types, :contract_creation) end - def tx_types( - %Transaction{to_address_hash: to_address_hash} = tx, + def transaction_types( + %Transaction{to_address_hash: to_address_hash} = transaction, types, :contract_creation ) do @@ -657,10 +674,10 @@ defmodule BlockScoutWeb.API.V2.TransactionView do types end - tx_types(tx, types, :contract_call) + transaction_types(transaction, types, :contract_call) end - def tx_types(%Transaction{to_address: to_address} = tx, types, :contract_call) do + def transaction_types(%Transaction{to_address: to_address} = transaction, types, :contract_call) do types = if Address.smart_contract?(to_address) do [:contract_call | types] @@ -668,10 +685,10 @@ defmodule BlockScoutWeb.API.V2.TransactionView do types end - tx_types(tx, types, :coin_transfer) + transaction_types(transaction, types, :coin_transfer) end - def tx_types(%Transaction{value: value} = tx, types, :coin_transfer) do + def transaction_types(%Transaction{value: value} = transaction, types, :coin_transfer) do types = if Decimal.compare(value.value, 0) == :gt do [:coin_transfer | types] @@ -679,32 +696,36 @@ defmodule BlockScoutWeb.API.V2.TransactionView do types end - tx_types(tx, types, :rootstock_remasc) + transaction_types(transaction, types, :rootstock_remasc) end - def tx_types(tx, types, :rootstock_remasc) do + def transaction_types(transaction, types, :rootstock_remasc) do types = - if Transaction.rootstock_remasc_transaction?(tx) do + if Transaction.rootstock_remasc_transaction?(transaction) do [:rootstock_remasc | types] else types end - tx_types(tx, types, :rootstock_bridge) + transaction_types(transaction, types, :rootstock_bridge) end - def tx_types(tx, types, :rootstock_bridge) do - if Transaction.rootstock_bridge_transaction?(tx) do + def transaction_types(transaction, types, :rootstock_bridge) do + if Transaction.rootstock_bridge_transaction?(transaction) 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 + @doc """ + Returns block's timestamp from Block/Transaction + """ + @spec block_timestamp(any()) :: :utc_datetime_usec | nil + def block_timestamp(%Transaction{block_timestamp: block_ts}) when not is_nil(block_ts), do: block_ts + def block_timestamp(%Transaction{block: %Block{} = block}), do: block.timestamp + def block_timestamp(%Block{} = block), do: block.timestamp + def block_timestamp(_), do: nil defp prepare_state_change(%StateChange{} = state_change) do coin_or_transfer = @@ -747,7 +768,7 @@ defmodule BlockScoutWeb.API.V2.TransactionView do change = 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} + %{"total" => TokenTransferView.prepare_token_transfer_total(token_transfer), "direction" => direction} end else state_change.balance_diff @@ -762,8 +783,8 @@ defmodule BlockScoutWeb.API.V2.TransactionView do transactions end - defp chain_type_fields(result, transaction, single_tx?, conn, _watchlist_names) do - if single_tx? do + defp chain_type_fields(result, transaction, single_transaction?, conn, _watchlist_names) do + if single_transaction? do # credo:disable-for-next-line Credo.Check.Design.AliasUsage BlockScoutWeb.API.V2.PolygonEdgeView.extend_transaction_json_response(result, transaction.hash, conn) else @@ -776,8 +797,8 @@ defmodule BlockScoutWeb.API.V2.TransactionView do transactions end - defp chain_type_fields(result, transaction, single_tx?, _conn, _watchlist_names) do - if single_tx? do + defp chain_type_fields(result, transaction, single_transaction?, _conn, _watchlist_names) do + if single_transaction? do # credo:disable-for-next-line Credo.Check.Design.AliasUsage BlockScoutWeb.API.V2.PolygonZkevmView.extend_transaction_json_response(result, transaction) else @@ -790,8 +811,8 @@ defmodule BlockScoutWeb.API.V2.TransactionView do transactions end - defp chain_type_fields(result, transaction, single_tx?, _conn, _watchlist_names) do - if single_tx? do + defp chain_type_fields(result, transaction, single_transaction?, _conn, _watchlist_names) do + if single_transaction? do # credo:disable-for-next-line Credo.Check.Design.AliasUsage BlockScoutWeb.API.V2.ZkSyncView.extend_transaction_json_response(result, transaction) else @@ -804,8 +825,8 @@ defmodule BlockScoutWeb.API.V2.TransactionView do transactions end - defp chain_type_fields(result, transaction, single_tx?, _conn, _watchlist_names) do - if single_tx? do + defp chain_type_fields(result, transaction, single_transaction?, _conn, _watchlist_names) do + if single_transaction? do # credo:disable-for-next-line Credo.Check.Design.AliasUsage BlockScoutWeb.API.V2.ArbitrumView.extend_transaction_json_response(result, transaction) else @@ -818,8 +839,8 @@ defmodule BlockScoutWeb.API.V2.TransactionView do transactions end - defp chain_type_fields(result, transaction, single_tx?, _conn, _watchlist_names) do - if single_tx? do + defp chain_type_fields(result, transaction, single_transaction?, _conn, _watchlist_names) do + if single_transaction? do # credo:disable-for-next-line Credo.Check.Design.AliasUsage BlockScoutWeb.API.V2.OptimismView.extend_transaction_json_response(result, transaction) else @@ -827,18 +848,32 @@ defmodule BlockScoutWeb.API.V2.TransactionView do end end + :scroll -> + defp chain_type_transformations(transactions) do + transactions + end + + defp chain_type_fields(result, transaction, single_transaction?, _conn, _watchlist_names) do + if single_transaction? do + # credo:disable-for-next-line Credo.Check.Design.AliasUsage + BlockScoutWeb.API.V2.ScrollView.extend_transaction_json_response(result, transaction) + else + result + end + end + :suave -> defp chain_type_transformations(transactions) do transactions end - defp chain_type_fields(result, transaction, single_tx?, conn, watchlist_names) do - if single_tx? do + defp chain_type_fields(result, transaction, single_transaction?, conn, watchlist_names) do + if single_transaction? do # credo:disable-for-next-line Credo.Check.Design.AliasUsage BlockScoutWeb.API.V2.SuaveView.extend_transaction_json_response( transaction, result, - single_tx?, + single_transaction?, conn, watchlist_names ) @@ -853,7 +888,7 @@ defmodule BlockScoutWeb.API.V2.TransactionView do BlockScoutWeb.API.V2.StabilityView.transform_transactions(transactions) end - defp chain_type_fields(result, transaction, _single_tx?, _conn, _watchlist_names) do + defp chain_type_fields(result, transaction, _single_transaction?, _conn, _watchlist_names) do # credo:disable-for-next-line Credo.Check.Design.AliasUsage BlockScoutWeb.API.V2.StabilityView.extend_transaction_json_response(result, transaction) end @@ -863,7 +898,7 @@ defmodule BlockScoutWeb.API.V2.TransactionView do transactions end - defp chain_type_fields(result, transaction, _single_tx?, _conn, _watchlist_names) do + defp chain_type_fields(result, transaction, _single_transaction?, _conn, _watchlist_names) do # credo:disable-for-next-line Credo.Check.Design.AliasUsage BlockScoutWeb.API.V2.EthereumView.extend_transaction_json_response(result, transaction) end @@ -873,7 +908,7 @@ defmodule BlockScoutWeb.API.V2.TransactionView do transactions end - defp chain_type_fields(result, transaction, _single_tx?, _conn, _watchlist_names) do + defp chain_type_fields(result, transaction, _single_transaction?, _conn, _watchlist_names) do # credo:disable-for-next-line Credo.Check.Design.AliasUsage BlockScoutWeb.API.V2.CeloView.extend_transaction_json_response(result, transaction) end @@ -883,7 +918,7 @@ defmodule BlockScoutWeb.API.V2.TransactionView do transactions end - defp chain_type_fields(result, _transaction, _single_tx?, _conn, _watchlist_names) do + defp chain_type_fields(result, _transaction, _single_transaction?, _conn, _watchlist_names) do result end end diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/v2/validator_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/v2/validator_view.ex index e5b7195..e54e4f7 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/api/v2/validator_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/api/v2/validator_view.ex @@ -4,14 +4,31 @@ defmodule BlockScoutWeb.API.V2.ValidatorView do alias BlockScoutWeb.API.V2.Helper def render("stability_validators.json", %{validators: validators, next_page_params: next_page_params}) do - %{"items" => Enum.map(validators, &prepare_validator(&1)), "next_page_params" => next_page_params} + %{"items" => Enum.map(validators, &prepare_stability_validator(&1)), "next_page_params" => next_page_params} end - defp prepare_validator(validator) do + def render("blackfort_validators.json", %{validators: validators, next_page_params: next_page_params}) do + %{"items" => Enum.map(validators, &prepare_blackfort_validator(&1)), "next_page_params" => next_page_params} + end + + defp prepare_stability_validator(validator) do %{ "address" => Helper.address_with_info(nil, validator.address, validator.address_hash, true), "state" => validator.state, "blocks_validated_count" => validator.blocks_validated } end + + defp prepare_blackfort_validator(validator) do + %{ + "address" => Helper.address_with_info(nil, validator.address, validator.address_hash, true), + "name" => validator.name, + "commission" => validator.commission, + "self_bonded_amount" => validator.self_bonded_amount, + "delegated_amount" => validator.delegated_amount, + "slashing_status_is_slashed" => validator.slashing_status_is_slashed, + "slashing_status_by_block" => validator.slashing_status_by_block, + "slashing_status_multiplier" => validator.slashing_status_multiplier + } + end end diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/v2/zksync_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/v2/zksync_view.ex index a5c418a..b2eeb59 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/api/v2/zksync_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/api/v2/zksync_view.ex @@ -15,14 +15,18 @@ defmodule BlockScoutWeb.API.V2.ZkSyncView do "number" => batch.number, "timestamp" => batch.timestamp, "root_hash" => batch.root_hash, - "l1_tx_count" => batch.l1_tx_count, - "l2_tx_count" => batch.l2_tx_count, + "l1_transaction_count" => batch.l1_transaction_count, + "l2_transaction_count" => batch.l2_transaction_count, + # todo: keep next line for compatibility with frontend and remove when new frontend is bound to `l1_transaction_count` property + "l1_tx_count" => batch.l1_transaction_count, + # todo: keep next line for compatibility with frontend and remove when new frontend is bound to `l2_transaction_count` property + "l2_tx_count" => batch.l2_transaction_count, "l1_gas_price" => batch.l1_gas_price, "l2_fair_gas_price" => batch.l2_fair_gas_price, "start_block" => batch.start_block, "end_block" => batch.end_block } - |> add_l1_txs_info_and_status(batch) + |> add_l1_transactions_info_and_status(batch) end @doc """ @@ -64,9 +68,11 @@ defmodule BlockScoutWeb.API.V2.ZkSyncView do %{ "number" => batch.number, "timestamp" => batch.timestamp, - "tx_count" => batch.l1_tx_count + batch.l2_tx_count + # todo: keep next line for compatibility with frontend and remove when new frontend is bound to `transaction_count` property + "tx_count" => batch.l1_transaction_count + batch.l2_transaction_count, + "transaction_count" => batch.l1_transaction_count + batch.l2_transaction_count } - |> add_l1_txs_info_and_status(batch) + |> add_l1_transactions_info_and_status(batch) end) end @@ -119,7 +125,7 @@ defmodule BlockScoutWeb.API.V2.ZkSyncView do defp do_add_zksync_info(out_json, zksync_entity) do res = %{} - |> do_add_l1_txs_info_and_status(%{ + |> do_add_l1_transactions_info_and_status(%{ batch_number: get_batch_number(zksync_entity), commit_transaction: zksync_entity.zksync_commit_transaction, prove_transaction: zksync_entity.zksync_prove_transaction, @@ -138,22 +144,22 @@ defmodule BlockScoutWeb.API.V2.ZkSyncView do end end - defp add_l1_txs_info_and_status(out_json, %TransactionBatch{} = batch) do - do_add_l1_txs_info_and_status(out_json, batch) + defp add_l1_transactions_info_and_status(out_json, %TransactionBatch{} = batch) do + do_add_l1_transactions_info_and_status(out_json, batch) end - defp do_add_l1_txs_info_and_status(out_json, zksync_item) do - l1_txs = get_associated_l1_txs(zksync_item) + defp do_add_l1_transactions_info_and_status(out_json, zksync_item) do + l1_transactions = get_associated_l1_transactions(zksync_item) out_json |> Map.merge(%{ "status" => batch_status(zksync_item), - "commit_transaction_hash" => APIV2Helper.get_2map_data(l1_txs, :commit_transaction, :hash), - "commit_transaction_timestamp" => APIV2Helper.get_2map_data(l1_txs, :commit_transaction, :ts), - "prove_transaction_hash" => APIV2Helper.get_2map_data(l1_txs, :prove_transaction, :hash), - "prove_transaction_timestamp" => APIV2Helper.get_2map_data(l1_txs, :prove_transaction, :ts), - "execute_transaction_hash" => APIV2Helper.get_2map_data(l1_txs, :execute_transaction, :hash), - "execute_transaction_timestamp" => APIV2Helper.get_2map_data(l1_txs, :execute_transaction, :ts) + "commit_transaction_hash" => APIV2Helper.get_2map_data(l1_transactions, :commit_transaction, :hash), + "commit_transaction_timestamp" => APIV2Helper.get_2map_data(l1_transactions, :commit_transaction, :ts), + "prove_transaction_hash" => APIV2Helper.get_2map_data(l1_transactions, :prove_transaction, :hash), + "prove_transaction_timestamp" => APIV2Helper.get_2map_data(l1_transactions, :prove_transaction, :ts), + "execute_transaction_hash" => APIV2Helper.get_2map_data(l1_transactions, :execute_transaction, :hash), + "execute_transaction_timestamp" => APIV2Helper.get_2map_data(l1_transactions, :execute_transaction, :ts) }) end @@ -165,13 +171,13 @@ defmodule BlockScoutWeb.API.V2.ZkSyncView do # # ## Returns # A map containing nesting maps describing corresponding L1 transactions - defp get_associated_l1_txs(zksync_item) do + defp get_associated_l1_transactions(zksync_item) do [:commit_transaction, :prove_transaction, :execute_transaction] - |> Enum.reduce(%{}, fn key, l1_txs -> + |> Enum.reduce(%{}, fn key, l1_transactions -> case Map.get(zksync_item, key) do - nil -> Map.put(l1_txs, key, nil) - %Ecto.Association.NotLoaded{} -> Map.put(l1_txs, key, nil) - value -> Map.put(l1_txs, key, %{hash: value.hash, ts: value.timestamp}) + nil -> Map.put(l1_transactions, key, nil) + %Ecto.Association.NotLoaded{} -> Map.put(l1_transactions, key, nil) + value -> Map.put(l1_transactions, key, %{hash: value.hash, ts: value.timestamp}) end end) end diff --git a/apps/block_scout_web/lib/block_scout_web/views/block_transaction_view.ex b/apps/block_scout_web/lib/block_scout_web/views/block_transaction_view.ex index aef7f41..025f278 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/block_transaction_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/block_transaction_view.ex @@ -1,7 +1,7 @@ defmodule BlockScoutWeb.BlockTransactionView do use BlockScoutWeb, :view - import BlockScoutWeb.Gettext, only: [gettext: 1] + use Gettext, backend: BlockScoutWeb.Gettext def block_not_found_message({:ok, true}) do gettext("Easy Cowboy! This block does not exist yet!") diff --git a/apps/block_scout_web/lib/block_scout_web/views/internal_transaction_view.ex b/apps/block_scout_web/lib/block_scout_web/views/internal_transaction_view.ex index 191be34..3d4cafd 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/internal_transaction_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/internal_transaction_view.ex @@ -3,7 +3,7 @@ defmodule BlockScoutWeb.InternalTransactionView do alias Explorer.Chain.InternalTransaction - import BlockScoutWeb.Gettext + use Gettext, backend: BlockScoutWeb.Gettext @doc """ Returns the formatted string for the type of the internal transaction. diff --git a/apps/block_scout_web/lib/block_scout_web/views/tab_helper.ex b/apps/block_scout_web/lib/block_scout_web/views/tab_helper.ex index e6b4ff7..8e2ec70 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/tab_helper.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/tab_helper.ex @@ -24,7 +24,7 @@ defmodule BlockScoutWeb.TabHelper do "active" else case request_path do - "/tx/" <> "0x" <> <<_tx_hash::binary-size(64)>> -> + "/tx/" <> "0x" <> <<_transaction_hash::binary-size(64)>> -> tab_status_selector(tab_name, show_token_transfers) _ -> 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 7381400..88fc209 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 @@ -22,8 +22,8 @@ defmodule BlockScoutWeb.TransactionStateView do Decimal.abs(val) end - def has_state_changes?(tx) do - has_diff?(from_loss(tx)) or has_diff?(to_profit(tx)) + def has_state_changes?(transaction) do + has_diff?(from_loss(transaction)) or has_diff?(to_profit(transaction)) end def display_value(balance, :coin, _token_id) do 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 64eafbc..81b1b06 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 @@ -11,7 +11,7 @@ defmodule BlockScoutWeb.TransactionView do alias Explorer.ExchangeRates.Token alias Timex.Duration - import BlockScoutWeb.Gettext + use Gettext, backend: BlockScoutWeb.Gettext import BlockScoutWeb.AddressView, only: [from_address_hash: 1, short_token_id: 2, tag_name_to_label: 1] import BlockScoutWeb.Tokens.Helper @@ -395,8 +395,7 @@ defmodule BlockScoutWeb.TransactionView do end def decoded_input_data(transaction) do - {result, _, _} = Transaction.decoded_input_data(transaction, []) - result + Transaction.decoded_input_data(transaction, []) end def decoded_revert_reason(revert_reason, transaction, options) do 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 72c1c1e..188303e 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 @@ -3,7 +3,7 @@ defmodule BlockScoutWeb.WeiHelper do Helper functions for interacting with `t:Explorer.Chain.Wei.t/0` values. """ - import BlockScoutWeb.Gettext + use Gettext, backend: BlockScoutWeb.Gettext alias BlockScoutWeb.CldrHelper alias Explorer.Chain.Wei diff --git a/apps/block_scout_web/mix.exs b/apps/block_scout_web/mix.exs index f2ebd15..7ffaf8b 100644 --- a/apps/block_scout_web/mix.exs +++ b/apps/block_scout_web/mix.exs @@ -23,7 +23,7 @@ defmodule BlockScoutWeb.Mixfile do dialyzer: :test ], start_permanent: Mix.env() == :prod, - version: "6.7.2", + version: "6.9.2", xref: [ exclude: [ Explorer.Chain.PolygonZkevm.Reader, @@ -106,7 +106,7 @@ defmodule BlockScoutWeb.Mixfile do # HTML CSS selectors for Phoenix controller tests {:floki, "~> 0.31"}, {:flow, "~> 1.2"}, - {:gettext, "~> 0.25.0"}, + {:gettext, "~> 0.26.1"}, {:hammer, "~> 6.0"}, {:httpoison, "~> 2.0"}, {:indexer, in_umbrella: true, runtime: false}, 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 e4ff798..77f2332 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 @@ -237,9 +237,9 @@ defmodule BlockScoutWeb.WebsocketV2Test do Subscriber.to(:transactions, :realtime) Import.all(@import_data) - assert_receive {:chain_event, :transactions, :realtime, txs}, :timer.seconds(5) + assert_receive {:chain_event, :transactions, :realtime, transactions}, :timer.seconds(5) - Notifier.handle_event({:chain_event, :transactions, :realtime, txs}) + Notifier.handle_event({:chain_event, :transactions, :realtime, transactions}) assert_receive %Phoenix.Socket.Message{ payload: %{transaction: 2}, @@ -280,7 +280,7 @@ defmodule BlockScoutWeb.WebsocketV2Test do :timer.seconds(5) end - test "broadcast array of txs to address" do + test "broadcast array of transactions to address" do topic = "addresses:0x8bf38d4764929064f2d4d3a56520a76ab3df415b" {:ok, _reply, _socket} = @@ -291,34 +291,34 @@ defmodule BlockScoutWeb.WebsocketV2Test do Subscriber.to(:transactions, :realtime) Import.all(@import_data) - assert_receive {:chain_event, :transactions, :realtime, txs}, :timer.seconds(5) - Notifier.handle_event({:chain_event, :transactions, :realtime, txs}) + assert_receive {:chain_event, :transactions, :realtime, transactions}, :timer.seconds(5) + Notifier.handle_event({:chain_event, :transactions, :realtime, transactions}) assert_receive %Phoenix.Socket.Message{ - payload: %{transactions: [tx_1, tx_2]}, + payload: %{transactions: [transaction_1, transaction_2]}, event: "transaction", topic: ^topic }, :timer.seconds(5) - tx_1 = tx_1 |> Jason.encode!() |> Jason.decode!() - compare_item(Repo.get_by(Transaction, %{hash: tx_1["hash"]}), tx_1) + transaction_1 = transaction_1 |> Jason.encode!() |> Jason.decode!() + compare_item(Repo.get_by(Transaction, %{hash: transaction_1["hash"]}), transaction_1) - tx_2 = tx_2 |> Jason.encode!() |> Jason.decode!() - compare_item(Repo.get_by(Transaction, %{hash: tx_2["hash"]}), tx_2) + transaction_2 = transaction_2 |> Jason.encode!() |> Jason.decode!() + compare_item(Repo.get_by(Transaction, %{hash: transaction_2["hash"]}), transaction_2) assert_receive %Phoenix.Socket.Message{ - payload: %{transactions: [tx_1, tx_2]}, + payload: %{transactions: [transaction_1, transaction_2]}, event: "pending_transaction", topic: ^topic }, :timer.seconds(5) - tx_1 = tx_1 |> Jason.encode!() |> Jason.decode!() - compare_item(Repo.get_by(Transaction, %{hash: tx_1["hash"]}), tx_1) + transaction_1 = transaction_1 |> Jason.encode!() |> Jason.decode!() + compare_item(Repo.get_by(Transaction, %{hash: transaction_1["hash"]}), transaction_1) - tx_2 = tx_2 |> Jason.encode!() |> Jason.decode!() - compare_item(Repo.get_by(Transaction, %{hash: tx_2["hash"]}), tx_2) + transaction_2 = transaction_2 |> Jason.encode!() |> Jason.decode!() + compare_item(Repo.get_by(Transaction, %{hash: transaction_2["hash"]}), transaction_2) end test "broadcast array of transfers to address" do @@ -369,17 +369,17 @@ defmodule BlockScoutWeb.WebsocketV2Test do 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 to_string(token_transfer.transaction_hash) == json["transaction_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 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) do assert to_string(transaction.hash) == json["hash"] - assert transaction.block_number == json["block"] + assert transaction.block_number == json["block_number"] 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"] diff --git a/apps/block_scout_web/test/block_scout_web/controllers/account/api/v2/user_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/account/api/v2/user_controller_test.exs index 30f44f6..68f7047 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/account/api/v2/user_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/account/api/v2/user_controller_test.exs @@ -2,6 +2,7 @@ defmodule BlockScoutWeb.Account.Api.V2.UserControllerTest do use BlockScoutWeb.ConnCase alias Explorer.Account.{ + Identity, TagAddress, TagTransaction, WatchlistAddress @@ -9,12 +10,11 @@ defmodule BlockScoutWeb.Account.Api.V2.UserControllerTest do alias Explorer.Chain.Address alias Explorer.Repo - alias BlockScoutWeb.Models.UserFromAuth setup %{conn: conn} do auth = build(:auth) - {:ok, user} = UserFromAuth.find_or_create(auth) + {:ok, user} = Identity.find_or_create(auth) {:ok, user: user, conn: Plug.Test.init_test_session(conn, current_user: user)} end @@ -30,7 +30,8 @@ defmodule BlockScoutWeb.Account.Api.V2.UserControllerTest do "nickname" => user.nickname, "name" => user.name, "email" => user.email, - "avatar" => user.avatar + "avatar" => user.avatar, + "address_hash" => user.address_hash } end @@ -182,7 +183,9 @@ defmodule BlockScoutWeb.Account.Api.V2.UserControllerTest do |> Map.get("items") assert Enum.all?(created, fn {_, _, map} -> - map in response + Enum.any?(response, fn item -> + addresses_json_match?(map, item) + end) end) end @@ -237,7 +240,11 @@ defmodule BlockScoutWeb.Account.Api.V2.UserControllerTest do |> json_response(200) |> Map.get("items") - assert Enum.all?(created, fn {_, _, map} -> map in response end) + assert Enum.all?(created, fn {_, _, map} -> + Enum.any?(response, fn item -> + addresses_json_match?(map, item) + end) + end) {_, _, %{"id" => id}} = Enum.at(created, 0) @@ -268,32 +275,32 @@ defmodule BlockScoutWeb.Account.Api.V2.UserControllerTest do end test "post private transaction tag", %{conn: conn} do - tx_hash_non_existing = to_string(build(:transaction).hash) - tx_hash = to_string(insert(:transaction).hash) + transaction_hash_non_existing = to_string(build(:transaction).hash) + transaction_hash = to_string(insert(:transaction).hash) assert conn |> post("/api/account/v2/user/tags/transaction", %{ - "transaction_hash" => tx_hash_non_existing, + "transaction_hash" => transaction_hash_non_existing, "name" => "MyName" }) - |> doc(description: "Error on try to create private transaction tag for tx does not exist") - |> json_response(422) == %{"errors" => %{"tx_hash" => ["Transaction does not exist"]}} + |> doc(description: "Error on try to create private transaction tag for transaction does not exist") + |> json_response(422) == %{"errors" => %{"transaction_hash" => ["Transaction does not exist"]}} tag_transaction_response = conn |> post("/api/account/v2/user/tags/transaction", %{ - "transaction_hash" => tx_hash, + "transaction_hash" => transaction_hash, "name" => "MyName" }) |> doc(description: "Create private transaction tag") |> json_response(200) conn - |> get("/api/account/v2/tags/transaction/#{tx_hash}") + |> get("/api/account/v2/tags/transaction/#{transaction_hash}") |> doc(description: "Get tags for transaction") |> json_response(200) - assert tag_transaction_response["transaction_hash"] == tx_hash + assert tag_transaction_response["transaction_hash"] == transaction_hash assert tag_transaction_response["name"] == "MyName" assert tag_transaction_response["id"] end @@ -343,11 +350,11 @@ defmodule BlockScoutWeb.Account.Api.V2.UserControllerTest do end test "edit private transaction tag", %{conn: conn} do - tx_tag = build(:tag_transaction) + transaction_tag = build(:tag_transaction) tag_response = conn - |> post("/api/account/v2/user/tags/transaction", tx_tag) + |> post("/api/account/v2/user/tags/transaction", transaction_tag) |> json_response(200) _response = @@ -355,20 +362,20 @@ defmodule BlockScoutWeb.Account.Api.V2.UserControllerTest do |> get("/api/account/v2/user/tags/transaction") |> json_response(200) == [tag_response] - assert tag_response["address_hash"] == tx_tag["address_hash"] - assert tag_response["name"] == tx_tag["name"] + assert tag_response["address_hash"] == transaction_tag["address_hash"] + assert tag_response["name"] == transaction_tag["name"] assert tag_response["id"] - new_tx_tag = build(:tag_transaction) + new_transaction_tag = build(:tag_transaction) new_tag_response = conn - |> put("/api/account/v2/user/tags/transaction/#{tag_response["id"]}", new_tx_tag) + |> put("/api/account/v2/user/tags/transaction/#{tag_response["id"]}", new_transaction_tag) |> doc(description: "Edit private transaction tag") |> json_response(200) - assert new_tag_response["address_hash"] == new_tx_tag["address_hash"] - assert new_tag_response["name"] == new_tx_tag["name"] + assert new_tag_response["address_hash"] == new_transaction_tag["address_hash"] + assert new_tag_response["name"] == new_transaction_tag["name"] assert new_tag_response["id"] == tag_response["id"] end @@ -378,25 +385,25 @@ defmodule BlockScoutWeb.Account.Api.V2.UserControllerTest do zipped = Enum.zip(transactions, names) created = - Enum.map(zipped, fn {tx_hash, name} -> + Enum.map(zipped, fn {transaction_hash, name} -> id = (conn |> post("/api/account/v2/user/tags/transaction", %{ - "transaction_hash" => tx_hash, + "transaction_hash" => transaction_hash, "name" => name }) |> json_response(200))["id"] - {tx_hash, %{"label" => name}, %{"transaction_hash" => tx_hash, "id" => id, "name" => name}} + {transaction_hash, %{"label" => name}, %{"transaction_hash" => transaction_hash, "id" => id, "name" => name}} end) - assert Enum.all?(created, fn {tx_hash, map_tag, _} -> + assert Enum.all?(created, fn {transaction_hash, map_tag, _} -> response = conn - |> get("/api/account/v2/tags/transaction/#{tx_hash}") + |> get("/api/account/v2/tags/transaction/#{transaction_hash}") |> json_response(200) - response["personal_tx_tag"] == map_tag + response["personal_transaction_tag"] == map_tag end) response = @@ -415,25 +422,25 @@ defmodule BlockScoutWeb.Account.Api.V2.UserControllerTest do zipped = Enum.zip(transactions, names) created = - Enum.map(zipped, fn {tx_hash, name} -> + Enum.map(zipped, fn {transaction_hash, name} -> id = (conn |> post("/api/account/v2/user/tags/transaction", %{ - "transaction_hash" => tx_hash, + "transaction_hash" => transaction_hash, "name" => name }) |> json_response(200))["id"] - {tx_hash, %{"label" => name}, %{"transaction_hash" => tx_hash, "id" => id, "name" => name}} + {transaction_hash, %{"label" => name}, %{"transaction_hash" => transaction_hash, "id" => id, "name" => name}} end) - assert Enum.all?(created, fn {tx_hash, map_tag, _} -> + assert Enum.all?(created, fn {transaction_hash, map_tag, _} -> response = conn - |> get("/api/account/v2/tags/transaction/#{tx_hash}") + |> get("/api/account/v2/tags/transaction/#{transaction_hash}") |> json_response(200) - response["personal_tx_tag"] == map_tag + response["personal_transaction_tag"] == map_tag end) response = @@ -469,7 +476,7 @@ defmodule BlockScoutWeb.Account.Api.V2.UserControllerTest do |> json_response(200) |> Map.get("items") - response["personal_tx_tag"] == nil + response["personal_transaction_tag"] == nil end) end @@ -1221,7 +1228,7 @@ defmodule BlockScoutWeb.Account.Api.V2.UserControllerTest do end defp compare_item(%TagTransaction{} = tag_transaction, json) do - assert json["transaction_hash"] == to_string(tag_transaction.tx_hash) + assert json["transaction_hash"] == to_string(tag_transaction.transaction_hash) assert json["name"] == tag_transaction.name assert json["id"] == tag_transaction.id end @@ -1264,4 +1271,14 @@ defmodule BlockScoutWeb.Account.Api.V2.UserControllerTest do assert second_page_resp["next_page_params"] == nil compare_item(Enum.at(list, 0), Enum.at(second_page_resp["items"], 0)) end + + defp addresses_json_match?(expected, actual) do + Enum.all?(expected, fn {key, value} -> + case value do + # Recursively compare nested maps + %{} -> addresses_json_match?(value, actual[key]) + _ -> actual[key] == value + end + end) + end end diff --git a/apps/block_scout_web/test/block_scout_web/controllers/account/custom_abi_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/account/custom_abi_controller_test.exs index d2b56b1..cea4aa4 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/account/custom_abi_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/account/custom_abi_controller_test.exs @@ -1,15 +1,15 @@ defmodule BlockScoutWeb.Account.CustomABIControllerTest do use BlockScoutWeb.ConnCase + alias Explorer.Account.Identity alias Explorer.TestHelper - alias BlockScoutWeb.Models.UserFromAuth @custom_abi "[{\"type\":\"function\",\"outputs\":[{\"type\":\"string\",\"name\":\"\"}],\"name\":\"name\",\"inputs\":[],\"constant\":true}]" setup %{conn: conn} do auth = build(:auth) - {:ok, user} = UserFromAuth.find_or_create(auth) + {:ok, user} = Identity.find_or_create(auth) {:ok, conn: Plug.Test.init_test_session(conn, current_user: user)} 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 04dd677..873597f 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 @@ -161,6 +161,10 @@ defmodule BlockScoutWeb.AddressTransactionControllerTest do end describe "GET token-transfers-csv/2" do + setup do + csv_setup() + end + test "do not export token transfers to csv without recaptcha recaptcha_response provided", %{conn: conn} do address = insert(:address) @@ -185,9 +189,16 @@ defmodule BlockScoutWeb.AddressTransactionControllerTest do assert conn.status == 404 end - test "do not export token transfers to csv without recaptcha passed", %{conn: conn} do - BlockScoutWeb.TestCaptchaHelper - |> expect(:recaptcha_passed?, fn _captcha_response -> false end) + test "do not export token transfers to csv without recaptcha passed", %{ + conn: conn, + v2_secret_key: recaptcha_secret_key + } do + expected_body = "secret=#{recaptcha_secret_key}&response=123" + + Explorer.Mox.HTTPoison + |> expect(:post, fn _url, ^expected_body, _headers, _options -> + {:ok, %HTTPoison.Response{status_code: 200, body: Jason.encode!(%{"success" => false})}} + end) address = insert(:address) @@ -242,9 +253,21 @@ defmodule BlockScoutWeb.AddressTransactionControllerTest do Application.put_env(:block_scout_web, :recaptcha, init_config) end - test "exports token transfers to csv", %{conn: conn} do - BlockScoutWeb.TestCaptchaHelper - |> expect(:recaptcha_passed?, fn _captcha_response -> true end) + test "exports token transfers to csv", %{conn: conn, v2_secret_key: recaptcha_secret_key} do + expected_body = "secret=#{recaptcha_secret_key}&response=123" + + Explorer.Mox.HTTPoison + |> expect(:post, fn _url, ^expected_body, _headers, _options -> + {:ok, + %HTTPoison.Response{ + status_code: 200, + body: + Jason.encode!(%{ + "success" => true, + "hostname" => Application.get_env(:block_scout_web, BlockScoutWeb.Endpoint)[:url][:host] + }) + }} + end) address = insert(:address) @@ -272,9 +295,25 @@ defmodule BlockScoutWeb.AddressTransactionControllerTest do end describe "GET transactions_csv/2" do - test "download csv file with transactions", %{conn: conn} do - BlockScoutWeb.TestCaptchaHelper - |> expect(:recaptcha_passed?, fn _captcha_response -> true end) + setup do + csv_setup() + end + + test "download csv file with transactions", %{conn: conn, v2_secret_key: recaptcha_secret_key} do + expected_body = "secret=#{recaptcha_secret_key}&response=123" + + Explorer.Mox.HTTPoison + |> expect(:post, fn _url, ^expected_body, _headers, _options -> + {:ok, + %HTTPoison.Response{ + status_code: 200, + body: + Jason.encode!(%{ + "success" => true, + "hostname" => Application.get_env(:block_scout_web, BlockScoutWeb.Endpoint)[:url][:host] + }) + }} + end) address = insert(:address) @@ -302,9 +341,25 @@ defmodule BlockScoutWeb.AddressTransactionControllerTest do end describe "GET internal_transactions_csv/2" do - test "download csv file with internal transactions", %{conn: conn} do - BlockScoutWeb.TestCaptchaHelper - |> expect(:recaptcha_passed?, fn _captcha_response -> true end) + setup do + csv_setup() + end + + test "download csv file with internal transactions", %{conn: conn, v2_secret_key: recaptcha_secret_key} do + expected_body = "secret=#{recaptcha_secret_key}&response=123" + + Explorer.Mox.HTTPoison + |> expect(:post, fn _url, ^expected_body, _headers, _options -> + {:ok, + %HTTPoison.Response{ + status_code: 200, + body: + Jason.encode!(%{ + "success" => true, + "hostname" => Application.get_env(:block_scout_web, BlockScoutWeb.Endpoint)[:url][:host] + }) + }} + end) address = insert(:address) @@ -369,9 +424,25 @@ defmodule BlockScoutWeb.AddressTransactionControllerTest do end describe "GET logs_csv/2" do - test "download csv file with logs", %{conn: conn} do - BlockScoutWeb.TestCaptchaHelper - |> expect(:recaptcha_passed?, fn _captcha_response -> true end) + setup do + csv_setup() + end + + test "download csv file with logs", %{conn: conn, v2_secret_key: recaptcha_secret_key} do + expected_body = "secret=#{recaptcha_secret_key}&response=123" + + Explorer.Mox.HTTPoison + |> expect(:post, fn _url, ^expected_body, _headers, _options -> + {:ok, + %HTTPoison.Response{ + status_code: 200, + body: + Jason.encode!(%{ + "success" => true, + "hostname" => Application.get_env(:block_scout_web, BlockScoutWeb.Endpoint)[:url][:host] + }) + }} + end) address = insert(:address) @@ -428,9 +499,21 @@ defmodule BlockScoutWeb.AddressTransactionControllerTest do assert conn.resp_body |> String.split("\n") |> Enum.count() == 5 end - test "handles null filter", %{conn: conn} do - BlockScoutWeb.TestCaptchaHelper - |> expect(:recaptcha_passed?, fn _captcha_response -> true end) + test "handles null filter", %{conn: conn, v2_secret_key: recaptcha_secret_key} do + expected_body = "secret=#{recaptcha_secret_key}&response=123" + + Explorer.Mox.HTTPoison + |> expect(:post, fn _url, ^expected_body, _headers, _options -> + {:ok, + %HTTPoison.Response{ + status_code: 200, + body: + Jason.encode!(%{ + "success" => true, + "hostname" => Application.get_env(:block_scout_web, BlockScoutWeb.Endpoint)[:url][:host] + }) + }} + end) address = insert(:address) @@ -463,4 +546,27 @@ defmodule BlockScoutWeb.AddressTransactionControllerTest do assert conn.resp_body |> String.split("\n") |> Enum.count() == 3 end end + + defp csv_setup() do + old_recaptcha_env = Application.get_env(:block_scout_web, :recaptcha) + old_http_adapter = Application.get_env(:block_scout_web, :http_adapter) + + v2_secret_key = "v2_secret_key" + v3_secret_key = "v3_secret_key" + + Application.put_env(:block_scout_web, :recaptcha, + v2_secret_key: v2_secret_key, + v3_secret_key: v3_secret_key, + is_disabled: false + ) + + Application.put_env(:block_scout_web, :http_adapter, Explorer.Mox.HTTPoison) + + on_exit(fn -> + Application.put_env(:block_scout_web, :recaptcha, old_recaptcha_env) + Application.put_env(:block_scout_web, :http_adapter, old_http_adapter) + end) + + {:ok, %{v2_secret_key: v2_secret_key, v3_secret_key: v3_secret_key}} + 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 0901f55..027db4a 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 @@ -5,6 +5,7 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do alias BlockScoutWeb.API.RPC.AddressController alias Explorer.Chain + alias Explorer.Chain.Cache.BackgroundMigrations alias Explorer.Chain.{Events.Subscriber, Transaction, Wei} alias Explorer.Counters.{AddressesCounter, AverageBlockTime} alias Indexer.Fetcher.OnDemand.CoinBalance, as: CoinBalanceOnDemand @@ -2373,6 +2374,39 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do %{params: %{"module" => "account", "action" => "tokennfttx"}} end + test "API endpoint works after `transactions` table denormalization finished", %{conn: conn, params: params} do + BackgroundMigrations.set_tt_denormalization_finished(true) + + address = insert(:address) + + transaction = + :transaction + |> insert(from_address: address) + |> with_block() + + token = insert(:token, name: "NFT", type: "ERC-721") + + insert(:token_transfer, + transaction: transaction, + from_address: address, + block_number: transaction.block_number, + token_contract_address: token.contract_address, + token_type: token.type, + token_ids: [100_500] + ) + + new_params = + params + |> Map.put("address", Explorer.Chain.Hash.to_string(address.hash)) + + response = + conn + |> get("/api", new_params) + + assert response.status == 200 + BackgroundMigrations.set_tt_denormalization_finished(false) + end + test "with missing address and contract address hash", %{conn: conn, params: params} do assert response = conn @@ -2572,12 +2606,12 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do erc_721_tt = for x <- 0..50 do - tx = insert(:transaction, input: "0xabcd010203040506") |> with_block() + transaction = insert(:transaction, input: "0xabcd010203040506") |> with_block() insert(:token_transfer, - transaction: tx, - block: tx.block, - block_number: tx.block_number, + transaction: transaction, + block: transaction.block, + block_number: transaction.block_number, from_address: address, token_contract_address: erc_721_token.contract_address, token_ids: [x] 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 7812d11..94e217d 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 @@ -611,7 +611,7 @@ defmodule BlockScoutWeb.API.RPC.ContractControllerTest do implementation_contract_address_hash_string = Base.encode16(implementation_contract.address_hash.bytes, case: :lower) - proxy_tx_input = + proxy_transaction_input = "0x11b804ab000000000000000000000000" <> implementation_contract_address_hash_string <> "000000000000000000000000000000000000000000000000000000000000006035323031313537360000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000284e159163400000000000000000000000034420c13696f4ac650b9fafe915553a1abcd7dd30000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000001c00000000000000000000000000000000000000000000000000000000000000220000000000000000000000000ff5ae9b0a7522736299d797d80b8fc6f31d61100000000000000000000000000ff5ae9b0a7522736299d797d80b8fc6f31d6110000000000000000000000000000000000000000000000000000000000000003e8000000000000000000000000000000000000000000000000000000000000000000000000000000000000000034420c13696f4ac650b9fafe915553a1abcd7dd300000000000000000000000000000000000000000000000000000000000000184f7074696d69736d2053756273637269626572204e465473000000000000000000000000000000000000000000000000000000000000000000000000000000054f504e46540000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000037697066733a2f2f516d66544e504839765651334b5952346d6b52325a6b757756424266456f5a5554545064395538666931503332752f300000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c82bbe41f2cf04e3a8efa18f7032bdd7f6d98a81000000000000000000000000efba8a2a82ec1fb1273806174f5e28fbb917cf9500000000000000000000000000000000000000000000000000000000" @@ -732,16 +732,16 @@ defmodule BlockScoutWeb.API.RPC.ContractControllerTest do abi: proxy_abi ) - tx = + transaction = insert(:transaction, created_contract_address_hash: proxy_address.hash, - input: proxy_tx_input + input: proxy_transaction_input ) |> with_block(status: :ok) name = implementation_contract.name - from = Address.checksum(tx.from_address_hash) - tx_hash = to_string(tx.hash) + from = Address.checksum(transaction.from_address_hash) + transaction_hash = to_string(transaction.hash) address_hash = Address.checksum(proxy_address.hash) {:ok, implementation_contract_address_hash} = @@ -1170,7 +1170,7 @@ defmodule BlockScoutWeb.API.RPC.ContractControllerTest do %{ "contractAddress" => contract_address, "contractCreator" => contract_creator, - "txHash" => tx_hash + "txHash" => transaction_hash } ] } = @@ -1180,7 +1180,7 @@ defmodule BlockScoutWeb.API.RPC.ContractControllerTest do assert contract_address == to_string(address.hash) assert contract_creator == to_string(transaction.from_address_hash) - assert tx_hash == to_string(transaction.hash) + assert transaction_hash == to_string(transaction.hash) 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 48646b3..1e25c00 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 @@ -256,6 +256,7 @@ defmodule BlockScoutWeb.API.RPC.EthControllerTest do insert(:log, block: block, + block_number: block.number, address: address, transaction: transaction, data: "0x020202", @@ -333,13 +334,37 @@ 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", block_number: block1.number) + insert(:log, + address: address, + transaction: transaction1, + data: "0x010101", + block: block1, + block_number: block1.number + ) - insert(:log, address: address, transaction: transaction2, data: "0x020202", block_number: block2.number) + insert(:log, + address: address, + transaction: transaction2, + data: "0x020202", + block: block2, + block_number: block2.number + ) - insert(:log, address: address, transaction: transaction3, data: "0x030303", block_number: block3.number) + insert(:log, + address: address, + transaction: transaction3, + data: "0x030303", + block: block3, + block_number: block3.number + ) - insert(:log, address: address, transaction: transaction4, data: "0x040404", block_number: block4.number) + insert(:log, + address: address, + transaction: transaction4, + data: "0x040404", + block: block4, + block_number: block4.number + ) params = params(api_params, [%{"address" => to_string(address.hash), "fromBlock" => 1, "toBlock" => 2}]) @@ -363,11 +388,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, address: address, transaction: transaction1, data: "0x010101", block_number: block1.number) + insert(:log, + address: address, + transaction: transaction1, + data: "0x010101", + block: block1, + block_number: block1.number + ) - insert(:log, address: address, transaction: transaction2, data: "0x020202", block_number: block2.number) + insert(:log, + address: address, + transaction: transaction2, + data: "0x020202", + block: block2, + block_number: block2.number + ) - insert(:log, address: address, transaction: transaction3, data: "0x030303", block_number: block3.number) + insert(:log, + address: address, + transaction: transaction3, + data: "0x030303", + block: block3, + block_number: block3.number + ) params = params(api_params, [%{"address" => to_string(address.hash), "blockHash" => to_string(block2.hash)}]) @@ -391,11 +434,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, address: address, transaction: transaction1, data: "0x010101", block_number: block1.number) + insert(:log, + address: address, + transaction: transaction1, + data: "0x010101", + block: block1, + block_number: block1.number + ) - insert(:log, address: address, transaction: transaction2, data: "0x020202", block_number: block2.number) + insert(:log, + address: address, + transaction: transaction2, + data: "0x020202", + block: block2, + block_number: block2.number + ) - insert(:log, address: address, transaction: transaction3, data: "0x030303", block_number: block3.number) + insert(:log, + address: address, + transaction: transaction3, + data: "0x030303", + block: block3, + block_number: block3.number + ) params = params(api_params, [%{"address" => to_string(address.hash), "fromBlock" => "earliest", "toBlock" => "earliest"}]) 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 2691f1e..df82a98 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 @@ -296,7 +296,13 @@ defmodule BlockScoutWeb.API.RPC.LogsControllerTest do |> insert(to_address: contract_address) |> with_block() - log = insert(:log, address: contract_address, transaction: transaction, block_number: transaction.block_number) + log = + insert(:log, + address: contract_address, + transaction: transaction, + block: block, + block_number: transaction.block_number + ) params = %{ "module" => "logs", @@ -353,12 +359,14 @@ defmodule BlockScoutWeb.API.RPC.LogsControllerTest do insert(:log, address: contract_address, transaction: transaction_block1, + block: transaction_block1.block, block_number: transaction_block1.block_number ) insert(:log, address: contract_address, transaction: transaction_block2, + block: transaction_block2.block, block_number: transaction_block2.block_number ) @@ -406,12 +414,14 @@ defmodule BlockScoutWeb.API.RPC.LogsControllerTest do insert(:log, address: contract_address, transaction: transaction_block1, + block: transaction_block1.block, block_number: transaction_block1.block_number ) insert(:log, address: contract_address, transaction: transaction_block2, + block: transaction_block2.block, block_number: transaction_block2.block_number ) @@ -450,12 +460,16 @@ defmodule BlockScoutWeb.API.RPC.LogsControllerTest do log1_details = [ address: contract_address, transaction: transaction, + block: block, + block_number: block.number, first_topic: topic(@first_topic_hex_string_1) ] log2_details = [ address: contract_address, transaction: transaction, + block: block, + block_number: block.number, first_topic: topic(@first_topic_hex_string_2) ] @@ -508,6 +522,8 @@ defmodule BlockScoutWeb.API.RPC.LogsControllerTest do log1_details = [ address: contract_address, transaction: transaction, + block: block, + block_number: block.number, first_topic: topic(@first_topic_hex_string_1), second_topic: topic(@second_topic_hex_string_1) ] @@ -515,6 +531,8 @@ defmodule BlockScoutWeb.API.RPC.LogsControllerTest do log2_details = [ address: contract_address, transaction: transaction, + block: block, + block_number: block.number, first_topic: topic(@first_topic_hex_string_2), second_topic: topic(@second_topic_hex_string_2) ] @@ -557,6 +575,8 @@ defmodule BlockScoutWeb.API.RPC.LogsControllerTest do log1_details = [ address: contract_address, transaction: transaction, + block: block, + block_number: block.number, first_topic: topic(@first_topic_hex_string_1), second_topic: topic(@second_topic_hex_string_1) ] @@ -564,6 +584,8 @@ defmodule BlockScoutWeb.API.RPC.LogsControllerTest do log2_details = [ address: contract_address, transaction: transaction, + block: block, + block_number: block.number, first_topic: topic(@first_topic_hex_string_2), second_topic: topic(@second_topic_hex_string_2) ] @@ -605,6 +627,8 @@ defmodule BlockScoutWeb.API.RPC.LogsControllerTest do log1_details = [ address: contract_address, transaction: transaction, + block: block, + block_number: block.number, first_topic: topic(@first_topic_hex_string_1), second_topic: topic(@second_topic_hex_string_1), third_topic: topic(@third_topic_hex_string_1), @@ -614,6 +638,8 @@ defmodule BlockScoutWeb.API.RPC.LogsControllerTest do log2_details = [ address: contract_address, transaction: transaction, + block: block, + block_number: block.number, first_topic: topic(@first_topic_hex_string_1), second_topic: topic(@second_topic_hex_string_1), third_topic: topic(@third_topic_hex_string_1), 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 6ec7c2e..c9b1cc6 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 @@ -83,6 +83,68 @@ defmodule BlockScoutWeb.API.RPC.StatsControllerTest do assert response["message"] == "OK" assert :ok = ExJsonSchema.Validator.validate(tokensupply_schema(), response) end + + test "with valid contract address and cmc format", %{conn: conn} do + token = insert(:token, total_supply: 110_052_089_716_627_912_057_222_572) + + params = %{ + "module" => "stats", + "action" => "tokensupply", + "contractaddress" => to_string(token.contract_address_hash), + "cmc" => "true" + } + + assert response = + conn + |> get("/api", params) + |> text_response(200) + + assert response == "110052089.716627912" + end + + test "with custom decimals and cmc format", %{conn: conn} do + token = + insert(:token, + total_supply: 1_234_567_890, + decimals: 6 + ) + + params = %{ + "module" => "stats", + "action" => "tokensupply", + "contractaddress" => to_string(token.contract_address_hash), + "cmc" => "true" + } + + assert response = + conn + |> get("/api", params) + |> text_response(200) + + assert response == "1234.567890000" + end + end + + test "with null decimals and cmc format", %{conn: conn} do + token = + insert(:token, + total_supply: 1_234_567_890, + decimals: nil + ) + + params = %{ + "module" => "stats", + "action" => "tokensupply", + "contractaddress" => to_string(token.contract_address_hash), + "cmc" => "true" + } + + assert response = + conn + |> get("/api", params) + |> text_response(200) + + assert response == "1234567890.000000000" end describe "ethsupplyexchange" do 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 913849e..0ecd2fd 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 @@ -736,6 +736,9 @@ defmodule BlockScoutWeb.API.RPC.TransactionControllerTest do "txhash" => "#{transaction.hash}" } + init_config = Application.get_env(:ethereum_jsonrpc, EthereumJSONRPC.Geth) + Application.put_env(:ethereum_jsonrpc, EthereumJSONRPC.Geth, tracer: "call_tracer", debug_trace_timeout: "5s") + assert response = conn |> get("/api", params) @@ -744,6 +747,8 @@ defmodule BlockScoutWeb.API.RPC.TransactionControllerTest do assert response["result"]["revertReason"] == hex_reason assert response["status"] == "1" assert response["message"] == "OK" + + Application.put_env(:ethereum_jsonrpc, EthereumJSONRPC.Geth, init_config) end end @@ -836,6 +841,9 @@ defmodule BlockScoutWeb.API.RPC.TransactionControllerTest do "txhash" => "#{transaction.hash}" } + init_config = Application.get_env(:ethereum_jsonrpc, EthereumJSONRPC.Geth) + Application.put_env(:ethereum_jsonrpc, EthereumJSONRPC.Geth, tracer: "call_tracer", debug_trace_timeout: "5s") + assert response = conn |> get("/api", params) @@ -844,6 +852,8 @@ defmodule BlockScoutWeb.API.RPC.TransactionControllerTest do assert response["result"]["revertReason"] in ["", "0x"] assert response["status"] == "1" assert response["message"] == "OK" + + Application.put_env(:ethereum_jsonrpc, EthereumJSONRPC.Geth, init_config) end defp resolve_schema(result \\ %{}) do 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 5f38ff2..ea3318f 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 @@ -4,10 +4,8 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do use BlockScoutWeb.ChannelCase alias ABI.{TypeDecoder, TypeEncoder} - alias BlockScoutWeb.Models.UserFromAuth alias Explorer.{Chain, Repo, TestHelper} alias Explorer.Chain.Address.Counters - alias Explorer.Chain.Events.Subscriber alias Explorer.Chain.{ Address, @@ -23,7 +21,7 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do Withdrawal } - alias Explorer.Account.WatchlistAddress + alias Explorer.Account.{Identity, WatchlistAddress} alias Explorer.Chain.Address.CurrentTokenBalance alias Indexer.Fetcher.OnDemand.ContractCode, as: ContractCodeOnDemand alias Plug.Conn @@ -82,7 +80,7 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do "public_tags" => [], "watchlist_names" => [], "creator_address_hash" => nil, - "creation_tx_hash" => nil, + "creation_transaction_hash" => nil, "token" => nil, "coin_balance" => nil, "proxy_type" => nil, @@ -115,7 +113,7 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do assert pattern_response["public_tags"] == response["public_tags"] assert pattern_response["watchlist_names"] == response["watchlist_names"] assert pattern_response["creator_address_hash"] == response["creator_address_hash"] - assert pattern_response["creation_tx_hash"] == response["creation_tx_hash"] + assert pattern_response["creation_transaction_hash"] == response["creation_transaction_hash"] assert pattern_response["token"] == response["token"] assert pattern_response["coin_balance"] == response["coin_balance"] assert pattern_response["implementation_address"] == response["implementation_address"] @@ -172,7 +170,7 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do implementation_contract_address_hash_string = Base.encode16(implementation_contract.address_hash.bytes, case: :lower) - proxy_tx_input = + proxy_transaction_input = "0x11b804ab000000000000000000000000" <> implementation_contract_address_hash_string <> "000000000000000000000000000000000000000000000000000000000000006035323031313537360000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000284e159163400000000000000000000000034420c13696f4ac650b9fafe915553a1abcd7dd30000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000001c00000000000000000000000000000000000000000000000000000000000000220000000000000000000000000ff5ae9b0a7522736299d797d80b8fc6f31d61100000000000000000000000000ff5ae9b0a7522736299d797d80b8fc6f31d6110000000000000000000000000000000000000000000000000000000000000003e8000000000000000000000000000000000000000000000000000000000000000000000000000000000000000034420c13696f4ac650b9fafe915553a1abcd7dd300000000000000000000000000000000000000000000000000000000000000184f7074696d69736d2053756273637269626572204e465473000000000000000000000000000000000000000000000000000000000000000000000000000000054f504e46540000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000037697066733a2f2f516d66544e504839765651334b5952346d6b52325a6b757756424266456f5a5554545064395538666931503332752f300000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c82bbe41f2cf04e3a8efa18f7032bdd7f6d98a81000000000000000000000000efba8a2a82ec1fb1273806174f5e28fbb917cf9500000000000000000000000000000000000000000000000000000000" @@ -185,16 +183,16 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do contract_code: proxy_deployed_bytecode ) - tx = + transaction = insert(:transaction, created_contract_address_hash: proxy_address.hash, - input: proxy_tx_input + input: proxy_transaction_input ) |> with_block(status: :ok) name = implementation_contract.name - from = Address.checksum(tx.from_address_hash) - tx_hash = to_string(tx.hash) + from = Address.checksum(transaction.from_address_hash) + transaction_hash = to_string(transaction.hash) address_hash = Address.checksum(proxy_address.hash) {:ok, implementation_contract_address_hash} = @@ -220,7 +218,7 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do "public_tags" => [], "watchlist_names" => [], "creator_address_hash" => ^from, - "creation_tx_hash" => ^tx_hash, + "creation_transaction_hash" => ^transaction_hash, "proxy_type" => "eip1167", "implementations" => [ %{"address" => ^checksummed_implementation_contract_address_hash, "name" => ^name} @@ -231,7 +229,7 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do test "get EIP-1967 proxy contract info", %{conn: conn} do smart_contract = insert(:smart_contract) - tx = + transaction = insert(:transaction, to_address_hash: nil, to_address: nil, @@ -247,8 +245,8 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do ) name = smart_contract.name - from = Address.checksum(tx.from_address_hash) - tx_hash = to_string(tx.hash) + from = Address.checksum(transaction.from_address_hash) + transaction_hash = to_string(transaction.hash) address_hash = Address.checksum(smart_contract.address_hash) implementation_address = insert(:address) @@ -266,7 +264,7 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do "public_tags" => [], "watchlist_names" => [], "creator_address_hash" => ^from, - "creation_tx_hash" => ^tx_hash, + "creation_transaction_hash" => ^transaction_hash, "proxy_type" => "eip1967", "implementations" => [%{"address" => ^implementation_address_hash_string, "name" => nil}] } = json_response(request, 200) @@ -275,7 +273,7 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do test "get watchlist id", %{conn: conn} do auth = build(:auth) address = insert(:address) - {:ok, user} = UserFromAuth.find_or_create(auth) + {:ok, user} = Identity.find_or_create(auth) conn = Plug.Test.init_test_session(conn, current_user: user) @@ -372,22 +370,22 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do test "get counters", %{conn: conn} do address = insert(:address) - tx_from = insert(:transaction, from_address: address) |> with_block() + transaction_from = insert(:transaction, from_address: address) |> with_block() insert(:transaction, to_address: address) |> with_block() - another_tx = insert(:transaction) |> with_block() + another_transaction = insert(:transaction) |> with_block() insert(:token_transfer, from_address: address, - transaction: another_tx, - block: another_tx.block, - block_number: another_tx.block_number + transaction: another_transaction, + block: another_transaction.block, + block_number: another_transaction.block_number ) insert(:token_transfer, to_address: address, - transaction: another_tx, - block: another_tx.block, - block_number: another_tx.block_number + transaction: another_transaction, + block: another_transaction.block, + block_number: another_transaction.block_number ) insert(:block, miner: address) @@ -398,7 +396,7 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do request = get(conn, "/api/v2/addresses/#{address.hash}/counters") - gas_used = to_string(tx_from.gas_used) + gas_used = to_string(transaction_from.gas_used) assert %{ "transactions_count" => "2", @@ -427,7 +425,7 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do test "get relevant transaction", %{conn: conn} do address = insert(:address) - tx = insert(:transaction, from_address: address) |> with_block() + transaction = insert(:transaction, from_address: address) |> with_block() insert(:transaction) |> with_block() @@ -436,14 +434,14 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do assert response = json_response(request, 200) assert Enum.count(response["items"]) == 1 assert response["next_page_params"] == nil - compare_item(tx, Enum.at(response["items"], 0)) + compare_item(transaction, Enum.at(response["items"], 0)) end test "get pending transaction", %{conn: conn} do address = insert(:address) - tx = insert(:transaction, from_address: address) |> with_block() - pending_tx = insert(:transaction, from_address: address) + transaction = insert(:transaction, from_address: address) |> with_block() + pending_transaction = insert(:transaction, from_address: address) insert(:transaction) |> with_block() insert(:transaction) @@ -453,28 +451,28 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do assert response = json_response(request, 200) assert Enum.count(response["items"]) == 2 assert response["next_page_params"] == nil - compare_item(pending_tx, Enum.at(response["items"], 0)) - compare_item(tx, Enum.at(response["items"], 1)) + compare_item(pending_transaction, Enum.at(response["items"], 0)) + compare_item(transaction, Enum.at(response["items"], 1)) end test "get only :to transaction", %{conn: conn} do address = insert(:address) insert(:transaction, from_address: address) |> with_block() - tx = insert(:transaction, to_address: address) |> with_block() + transaction = insert(:transaction, to_address: address) |> with_block() request = get(conn, "/api/v2/addresses/#{address.hash}/transactions", %{"filter" => "to"}) assert response = json_response(request, 200) assert Enum.count(response["items"]) == 1 assert response["next_page_params"] == nil - compare_item(tx, Enum.at(response["items"], 0)) + compare_item(transaction, Enum.at(response["items"], 0)) end test "get only :from transactions", %{conn: conn} do address = insert(:address) - tx = insert(:transaction, from_address: address) |> with_block() + transaction = insert(:transaction, from_address: address) |> with_block() insert(:transaction, to_address: address) |> with_block() request = get(conn, "/api/v2/addresses/#{address.hash}/transactions", %{"filter" => "from"}) @@ -482,13 +480,13 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do assert response = json_response(request, 200) assert Enum.count(response["items"]) == 1 assert response["next_page_params"] == nil - compare_item(tx, Enum.at(response["items"], 0)) + compare_item(transaction, Enum.at(response["items"], 0)) end - test "validated txs can paginate", %{conn: conn} do + test "validated transactions can paginate", %{conn: conn} do address = insert(:address) - txs = insert_list(51, :transaction, from_address: address) |> with_block() + transactions = insert_list(51, :transaction, from_address: address) |> with_block() request = get(conn, "/api/v2/addresses/#{address.hash}/transactions") assert response = json_response(request, 200) @@ -496,13 +494,13 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do request_2nd_page = get(conn, "/api/v2/addresses/#{address.hash}/transactions", response["next_page_params"]) assert response_2nd_page = json_response(request_2nd_page, 200) - check_paginated_response(response, response_2nd_page, txs) + check_paginated_response(response, response_2nd_page, transactions) end - test "pending txs can paginate", %{conn: conn} do + test "pending transactions can paginate", %{conn: conn} do address = insert(:address) - txs = insert_list(51, :transaction, from_address: address) + transactions = insert_list(51, :transaction, from_address: address) request = get(conn, "/api/v2/addresses/#{address.hash}/transactions") assert response = json_response(request, 200) @@ -510,14 +508,14 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do request_2nd_page = get(conn, "/api/v2/addresses/#{address.hash}/transactions", response["next_page_params"]) assert response_2nd_page = json_response(request_2nd_page, 200) - check_paginated_response(response, response_2nd_page, txs) + check_paginated_response(response, response_2nd_page, transactions) end - test "pending + validated txs can paginate", %{conn: conn} do + test "pending + validated transactions can paginate", %{conn: conn} do address = insert(:address) - txs_pending = insert_list(51, :transaction, from_address: address) - txs_validated = insert_list(50, :transaction, to_address: address) |> with_block() + transactions_pending = insert_list(51, :transaction, from_address: address) + transactions_validated = insert_list(50, :transaction, to_address: address) |> with_block() request = get(conn, "/api/v2/addresses/#{address.hash}/transactions") assert response = json_response(request, 200) @@ -527,25 +525,29 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do assert Enum.count(response["items"]) == 50 assert response["next_page_params"] != nil - compare_item(Enum.at(txs_pending, 50), Enum.at(response["items"], 0)) - compare_item(Enum.at(txs_pending, 1), Enum.at(response["items"], 49)) + compare_item(Enum.at(transactions_pending, 50), Enum.at(response["items"], 0)) + compare_item(Enum.at(transactions_pending, 1), Enum.at(response["items"], 49)) assert Enum.count(response_2nd_page["items"]) == 50 assert response_2nd_page["next_page_params"] != nil - compare_item(Enum.at(txs_pending, 0), Enum.at(response_2nd_page["items"], 0)) - compare_item(Enum.at(txs_validated, 49), Enum.at(response_2nd_page["items"], 1)) - compare_item(Enum.at(txs_validated, 1), Enum.at(response_2nd_page["items"], 49)) + compare_item(Enum.at(transactions_pending, 0), Enum.at(response_2nd_page["items"], 0)) + compare_item(Enum.at(transactions_validated, 49), Enum.at(response_2nd_page["items"], 1)) + compare_item(Enum.at(transactions_validated, 1), Enum.at(response_2nd_page["items"], 49)) request = get(conn, "/api/v2/addresses/#{address.hash}/transactions", response_2nd_page["next_page_params"]) assert response = json_response(request, 200) - check_paginated_response(response_2nd_page, response, txs_validated ++ [Enum.at(txs_pending, 0)]) + check_paginated_response( + response_2nd_page, + response, + transactions_validated ++ [Enum.at(transactions_pending, 0)] + ) end - test ":to txs can paginate", %{conn: conn} do + test ":to transactions can paginate", %{conn: conn} do address = insert(:address) - txs = insert_list(51, :transaction, to_address: address) |> with_block() + transactions = insert_list(51, :transaction, to_address: address) |> with_block() insert_list(51, :transaction, from_address: address) |> with_block() filter = %{"filter" => "to"} @@ -557,14 +559,14 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do assert response_2nd_page = json_response(request_2nd_page, 200) - check_paginated_response(response, response_2nd_page, txs) + check_paginated_response(response, response_2nd_page, transactions) end - test ":from txs can paginate", %{conn: conn} do + test ":from transactions can paginate", %{conn: conn} do address = insert(:address) insert_list(51, :transaction, to_address: address) |> with_block() - txs = insert_list(51, :transaction, from_address: address) |> with_block() + transactions = insert_list(51, :transaction, from_address: address) |> with_block() filter = %{"filter" => "from"} request = get(conn, "/api/v2/addresses/#{address.hash}/transactions", filter) @@ -575,14 +577,14 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do assert response_2nd_page = json_response(request_2nd_page, 200) - check_paginated_response(response, response_2nd_page, txs) + check_paginated_response(response, response_2nd_page, transactions) end - test ":from + :to txs can paginate", %{conn: conn} do + test ":from + :to transactions can paginate", %{conn: conn} do address = insert(:address) - txs_from = insert_list(50, :transaction, from_address: address) |> with_block() - txs_to = insert_list(51, :transaction, to_address: address) |> with_block() + transactions_from = insert_list(50, :transaction, from_address: address) |> with_block() + transactions_to = insert_list(51, :transaction, to_address: address) |> with_block() request = get(conn, "/api/v2/addresses/#{address.hash}/transactions") assert response = json_response(request, 200) @@ -592,25 +594,25 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do assert Enum.count(response["items"]) == 50 assert response["next_page_params"] != nil - compare_item(Enum.at(txs_to, 50), Enum.at(response["items"], 0)) - compare_item(Enum.at(txs_to, 1), Enum.at(response["items"], 49)) + compare_item(Enum.at(transactions_to, 50), Enum.at(response["items"], 0)) + compare_item(Enum.at(transactions_to, 1), Enum.at(response["items"], 49)) assert Enum.count(response_2nd_page["items"]) == 50 assert response_2nd_page["next_page_params"] != nil - compare_item(Enum.at(txs_to, 0), Enum.at(response_2nd_page["items"], 0)) - compare_item(Enum.at(txs_from, 49), Enum.at(response_2nd_page["items"], 1)) - compare_item(Enum.at(txs_from, 1), Enum.at(response_2nd_page["items"], 49)) + compare_item(Enum.at(transactions_to, 0), Enum.at(response_2nd_page["items"], 0)) + compare_item(Enum.at(transactions_from, 49), Enum.at(response_2nd_page["items"], 1)) + compare_item(Enum.at(transactions_from, 1), Enum.at(response_2nd_page["items"], 49)) request = get(conn, "/api/v2/addresses/#{address.hash}/transactions", response_2nd_page["next_page_params"]) assert response = json_response(request, 200) - check_paginated_response(response_2nd_page, response, txs_from ++ [Enum.at(txs_to, 0)]) + check_paginated_response(response_2nd_page, response, transactions_from ++ [Enum.at(transactions_to, 0)]) end test "ignores wrong ordering params", %{conn: conn} do address = insert(:address) - txs = insert_list(51, :transaction, from_address: address) |> with_block() + transactions = 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) @@ -624,16 +626,16 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do assert response_2nd_page = json_response(request_2nd_page, 200) - check_paginated_response(response, response_2nd_page, txs) + check_paginated_response(response, response_2nd_page, transactions) 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) + transactions = insert_list(51, :transaction, from_address: address) |> with_block(block) - [_, tx_before_last | _] = txs + [_, transaction_before_last | _] = transactions request = get(conn, "/api/v2/addresses/#{address.hash}/transactions") assert response = json_response(request, 200) @@ -642,20 +644,20 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do get( conn, "/api/v2/addresses/#{address.hash}/transactions", - %{"block_number" => to_string(block.number), "index" => to_string(tx_before_last.index)} + %{"block_number" => to_string(block.number), "index" => to_string(transaction_before_last.index)} ) assert response_2nd_page = json_response(request_2nd_page, 200) - check_paginated_response(response, response_2nd_page, txs) + check_paginated_response(response, response_2nd_page, transactions) 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) + transactions = insert_list(51, :transaction, from_address: address) - [_, tx_before_last | _] = txs + [_, transaction_before_last | _] = transactions request = get(conn, "/api/v2/addresses/#{address.hash}/transactions") assert response = json_response(request, 200) @@ -664,22 +666,25 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do get( conn, "/api/v2/addresses/#{address.hash}/transactions", - %{"inserted_at" => to_string(tx_before_last.inserted_at), "hash" => to_string(tx_before_last.hash)} + %{ + "inserted_at" => to_string(transaction_before_last.inserted_at), + "hash" => to_string(transaction_before_last.hash) + } ) assert response_2nd_page_pending = json_response(request_2nd_page_pending, 200) - check_paginated_response(response, response_2nd_page_pending, txs) + check_paginated_response(response, response_2nd_page_pending, transactions) 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() + transactions_from = insert_list(25, :transaction, from_address: address) |> with_block() + transactions_to = insert_list(26, :transaction, to_address: address) |> with_block() - txs = - (txs_from ++ txs_to) + transactions = + (transactions_from ++ transactions_to) |> Enum.sort( &(Decimal.compare(&1 |> Transaction.fee(:wei) |> elem(1), &2 |> Transaction.fee(:wei) |> elem(1)) in [ :eq, @@ -701,24 +706,24 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do 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)) + compare_item(Enum.at(transactions, 0), Enum.at(response["items"], 0)) + compare_item(Enum.at(transactions, 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)) + compare_item(Enum.at(transactions, 50), Enum.at(response_2nd_page["items"], 0)) - check_paginated_response(response, response_2nd_page, txs |> Enum.reverse()) + check_paginated_response(response, response_2nd_page, transactions |> 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() + transactions_from = insert_list(25, :transaction, from_address: address) |> with_block() + transactions_to = insert_list(26, :transaction, to_address: address) |> with_block() - txs = - (txs_from ++ txs_to) + transactions = + (transactions_from ++ transactions_to) |> Enum.sort( &(Decimal.compare(&1 |> Transaction.fee(:wei) |> elem(1), &2 |> Transaction.fee(:wei) |> elem(1)) in [ :eq, @@ -740,24 +745,24 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do 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)) + compare_item(Enum.at(transactions, 0), Enum.at(response["items"], 0)) + compare_item(Enum.at(transactions, 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)) + compare_item(Enum.at(transactions, 50), Enum.at(response_2nd_page["items"], 0)) - check_paginated_response(response, response_2nd_page, txs |> Enum.reverse()) + check_paginated_response(response, response_2nd_page, transactions |> 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() + transactions_from = insert_list(25, :transaction, from_address: address) |> with_block() + transactions_to = insert_list(26, :transaction, to_address: address) |> with_block() - txs = - (txs_from ++ txs_to) + transactions = + (transactions_from ++ transactions_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"}) @@ -774,24 +779,24 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do 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)) + compare_item(Enum.at(transactions, 0), Enum.at(response["items"], 0)) + compare_item(Enum.at(transactions, 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)) + compare_item(Enum.at(transactions, 50), Enum.at(response_2nd_page["items"], 0)) - check_paginated_response(response, response_2nd_page, txs |> Enum.reverse()) + check_paginated_response(response, response_2nd_page, transactions |> 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() + transactions_from = insert_list(25, :transaction, from_address: address) |> with_block() + transactions_to = insert_list(26, :transaction, to_address: address) |> with_block() - txs = - (txs_from ++ txs_to) + transactions = + (transactions_from ++ transactions_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"}) @@ -808,14 +813,90 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do 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)) + compare_item(Enum.at(transactions, 0), Enum.at(response["items"], 0)) + compare_item(Enum.at(transactions, 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(transactions, 50), Enum.at(response_2nd_page["items"], 0)) + + check_paginated_response(response, response_2nd_page, transactions |> Enum.reverse()) + end + + test "can order and paginate by block number ascending", %{conn: conn} do + address = insert(:address) + + transactions_from = + for _ <- 0..24, do: insert(:transaction, from_address: address) |> with_block() + + transactions_to = for _ <- 0..25, do: insert(:transaction, to_address: address) |> with_block() + + transactions = + (transactions_from ++ transactions_to) + |> Enum.sort_by(& &1.block.number) + + request = + get(conn, "/api/v2/addresses/#{address.hash}/transactions", %{"sort" => "block_number", "order" => "asc"}) + + assert response = json_response(request, 200) + + request_2nd_page = + get( + conn, + "/api/v2/addresses/#{address.hash}/transactions", + %{"sort" => "block_number", "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(transactions, 0), Enum.at(response["items"], 0)) + compare_item(Enum.at(transactions, 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(transactions, 50), Enum.at(response_2nd_page["items"], 0)) + + check_paginated_response(response, response_2nd_page, transactions |> Enum.reverse()) + end + + test "can order and paginate by block number descending", %{conn: conn} do + address = insert(:address) + + transactions_from = + for _ <- 0..24, do: insert(:transaction, from_address: address) |> with_block() + + transactions_to = for _ <- 0..25, do: insert(:transaction, to_address: address) |> with_block() + + transactions = + (transactions_from ++ transactions_to) + |> Enum.sort_by(& &1.block.number, :desc) + + request = + get(conn, "/api/v2/addresses/#{address.hash}/transactions", %{"sort" => "block_number", "order" => "desc"}) + + assert response = json_response(request, 200) + + request_2nd_page = + get( + conn, + "/api/v2/addresses/#{address.hash}/transactions", + %{"sort" => "block_number", "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(transactions, 0), Enum.at(response["items"], 0)) + compare_item(Enum.at(transactions, 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)) + compare_item(Enum.at(transactions, 50), Enum.at(response_2nd_page["items"], 0)) - check_paginated_response(response, response_2nd_page, txs |> Enum.reverse()) + check_paginated_response(response, response_2nd_page, transactions |> Enum.reverse()) end end @@ -855,12 +936,21 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do test "get relevant token transfer", %{conn: conn} do address = insert(:address) - tx = insert(:transaction, input: "0xabcd010203040506") |> with_block() + transaction = insert(:transaction, input: "0xabcd010203040506") |> with_block() - insert(:token_transfer, transaction: tx, block: tx.block, block_number: tx.block_number) + insert(:token_transfer, + transaction: transaction, + block: transaction.block, + block_number: transaction.block_number + ) token_transfer = - insert(:token_transfer, transaction: tx, block: tx.block, block_number: tx.block_number, from_address: address) + insert(:token_transfer, + transaction: transaction, + block: transaction.block, + block_number: transaction.block_number, + from_address: address + ) request = get(conn, "/api/v2/addresses/#{address.hash}/token-transfers") @@ -891,17 +981,26 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do address = insert(:address) - tx = + transaction = insert(:transaction, input: "0x731133e9000000000000000000000000bb36c792b9b45aaf8b848a1392b0d6559202729e000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000001700000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000" ) |> with_block() - insert(:token_transfer, transaction: tx, block: tx.block, block_number: tx.block_number) + insert(:token_transfer, + transaction: transaction, + block: transaction.block, + block_number: transaction.block_number + ) token_transfer = - insert(:token_transfer, transaction: tx, block: tx.block, block_number: tx.block_number, from_address: address) + insert(:token_transfer, + transaction: transaction, + block: transaction.block, + block_number: transaction.block_number, + from_address: address + ) request = get(conn, "/api/v2/addresses/#{address.hash}/token-transfers") @@ -917,17 +1016,26 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do address = insert(:address) - tx = insert(:transaction, input: "0xabcd010203040506") |> with_block() + transaction = insert(:transaction, input: "0xabcd010203040506") |> with_block() - insert(:token_transfer, transaction: tx, block: tx.block, block_number: tx.block_number) + insert(:token_transfer, + transaction: transaction, + block: transaction.block, + block_number: transaction.block_number + ) - insert(:token_transfer, transaction: tx, block: tx.block, block_number: tx.block_number, from_address: address) + insert(:token_transfer, + transaction: transaction, + block: transaction.block, + block_number: transaction.block_number, + from_address: address + ) token_transfer = insert(:token_transfer, - transaction: tx, - block: tx.block, - block_number: tx.block_number, + transaction: transaction, + block: transaction.block, + block_number: transaction.block_number, from_address: address, token_contract_address: token.contract_address ) @@ -950,19 +1058,19 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do token_transfers = for _ <- 0..50 do - tx = insert(:transaction, input: "0xabcd010203040506") |> with_block() + transaction = insert(:transaction, input: "0xabcd010203040506") |> with_block() insert(:token_transfer, - transaction: tx, - block: tx.block, - block_number: tx.block_number, + transaction: transaction, + block: transaction.block, + block_number: transaction.block_number, from_address: address ) insert(:token_transfer, - transaction: tx, - block: tx.block, - block_number: tx.block_number, + transaction: transaction, + block: transaction.block, + block_number: transaction.block_number, from_address: address, token_contract_address: token.contract_address ) @@ -983,12 +1091,22 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do test "get only :to token transfer", %{conn: conn} do address = insert(:address) - tx = insert(:transaction, input: "0xabcd010203040506") |> with_block() + transaction = insert(:transaction, input: "0xabcd010203040506") |> with_block() - insert(:token_transfer, transaction: tx, block: tx.block, block_number: tx.block_number, from_address: address) + insert(:token_transfer, + transaction: transaction, + block: transaction.block, + block_number: transaction.block_number, + from_address: address + ) token_transfer = - insert(:token_transfer, transaction: tx, block: tx.block, block_number: tx.block_number, to_address: address) + insert(:token_transfer, + transaction: transaction, + block: transaction.block, + block_number: transaction.block_number, + to_address: address + ) request = get(conn, "/api/v2/addresses/#{address.hash}/token-transfers", %{"filter" => "to"}) @@ -1001,12 +1119,23 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do test "get only :from token transfer", %{conn: conn} do address = insert(:address) - tx = insert(:transaction, input: "0xabcd010203040506") |> with_block() + transaction = insert(:transaction, input: "0xabcd010203040506") |> with_block() token_transfer = - insert(:token_transfer, transaction: tx, block: tx.block, block_number: tx.block_number, from_address: address) + insert(:token_transfer, + transaction: transaction, + block: transaction.block, + block_number: transaction.block_number, + from_address: address + ) + + insert(:token_transfer, + transaction: transaction, + block: transaction.block, + block_number: transaction.block_number, + to_address: address + ) - insert(:token_transfer, transaction: tx, block: tx.block, block_number: tx.block_number, to_address: address) request = get(conn, "/api/v2/addresses/#{address.hash}/token-transfers", %{"filter" => "from"}) assert response = json_response(request, 200) @@ -1020,12 +1149,12 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do token_transfers = for _ <- 0..50 do - tx = insert(:transaction, input: "0xabcd010203040506") |> with_block() + transaction = insert(:transaction, input: "0xabcd010203040506") |> with_block() insert(:token_transfer, - transaction: tx, - block: tx.block, - block_number: tx.block_number, + transaction: transaction, + block: transaction.block, + block_number: transaction.block_number, from_address: address ) end @@ -1043,14 +1172,26 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do address = insert(:address) for _ <- 0..50 do - tx = insert(:transaction, input: "0xabcd010203040506") |> with_block() - insert(:token_transfer, transaction: tx, block: tx.block, block_number: tx.block_number, from_address: address) + transaction = insert(:transaction, input: "0xabcd010203040506") |> with_block() + + insert(:token_transfer, + transaction: transaction, + block: transaction.block, + block_number: transaction.block_number, + from_address: address + ) end token_transfers = for _ <- 0..50 do - tx = insert(:transaction, input: "0xabcd010203040506") |> with_block() - insert(:token_transfer, transaction: tx, block: tx.block, block_number: tx.block_number, to_address: address) + transaction = insert(:transaction, input: "0xabcd010203040506") |> with_block() + + insert(:token_transfer, + transaction: transaction, + block: transaction.block, + block_number: transaction.block_number, + to_address: address + ) end filter = %{"filter" => "to"} @@ -1070,19 +1211,25 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do token_transfers = for _ <- 0..50 do - tx = insert(:transaction, input: "0xabcd010203040506") |> with_block() + transaction = insert(:transaction, input: "0xabcd010203040506") |> with_block() insert(:token_transfer, - transaction: tx, - block: tx.block, - block_number: tx.block_number, + transaction: transaction, + block: transaction.block, + block_number: transaction.block_number, from_address: address ) end for _ <- 0..50 do - tx = insert(:transaction, input: "0xabcd010203040506") |> with_block() - insert(:token_transfer, transaction: tx, block: tx.block, block_number: tx.block_number, to_address: address) + transaction = insert(:transaction, input: "0xabcd010203040506") |> with_block() + + insert(:token_transfer, + transaction: transaction, + block: transaction.block, + block_number: transaction.block_number, + to_address: address + ) end filter = %{"filter" => "from"} @@ -1102,20 +1249,26 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do tt_from = for _ <- 0..49 do - tx = insert(:transaction, input: "0xabcd010203040506") |> with_block() + transaction = insert(:transaction, input: "0xabcd010203040506") |> with_block() insert(:token_transfer, - transaction: tx, - block: tx.block, - block_number: tx.block_number, + transaction: transaction, + block: transaction.block, + block_number: transaction.block_number, from_address: address ) end tt_to = for _ <- 0..50 do - tx = insert(:transaction, input: "0xabcd010203040506") |> with_block() - insert(:token_transfer, transaction: tx, block: tx.block, block_number: tx.block_number, to_address: address) + transaction = insert(:transaction, input: "0xabcd010203040506") |> with_block() + + insert(:token_transfer, + transaction: transaction, + block: transaction.block, + block_number: transaction.block_number, + to_address: address + ) end request = get(conn, "/api/v2/addresses/#{address.hash}/token-transfers") @@ -1148,12 +1301,12 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do erc_20_tt = for _ <- 0..50 do - tx = insert(:transaction, input: "0xabcd010203040506") |> with_block() + transaction = insert(:transaction, input: "0xabcd010203040506") |> with_block() insert(:token_transfer, - transaction: tx, - block: tx.block, - block_number: tx.block_number, + transaction: transaction, + block: transaction.block, + block_number: transaction.block_number, from_address: address, token_contract_address: erc_20_token.contract_address, token_type: "ERC-20" @@ -1164,12 +1317,12 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do erc_721_tt = for x <- 0..50 do - tx = insert(:transaction, input: "0xabcd010203040506") |> with_block() + transaction = insert(:transaction, input: "0xabcd010203040506") |> with_block() insert(:token_transfer, - transaction: tx, - block: tx.block, - block_number: tx.block_number, + transaction: transaction, + block: transaction.block, + block_number: transaction.block_number, from_address: address, token_contract_address: erc_721_token.contract_address, token_ids: [x], @@ -1181,12 +1334,12 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do erc_1155_tt = for x <- 0..50 do - tx = insert(:transaction, input: "0xabcd010203040506") |> with_block() + transaction = insert(:transaction, input: "0xabcd010203040506") |> with_block() insert(:token_transfer, - transaction: tx, - block: tx.block, - block_number: tx.block_number, + transaction: transaction, + block: transaction.block, + block_number: transaction.block_number, from_address: address, token_contract_address: erc_1155_token.contract_address, token_ids: [x], @@ -1276,12 +1429,12 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do erc_20_tt = for _ <- 0..50 do - tx = insert(:transaction, input: "0xabcd010203040506") |> with_block() + transaction = insert(:transaction, input: "0xabcd010203040506") |> with_block() insert(:token_transfer, - transaction: tx, - block: tx.block, - block_number: tx.block_number, + transaction: transaction, + block: transaction.block, + block_number: transaction.block_number, from_address: address, token_contract_address: erc_20_token.contract_address, token_type: "ERC-20" @@ -1292,12 +1445,12 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do erc_721_tt = for x <- 0..50 do - tx = insert(:transaction, input: "0xabcd010203040506") |> with_block() + transaction = insert(:transaction, input: "0xabcd010203040506") |> with_block() insert(:token_transfer, - transaction: tx, - block: tx.block, - block_number: tx.block_number, + transaction: transaction, + block: transaction.block, + block_number: transaction.block_number, to_address: address, token_contract_address: erc_721_token.contract_address, token_ids: [x], @@ -1356,13 +1509,13 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do tt = for _ <- 0..50 do - tx = insert(:transaction, input: "0xabcd010203040506") |> with_block() + transaction = insert(:transaction, input: "0xabcd010203040506") |> with_block() insert(:token_transfer, to_address: address, - transaction: tx, - block: tx.block, - block_number: tx.block_number, + transaction: transaction, + block: transaction.block, + block_number: transaction.block_number, token_contract_address: token.contract_address, token_ids: Enum.map(0..50, fn _x -> id end), token_type: "ERC-1155", @@ -1392,13 +1545,13 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do token_transfers = for i <- 0..50 do - tx = insert(:transaction, input: "0xabcd010203040506") |> with_block() + transaction = insert(:transaction, input: "0xabcd010203040506") |> with_block() insert(:token_transfer, - transaction: tx, + transaction: transaction, to_address: address, - block: tx.block, - block_number: tx.block_number, + block: transaction.block, + block_number: transaction.block_number, token_contract_address: token.contract_address, token_ids: [i], token_type: "ERC-721" @@ -1419,14 +1572,14 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do address = insert(:address) token = insert(:token, type: "ERC-1155") - tx = insert(:transaction, input: "0xabcd010203040506") |> with_block() + transaction = insert(:transaction, input: "0xabcd010203040506") |> with_block() tt = insert(:token_transfer, - transaction: tx, + transaction: transaction, to_address: address, - block: tx.block, - block_number: tx.block_number, + block: transaction.block, + block_number: transaction.block_number, token_contract_address: token.contract_address, token_ids: Enum.map(0..50, fn x -> x end), token_type: "ERC-1155", @@ -1462,14 +1615,14 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do token = insert(:token, type: "ERC-1155") - tx_1 = insert(:transaction, input: "0xabcd010203040506") |> with_block() + transaction_1 = insert(:transaction, input: "0xabcd010203040506") |> with_block() tt_1 = insert(:token_transfer, - transaction: tx_1, + transaction: transaction_1, to_address: address, - block: tx_1.block, - block_number: tx_1.block_number, + block: transaction_1.block, + block_number: transaction_1.block_number, token_contract_address: token.contract_address, token_ids: Enum.map(0..24, fn x -> x end), token_type: "ERC-1155", @@ -1481,14 +1634,14 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do %TokenTransfer{tt_1 | token_ids: [i], amount: i} end - tx_2 = insert(:transaction, input: "0xabcd010203040506") |> with_block() + transaction_2 = insert(:transaction, input: "0xabcd010203040506") |> with_block() tt_2 = insert(:token_transfer, - transaction: tx_2, + transaction: transaction_2, to_address: address, - block: tx_2.block, - block_number: tx_2.block_number, + block: transaction_2.block, + block_number: transaction_2.block_number, token_contract_address: token.contract_address, token_ids: Enum.map(25..49, fn x -> x end), token_type: "ERC-1155", @@ -1502,10 +1655,10 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do tt_3 = insert(:token_transfer, - transaction: tx_2, + transaction: transaction_2, from_address: address, - block: tx_2.block, - block_number: tx_2.block_number, + block: transaction_2.block, + block_number: transaction_2.block_number, token_contract_address: token.contract_address, token_ids: [50], token_type: "ERC-1155", @@ -1527,14 +1680,14 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do token = insert(:token, type: "ERC-1155") - tx_1 = insert(:transaction, input: "0xabcd010203040506") |> with_block() + transaction_1 = insert(:transaction, input: "0xabcd010203040506") |> with_block() tt_1 = insert(:token_transfer, - transaction: tx_1, + transaction: transaction_1, from_address: address, - block: tx_1.block, - block_number: tx_1.block_number, + block: transaction_1.block, + block_number: transaction_1.block_number, token_contract_address: token.contract_address, token_ids: Enum.map(0..24, fn x -> x end), token_type: "ERC-1155", @@ -1546,14 +1699,14 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do %TokenTransfer{tt_1 | token_ids: [i], amount: i} end - tx_2 = insert(:transaction, input: "0xabcd010203040506") |> with_block() + transaction_2 = insert(:transaction, input: "0xabcd010203040506") |> with_block() tt_2 = insert(:token_transfer, - transaction: tx_2, + transaction: transaction_2, to_address: address, - block: tx_2.block, - block_number: tx_2.block_number, + block: transaction_2.block, + block_number: transaction_2.block_number, token_contract_address: token.contract_address, token_ids: Enum.map(25..50, fn x -> x end), token_type: "ERC-1155", @@ -1591,32 +1744,32 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do assert %{"message" => "Invalid parameter(s)"} = json_response(request, 422) end - test "get internal tx and filter working", %{conn: conn} do + test "get internal transaction and filter working", %{conn: conn} do address = insert(:address) - tx = + transaction = :transaction |> insert() |> with_block() - internal_tx_from = + internal_transaction_from = insert(:internal_transaction, - transaction: tx, + transaction: transaction, index: 1, - block_number: tx.block_number, - transaction_index: tx.index, - block_hash: tx.block_hash, + block_number: transaction.block_number, + transaction_index: transaction.index, + block_hash: transaction.block_hash, block_index: 1, from_address: address ) - internal_tx_to = + internal_transaction_to = insert(:internal_transaction, - transaction: tx, + transaction: transaction, index: 2, - block_number: tx.block_number, - transaction_index: tx.index, - block_hash: tx.block_hash, + block_number: transaction.block_number, + transaction_index: transaction.index, + block_hash: transaction.block_hash, block_index: 2, to_address: address ) @@ -1627,40 +1780,40 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do assert Enum.count(response["items"]) == 2 assert response["next_page_params"] == nil - compare_item(internal_tx_from, Enum.at(response["items"], 1)) - compare_item(internal_tx_to, Enum.at(response["items"], 0)) + compare_item(internal_transaction_from, Enum.at(response["items"], 1)) + compare_item(internal_transaction_to, Enum.at(response["items"], 0)) request = get(conn, "/api/v2/addresses/#{address.hash}/internal-transactions", %{"filter" => "from"}) assert response = json_response(request, 200) assert Enum.count(response["items"]) == 1 assert response["next_page_params"] == nil - compare_item(internal_tx_from, Enum.at(response["items"], 0)) + compare_item(internal_transaction_from, Enum.at(response["items"], 0)) request = get(conn, "/api/v2/addresses/#{address.hash}/internal-transactions", %{"filter" => "to"}) assert response = json_response(request, 200) assert Enum.count(response["items"]) == 1 assert response["next_page_params"] == nil - compare_item(internal_tx_to, Enum.at(response["items"], 0)) + compare_item(internal_transaction_to, Enum.at(response["items"], 0)) end - test "internal txs can paginate", %{conn: conn} do + test "internal transactions can paginate", %{conn: conn} do address = insert(:address) - tx = + transaction = :transaction |> insert() |> with_block() - itxs_from = + internal_transactions_from = for i <- 1..51 do insert(:internal_transaction, - transaction: tx, + transaction: transaction, index: i, - block_number: tx.block_number, - transaction_index: tx.index, - block_hash: tx.block_hash, + block_number: transaction.block_number, + transaction_index: transaction.index, + block_hash: transaction.block_hash, block_index: i, from_address: address ) @@ -1674,16 +1827,16 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do assert response_2nd_page = json_response(request_2nd_page, 200) - check_paginated_response(response, response_2nd_page, itxs_from) + check_paginated_response(response, response_2nd_page, internal_transactions_from) - itxs_to = + internal_transactions_to = for i <- 52..102 do insert(:internal_transaction, - transaction: tx, + transaction: transaction, index: i, - block_number: tx.block_number, - transaction_index: tx.index, - block_hash: tx.block_hash, + block_number: transaction.block_number, + transaction_index: transaction.index, + block_hash: transaction.block_hash, block_index: i, to_address: address ) @@ -1702,7 +1855,7 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do assert response_2nd_page = json_response(request_2nd_page, 200) - check_paginated_response(response, response_2nd_page, itxs_to) + check_paginated_response(response, response_2nd_page, internal_transactions_to) filter = %{"filter" => "from"} request = get(conn, "/api/v2/addresses/#{address.hash}/internal-transactions", filter) @@ -1717,7 +1870,7 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do assert response_2nd_page = json_response(request_2nd_page, 200) - check_paginated_response(response, response_2nd_page, itxs_from) + check_paginated_response(response, response_2nd_page, internal_transactions_from) end end @@ -1904,17 +2057,17 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do test "get log", %{conn: conn} do address = insert(:address) - tx = + transaction = :transaction |> insert() |> with_block() log = insert(:log, - transaction: tx, + transaction: transaction, index: 1, - block: tx.block, - block_number: tx.block_number, + block: transaction.block, + block_number: transaction.block_number, address: address ) @@ -1932,16 +2085,16 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do logs = for x <- 0..50 do - tx = + transaction = :transaction |> insert() |> with_block() insert(:log, - transaction: tx, + transaction: transaction, index: x, - block: tx.block, - block_number: tx.block_number, + block: transaction.block, + block_number: transaction.block_number, address: address ) end @@ -1958,17 +2111,17 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do test "regression test for 9926", %{conn: conn} do address = insert(:address, hash: "0x036cec1a199234fC02f72d29e596a09440825f1C") - tx = + transaction = :transaction |> insert() |> with_block() log = insert(:log, - transaction: tx, + transaction: transaction, index: 1, - block: tx.block, - block_number: tx.block_number, + block: transaction.block, + block_number: transaction.block_number, address: address ) @@ -2036,30 +2189,30 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do address = insert(:address) for x <- 0..20 do - tx = + transaction = :transaction |> insert() |> with_block() insert(:log, - transaction: tx, + transaction: transaction, index: x, - block: tx.block, - block_number: tx.block_number, + block: transaction.block, + block_number: transaction.block_number, address: address ) end - tx = + transaction = :transaction |> insert() |> with_block() log = insert(:log, - transaction: tx, - block: tx.block, - block_number: tx.block_number, + transaction: transaction, + block: transaction.block, + block_number: transaction.block_number, address: address, first_topic: topic(@first_topic_hex_string_1) ) @@ -2554,7 +2707,7 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do "token_balances_count" => 0, "logs_count" => 0, "withdrawals_count" => 0, - "internal_txs_count" => 0 + "internal_transactions_count" => 0 } = json_response(request, 200) end @@ -2563,36 +2716,36 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do insert(:transaction, from_address: address) |> with_block() insert(:transaction, to_address: address) |> with_block() - another_tx = insert(:transaction) |> with_block() + another_transaction = insert(:transaction) |> with_block() insert(:token_transfer, from_address: address, - transaction: another_tx, - block: another_tx.block, - block_number: another_tx.block_number + transaction: another_transaction, + block: another_transaction.block, + block_number: another_transaction.block_number ) insert(:token_transfer, to_address: address, - transaction: another_tx, - block: another_tx.block, - block_number: another_tx.block_number + transaction: another_transaction, + block: another_transaction.block, + block_number: another_transaction.block_number ) insert(:block, miner: address) - tx = + transaction = :transaction |> insert() |> with_block() for x <- 1..2 do insert(:internal_transaction, - transaction: tx, + transaction: transaction, index: x, - block_number: tx.block_number, - transaction_index: tx.index, - block_hash: tx.block_hash, + block_number: transaction.block_number, + transaction_index: transaction.index, + block_hash: transaction.block_hash, block_index: x, from_address: address ) @@ -2603,16 +2756,16 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do end for x <- 0..60 do - tx = + transaction = :transaction |> insert() |> with_block() insert(:log, - transaction: tx, + transaction: transaction, index: x, - block: tx.block, - block_number: tx.block_number, + block: transaction.block, + block_number: transaction.block_number, address: address ) end @@ -2626,16 +2779,16 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do "token_balances_count" => 51, "logs_count" => 51, "withdrawals_count" => 51, - "internal_txs_count" => 2 + "internal_transactions_count" => 2 } = json_response(request, 200) for x <- 3..4 do insert(:internal_transaction, - transaction: tx, + transaction: transaction, index: x, - block_number: tx.block_number, - transaction_index: tx.index, - block_hash: tx.block_hash, + block_number: transaction.block_number, + transaction_index: transaction.index, + block_hash: transaction.block_hash, block_index: x, from_address: address ) @@ -2650,7 +2803,7 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do "token_balances_count" => 51, "logs_count" => 51, "withdrawals_count" => 51, - "internal_txs_count" => 2 + "internal_transactions_count" => 2 } = json_response(request, 200) end @@ -2659,36 +2812,36 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do insert(:transaction, from_address: address) |> with_block() insert(:transaction, to_address: address) |> with_block() - another_tx = insert(:transaction) |> with_block() + another_transaction = insert(:transaction) |> with_block() insert(:token_transfer, from_address: address, - transaction: another_tx, - block: another_tx.block, - block_number: another_tx.block_number + transaction: another_transaction, + block: another_transaction.block, + block_number: another_transaction.block_number ) insert(:token_transfer, to_address: address, - transaction: another_tx, - block: another_tx.block, - block_number: another_tx.block_number + transaction: another_transaction, + block: another_transaction.block, + block_number: another_transaction.block_number ) insert(:block, miner: address) - tx = + transaction = :transaction |> insert() |> with_block() for x <- 1..2 do insert(:internal_transaction, - transaction: tx, + transaction: transaction, index: x, - block_number: tx.block_number, - transaction_index: tx.index, - block_hash: tx.block_hash, + block_number: transaction.block_number, + transaction_index: transaction.index, + block_hash: transaction.block_hash, block_index: x, from_address: address ) @@ -2699,16 +2852,16 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do end for x <- 0..60 do - tx = + transaction = :transaction |> insert() |> with_block() insert(:log, - transaction: tx, + transaction: transaction, index: x, - block: tx.block, - block_number: tx.block_number, + block: transaction.block, + block_number: transaction.block_number, address: address ) end @@ -2722,7 +2875,7 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do "token_balances_count" => 51, "logs_count" => 51, "withdrawals_count" => 51, - "internal_txs_count" => 2 + "internal_transactions_count" => 2 } = json_response(request, 200) old_env = Application.get_env(:explorer, Explorer.Chain.Cache.AddressesTabsCounters) @@ -2731,11 +2884,11 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do for x <- 3..4 do insert(:internal_transaction, - transaction: tx, + transaction: transaction, index: x, - block_number: tx.block_number, - transaction_index: tx.index, - block_hash: tx.block_hash, + block_number: transaction.block_number, + transaction_index: transaction.index, + block_hash: transaction.block_hash, block_index: x, from_address: address ) @@ -2753,7 +2906,7 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do "token_balances_count" => 51, "logs_count" => 51, "withdrawals_count" => 51, - "internal_txs_count" => 4 + "internal_transactions_count" => 4 } = json_response(request, 200) Application.put_env(:explorer, Explorer.Chain.Cache.AddressesTabsCounters, old_env) @@ -3310,12 +3463,12 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do defp compare_item(%Address{} = address, json) do assert Address.checksum(address.hash) == json["hash"] - assert to_string(address.transactions_count) == json["tx_count"] + assert to_string(address.transactions_count) == json["transaction_count"] end defp compare_item(%Transaction{} = transaction, json) do assert to_string(transaction.hash) == json["hash"] - assert transaction.block_number == json["block"] + assert transaction.block_number == json["block_number"] 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"] @@ -3324,21 +3477,21 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do 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 to_string(token_transfer.transaction_hash) == json["transaction_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 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"] + defp compare_item(%InternalTransaction{} = internal_transaction, json) do + assert internal_transaction.block_number == json["block_number"] + assert to_string(internal_transaction.gas) == json["gas_limit"] + assert internal_transaction.index == json["index"] + assert to_string(internal_transaction.transaction_hash) == json["transaction_hash"] + assert Address.checksum(internal_transaction.from_address_hash) == json["from"]["hash"] + assert Address.checksum(internal_transaction.to_address_hash) == json["to"]["hash"] end defp compare_item(%Block{} = block, json) do @@ -3374,7 +3527,7 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest 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 to_string(log.transaction_hash) == json["transaction_hash"] assert json["block_number"] == log.block_number assert json["block_hash"] == to_string(log.block_hash) end diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/advanced_filter_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/advanced_filter_controller_test.exs index 4e14bc9..cf4c2b6 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/advanced_filter_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/advanced_filter_controller_test.exs @@ -3,7 +3,8 @@ defmodule BlockScoutWeb.API.V2.AdvancedFilterControllerTest do import Mox - alias Explorer.Chain.{AdvancedFilter, Data} + alias Explorer.Chain.SmartContract + alias Explorer.Chain.{AdvancedFilter, Data, Hash} alias Explorer.{Factory, TestHelper} describe "/advanced_filters" do @@ -15,13 +16,13 @@ defmodule BlockScoutWeb.API.V2.AdvancedFilterControllerTest do end test "get and paginate advanced filter (transactions split between pages)", %{conn: conn} do - first_tx = :transaction |> insert() |> with_block() - insert_list(3, :token_transfer, transaction: first_tx) + first_transaction = :transaction |> insert() |> with_block() + insert_list(3, :token_transfer, transaction: first_transaction) - for i <- 0..2 do + for i <- 1..3 do insert(:internal_transaction, - transaction: first_tx, - block_hash: first_tx.block_hash, + transaction: first_transaction, + block_hash: first_transaction.block_hash, index: i, block_index: i ) @@ -38,20 +39,20 @@ defmodule BlockScoutWeb.API.V2.AdvancedFilterControllerTest do end test "get and paginate advanced filter (token transfers split between pages)", %{conn: conn} do - first_tx = :transaction |> insert() |> with_block() - insert_list(3, :token_transfer, transaction: first_tx) + first_transaction = :transaction |> insert() |> with_block() + insert_list(3, :token_transfer, transaction: first_transaction) - for i <- 0..2 do + for i <- 1..3 do insert(:internal_transaction, - transaction: first_tx, - block_hash: first_tx.block_hash, + transaction: first_transaction, + block_hash: first_transaction.block_hash, index: i, block_index: i ) end - second_tx = :transaction |> insert() |> with_block() - insert_list(50, :token_transfer, transaction: second_tx, block_number: second_tx.block_number) + second_transaction = :transaction |> insert() |> with_block() + insert_list(50, :token_transfer, transaction: second_transaction, block_number: second_transaction.block_number) request = get(conn, "/api/v2/advanced-filters") assert response = json_response(request, 200) @@ -62,23 +63,23 @@ defmodule BlockScoutWeb.API.V2.AdvancedFilterControllerTest do end test "get and paginate advanced filter (batch token transfers split between pages)", %{conn: conn} do - first_tx = :transaction |> insert() |> with_block() - insert_list(3, :token_transfer, transaction: first_tx) + first_transaction = :transaction |> insert() |> with_block() + insert_list(3, :token_transfer, transaction: first_transaction) - for i <- 0..2 do + for i <- 1..3 do insert(:internal_transaction, - transaction: first_tx, - block_hash: first_tx.block_hash, + transaction: first_transaction, + block_hash: first_transaction.block_hash, index: i, block_index: i ) end - second_tx = :transaction |> insert() |> with_block() + second_transaction = :transaction |> insert() |> with_block() insert_list(5, :token_transfer, - transaction: second_tx, - block_number: second_tx.block_number, + transaction: second_transaction, + block_number: second_transaction.block_number, token_type: "ERC-1155", token_ids: 0..10 |> Enum.to_list(), amounts: 10..20 |> Enum.to_list() @@ -93,24 +94,24 @@ defmodule BlockScoutWeb.API.V2.AdvancedFilterControllerTest do end test "get and paginate advanced filter (internal transactions split between pages)", %{conn: conn} do - first_tx = :transaction |> insert() |> with_block() - insert_list(3, :token_transfer, transaction: first_tx) + first_transaction = :transaction |> insert() |> with_block() + insert_list(3, :token_transfer, transaction: first_transaction) - for i <- 0..2 do + for i <- 1..3 do insert(:internal_transaction, - transaction: first_tx, - block_hash: first_tx.block_hash, + transaction: first_transaction, + block_hash: first_transaction.block_hash, index: i, block_index: i ) end - second_tx = :transaction |> insert() |> with_block() + second_transaction = :transaction |> insert() |> with_block() - for i <- 0..49 do + for i <- 1..50 do insert(:internal_transaction, - transaction: second_tx, - block_hash: second_tx.block_hash, + transaction: second_transaction, + block_hash: second_transaction.block_hash, index: i, block_index: i ) @@ -124,49 +125,58 @@ defmodule BlockScoutWeb.API.V2.AdvancedFilterControllerTest do check_paginated_response(AdvancedFilter.list(), response["items"], response_2nd_page["items"]) end - test "filter by tx_type", %{conn: conn} do + test "filter by transaction_type", %{conn: conn} do 30 |> insert_list(:transaction) |> with_block() - tx = insert(:transaction) |> with_block() + transaction = insert(:transaction) |> with_block() for token_type <- ~w(ERC-20 ERC-404 ERC-721 ERC-1155), + token = insert(:token, type: token_type), _ <- 0..4 do - insert(:token_transfer, transaction: tx, token_type: token_type) + insert(:token_transfer, + transaction: transaction, + token_type: token_type, + token: token, + token_contract_address_hash: token.contract_address_hash, + token_contract_address: token.contract_address + ) end - tx = :transaction |> insert() |> with_block() + transaction = :transaction |> insert() |> with_block() - for i <- 0..29 do + for i <- 1..30 do insert(:internal_transaction, - transaction: tx, - block_hash: tx.block_hash, + transaction: transaction, + block_hash: transaction.block_hash, index: i, block_index: i ) end - for tx_type_filter_string <- + for transaction_type_filter_string <- ~w(COIN_TRANSFER COIN_TRANSFER,ERC-404 ERC-721,ERC-1155 ERC-20,COIN_TRANSFER,ERC-1155) do - tx_type_filter = tx_type_filter_string |> String.split(",") - request = get(conn, "/api/v2/advanced-filters", %{"tx_types" => tx_type_filter_string}) + transaction_type_filter = transaction_type_filter_string |> String.split(",") + request = get(conn, "/api/v2/advanced-filters", %{"transaction_types" => transaction_type_filter_string}) assert response = json_response(request, 200) - assert Enum.all?(response["items"], fn item -> String.upcase(item["type"]) in tx_type_filter end) + assert Enum.all?(response["items"], fn item -> String.upcase(item["type"]) in transaction_type_filter end) if response["next_page_params"] do request_2nd_page = get( conn, "/api/v2/advanced-filters", - Map.merge(%{"tx_types" => tx_type_filter_string}, response["next_page_params"]) + Map.merge(%{"transaction_types" => transaction_type_filter_string}, response["next_page_params"]) ) assert response_2nd_page = json_response(request_2nd_page, 200) - assert Enum.all?(response_2nd_page["items"], fn item -> String.upcase(item["type"]) in tx_type_filter end) + assert Enum.all?(response_2nd_page["items"], fn item -> + String.upcase(item["type"]) in transaction_type_filter + end) check_paginated_response( - AdvancedFilter.list(tx_types: tx_type_filter), + AdvancedFilter.list(transaction_types: transaction_type_filter), response["items"], response_2nd_page["items"] ) @@ -177,7 +187,7 @@ defmodule BlockScoutWeb.API.V2.AdvancedFilterControllerTest do test "filter by methods", %{conn: conn} do TestHelper.get_eip1967_implementation_zero_addresses() - tx = :transaction |> insert() |> with_block() + transaction = :transaction |> insert() |> with_block() smart_contract = build(:smart_contract) @@ -199,24 +209,24 @@ defmodule BlockScoutWeb.API.V2.AdvancedFilterControllerTest do {:ok, method3} = Data.cast(method_id3_string <> "ab0ba0") {:ok, method4} = Data.cast(method_id4_string <> "ab0ba0") - for i <- 0..4 do + for i <- 1..5 do insert(:internal_transaction, - transaction: tx, + transaction: transaction, to_address_hash: contract_address.hash, to_address: contract_address, - block_hash: tx.block_hash, + block_hash: transaction.block_hash, index: i, block_index: i, input: method1 ) end - for i <- 5..9 do + for i <- 6..10 do insert(:internal_transaction, - transaction: tx, + transaction: transaction, to_address_hash: contract_address.hash, to_address: contract_address, - block_hash: tx.block_hash, + block_hash: transaction.block_hash, index: i, block_index: i, input: method2 @@ -255,25 +265,33 @@ defmodule BlockScoutWeb.API.V2.AdvancedFilterControllerTest do end test "filter by age", %{conn: conn} do - first_timestamp = ~U[2023-12-12 00:00:00.000000Z] + [_, transaction_a, _, transaction_b, _] = + for i <- 0..4 do + tx = :transaction |> insert() |> with_block(status: :ok) - for i <- 0..4 do - tx = :transaction |> insert() |> with_block(block_timestamp: Timex.shift(first_timestamp, days: i)) + insert(:internal_transaction, + transaction: tx, + index: i + 1, + block_index: i + 1, + block_hash: tx.block_hash, + block: tx.block + ) - insert(:internal_transaction, - transaction: tx, - block_hash: tx.block_hash, - index: i, - block_index: i - ) + insert(:token_transfer, + transaction: tx, + block_number: tx.block_number, + log_index: i, + block_hash: tx.block_hash, + block: tx.block + ) - insert(:token_transfer, transaction: tx, block_number: tx.block_number, log_index: i) - end + tx + end request = get(conn, "/api/v2/advanced-filters", %{ - "age_from" => "2023-12-14T00:00:00Z", - "age_to" => "2023-12-16T00:00:00Z" + "age_from" => DateTime.to_iso8601(transaction_a.block.timestamp), + "age_to" => DateTime.to_iso8601(transaction_b.block.timestamp) }) assert response = json_response(request, 200) @@ -285,36 +303,36 @@ defmodule BlockScoutWeb.API.V2.AdvancedFilterControllerTest do address = insert(:address) for i <- 0..4 do - tx = :transaction |> insert() |> with_block() + transaction = :transaction |> insert() |> with_block() if i < 2 do :transaction |> insert(from_address_hash: address.hash, from_address: address) |> with_block() insert(:internal_transaction, - transaction: tx, + transaction: transaction, from_address_hash: address.hash, from_address: address, - block_hash: tx.block_hash, - index: i, - block_index: i + block_hash: transaction.block_hash, + index: i + 1, + block_index: i + 1 ) insert(:token_transfer, from_address_hash: address.hash, from_address: address, - transaction: tx, - block_number: tx.block_number, + transaction: transaction, + block_number: transaction.block_number, log_index: i ) else insert(:internal_transaction, - transaction: tx, - block_hash: tx.block_hash, - index: i, - block_index: i + transaction: transaction, + block_hash: transaction.block_hash, + index: i + 1, + block_index: i + 1 ) - insert(:token_transfer, transaction: tx, block_number: tx.block_number, log_index: i) + insert(:token_transfer, transaction: transaction, block_number: transaction.block_number, log_index: i) end end @@ -329,36 +347,36 @@ defmodule BlockScoutWeb.API.V2.AdvancedFilterControllerTest do address = insert(:address) for i <- 0..4 do - tx = :transaction |> insert() |> with_block() + transaction = :transaction |> insert() |> with_block() if i < 4 do :transaction |> insert(from_address_hash: address.hash, from_address: address) |> with_block() insert(:internal_transaction, - transaction: tx, + transaction: transaction, from_address_hash: address.hash, from_address: address, - block_hash: tx.block_hash, - index: i, - block_index: i + block_hash: transaction.block_hash, + index: i + 1, + block_index: i + 1 ) insert(:token_transfer, from_address_hash: address.hash, from_address: address, - transaction: tx, - block_number: tx.block_number, + transaction: transaction, + block_number: transaction.block_number, log_index: i ) else insert(:internal_transaction, - transaction: tx, - block_hash: tx.block_hash, - index: i, - block_index: i + transaction: transaction, + block_hash: transaction.block_hash, + index: i + 1, + block_index: i + 1 ) - insert(:token_transfer, transaction: tx, block_number: tx.block_number, log_index: i) + insert(:token_transfer, transaction: transaction, block_number: transaction.block_number, log_index: i) end end @@ -374,7 +392,7 @@ defmodule BlockScoutWeb.API.V2.AdvancedFilterControllerTest do address_to_exclude = insert(:address) for i <- 0..2 do - tx = + transaction = :transaction |> insert(from_address_hash: address_to_exclude.hash, from_address: address_to_exclude) |> with_block() @@ -385,30 +403,30 @@ defmodule BlockScoutWeb.API.V2.AdvancedFilterControllerTest do |> with_block() insert(:internal_transaction, - transaction: tx, + transaction: transaction, from_address_hash: address_to_include.hash, from_address: address_to_include, - block_hash: tx.block_hash, - index: i, - block_index: i + block_hash: transaction.block_hash, + index: i + 1, + block_index: i + 1 ) insert(:token_transfer, from_address_hash: address_to_include.hash, from_address: address_to_include, - transaction: tx, - block_number: tx.block_number, + transaction: transaction, + block_number: transaction.block_number, log_index: i ) else insert(:internal_transaction, - transaction: tx, - block_hash: tx.block_hash, - index: i, - block_index: i + transaction: transaction, + block_hash: transaction.block_hash, + index: i + 1, + block_index: i + 1 ) - insert(:token_transfer, transaction: tx, block_number: tx.block_number, log_index: i) + insert(:token_transfer, transaction: transaction, block_number: transaction.block_number, log_index: i) end end @@ -427,36 +445,36 @@ defmodule BlockScoutWeb.API.V2.AdvancedFilterControllerTest do address = insert(:address) for i <- 0..4 do - tx = :transaction |> insert() |> with_block() + transaction = :transaction |> insert() |> with_block() if i < 2 do :transaction |> insert(to_address_hash: address.hash, to_address: address) |> with_block() insert(:internal_transaction, - transaction: tx, + transaction: transaction, to_address_hash: address.hash, to_address: address, - block_hash: tx.block_hash, - index: i, - block_index: i + block_hash: transaction.block_hash, + index: i + 1, + block_index: i + 1 ) insert(:token_transfer, to_address_hash: address.hash, to_address: address, - transaction: tx, - block_number: tx.block_number, + transaction: transaction, + block_number: transaction.block_number, log_index: i ) else insert(:internal_transaction, - transaction: tx, - block_hash: tx.block_hash, - index: i, - block_index: i + transaction: transaction, + block_hash: transaction.block_hash, + index: i + 1, + block_index: i + 1 ) - insert(:token_transfer, transaction: tx, block_number: tx.block_number, log_index: i) + insert(:token_transfer, transaction: transaction, block_number: transaction.block_number, log_index: i) end end @@ -471,36 +489,36 @@ defmodule BlockScoutWeb.API.V2.AdvancedFilterControllerTest do address = insert(:address) for i <- 0..4 do - tx = :transaction |> insert() |> with_block() + transaction = :transaction |> insert() |> with_block() if i < 4 do :transaction |> insert(to_address_hash: address.hash, to_address: address) |> with_block() insert(:internal_transaction, - transaction: tx, + transaction: transaction, to_address_hash: address.hash, to_address: address, - block_hash: tx.block_hash, - index: i, - block_index: i + block_hash: transaction.block_hash, + index: i + 1, + block_index: i + 1 ) insert(:token_transfer, to_address_hash: address.hash, to_address: address, - transaction: tx, - block_number: tx.block_number, + transaction: transaction, + block_number: transaction.block_number, log_index: i ) else insert(:internal_transaction, - transaction: tx, - block_hash: tx.block_hash, - index: i, - block_index: i + transaction: transaction, + block_hash: transaction.block_hash, + index: i + 1, + block_index: i + 1 ) - insert(:token_transfer, transaction: tx, block_number: tx.block_number, log_index: i) + insert(:token_transfer, transaction: transaction, block_number: transaction.block_number, log_index: i) end end @@ -516,7 +534,7 @@ defmodule BlockScoutWeb.API.V2.AdvancedFilterControllerTest do address_to_exclude = insert(:address) for i <- 0..2 do - tx = + transaction = :transaction |> insert(to_address_hash: address_to_exclude.hash, to_address: address_to_exclude) |> with_block() @@ -527,30 +545,30 @@ defmodule BlockScoutWeb.API.V2.AdvancedFilterControllerTest do |> with_block() insert(:internal_transaction, - transaction: tx, + transaction: transaction, to_address_hash: address_to_include.hash, to_address: address_to_include, - block_hash: tx.block_hash, - index: i, - block_index: i + block_hash: transaction.block_hash, + index: i + 1, + block_index: i + 1 ) insert(:token_transfer, to_address_hash: address_to_include.hash, to_address: address_to_include, - transaction: tx, - block_number: tx.block_number, + transaction: transaction, + block_number: transaction.block_number, log_index: i ) else insert(:internal_transaction, - transaction: tx, - block_hash: tx.block_hash, - index: i, - block_index: i + transaction: transaction, + block_hash: transaction.block_hash, + index: i + 1, + block_index: i + 1 ) - insert(:token_transfer, transaction: tx, block_number: tx.block_number, log_index: i) + insert(:token_transfer, transaction: transaction, block_number: transaction.block_number, log_index: i) end end @@ -570,26 +588,26 @@ defmodule BlockScoutWeb.API.V2.AdvancedFilterControllerTest do to_address = insert(:address) for i <- 0..8 do - tx = :transaction |> insert() |> with_block() + transaction = :transaction |> insert() |> with_block() cond do i < 2 -> :transaction |> insert(from_address_hash: from_address.hash, from_address: from_address) |> with_block() insert(:internal_transaction, - transaction: tx, + transaction: transaction, from_address_hash: from_address.hash, from_address: from_address, - block_hash: tx.block_hash, - index: i, - block_index: i + block_hash: transaction.block_hash, + index: i + 1, + block_index: i + 1 ) insert(:token_transfer, from_address_hash: from_address.hash, from_address: from_address, - transaction: tx, - block_number: tx.block_number, + transaction: transaction, + block_number: transaction.block_number, log_index: i ) @@ -597,19 +615,19 @@ defmodule BlockScoutWeb.API.V2.AdvancedFilterControllerTest do :transaction |> insert(to_address_hash: to_address.hash, to_address: to_address) |> with_block() insert(:internal_transaction, - transaction: tx, + transaction: transaction, to_address_hash: to_address.hash, to_address: to_address, - block_hash: tx.block_hash, - index: i, - block_index: i + block_hash: transaction.block_hash, + index: i + 1, + block_index: i + 1 ) insert(:token_transfer, to_address_hash: to_address.hash, to_address: to_address, - transaction: tx, - block_number: tx.block_number, + transaction: transaction, + block_number: transaction.block_number, log_index: i ) @@ -624,14 +642,14 @@ defmodule BlockScoutWeb.API.V2.AdvancedFilterControllerTest do |> with_block() insert(:internal_transaction, - transaction: tx, + transaction: transaction, to_address_hash: to_address.hash, to_address: to_address, from_address_hash: from_address.hash, from_address: from_address, - block_hash: tx.block_hash, - index: i, - block_index: i + block_hash: transaction.block_hash, + index: i + 1, + block_index: i + 1 ) insert(:token_transfer, @@ -639,20 +657,20 @@ defmodule BlockScoutWeb.API.V2.AdvancedFilterControllerTest do to_address: to_address, from_address_hash: from_address.hash, from_address: from_address, - transaction: tx, - block_number: tx.block_number, + transaction: transaction, + block_number: transaction.block_number, log_index: i ) true -> insert(:internal_transaction, - transaction: tx, - block_hash: tx.block_hash, - index: i, - block_index: i + transaction: transaction, + block_hash: transaction.block_hash, + index: i + 1, + block_index: i + 1 ) - insert(:token_transfer, transaction: tx, block_number: tx.block_number, log_index: i) + insert(:token_transfer, transaction: transaction, block_number: transaction.block_number, log_index: i) end end @@ -673,26 +691,26 @@ defmodule BlockScoutWeb.API.V2.AdvancedFilterControllerTest do to_address = insert(:address) for i <- 0..8 do - tx = :transaction |> insert() |> with_block() + transaction = :transaction |> insert() |> with_block() cond do i < 2 -> :transaction |> insert(from_address_hash: from_address.hash, from_address: from_address) |> with_block() insert(:internal_transaction, - transaction: tx, + transaction: transaction, from_address_hash: from_address.hash, from_address: from_address, - block_hash: tx.block_hash, - index: i, - block_index: i + block_hash: transaction.block_hash, + index: i + 1, + block_index: i + 1 ) insert(:token_transfer, from_address_hash: from_address.hash, from_address: from_address, - transaction: tx, - block_number: tx.block_number, + transaction: transaction, + block_number: transaction.block_number, log_index: i ) @@ -700,19 +718,19 @@ defmodule BlockScoutWeb.API.V2.AdvancedFilterControllerTest do :transaction |> insert(to_address_hash: to_address.hash, to_address: to_address) |> with_block() insert(:internal_transaction, - transaction: tx, + transaction: transaction, to_address_hash: to_address.hash, to_address: to_address, - block_hash: tx.block_hash, - index: i, - block_index: i + block_hash: transaction.block_hash, + index: i + 1, + block_index: i + 1 ) insert(:token_transfer, to_address_hash: to_address.hash, to_address: to_address, - transaction: tx, - block_number: tx.block_number, + transaction: transaction, + block_number: transaction.block_number, log_index: i ) @@ -727,14 +745,14 @@ defmodule BlockScoutWeb.API.V2.AdvancedFilterControllerTest do |> with_block() insert(:internal_transaction, - transaction: tx, + transaction: transaction, to_address_hash: to_address.hash, to_address: to_address, from_address_hash: from_address.hash, from_address: from_address, - block_hash: tx.block_hash, - index: i, - block_index: i + block_hash: transaction.block_hash, + index: i + 1, + block_index: i + 1 ) insert(:token_transfer, @@ -742,20 +760,20 @@ defmodule BlockScoutWeb.API.V2.AdvancedFilterControllerTest do to_address: to_address, from_address_hash: from_address.hash, from_address: from_address, - transaction: tx, - block_number: tx.block_number, + transaction: transaction, + block_number: transaction.block_number, log_index: i ) true -> insert(:internal_transaction, - transaction: tx, - block_hash: tx.block_hash, - index: i, - block_index: i + transaction: transaction, + block_hash: transaction.block_hash, + index: i + 1, + block_index: i + 1 ) - insert(:token_transfer, transaction: tx, block_number: tx.block_number, log_index: i) + insert(:token_transfer, transaction: transaction, block_number: transaction.block_number, log_index: i) end end @@ -772,13 +790,13 @@ defmodule BlockScoutWeb.API.V2.AdvancedFilterControllerTest do test "filter by amount", %{conn: conn} do for i <- 0..4 do - tx = :transaction |> insert(value: i * 10 ** 18) |> with_block() + transaction = :transaction |> insert(value: i * 10 ** 18) |> with_block() insert(:internal_transaction, - transaction: tx, - block_hash: tx.block_hash, - index: 0, - block_index: 0, + transaction: transaction, + block_hash: transaction.block_hash, + index: 1, + block_index: 1, value: i * 10 ** 18 ) @@ -787,8 +805,8 @@ defmodule BlockScoutWeb.API.V2.AdvancedFilterControllerTest do insert(:token_transfer, amount: i * 10 ** 10, token_contract_address: token.contract_address, - transaction: tx, - block_number: tx.block_number, + transaction: transaction, + block_number: transaction.block_number, log_index: 0 ) end @@ -804,13 +822,13 @@ defmodule BlockScoutWeb.API.V2.AdvancedFilterControllerTest do token_b = insert(:token) token_c = insert(:token) - tx = :transaction |> insert() |> with_block() + transaction = :transaction |> insert() |> with_block() for token <- [token_a, token_b, token_c, token_a, token_b, token_c, token_a, token_b, token_c] do insert(:token_transfer, token_contract_address: token.contract_address, - transaction: tx, - block_number: tx.block_number, + transaction: transaction, + block_number: transaction.block_number, log_index: 0 ) end @@ -831,13 +849,13 @@ defmodule BlockScoutWeb.API.V2.AdvancedFilterControllerTest do token_b = insert(:token) token_c = insert(:token) - tx = :transaction |> insert() |> with_block() + transaction = :transaction |> insert() |> with_block() for token <- [token_a, token_b, token_c, token_a, token_b, token_c, token_a, token_b, token_c] do insert(:token_transfer, token_contract_address: token.contract_address, - transaction: tx, - block_number: tx.block_number, + transaction: transaction, + block_number: transaction.block_number, log_index: 0 ) end @@ -858,13 +876,13 @@ defmodule BlockScoutWeb.API.V2.AdvancedFilterControllerTest do token_b = insert(:token) token_c = insert(:token) - tx = :transaction |> insert() |> with_block() + transaction = :transaction |> insert() |> with_block() for token <- [token_a, token_b, token_c, token_a, token_b, token_c, token_a, token_b, token_c] do insert(:token_transfer, token_contract_address: token.contract_address, - transaction: tx, - block_number: tx.block_number, + transaction: transaction, + block_number: transaction.block_number, log_index: 0 ) end @@ -885,13 +903,13 @@ defmodule BlockScoutWeb.API.V2.AdvancedFilterControllerTest do token_b = insert(:token) token_c = insert(:token) - tx = :transaction |> insert() |> with_block() + transaction = :transaction |> insert() |> with_block() for token <- [token_a, token_b, token_c, token_a, token_b, token_c, token_a, token_b, token_c] do insert(:token_transfer, token_contract_address: token.contract_address, - transaction: tx, - block_number: tx.block_number, + transaction: transaction, + block_number: transaction.block_number, log_index: 0 ) end @@ -906,13 +924,110 @@ defmodule BlockScoutWeb.API.V2.AdvancedFilterControllerTest do assert Enum.count(response["items"]) == 3 end + + test "correct query with all filters and pagination", %{conn: conn} do + for address_relation <- [:or, :and] do + method_id_string = "0xa9059cbb" + {:ok, method} = Data.cast(method_id_string <> "ab0ba0") + transaction_from_address = insert(:address) + transaction_to_address = insert(:address) + token_transfer_from_address = insert(:address) + token_transfer_to_address = insert(:address) + token = insert(:token) + {:ok, burn_address_hash} = Hash.Address.cast(SmartContract.burn_address_hash_string()) + + insert_list(5, :transaction) + + transactions = + for _ <- 0..29 do + transaction = + insert(:transaction, + from_address: transaction_from_address, + from_address_hash: transaction_from_address.hash, + to_address: transaction_to_address, + to_address_hash: transaction_to_address.hash, + value: Enum.random(0..1_000_000), + input: method + ) + |> with_block() + + insert(:token_transfer, + transaction: transaction, + block_number: transaction.block_number, + amount: Enum.random(0..1_000_000), + from_address: token_transfer_from_address, + from_address_hash: token_transfer_from_address.hash, + to_address: token_transfer_to_address, + to_address_hash: token_transfer_to_address.hash, + token_contract_address: token.contract_address, + token_contract_address_hash: token.contract_address_hash + ) + + transaction + end + + insert_list(5, :transaction) + + from_timestamp = List.first(transactions).block.timestamp + to_timestamp = List.last(transactions).block.timestamp + + params = %{ + "tx_types" => "coin_transfer,ERC-20", + "methods" => method_id_string, + "age_from" => from_timestamp |> DateTime.to_iso8601(), + "age_to" => to_timestamp |> DateTime.to_iso8601(), + "from_address_hashes_to_include" => "#{transaction_from_address.hash},#{token_transfer_from_address.hash}", + "to_address_hashes_to_include" => "#{transaction_to_address.hash},#{token_transfer_to_address.hash}", + "address_relation" => to_string(address_relation), + "amount_from" => "0", + "amount_to" => "1000000", + "token_contract_address_hashes_to_include" => "native,#{token.contract_address_hash}", + "token_contract_address_hashes_to_exclude" => "#{burn_address_hash}" + } + + request = + get(conn, "/api/v2/advanced-filters", params) + + assert response = json_response(request, 200) + request_2nd_page = get(conn, "/api/v2/advanced-filters", Map.merge(params, response["next_page_params"])) + assert response_2nd_page = json_response(request_2nd_page, 200) + + check_paginated_response( + AdvancedFilter.list( + tx_types: ["COIN_TRANSFER", "ERC-20"], + methods: ["0xa9059cbb"], + age: [from: from_timestamp, to: to_timestamp], + from_address_hashes: [ + include: [transaction_from_address.hash, token_transfer_from_address.hash], + exclude: nil + ], + to_address_hashes: [ + include: [transaction_to_address.hash, token_transfer_to_address.hash], + exclude: nil + ], + address_relation: address_relation, + amount: [from: Decimal.new("0"), to: Decimal.new("1000000")], + token_contract_address_hashes: [ + include: [ + "native", + token.contract_address_hash + ], + exclude: [burn_address_hash] + ], + api?: true + ), + response["items"], + response_2nd_page["items"] + ) + end + end end describe "/advanced_filters/methods?q=" do - test "returns 404 if method does not exist", %{conn: conn} do + test "returns empty list if method does not exist", %{conn: conn} do request = get(conn, "/api/v2/advanced-filters/methods", %{"q" => "foo"}) - assert response = json_response(request, 404) - assert response["message"] == "Not found" + assert response = json_response(request, 200) + assert response == [] end test "finds method by name", %{conn: conn} do 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 bfb690c..cdbd546 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 @@ -282,14 +282,14 @@ defmodule BlockScoutWeb.API.V2.BlockControllerTest do assert response["next_page_params"] == nil end - test "get relevant tx", %{conn: conn} do + test "get relevant transaction", %{conn: conn} do 10 |> insert_list(:transaction) |> with_block() block = insert(:block) - tx = + transaction = :transaction |> insert() |> with_block(block) @@ -298,21 +298,21 @@ defmodule BlockScoutWeb.API.V2.BlockControllerTest do assert response = json_response(request, 200) assert Enum.count(response["items"]) == 1 assert response["next_page_params"] == nil - compare_item(tx, Enum.at(response["items"], 0)) + compare_item(transaction, Enum.at(response["items"], 0)) request = get(conn, "/api/v2/blocks/#{block.hash}/transactions") assert response_1 = json_response(request, 200) assert response_1 == response end - test "get txs with working next_page_params", %{conn: conn} do + test "get transactions with working next_page_params", %{conn: conn} do 2 |> insert_list(:transaction) |> with_block() block = insert(:block) - txs = + transactions = 51 |> insert_list(:transaction) |> with_block(block) @@ -324,7 +324,7 @@ defmodule BlockScoutWeb.API.V2.BlockControllerTest do request_2nd_page = get(conn, "/api/v2/blocks/#{block.number}/transactions", response["next_page_params"]) assert response_2nd_page = json_response(request_2nd_page, 200) - check_paginated_response(response, response_2nd_page, txs) + check_paginated_response(response, response_2nd_page, transactions) request_1 = get(conn, "/api/v2/blocks/#{block.hash}/transactions") assert response_1 = json_response(request_1, 200) @@ -443,34 +443,34 @@ defmodule BlockScoutWeb.API.V2.BlockControllerTest do request = get(conn, "/api/v2/blocks/#{block.hash}/internal-transactions") assert %{"items" => [], "next_page_params" => nil} = json_response(request, 200) - tx = + transaction = :transaction |> insert() |> with_block(block) insert(:internal_transaction, - transaction: tx, + transaction: transaction, index: 0, - block_number: tx.block_number, - transaction_index: tx.index, - block_hash: tx.block_hash, + block_number: transaction.block_number, + transaction_index: transaction.index, + block_hash: transaction.block_hash, block_index: 0 ) - internal_txs = + internal_transactions = 51..1 |> Enum.map(fn index -> - tx = + transaction = :transaction |> insert() |> with_block(block) insert(:internal_transaction, - transaction: tx, + transaction: transaction, index: index, - block_number: tx.block_number, - transaction_index: tx.index, - block_hash: tx.block_hash, + block_number: transaction.block_number, + transaction_index: transaction.index, + block_hash: transaction.block_hash, block_index: index ) end) @@ -482,7 +482,7 @@ defmodule BlockScoutWeb.API.V2.BlockControllerTest do assert response_2nd_page = json_response(request_2nd_page, 200) - check_paginated_response(response, response_2nd_page, internal_txs) + check_paginated_response(response, response_2nd_page, internal_transactions) end end @@ -493,7 +493,7 @@ defmodule BlockScoutWeb.API.V2.BlockControllerTest do defp compare_item(%Transaction{} = transaction, json) do assert to_string(transaction.hash) == json["hash"] - assert transaction.block_number == json["block"] + assert transaction.block_number == json["block_number"] 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"] @@ -503,13 +503,13 @@ defmodule BlockScoutWeb.API.V2.BlockControllerTest do assert withdrawal.index == json["index"] 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"] + defp compare_item(%InternalTransaction{} = internal_transaction, json) do + assert internal_transaction.block_number == json["block_number"] + assert to_string(internal_transaction.gas) == json["gas_limit"] + assert internal_transaction.index == json["index"] + assert to_string(internal_transaction.transaction_hash) == json["transaction_hash"] + assert Address.checksum(internal_transaction.from_address_hash) == json["from"]["hash"] + assert Address.checksum(internal_transaction.to_address_hash) == json["to"]["hash"] end 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/internal_transaction_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/internal_transaction_controller_test.exs new file mode 100644 index 0000000..cee0215 --- /dev/null +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/internal_transaction_controller_test.exs @@ -0,0 +1,94 @@ +defmodule BlockScoutWeb.API.V2.InternalTransactionControllerTest do + use BlockScoutWeb.ConnCase + + alias Explorer.Chain.{Address, InternalTransaction} + + # todo: enable when /internal-transactions API endpoint will be enabled + # describe "/internal-transactions" do + # test "empty list", %{conn: conn} do + # request = get(conn, "/api/v2/internal-transactions") + + # assert response = json_response(request, 200) + # assert response["items"] == [] + # assert response["next_page_params"] == nil + # end + + # test "non empty list", %{conn: conn} do + # tx = + # :transaction + # |> insert() + # |> with_block() + + # insert(:internal_transaction, + # transaction: tx, + # block_hash: tx.block_hash, + # index: 0, + # block_index: 0 + # ) + + # request = get(conn, "/api/v2/internal-transactions") + + # assert response = json_response(request, 200) + # assert Enum.count(response["items"]) == 1 + # assert response["next_page_params"] == nil + # end + + # test "internal transactions with next_page_params", %{conn: conn} do + # transaction = insert(:transaction) |> with_block() + + # internal_transaction = + # insert(:internal_transaction, + # transaction: transaction, + # transaction_index: 0, + # block_number: transaction.block_number, + # block_hash: transaction.block_hash, + # index: 0, + # block_index: 0 + # ) + + # transaction_2 = insert(:transaction) |> with_block() + + # internal_transactions = + # for i <- 0..49 do + # insert(:internal_transaction, + # transaction: transaction_2, + # transaction_index: 0, + # block_number: transaction_2.block_number, + # block_hash: transaction_2.block_hash, + # index: i, + # block_index: i + # ) + # end + + # internal_transactions = [internal_transaction | internal_transactions] + + # request = get(conn, "/api/v2/internal-transactions") + # assert response = json_response(request, 200) + + # request_2nd_page = get(conn, "/api/v2/internal-transactions", response["next_page_params"]) + # assert response_2nd_page = json_response(request_2nd_page, 200) + + # check_paginated_response(response, response_2nd_page, internal_transactions) + # end + # end + + # defp compare_item(%InternalTransaction{} = internal_transaction, json) do + # assert Address.checksum(internal_transaction.from_address_hash) == json["from"]["hash"] + # assert Address.checksum(internal_transaction.to_address_hash) == json["to"]["hash"] + # assert to_string(internal_transaction.transaction_hash) == json["transaction_hash"] + # assert internal_transaction.block_number == json["block_number"] + # assert internal_transaction.block_index == json["block_index"] + # end + + # defp check_paginated_response(first_page_resp, second_page_resp, internal_transactions) do + # assert Enum.count(first_page_resp["items"]) == 50 + # assert first_page_resp["next_page_params"] != nil + # compare_item(Enum.at(internal_transactions, 50), Enum.at(first_page_resp["items"], 0)) + + # compare_item(Enum.at(internal_transactions, 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(internal_transactions, 0), Enum.at(second_page_resp["items"], 0)) + # end +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 1ea1c33..3445aa5 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 @@ -1,8 +1,7 @@ defmodule BlockScoutWeb.API.V2.MainPageControllerTest do use BlockScoutWeb.ConnCase - alias BlockScoutWeb.Models.UserFromAuth - alias Explorer.Account.WatchlistAddress + alias Explorer.Account.{Identity, WatchlistAddress} alias Explorer.Chain.{Address, Block, Transaction} alias Explorer.Repo @@ -37,20 +36,20 @@ defmodule BlockScoutWeb.API.V2.MainPageControllerTest do end describe "/main-page/transactions" do - test "get empty list when no txs", %{conn: conn} do + test "get empty list when no transactions", %{conn: conn} do request = get(conn, "/api/v2/main-page/transactions") assert [] = json_response(request, 200) end - test "get last 6 txs", %{conn: conn} do - txs = insert_list(10, :transaction) |> with_block() |> Enum.take(-6) |> Enum.reverse() + test "get last 6 transactions", %{conn: conn} do + transactions = insert_list(10, :transaction) |> with_block() |> Enum.take(-6) |> Enum.reverse() request = get(conn, "/api/v2/main-page/transactions") assert response = json_response(request, 200) assert Enum.count(response) == 6 for i <- 0..5 do - compare_item(Enum.at(txs, i), Enum.at(response, i)) + compare_item(Enum.at(transactions, i), Enum.at(response, i)) end end end @@ -61,11 +60,11 @@ defmodule BlockScoutWeb.API.V2.MainPageControllerTest do assert %{"message" => "Unauthorized"} = json_response(request, 401) end - test "get last 6 txs", %{conn: conn} do + test "get last 6 transactions", %{conn: conn} do insert_list(10, :transaction) |> with_block() auth = build(:auth) - {:ok, user} = UserFromAuth.find_or_create(auth) + {:ok, user} = Identity.find_or_create(auth) conn = Plug.Test.init_test_session(conn, current_user: user) @@ -107,17 +106,17 @@ defmodule BlockScoutWeb.API.V2.MainPageControllerTest do notify_email: true }) - txs_1 = insert_list(2, :transaction, from_address: address_1) |> with_block() - txs_2 = insert_list(1, :transaction, from_address: address_2, to_address: address_1) |> with_block() - txs_3 = insert_list(3, :transaction, to_address: address_2) |> with_block() - txs = (txs_1 ++ txs_2 ++ txs_3) |> Enum.reverse() + transactions_1 = insert_list(2, :transaction, from_address: address_1) |> with_block() + transactions_2 = insert_list(1, :transaction, from_address: address_2, to_address: address_1) |> with_block() + transactions_3 = insert_list(3, :transaction, to_address: address_2) |> with_block() + transactions = (transactions_1 ++ transactions_2 ++ transactions_3) |> Enum.reverse() request = get(conn, "/api/v2/main-page/transactions/watchlist") assert response = json_response(request, 200) assert Enum.count(response) == 6 for i <- 0..5 do - compare_item(Enum.at(txs, i), Enum.at(response, i), %{ + compare_item(Enum.at(transactions, i), Enum.at(response, i), %{ address_1.hash => watchlist_address_1.name, address_2.hash => watchlist_address_2.name }) @@ -144,7 +143,7 @@ defmodule BlockScoutWeb.API.V2.MainPageControllerTest do defp compare_item(%Transaction{} = transaction, json) do assert to_string(transaction.hash) == json["hash"] - assert transaction.block_number == json["block"] + assert transaction.block_number == json["block_number"] 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"] @@ -152,7 +151,7 @@ defmodule BlockScoutWeb.API.V2.MainPageControllerTest do defp compare_item(%Transaction{} = transaction, json, wl_names) do assert to_string(transaction.hash) == json["hash"] - assert transaction.block_number == json["block"] + assert transaction.block_number == json["block_number"] 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"] 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 f5d15a7..2f66211 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 @@ -202,9 +202,9 @@ defmodule BlockScoutWeb.API.V2.SearchControllerTest do end test "search transaction", %{conn: conn} do - tx = insert(:transaction, block_timestamp: nil) + transaction = insert(:transaction, block_timestamp: nil) - request = get(conn, "/api/v2/search?q=#{tx.hash}") + request = get(conn, "/api/v2/search?q=#{transaction.hash}") assert response = json_response(request, 200) assert Enum.count(response["items"]) == 1 @@ -213,15 +213,15 @@ defmodule BlockScoutWeb.API.V2.SearchControllerTest do 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["transaction_hash"] == to_string(transaction.hash) + assert item["url"] =~ to_string(transaction.hash) assert item["timestamp"] == nil end test "search transaction with timestamp", %{conn: conn} do - tx = :transaction |> insert() |> with_block() + transaction = :transaction |> insert() |> with_block() - request = get(conn, "/api/v2/search?q=#{tx.hash}") + request = get(conn, "/api/v2/search?q=#{transaction.hash}") assert response = json_response(request, 200) assert Enum.count(response["items"]) == 1 @@ -230,9 +230,11 @@ defmodule BlockScoutWeb.API.V2.SearchControllerTest do 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") + assert item["transaction_hash"] == to_string(transaction.hash) + assert item["url"] =~ to_string(transaction.hash) + + assert item["timestamp"] == + Repo.preload(transaction, [:block]).block.timestamp |> to_string() |> String.replace(" ", "T") end test "search tags", %{conn: conn} do 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 0353409..5d4c022 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 @@ -5,8 +5,8 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do import Mox alias BlockScoutWeb.AddressContractView - alias BlockScoutWeb.Models.UserFromAuth alias Explorer.Chain.{Address, SmartContract} + alias Explorer.Account.Identity alias Explorer.TestHelper alias Plug.Conn @@ -263,7 +263,7 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do "constructor_args" => target_contract.constructor_arguments, "decoded_constructor_args" => [ ["0x0000000000000000000000000000000000000000", %{"name" => "_proxyStorage", "type" => "address"}], - ["0x2cf6e7c9ec35d0b08a1062e13854f74b1aaae54e", %{"name" => "_implementationAddress", "type" => "address"}] + ["0x2Cf6E7c9eC35D0B08A1062e13854f74b1aaae54e", %{"name" => "_implementationAddress", "type" => "address"}] ], "is_self_destructed" => false, "deployed_bytecode" => @@ -455,7 +455,7 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do implementation_contract_address_hash_string = Base.encode16(implementation_contract.address_hash.bytes, case: :lower) - proxy_tx_input = + proxy_transaction_input = "0x11b804ab000000000000000000000000" <> implementation_contract_address_hash_string <> "000000000000000000000000000000000000000000000000000000000000006035323031313537360000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000284e159163400000000000000000000000034420c13696f4ac650b9fafe915553a1abcd7dd30000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000001c00000000000000000000000000000000000000000000000000000000000000220000000000000000000000000ff5ae9b0a7522736299d797d80b8fc6f31d61100000000000000000000000000ff5ae9b0a7522736299d797d80b8fc6f31d6110000000000000000000000000000000000000000000000000000000000000003e8000000000000000000000000000000000000000000000000000000000000000000000000000000000000000034420c13696f4ac650b9fafe915553a1abcd7dd300000000000000000000000000000000000000000000000000000000000000184f7074696d69736d2053756273637269626572204e465473000000000000000000000000000000000000000000000000000000000000000000000000000000054f504e46540000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000037697066733a2f2f516d66544e504839765651334b5952346d6b52325a6b757756424266456f5a5554545064395538666931503332752f300000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c82bbe41f2cf04e3a8efa18f7032bdd7f6d98a81000000000000000000000000efba8a2a82ec1fb1273806174f5e28fbb917cf9500000000000000000000000000000000000000000000000000000000" @@ -470,7 +470,7 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do insert(:transaction, created_contract_address_hash: proxy_address.hash, - input: proxy_tx_input + input: proxy_transaction_input ) |> with_block(status: :ok) @@ -479,7 +479,7 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do "has_custom_methods_write" => false, "is_self_destructed" => false, "deployed_bytecode" => proxy_deployed_bytecode, - "creation_bytecode" => proxy_tx_input, + "creation_bytecode" => proxy_transaction_input, "proxy_type" => "eip1167", "implementations" => [ %{ @@ -618,7 +618,7 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do implementation_contract_address_hash_string = Base.encode16(implementation_contract.address_hash.bytes, case: :lower) - proxy_tx_input = + proxy_transaction_input = "0x684fbe55000000000000000000000000af1caf51d49b0e63d1ff7e5d4ed6ea26d15f3f9d000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003" proxy_deployed_bytecode = @@ -633,7 +633,7 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do insert(:transaction, created_contract_address_hash: proxy_address.hash, - input: proxy_tx_input + input: proxy_transaction_input ) |> with_block(status: :ok) @@ -648,7 +648,7 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do "has_custom_methods_write" => false, "is_self_destructed" => false, "deployed_bytecode" => proxy_deployed_bytecode, - "creation_bytecode" => proxy_tx_input + "creation_bytecode" => proxy_transaction_input } request = get(conn, "/api/v2/smart-contracts/#{Address.checksum(proxy_address.hash)}") @@ -849,7 +849,7 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do assert response["decoded_constructor_args"] == [ [ - "0xc35dadb65012ec5796536bd9864ed8773abc74c4", + "0xc35DADB65012eC5796536bD9864eD8773aBc74C4", %{ "internalType" => "address", "name" => "_factory", @@ -857,7 +857,7 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do } ], [ - "0xb4fbf271143f4fbf7b91a5ded31805e42b2208d6", + "0xB4FBF271143F4FBf7B91A5ded31805e42b2208d6", %{ "internalType" => "address", "name" => "_WETH", @@ -954,7 +954,7 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do assert response == %{ - "proxy_type" => nil, + "proxy_type" => "unknown", "implementations" => [], "has_custom_methods_read" => false, "has_custom_methods_write" => false, @@ -1159,8 +1159,8 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do assert response == %{ - "proxy_type" => nil, - "implementations" => [], + "proxy_type" => "eip1967", + "implementations" => [%{"address" => formatted_implementation_address_hash_string, "name" => nil}], "has_custom_methods_read" => false, "has_custom_methods_write" => false, "is_self_destructed" => false, @@ -1284,8 +1284,8 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do assert response == %{ - "proxy_type" => nil, - "implementations" => [], + "proxy_type" => "eip1967", + "implementations" => [%{"address" => formatted_implementation_address_hash_string, "name" => nil}], "has_custom_methods_read" => false, "has_custom_methods_write" => false, "is_self_destructed" => false, @@ -1409,8 +1409,8 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do assert response == %{ - "proxy_type" => nil, - "implementations" => [], + "proxy_type" => "eip1967", + "implementations" => [%{"address" => formatted_implementation_address_hash_string, "name" => nil}], "has_custom_methods_read" => false, "has_custom_methods_write" => false, "is_self_destructed" => false, @@ -2763,7 +2763,7 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do setup %{conn: conn} do auth = build(:auth) - {:ok, user} = UserFromAuth.find_or_create(auth) + {:ok, user} = Identity.find_or_create(auth) {:ok, conn: Plug.Test.init_test_session(conn, current_user: user)} end @@ -3580,7 +3580,7 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do end |> Enum.reverse() - ordering_params = %{"sort" => "txs_count", "order" => "asc"} + ordering_params = %{"sort" => "transactions_count", "order" => "asc"} request = get(conn, "/api/v2/smart-contracts", ordering_params) assert response = json_response(request, 200) @@ -3600,7 +3600,7 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do insert(:smart_contract, address_hash: address.hash, address: address) end - ordering_params = %{"sort" => "txs_count", "order" => "desc"} + ordering_params = %{"sort" => "transactions_count", "order" => "desc"} request = get(conn, "/api/v2/smart-contracts", ordering_params) assert response = json_response(request, 200) 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 bf1a852..1e39149 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 @@ -131,12 +131,12 @@ defmodule BlockScoutWeb.API.V2.TokenControllerTest do token_transfers = for _ <- 0..50 do - tx = insert(:transaction, input: "0xabcd010203040506") |> with_block() + transaction = insert(:transaction, input: "0xabcd010203040506") |> with_block() insert(:token_transfer, - transaction: tx, - block: tx.block, - block_number: tx.block_number, + transaction: transaction, + block: transaction.block, + block_number: transaction.block_number, token_contract_address: token.contract_address ) end @@ -161,12 +161,12 @@ defmodule BlockScoutWeb.API.V2.TokenControllerTest do tt = for _ <- 0..50 do - tx = insert(:transaction, input: "0xabcd010203040506") |> with_block() + transaction = insert(:transaction, input: "0xabcd010203040506") |> with_block() insert(:token_transfer, - transaction: tx, - block: tx.block, - block_number: tx.block_number, + transaction: transaction, + block: transaction.block, + block_number: transaction.block_number, token_contract_address: token.contract_address, token_ids: Enum.map(0..50, fn _x -> id end), token_type: "ERC-1155", @@ -195,12 +195,12 @@ defmodule BlockScoutWeb.API.V2.TokenControllerTest do token_transfers = for i <- 0..50 do - tx = insert(:transaction, input: "0xabcd010203040506") |> with_block() + transaction = insert(:transaction, input: "0xabcd010203040506") |> with_block() insert(:token_transfer, - transaction: tx, - block: tx.block, - block_number: tx.block_number, + transaction: transaction, + block: transaction.block, + block_number: transaction.block_number, token_contract_address: token.contract_address, token_ids: [i], token_type: "ERC-721" @@ -220,13 +220,13 @@ defmodule BlockScoutWeb.API.V2.TokenControllerTest do test "check that pagination works fine with 1155 batches #1 (large batch)", %{conn: conn} do token = insert(:token, type: "ERC-1155") - tx = insert(:transaction, input: "0xabcd010203040506") |> with_block() + transaction = insert(:transaction, input: "0xabcd010203040506") |> with_block() tt = insert(:token_transfer, - transaction: tx, - block: tx.block, - block_number: tx.block_number, + transaction: transaction, + block: transaction.block, + block_number: transaction.block_number, token_contract_address: token.contract_address, token_ids: Enum.map(0..50, fn x -> x end), token_type: "ERC-1155", @@ -253,13 +253,13 @@ defmodule BlockScoutWeb.API.V2.TokenControllerTest do %{conn: conn} do token = insert(:token, type: "ERC-1155") - tx_1 = insert(:transaction, input: "0xabcd010203040506") |> with_block() + transaction_1 = insert(:transaction, input: "0xabcd010203040506") |> with_block() tt_1 = insert(:token_transfer, - transaction: tx_1, - block: tx_1.block, - block_number: tx_1.block_number, + transaction: transaction_1, + block: transaction_1.block, + block_number: transaction_1.block_number, token_contract_address: token.contract_address, token_ids: Enum.map(0..24, fn x -> x end), token_type: "ERC-1155", @@ -271,13 +271,13 @@ defmodule BlockScoutWeb.API.V2.TokenControllerTest do %TokenTransfer{tt_1 | token_ids: [i], amount: i} end - tx_2 = insert(:transaction, input: "0xabcd010203040506") |> with_block() + transaction_2 = insert(:transaction, input: "0xabcd010203040506") |> with_block() tt_2 = insert(:token_transfer, - transaction: tx_2, - block: tx_2.block, - block_number: tx_2.block_number, + transaction: transaction_2, + block: transaction_2.block, + block_number: transaction_2.block_number, token_contract_address: token.contract_address, token_ids: Enum.map(25..49, fn x -> x end), token_type: "ERC-1155", @@ -291,9 +291,9 @@ defmodule BlockScoutWeb.API.V2.TokenControllerTest do tt_3 = insert(:token_transfer, - transaction: tx_2, - block: tx_2.block, - block_number: tx_2.block_number, + transaction: transaction_2, + block: transaction_2.block, + block_number: transaction_2.block_number, token_contract_address: token.contract_address, token_ids: [50], token_type: "ERC-1155", @@ -314,13 +314,13 @@ defmodule BlockScoutWeb.API.V2.TokenControllerTest do test "check that pagination works fine with 1155 batches #3", %{conn: conn} do token = insert(:token, type: "ERC-1155") - tx_1 = insert(:transaction, input: "0xabcd010203040506") |> with_block() + transaction_1 = insert(:transaction, input: "0xabcd010203040506") |> with_block() tt_1 = insert(:token_transfer, - transaction: tx_1, - block: tx_1.block, - block_number: tx_1.block_number, + transaction: transaction_1, + block: transaction_1.block, + block_number: transaction_1.block_number, token_contract_address: token.contract_address, token_ids: Enum.map(0..24, fn x -> x end), token_type: "ERC-1155", @@ -332,13 +332,13 @@ defmodule BlockScoutWeb.API.V2.TokenControllerTest do %TokenTransfer{tt_1 | token_ids: [i], amount: i} end - tx_2 = insert(:transaction, input: "0xabcd010203040506") |> with_block() + transaction_2 = insert(:transaction, input: "0xabcd010203040506") |> with_block() tt_2 = insert(:token_transfer, - transaction: tx_2, - block: tx_2.block, - block_number: tx_2.block_number, + transaction: transaction_2, + block: transaction_2.block, + block_number: transaction_2.block_number, token_contract_address: token.contract_address, token_ids: Enum.map(25..50, fn x -> x end), token_type: "ERC-1155", @@ -1237,12 +1237,12 @@ defmodule BlockScoutWeb.API.V2.TokenControllerTest do token_transfers = for _i <- 0..50 do - tx = insert(:transaction, input: "0xabcd010203040506") |> with_block() + transaction = insert(:transaction, input: "0xabcd010203040506") |> with_block() insert(:token_transfer, - transaction: tx, - block: tx.block, - block_number: tx.block_number, + transaction: transaction, + block: transaction.block, + block_number: transaction.block_number, token_contract_address: token.contract_address, token_ids: [id], token_type: "ERC-721" @@ -1271,13 +1271,13 @@ defmodule BlockScoutWeb.API.V2.TokenControllerTest do insert(:token_instance, token_id: id, token_contract_address_hash: token.contract_address_hash) - tx = insert(:transaction, input: "0xabcd010203040506") |> with_block() + transaction = insert(:transaction, input: "0xabcd010203040506") |> with_block() tt = insert(:token_transfer, - transaction: tx, - block: tx.block, - block_number: tx.block_number, + transaction: transaction, + block: transaction.block, + block_number: transaction.block_number, token_contract_address: token.contract_address, token_ids: Enum.map(0..50, fn _x -> id end), token_type: "ERC-1155", @@ -1299,14 +1299,14 @@ defmodule BlockScoutWeb.API.V2.TokenControllerTest do amount = 101 insert(:token_instance, token_id: id, token_contract_address_hash: token.contract_address_hash) - tx = insert(:transaction, input: "0xabcd010203040506") |> with_block() + transaction = insert(:transaction, input: "0xabcd010203040506") |> with_block() tt = for _ <- 0..50 do insert(:token_transfer, - transaction: tx, - block: tx.block, - block_number: tx.block_number, + transaction: transaction, + block: transaction.block, + block_number: transaction.block_number, token_contract_address: token.contract_address, token_ids: Enum.map(0..50, fn x -> x end) ++ [id], token_type: "ERC-1155", @@ -1455,6 +1455,20 @@ defmodule BlockScoutWeb.API.V2.TokenControllerTest do setup :verify_on_exit! setup %{json_rpc_named_arguments: json_rpc_named_arguments} do + old_recaptcha_env = Application.get_env(:block_scout_web, :recaptcha) + old_http_adapter = Application.get_env(:block_scout_web, :http_adapter) + + v2_secret_key = "v2_secret_key" + v3_secret_key = "v3_secret_key" + + Application.put_env(:block_scout_web, :recaptcha, + v2_secret_key: v2_secret_key, + v3_secret_key: v3_secret_key, + is_disabled: false + ) + + Application.put_env(:block_scout_web, :http_adapter, Explorer.Mox.HTTPoison) + mocked_json_rpc_named_arguments = Keyword.put(json_rpc_named_arguments, :transport, EthereumJSONRPC.Mox) start_supervised!({Task.Supervisor, name: Indexer.TaskSupervisor}) @@ -1468,12 +1482,29 @@ defmodule BlockScoutWeb.API.V2.TokenControllerTest do Subscriber.to(:fetched_token_instance_metadata, :on_demand) - :ok + on_exit(fn -> + Application.put_env(:block_scout_web, :recaptcha, old_recaptcha_env) + Application.put_env(:block_scout_web, :http_adapter, old_http_adapter) + end) + + {:ok, %{v2_secret_key: v2_secret_key, v3_secret_key: v3_secret_key}} end - test "token instance metadata on-demand re-fetcher is called", %{conn: conn} do - BlockScoutWeb.TestCaptchaHelper - |> expect(:recaptcha_passed?, fn _captcha_response -> true end) + test "token instance metadata on-demand re-fetcher is called", %{conn: conn, v2_secret_key: v2_secret_key} do + expected_body = "secret=#{v2_secret_key}&response=123" + + Explorer.Mox.HTTPoison + |> expect(:post, fn _url, ^expected_body, _headers, _options -> + {:ok, + %HTTPoison.Response{ + status_code: 200, + body: + Jason.encode!(%{ + "success" => true, + "hostname" => Application.get_env(:block_scout_web, BlockScoutWeb.Endpoint)[:url][:host] + }) + }} + end) token = insert(:token, type: "ERC-721") token_id = 1 @@ -1534,9 +1565,24 @@ defmodule BlockScoutWeb.API.V2.TokenControllerTest do Application.put_env(:explorer, :http_adapter, HTTPoison) end - test "don't fetch token instance metadata for non-existent token instance", %{conn: conn} do - BlockScoutWeb.TestCaptchaHelper - |> expect(:recaptcha_passed?, fn _captcha_response -> true end) + test "don't fetch token instance metadata for non-existent token instance", %{ + conn: conn, + v2_secret_key: v2_secret_key + } do + expected_body = "secret=#{v2_secret_key}&response=123" + + Explorer.Mox.HTTPoison + |> expect(:post, fn _url, ^expected_body, _headers, _options -> + {:ok, + %HTTPoison.Response{ + status_code: 200, + body: + Jason.encode!(%{ + "success" => true, + "hostname" => Application.get_env(:block_scout_web, BlockScoutWeb.Endpoint)[:url][:host] + }) + }} + end) token = insert(:token, type: "ERC-721") token_id = 0 @@ -1550,6 +1596,84 @@ defmodule BlockScoutWeb.API.V2.TokenControllerTest do assert %{"message" => "Not found"} = json_response(request, 404) end + + test "fetch token instance metadata for existing token instance with no metadata", %{ + conn: conn, + v2_secret_key: v2_secret_key + } do + expected_body = "secret=#{v2_secret_key}&response=123" + + Explorer.Mox.HTTPoison + |> expect(:post, fn _url, ^expected_body, _headers, _options -> + {:ok, + %HTTPoison.Response{ + status_code: 200, + body: + Jason.encode!(%{ + "success" => true, + "hostname" => Application.get_env(:block_scout_web, BlockScoutWeb.Endpoint)[:url][:host] + }) + }} + end) + + token = insert(:token, type: "ERC-721") + token_id = 1 + + insert(:token_instance, + token_id: token_id, + token_contract_address_hash: token.contract_address_hash, + metadata: nil + ) + + metadata = %{"name" => "Super Token"} + url = "http://metadata.endpoint.com" + token_contract_address_hash_string = to_string(token.contract_address_hash) + + TestHelper.fetch_token_uri_mock(url, token_contract_address_hash_string) + + Application.put_env(:explorer, :http_adapter, Explorer.Mox.HTTPoison) + + Explorer.Mox.HTTPoison + |> expect(:get, fn ^url, _headers, _options -> + {:ok, %HTTPoison.Response{status_code: 200, body: Jason.encode!(metadata)}} + end) + + topic = "token_instances:#{token_contract_address_hash_string}" + + {:ok, _reply, _socket} = + BlockScoutWeb.UserSocketV2 + |> socket("no_id", %{}) + |> subscribe_and_join(topic) + + request = + patch(conn, "/api/v2/tokens/#{token.contract_address.hash}/instances/#{token_id}/refetch-metadata", %{ + "recaptcha_response" => "123" + }) + + assert %{"message" => "OK"} = json_response(request, 200) + + :timer.sleep(100) + + assert_receive( + {:chain_event, :fetched_token_instance_metadata, :on_demand, + [^token_contract_address_hash_string, ^token_id, ^metadata]} + ) + + assert_receive %Phoenix.Socket.Message{ + payload: %{token_id: ^token_id, fetched_metadata: ^metadata}, + event: "fetched_token_instance_metadata", + topic: ^topic + }, + :timer.seconds(1) + + token_instance_from_db = + Repo.get_by(Instance, token_id: token_id, token_contract_address_hash: token.contract_address_hash) + + assert(token_instance_from_db) + assert token_instance_from_db.metadata == metadata + + Application.put_env(:explorer, :http_adapter, HTTPoison) + end end def compare_item(%Address{} = address, json) do @@ -1573,11 +1697,11 @@ defmodule BlockScoutWeb.API.V2.TokenControllerTest do def 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 to_string(token_transfer.transaction_hash) == json["transaction_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 token_transfer.log_index == json["log_index"] assert check_total(Repo.preload(token_transfer, [{:token, :contract_address}]).token, json["total"], token_transfer) end diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/token_transfer_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/token_transfer_controller_test.exs new file mode 100644 index 0000000..b3944a6 --- /dev/null +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/token_transfer_controller_test.exs @@ -0,0 +1,177 @@ +defmodule BlockScoutWeb.API.V2.TokenTransferControllerTest do + use BlockScoutWeb.ConnCase + + alias Explorer.Chain.{Address, TokenTransfer} + + describe "/token-transfers" do + test "empty list", %{conn: conn} do + request = get(conn, "/api/v2/token-transfers") + + assert response = json_response(request, 200) + assert response["items"] == [] + assert response["next_page_params"] == nil + end + + test "non empty list", %{conn: conn} do + transaction = + :transaction + |> insert() + |> with_block() + + 1 |> insert_list(:token_transfer, transaction: transaction) + + request = get(conn, "/api/v2/token-transfers") + + assert response = json_response(request, 200) + assert Enum.count(response["items"]) == 1 + assert response["next_page_params"] == nil + end + + test "filters by type", %{conn: conn} do + transaction = + :transaction + |> insert() + |> with_block() + + token = insert(:token, type: "ERC-721") + + insert(:token_transfer, + transaction: transaction, + token: token, + token_type: "ERC-721" + ) + + request = get(conn, "/api/v2/token-transfers?type=ERC-1155") + + assert response = json_response(request, 200) + assert Enum.count(response["items"]) == 0 + assert response["next_page_params"] == nil + end + + test "returns all transfers if filter is incorrect", %{conn: conn} do + transaction = + :transaction + |> insert() + |> with_block() + + token = insert(:token, type: "ERC-100500") + + insert(:token_transfer, + transaction: transaction, + token: token, + token_type: "ERC-721" + ) + + insert(:token_transfer, + transaction: transaction, + token: token, + token_type: "ERC-20" + ) + + request = get(conn, "/api/v2/token-transfers?type=ERC-20") + + assert response = json_response(request, 200) + assert Enum.count(response["items"]) == 2 + assert response["next_page_params"] == nil + end + + test "token transfers with next_page_params", %{conn: conn} do + token_transfers = + for _i <- 0..50 do + transaction = insert(:transaction) |> with_block() + + insert(:token_transfer, + transaction: transaction, + block: transaction.block, + block_number: transaction.block_number + ) + end + + request = get(conn, "/api/v2/token-transfers") + assert response = json_response(request, 200) + + request_2nd_page = get(conn, "/api/v2/token-transfers", response["next_page_params"]) + assert response_2nd_page = json_response(request_2nd_page, 200) + + check_paginated_response(response, response_2nd_page, nil, token_transfers) + end + + test "flatten erc1155 batch token transfer", %{conn: conn} do + transaction = insert(:transaction) |> with_block() + + insert(:token_transfer, + transaction: transaction, + block: transaction.block, + block_number: transaction.block_number, + token_ids: [1, 2, 3], + amounts: [500, 600, 700], + token_type: "ERC-1155" + ) + + request = get(conn, "/api/v2/token-transfers") + assert response = json_response(request, 200) + assert Enum.count(response["items"]) == 3 + assert Enum.at(response["items"], 0)["total"] == %{"decimals" => "18", "value" => "700", "token_id" => "3"} + end + + test "paginates erc1155 batch token transfers", %{conn: conn} do + token_transfers = + for _i <- 0..50 do + transaction = insert(:transaction) |> with_block() + + insert(:token_transfer, + transaction: transaction, + block: transaction.block, + block_number: transaction.block_number, + token_ids: [1, 2], + amounts: [500, 600], + token_type: "ERC-1155" + ) + end + + request = get(conn, "/api/v2/token-transfers") + assert response = json_response(request, 200) + + request_2nd_page = get(conn, "/api/v2/token-transfers", response["next_page_params"]) + assert response_2nd_page = json_response(request_2nd_page, 200) + + request_3d_page = get(conn, "/api/v2/token-transfers", response_2nd_page["next_page_params"]) + assert response_3d_page = json_response(request_3d_page, 200) + + check_paginated_response(response, response_2nd_page, response_3d_page, token_transfers) + end + 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["transaction_hash"] + assert token_transfer.transaction.block_timestamp == Timex.parse!(json["timestamp"], "{ISO:Extended:Z}") + assert json["method"] == nil + assert token_transfer.block_number == json["block_number"] + assert token_transfer.log_index == json["log_index"] + end + + defp check_paginated_response(first_page_resp, second_page_resp, third_page_resp, token_transfers) do + assert Enum.count(first_page_resp["items"]) == 50 + assert first_page_resp["next_page_params"] != nil + compare_item(Enum.at(token_transfers, 50), Enum.at(first_page_resp["items"], 0)) + + if is_nil(third_page_resp) do + compare_item(Enum.at(token_transfers, 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(token_transfers, 0), Enum.at(second_page_resp["items"], 0)) + else + assert Enum.count(second_page_resp["items"]) == 50 + assert second_page_resp["next_page_params"] !== nil + + compare_item(Enum.at(token_transfers, 1), Enum.at(second_page_resp["items"], 49)) + + assert Enum.count(third_page_resp["items"]) == 2 + assert third_page_resp["next_page_params"] == nil + compare_item(Enum.at(token_transfers, 0), Enum.at(third_page_resp["items"], 0)) + end + end +end 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 5b771b7..1ecfe52 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 @@ -4,8 +4,7 @@ defmodule BlockScoutWeb.API.V2.TransactionControllerTest do import Explorer.Chain, only: [hash_to_lower_case_string: 1] import Mox - alias BlockScoutWeb.Models.UserFromAuth - alias Explorer.Account.WatchlistAddress + alias Explorer.Account.{Identity, WatchlistAddress} alias Explorer.Chain.{Address, InternalTransaction, Log, Token, TokenTransfer, Transaction, Wei} alias Explorer.Repo @@ -37,8 +36,8 @@ defmodule BlockScoutWeb.API.V2.TransactionControllerTest do assert response["next_page_params"] == nil end - test "txs with next_page_params", %{conn: conn} do - txs = + test "transactions with next_page_params", %{conn: conn} do + transactions = 51 |> insert_list(:transaction) |> with_block() @@ -49,15 +48,15 @@ defmodule BlockScoutWeb.API.V2.TransactionControllerTest do request_2nd_page = get(conn, "/api/v2/transactions", response["next_page_params"]) assert response_2nd_page = json_response(request_2nd_page, 200) - check_paginated_response(response, response_2nd_page, txs) + check_paginated_response(response, response_2nd_page, transactions) end test "filter=pending", %{conn: conn} do - pending_txs = + pending_transactions = 51 |> insert_list(:transaction) - _mined_txs = + _mined_transactions = 51 |> insert_list(:transaction) |> with_block() @@ -70,15 +69,15 @@ defmodule BlockScoutWeb.API.V2.TransactionControllerTest do request_2nd_page = get(conn, "/api/v2/transactions", Map.merge(response["next_page_params"], filter)) assert response_2nd_page = json_response(request_2nd_page, 200) - check_paginated_response(response, response_2nd_page, pending_txs) + check_paginated_response(response, response_2nd_page, pending_transactions) end test "filter=validated", %{conn: conn} do - _pending_txs = + _pending_transactions = 51 |> insert_list(:transaction) - mined_txs = + mined_transactions = 51 |> insert_list(:transaction) |> with_block() @@ -91,7 +90,7 @@ defmodule BlockScoutWeb.API.V2.TransactionControllerTest do request_2nd_page = get(conn, "/api/v2/transactions", Map.merge(response["next_page_params"], filter)) assert response_2nd_page = json_response(request_2nd_page, 200) - check_paginated_response(response, response_2nd_page, mined_txs) + check_paginated_response(response, response_2nd_page, mined_transactions) end end @@ -109,7 +108,7 @@ defmodule BlockScoutWeb.API.V2.TransactionControllerTest do auth = build(:auth) insert(:address) - {:ok, user} = UserFromAuth.find_or_create(auth) + {:ok, user} = Identity.find_or_create(auth) conn = Plug.Test.init_test_session(conn, current_user: user) @@ -120,9 +119,9 @@ defmodule BlockScoutWeb.API.V2.TransactionControllerTest do assert response["next_page_params"] == nil end - test "watchlist txs can paginate", %{conn: conn} do + test "watchlist transactions can paginate", %{conn: conn} do auth = build(:auth) - {:ok, user} = UserFromAuth.find_or_create(auth) + {:ok, user} = Identity.find_or_create(auth) conn = Plug.Test.init_test_session(conn, current_user: user) @@ -171,17 +170,17 @@ defmodule BlockScoutWeb.API.V2.TransactionControllerTest do |> insert_list(:transaction) |> with_block() - txs_1 = + transactions_1 = 25 |> insert_list(:transaction, from_address: address_1) |> with_block() - txs_2 = + transactions_2 = 1 |> insert_list(:transaction, from_address: address_2, to_address: address_1) |> with_block() - txs_3 = + transactions_3 = 25 |> insert_list(:transaction, from_address: address_2) |> with_block() @@ -192,61 +191,61 @@ defmodule BlockScoutWeb.API.V2.TransactionControllerTest do request_2nd_page = get(conn, "/api/v2/transactions/watchlist", response["next_page_params"]) assert response_2nd_page = json_response(request_2nd_page, 200) - check_paginated_response(response, response_2nd_page, txs_1 ++ txs_2 ++ txs_3, %{ + check_paginated_response(response, response_2nd_page, transactions_1 ++ transactions_2 ++ transactions_3, %{ address_1.hash => watchlist_address_1.name, address_2.hash => watchlist_address_2.name }) end end - describe "/transactions/{tx_hash}" do - test "return 404 on non existing tx", %{conn: conn} do - tx = build(:transaction) - request = get(conn, "/api/v2/transactions/#{to_string(tx.hash)}") + describe "/transactions/{transaction_hash}" do + test "return 404 on non existing transaction", %{conn: conn} do + transaction = build(:transaction) + request = get(conn, "/api/v2/transactions/#{to_string(transaction.hash)}") assert %{"message" => "Not found"} = json_response(request, 404) end - test "return 422 on invalid tx hash", %{conn: conn} do + test "return 422 on invalid transaction hash", %{conn: conn} do request = get(conn, "/api/v2/transactions/0x") assert %{"message" => "Invalid parameter(s)"} = json_response(request, 422) end - test "return existing tx", %{conn: conn} do - tx = + test "return existing transaction", %{conn: conn} do + transaction = :transaction |> insert() |> with_block() - request = get(conn, "/api/v2/transactions/" <> to_string(tx.hash)) + request = get(conn, "/api/v2/transactions/" <> to_string(transaction.hash)) assert response = json_response(request, 200) - compare_item(tx, response) + compare_item(transaction, response) end test "batch 1155 flattened", %{conn: conn} do token = insert(:token, type: "ERC-1155") - tx = + transaction = :transaction |> insert() |> with_block() insert(:token_transfer, - transaction: tx, - block: tx.block, - block_number: tx.block_number, + transaction: transaction, + block: transaction.block, + block_number: transaction.block_number, token_contract_address: token.contract_address, token_ids: Enum.map(0..50, fn x -> x end), token_type: "ERC-1155", amounts: Enum.map(0..50, fn x -> x end) ) - request = get(conn, "/api/v2/transactions/" <> to_string(tx.hash)) + request = get(conn, "/api/v2/transactions/" <> to_string(transaction.hash)) assert response = json_response(request, 200) - compare_item(tx, response) + compare_item(transaction, response) assert Enum.count(response["token_transfers"]) == 10 end @@ -254,16 +253,16 @@ defmodule BlockScoutWeb.API.V2.TransactionControllerTest do test "single 1155 flattened", %{conn: conn} do token = insert(:token, type: "ERC-1155") - tx = + transaction = :transaction |> insert() |> with_block() tt = insert(:token_transfer, - transaction: tx, - block: tx.block, - block_number: tx.block_number, + transaction: transaction, + block: transaction.block, + block_number: transaction.block_number, token_contract_address: token.contract_address, token_ids: [1], token_type: "ERC-1155", @@ -271,10 +270,10 @@ defmodule BlockScoutWeb.API.V2.TransactionControllerTest do amount: nil ) - request = get(conn, "/api/v2/transactions/" <> to_string(tx.hash)) + request = get(conn, "/api/v2/transactions/" <> to_string(transaction.hash)) assert response = json_response(request, 200) - compare_item(tx, response) + compare_item(transaction, response) assert Enum.count(response["token_transfers"]) == 1 assert is_map(Enum.at(response["token_transfers"], 0)["total"]) @@ -282,27 +281,27 @@ defmodule BlockScoutWeb.API.V2.TransactionControllerTest do end end - describe "/transactions/{tx_hash}/internal-transactions" do - test "return 404 on non existing tx", %{conn: conn} do - tx = build(:transaction) - request = get(conn, "/api/v2/transactions/#{to_string(tx.hash)}/internal-transactions") + describe "/transactions/{transaction_hash}/internal-transactions" do + test "return 404 on non existing transaction", %{conn: conn} do + transaction = build(:transaction) + request = get(conn, "/api/v2/transactions/#{to_string(transaction.hash)}/internal-transactions") assert %{"message" => "Not found"} = json_response(request, 404) end - test "return 422 on invalid tx hash", %{conn: conn} do + test "return 422 on invalid transaction hash", %{conn: conn} do request = get(conn, "/api/v2/transactions/0x/internal-transactions") assert %{"message" => "Invalid parameter(s)"} = json_response(request, 422) end test "return empty list", %{conn: conn} do - tx = + transaction = :transaction |> insert() |> with_block() - request = get(conn, "/api/v2/transactions/#{to_string(tx.hash)}/internal-transactions") + request = get(conn, "/api/v2/transactions/#{to_string(transaction.hash)}/internal-transactions") assert response = json_response(request, 200) assert response["items"] == [] @@ -310,31 +309,31 @@ defmodule BlockScoutWeb.API.V2.TransactionControllerTest do end test "return relevant internal transaction", %{conn: conn} do - tx = + transaction = :transaction |> insert() |> with_block() insert(:internal_transaction, - transaction: tx, + transaction: transaction, index: 0, - block_number: tx.block_number, - transaction_index: tx.index, - block_hash: tx.block_hash, + block_number: transaction.block_number, + transaction_index: transaction.index, + block_hash: transaction.block_hash, block_index: 0 ) - internal_tx = + internal_transaction = insert(:internal_transaction, - transaction: tx, + transaction: transaction, index: 1, - block_number: tx.block_number, - transaction_index: tx.index, - block_hash: tx.block_hash, + block_number: transaction.block_number, + transaction_index: transaction.index, + block_hash: transaction.block_hash, block_index: 1 ) - tx_1 = + transaction_1 = :transaction |> insert() |> with_block() @@ -342,84 +341,88 @@ defmodule BlockScoutWeb.API.V2.TransactionControllerTest do 0..5 |> Enum.map(fn index -> insert(:internal_transaction, - transaction: tx_1, + transaction: transaction_1, index: index, - block_number: tx_1.block_number, - transaction_index: tx_1.index, - block_hash: tx_1.block_hash, + block_number: transaction_1.block_number, + transaction_index: transaction_1.index, + block_hash: transaction_1.block_hash, block_index: index ) end) - request = get(conn, "/api/v2/transactions/#{to_string(tx.hash)}/internal-transactions") + request = get(conn, "/api/v2/transactions/#{to_string(transaction.hash)}/internal-transactions") assert response = json_response(request, 200) assert Enum.count(response["items"]) == 1 assert response["next_page_params"] == nil - compare_item(internal_tx, Enum.at(response["items"], 0)) + compare_item(internal_transaction, Enum.at(response["items"], 0)) end test "return list with next_page_params", %{conn: conn} do - tx = + transaction = :transaction |> insert() |> with_block() insert(:internal_transaction, - transaction: tx, + transaction: transaction, index: 0, - block_number: tx.block_number, - transaction_index: tx.index, - block_hash: tx.block_hash, + block_number: transaction.block_number, + transaction_index: transaction.index, + block_hash: transaction.block_hash, block_index: 0 ) - internal_txs = + internal_transactions = 51..1 |> Enum.map(fn index -> insert(:internal_transaction, - transaction: tx, + transaction: transaction, index: index, - block_number: tx.block_number, - transaction_index: tx.index, - block_hash: tx.block_hash, + block_number: transaction.block_number, + transaction_index: transaction.index, + block_hash: transaction.block_hash, block_index: index ) end) - request = get(conn, "/api/v2/transactions/#{to_string(tx.hash)}/internal-transactions") + request = get(conn, "/api/v2/transactions/#{to_string(transaction.hash)}/internal-transactions") assert response = json_response(request, 200) request_2nd_page = - get(conn, "/api/v2/transactions/#{to_string(tx.hash)}/internal-transactions", response["next_page_params"]) + get( + conn, + "/api/v2/transactions/#{to_string(transaction.hash)}/internal-transactions", + response["next_page_params"] + ) assert response_2nd_page = json_response(request_2nd_page, 200) - check_paginated_response(response, response_2nd_page, internal_txs) + check_paginated_response(response, response_2nd_page, internal_transactions) end end - describe "/transactions/{tx_hash}/logs" do - test "return 404 on non existing tx", %{conn: conn} do - tx = build(:transaction) - request = get(conn, "/api/v2/transactions/#{to_string(tx.hash)}/logs") + describe "/transactions/{transaction_hash}/logs" do + test "return 404 on non existing transaction", %{conn: conn} do + transaction = build(:transaction) + request = get(conn, "/api/v2/transactions/#{to_string(transaction.hash)}/logs") assert %{"message" => "Not found"} = json_response(request, 404) end - test "return 422 on invalid tx hash", %{conn: conn} do + test "return 422 on invalid transaction hash", %{conn: conn} do request = get(conn, "/api/v2/transactions/0x/logs") assert %{"message" => "Invalid parameter(s)"} = json_response(request, 422) end test "return empty list", %{conn: conn} do - tx = + transaction = :transaction |> insert() |> with_block() - request = get(conn, "/api/v2/transactions/#{to_string(tx.hash)}/logs") + request = get(conn, "/api/v2/transactions/#{to_string(transaction.hash)}/logs") assert response = json_response(request, 200) assert response["items"] == [] @@ -427,20 +430,20 @@ defmodule BlockScoutWeb.API.V2.TransactionControllerTest do end test "return relevant log", %{conn: conn} do - tx = + transaction = :transaction |> insert() |> with_block() log = insert(:log, - transaction: tx, + transaction: transaction, index: 1, - block: tx.block, - block_number: tx.block_number + block: transaction.block, + block_number: transaction.block_number ) - tx_1 = + transaction_1 = :transaction |> insert() |> with_block() @@ -448,14 +451,14 @@ defmodule BlockScoutWeb.API.V2.TransactionControllerTest do 0..5 |> Enum.map(fn index -> insert(:log, - transaction: tx_1, + transaction: transaction_1, index: index, - block: tx_1.block, - block_number: tx_1.block_number + block: transaction_1.block, + block_number: transaction_1.block_number ) end) - request = get(conn, "/api/v2/transactions/#{to_string(tx.hash)}/logs") + request = get(conn, "/api/v2/transactions/#{to_string(transaction.hash)}/logs") assert response = json_response(request, 200) assert Enum.count(response["items"]) == 1 @@ -464,7 +467,7 @@ defmodule BlockScoutWeb.API.V2.TransactionControllerTest do end test "return list with next_page_params", %{conn: conn} do - tx = + transaction = :transaction |> insert() |> with_block() @@ -473,17 +476,18 @@ defmodule BlockScoutWeb.API.V2.TransactionControllerTest do 50..0 |> Enum.map(fn index -> insert(:log, - transaction: tx, + transaction: transaction, index: index, - block: tx.block, - block_number: tx.block_number + block: transaction.block, + block_number: transaction.block_number ) end) - request = get(conn, "/api/v2/transactions/#{to_string(tx.hash)}/logs") + request = get(conn, "/api/v2/transactions/#{to_string(transaction.hash)}/logs") assert response = json_response(request, 200) - request_2nd_page = get(conn, "/api/v2/transactions/#{to_string(tx.hash)}/logs", response["next_page_params"]) + request_2nd_page = + get(conn, "/api/v2/transactions/#{to_string(transaction.hash)}/logs", response["next_page_params"]) assert response_2nd_page = json_response(request_2nd_page, 200) @@ -491,27 +495,27 @@ defmodule BlockScoutWeb.API.V2.TransactionControllerTest do end end - describe "/transactions/{tx_hash}/token-transfers" do - test "return 404 on non existing tx", %{conn: conn} do - tx = build(:transaction) - request = get(conn, "/api/v2/transactions/#{to_string(tx.hash)}/token-transfers") + describe "/transactions/{transaction_hash}/token-transfers" do + test "return 404 on non existing transaction", %{conn: conn} do + transaction = build(:transaction) + request = get(conn, "/api/v2/transactions/#{to_string(transaction.hash)}/token-transfers") assert %{"message" => "Not found"} = json_response(request, 404) end - test "return 422 on invalid tx hash", %{conn: conn} do + test "return 422 on invalid transaction hash", %{conn: conn} do request = get(conn, "/api/v2/transactions/0x/token-transfers") assert %{"message" => "Invalid parameter(s)"} = json_response(request, 422) end test "return empty list", %{conn: conn} do - tx = + transaction = :transaction |> insert() |> with_block() - request = get(conn, "/api/v2/transactions/#{to_string(tx.hash)}/token-transfers") + request = get(conn, "/api/v2/transactions/#{to_string(transaction.hash)}/token-transfers") assert response = json_response(request, 200) assert response["items"] == [] @@ -519,21 +523,30 @@ defmodule BlockScoutWeb.API.V2.TransactionControllerTest do end test "return relevant token transfer", %{conn: conn} do - tx = + transaction = :transaction |> insert() |> with_block() - token_transfer = insert(:token_transfer, transaction: tx, block: tx.block, block_number: tx.block_number) + token_transfer = + insert(:token_transfer, + transaction: transaction, + block: transaction.block, + block_number: transaction.block_number + ) - tx_1 = + transaction_1 = :transaction |> insert() |> with_block() - insert_list(6, :token_transfer, transaction: tx_1, block: tx_1.block, block_number: tx_1.block_number) + insert_list(6, :token_transfer, + transaction: transaction_1, + block: transaction_1.block, + block_number: transaction_1.block_number + ) - request = get(conn, "/api/v2/transactions/#{to_string(tx.hash)}/token-transfers") + request = get(conn, "/api/v2/transactions/#{to_string(transaction.hash)}/token-transfers") assert response = json_response(request, 200) assert Enum.count(response["items"]) == 1 @@ -542,20 +555,24 @@ defmodule BlockScoutWeb.API.V2.TransactionControllerTest do end test "return list with next_page_params", %{conn: conn} do - tx = + transaction = :transaction |> insert() |> with_block() token_transfers = - insert_list(51, :token_transfer, transaction: tx, block: tx.block, block_number: tx.block_number) + insert_list(51, :token_transfer, + transaction: transaction, + block: transaction.block, + block_number: transaction.block_number + ) |> Enum.reverse() - request = get(conn, "/api/v2/transactions/#{to_string(tx.hash)}/token-transfers") + request = get(conn, "/api/v2/transactions/#{to_string(transaction.hash)}/token-transfers") assert response = json_response(request, 200) request_2nd_page = - get(conn, "/api/v2/transactions/#{to_string(tx.hash)}/token-transfers", response["next_page_params"]) + get(conn, "/api/v2/transactions/#{to_string(transaction.hash)}/token-transfers", response["next_page_params"]) assert response_2nd_page = json_response(request_2nd_page, 200) @@ -563,7 +580,7 @@ defmodule BlockScoutWeb.API.V2.TransactionControllerTest do end test "check filters", %{conn: conn} do - tx = + transaction = :transaction |> insert() |> with_block() @@ -573,9 +590,9 @@ defmodule BlockScoutWeb.API.V2.TransactionControllerTest do erc_1155_tt = for x <- 0..50 do insert(:token_transfer, - transaction: tx, - block: tx.block, - block_number: tx.block_number, + transaction: transaction, + block: transaction.block, + block_number: transaction.block_number, token_contract_address: erc_1155_token.contract_address, token_ids: [x], token_type: "ERC-1155" @@ -588,9 +605,9 @@ defmodule BlockScoutWeb.API.V2.TransactionControllerTest do erc_721_tt = for x <- 0..50 do insert(:token_transfer, - transaction: tx, - block: tx.block, - block_number: tx.block_number, + transaction: transaction, + block: transaction.block, + block_number: transaction.block_number, token_contract_address: erc_721_token.contract_address, token_ids: [x], token_type: "ERC-721" @@ -603,9 +620,9 @@ defmodule BlockScoutWeb.API.V2.TransactionControllerTest do erc_20_tt = for _ <- 0..50 do insert(:token_transfer, - transaction: tx, - block: tx.block, - block_number: tx.block_number, + transaction: transaction, + block: transaction.block, + block_number: transaction.block_number, token_contract_address: erc_20_token.contract_address, token_type: "ERC-20" ) @@ -614,13 +631,13 @@ defmodule BlockScoutWeb.API.V2.TransactionControllerTest do # -- ERC-20 -- filter = %{"type" => "ERC-20"} - request = get(conn, "/api/v2/transactions/#{to_string(tx.hash)}/token-transfers", filter) + request = get(conn, "/api/v2/transactions/#{to_string(transaction.hash)}/token-transfers", filter) assert response = json_response(request, 200) request_2nd_page = get( conn, - "/api/v2/transactions/#{to_string(tx.hash)}/token-transfers", + "/api/v2/transactions/#{to_string(transaction.hash)}/token-transfers", Map.merge(response["next_page_params"], filter) ) @@ -631,13 +648,13 @@ defmodule BlockScoutWeb.API.V2.TransactionControllerTest do # -- ERC-721 -- filter = %{"type" => "ERC-721"} - request = get(conn, "/api/v2/transactions/#{to_string(tx.hash)}/token-transfers", filter) + request = get(conn, "/api/v2/transactions/#{to_string(transaction.hash)}/token-transfers", filter) assert response = json_response(request, 200) request_2nd_page = get( conn, - "/api/v2/transactions/#{to_string(tx.hash)}/token-transfers", + "/api/v2/transactions/#{to_string(transaction.hash)}/token-transfers", Map.merge(response["next_page_params"], filter) ) @@ -648,13 +665,13 @@ defmodule BlockScoutWeb.API.V2.TransactionControllerTest do # -- ERC-1155 -- filter = %{"type" => "ERC-1155"} - request = get(conn, "/api/v2/transactions/#{to_string(tx.hash)}/token-transfers", filter) + request = get(conn, "/api/v2/transactions/#{to_string(transaction.hash)}/token-transfers", filter) assert response = json_response(request, 200) request_2nd_page = get( conn, - "/api/v2/transactions/#{to_string(tx.hash)}/token-transfers", + "/api/v2/transactions/#{to_string(transaction.hash)}/token-transfers", Map.merge(response["next_page_params"], filter) ) @@ -665,13 +682,13 @@ defmodule BlockScoutWeb.API.V2.TransactionControllerTest do # two filters simultaneously filter = %{"type" => "ERC-1155,ERC-20"} - request = get(conn, "/api/v2/transactions/#{to_string(tx.hash)}/token-transfers", filter) + request = get(conn, "/api/v2/transactions/#{to_string(transaction.hash)}/token-transfers", filter) assert response = json_response(request, 200) request_2nd_page = get( conn, - "/api/v2/transactions/#{to_string(tx.hash)}/token-transfers", + "/api/v2/transactions/#{to_string(transaction.hash)}/token-transfers", Map.merge(response["next_page_params"], filter) ) @@ -691,7 +708,7 @@ defmodule BlockScoutWeb.API.V2.TransactionControllerTest do request_3rd_page = get( conn, - "/api/v2/transactions/#{to_string(tx.hash)}/token-transfers", + "/api/v2/transactions/#{to_string(transaction.hash)}/token-transfers", Map.merge(response_2nd_page["next_page_params"], filter) ) @@ -709,7 +726,7 @@ defmodule BlockScoutWeb.API.V2.TransactionControllerTest do insert(:token_instance, token_id: id, token_contract_address_hash: token.contract_address_hash) - tx = + transaction = :transaction |> insert() |> with_block() @@ -717,9 +734,9 @@ defmodule BlockScoutWeb.API.V2.TransactionControllerTest do tt = for _ <- 0..50 do insert(:token_transfer, - transaction: tx, - block: tx.block, - block_number: tx.block_number, + transaction: transaction, + block: transaction.block, + block_number: transaction.block_number, token_contract_address: token.contract_address, token_ids: Enum.map(0..50, fn _x -> id end), token_type: "ERC-1155", @@ -732,11 +749,11 @@ defmodule BlockScoutWeb.API.V2.TransactionControllerTest do %TokenTransfer{i | token_ids: [id], amount: Decimal.new(1275)} end - request = get(conn, "/api/v2/transactions/#{to_string(tx.hash)}/token-transfers") + request = get(conn, "/api/v2/transactions/#{to_string(transaction.hash)}/token-transfers") assert response = json_response(request, 200) request_2nd_page = - get(conn, "/api/v2/transactions/#{to_string(tx.hash)}/token-transfers", response["next_page_params"]) + get(conn, "/api/v2/transactions/#{to_string(transaction.hash)}/token-transfers", response["next_page_params"]) assert response_2nd_page = json_response(request_2nd_page, 200) @@ -746,7 +763,7 @@ defmodule BlockScoutWeb.API.V2.TransactionControllerTest do test "check that pagination works for 721 tokens", %{conn: conn} do token = insert(:token, type: "ERC-721") - tx = + transaction = :transaction |> insert() |> with_block() @@ -754,20 +771,20 @@ defmodule BlockScoutWeb.API.V2.TransactionControllerTest do token_transfers = for i <- 0..50 do insert(:token_transfer, - transaction: tx, - block: tx.block, - block_number: tx.block_number, + transaction: transaction, + block: transaction.block, + block_number: transaction.block_number, token_contract_address: token.contract_address, token_ids: [i], token_type: "ERC-721" ) end - request = get(conn, "/api/v2/transactions/#{to_string(tx.hash)}/token-transfers") + request = get(conn, "/api/v2/transactions/#{to_string(transaction.hash)}/token-transfers") assert response = json_response(request, 200) request_2nd_page = - get(conn, "/api/v2/transactions/#{to_string(tx.hash)}/token-transfers", response["next_page_params"]) + get(conn, "/api/v2/transactions/#{to_string(transaction.hash)}/token-transfers", response["next_page_params"]) assert response_2nd_page = json_response(request_2nd_page, 200) @@ -777,16 +794,16 @@ defmodule BlockScoutWeb.API.V2.TransactionControllerTest do test "check that pagination works fine with 1155 batches #1 (large batch)", %{conn: conn} do token = insert(:token, type: "ERC-1155") - tx = + transaction = :transaction |> insert() |> with_block() tt = insert(:token_transfer, - transaction: tx, - block: tx.block, - block_number: tx.block_number, + transaction: transaction, + block: transaction.block, + block_number: transaction.block_number, token_contract_address: token.contract_address, token_ids: Enum.map(0..50, fn x -> x end), token_type: "ERC-1155", @@ -798,11 +815,11 @@ defmodule BlockScoutWeb.API.V2.TransactionControllerTest do %TokenTransfer{tt | token_ids: [i], amount: i} end - request = get(conn, "/api/v2/transactions/#{to_string(tx.hash)}/token-transfers") + request = get(conn, "/api/v2/transactions/#{to_string(transaction.hash)}/token-transfers") assert response = json_response(request, 200) request_2nd_page = - get(conn, "/api/v2/transactions/#{to_string(tx.hash)}/token-transfers", response["next_page_params"]) + get(conn, "/api/v2/transactions/#{to_string(transaction.hash)}/token-transfers", response["next_page_params"]) assert response_2nd_page = json_response(request_2nd_page, 200) @@ -813,16 +830,16 @@ defmodule BlockScoutWeb.API.V2.TransactionControllerTest do %{conn: conn} do token = insert(:token, type: "ERC-1155") - tx = + transaction = :transaction |> insert() |> with_block() tt_1 = insert(:token_transfer, - transaction: tx, - block: tx.block, - block_number: tx.block_number, + transaction: transaction, + block: transaction.block, + block_number: transaction.block_number, token_contract_address: token.contract_address, token_ids: Enum.map(0..24, fn x -> x end), token_type: "ERC-1155", @@ -836,9 +853,9 @@ defmodule BlockScoutWeb.API.V2.TransactionControllerTest do tt_2 = insert(:token_transfer, - transaction: tx, - block: tx.block, - block_number: tx.block_number, + transaction: transaction, + block: transaction.block, + block_number: transaction.block_number, token_contract_address: token.contract_address, token_ids: Enum.map(25..49, fn x -> x end), token_type: "ERC-1155", @@ -852,20 +869,20 @@ defmodule BlockScoutWeb.API.V2.TransactionControllerTest do tt_3 = insert(:token_transfer, - transaction: tx, - block: tx.block, - block_number: tx.block_number, + transaction: transaction, + block: transaction.block, + block_number: transaction.block_number, token_contract_address: token.contract_address, token_ids: [50], token_type: "ERC-1155", amounts: [50] ) - request = get(conn, "/api/v2/transactions/#{to_string(tx.hash)}/token-transfers") + request = get(conn, "/api/v2/transactions/#{to_string(transaction.hash)}/token-transfers") assert response = json_response(request, 200) request_2nd_page = - get(conn, "/api/v2/transactions/#{to_string(tx.hash)}/token-transfers", response["next_page_params"]) + get(conn, "/api/v2/transactions/#{to_string(transaction.hash)}/token-transfers", response["next_page_params"]) assert response_2nd_page = json_response(request_2nd_page, 200) @@ -875,13 +892,13 @@ defmodule BlockScoutWeb.API.V2.TransactionControllerTest do test "check that pagination works fine with 1155 batches #3", %{conn: conn} do token = insert(:token, type: "ERC-1155") - tx = insert(:transaction, input: "0xabcd010203040506") |> with_block() + transaction = insert(:transaction, input: "0xabcd010203040506") |> with_block() tt_1 = insert(:token_transfer, - transaction: tx, - block: tx.block, - block_number: tx.block_number, + transaction: transaction, + block: transaction.block, + block_number: transaction.block_number, token_contract_address: token.contract_address, token_ids: Enum.map(0..24, fn x -> x end), token_type: "ERC-1155", @@ -895,9 +912,9 @@ defmodule BlockScoutWeb.API.V2.TransactionControllerTest do tt_2 = insert(:token_transfer, - transaction: tx, - block: tx.block, - block_number: tx.block_number, + transaction: transaction, + block: transaction.block, + block_number: transaction.block_number, token_contract_address: token.contract_address, token_ids: Enum.map(25..50, fn x -> x end), token_type: "ERC-1155", @@ -909,11 +926,11 @@ defmodule BlockScoutWeb.API.V2.TransactionControllerTest do %TokenTransfer{tt_2 | token_ids: [i], amount: i} end - request = get(conn, "/api/v2/transactions/#{to_string(tx.hash)}/token-transfers") + request = get(conn, "/api/v2/transactions/#{to_string(transaction.hash)}/token-transfers") assert response = json_response(request, 200) request_2nd_page = - get(conn, "/api/v2/transactions/#{to_string(tx.hash)}/token-transfers", response["next_page_params"]) + get(conn, "/api/v2/transactions/#{to_string(transaction.hash)}/token-transfers", response["next_page_params"]) assert response_2nd_page = json_response(request_2nd_page, 200) @@ -921,21 +938,21 @@ defmodule BlockScoutWeb.API.V2.TransactionControllerTest do end end - describe "/transactions/{tx_hash}/state-changes" do - test "return 404 on non existing tx", %{conn: conn} do - tx = build(:transaction) - request = get(conn, "/api/v2/transactions/#{to_string(tx.hash)}/state-changes") + describe "/transactions/{transaction_hash}/state-changes" do + test "return 404 on non existing transaction", %{conn: conn} do + transaction = build(:transaction) + request = get(conn, "/api/v2/transactions/#{to_string(transaction.hash)}/state-changes") assert %{"message" => "Not found"} = json_response(request, 404) end - test "return 422 on invalid tx hash", %{conn: conn} do + test "return 422 on invalid transaction hash", %{conn: conn} do request = get(conn, "/api/v2/transactions/0x/state-changes") assert %{"message" => "Invalid parameter(s)"} = json_response(request, 422) end - test "return existing tx", %{conn: conn} do + test "return existing transaction", %{conn: conn} do block_before = insert(:block) transaction = @@ -1039,7 +1056,11 @@ defmodule BlockScoutWeb.API.V2.TransactionControllerTest do internal_transaction_from = insert(:address) internal_transaction_to = insert(:address) + internal_transaction_from_delegatecall = insert(:address) + internal_transaction_to_delegatecall = insert(:address) + insert(:internal_transaction, + call_type: :call, transaction: transaction, index: 0, block_number: transaction.block_number, @@ -1053,7 +1074,9 @@ defmodule BlockScoutWeb.API.V2.TransactionControllerTest do to_address: internal_transaction_to ) + # must be ignored, hence we expect only 5 state changes insert(:internal_transaction, + call_type: :delegatecall, transaction: transaction, index: 1, block_number: transaction.block_number, @@ -1061,6 +1084,21 @@ defmodule BlockScoutWeb.API.V2.TransactionControllerTest do block_hash: transaction.block_hash, block_index: 1, value: %Wei{value: Decimal.new(7)}, + from_address_hash: internal_transaction_from_delegatecall.hash, + from_address: internal_transaction_from_delegatecall, + to_address_hash: internal_transaction_to_delegatecall.hash, + to_address: internal_transaction_to_delegatecall + ) + + insert(:internal_transaction, + call_type: :call, + transaction: transaction, + index: 2, + block_number: transaction.block_number, + transaction_index: transaction.index, + block_hash: transaction.block_hash, + block_index: 2, + value: %Wei{value: Decimal.new(7)}, from_address_hash: internal_transaction_from.hash, from_address: internal_transaction_from, to_address_hash: internal_transaction_to.hash, @@ -1070,31 +1108,36 @@ defmodule BlockScoutWeb.API.V2.TransactionControllerTest do insert(:address_coin_balance, address: transaction.from_address, address_hash: transaction.from_address_hash, - block_number: block_before.number + block_number: block_before.number, + value: %Wei{value: Decimal.new(1000)} ) insert(:address_coin_balance, address: transaction.to_address, address_hash: transaction.to_address_hash, - block_number: block_before.number + block_number: block_before.number, + value: %Wei{value: Decimal.new(1000)} ) insert(:address_coin_balance, address: transaction.block.miner, address_hash: transaction.block.miner_hash, - block_number: block_before.number + block_number: block_before.number, + value: %Wei{value: Decimal.new(1000)} ) insert(:address_coin_balance, address: internal_transaction_from, address_hash: internal_transaction_from.hash, - block_number: block_before.number + block_number: block_before.number, + value: %Wei{value: Decimal.new(1000)} ) insert(:address_coin_balance, address: internal_transaction_to, address_hash: internal_transaction_to.hash, - block_number: block_before.number + block_number: block_before.number, + value: %Wei{value: Decimal.new(1000)} ) request = get(conn, "/api/v2/transactions/#{to_string(transaction.hash)}/state-changes") @@ -1109,7 +1152,7 @@ defmodule BlockScoutWeb.API.V2.TransactionControllerTest do test "when gas is paid with token and token is present in db", %{conn: conn} do token = insert(:token) - tx = + transaction = :transaction |> insert(gas_token_contract_address: token.contract_address) |> with_block() @@ -1136,7 +1179,7 @@ defmodule BlockScoutWeb.API.V2.TransactionControllerTest do ] } = json_response(request, 200) - request = get(conn, "/api/v2/transactions/#{to_string(tx.hash)}") + request = get(conn, "/api/v2/transactions/#{to_string(transaction.hash)}") assert %{ "celo" => %{ @@ -1149,7 +1192,7 @@ defmodule BlockScoutWeb.API.V2.TransactionControllerTest do } } = json_response(request, 200) - request = get(conn, "/api/v2/addresses/#{to_string(tx.from_address_hash)}/transactions") + request = get(conn, "/api/v2/addresses/#{to_string(transaction.from_address_hash)}/transactions") assert %{ "items" => [ @@ -1185,7 +1228,7 @@ defmodule BlockScoutWeb.API.V2.TransactionControllerTest do test "when gas is paid with token and token is not present in db", %{conn: conn} do unknown_token_address = insert(:address) - tx = + transaction = :transaction |> insert(gas_token_contract_address: unknown_token_address) |> with_block() @@ -1206,7 +1249,7 @@ defmodule BlockScoutWeb.API.V2.TransactionControllerTest do ] } = json_response(request, 200) - request = get(conn, "/api/v2/transactions/#{to_string(tx.hash)}") + request = get(conn, "/api/v2/transactions/#{to_string(transaction.hash)}") assert %{ "celo" => %{ @@ -1216,7 +1259,7 @@ defmodule BlockScoutWeb.API.V2.TransactionControllerTest do } } = json_response(request, 200) - request = get(conn, "/api/v2/addresses/#{to_string(tx.from_address_hash)}/transactions") + request = get(conn, "/api/v2/addresses/#{to_string(transaction.from_address_hash)}/transactions") assert %{ "items" => [ @@ -1244,7 +1287,7 @@ defmodule BlockScoutWeb.API.V2.TransactionControllerTest do end test "when gas is paid in native coin", %{conn: conn} do - tx = :transaction |> insert() |> with_block() + transaction = :transaction |> insert() |> with_block() request = get(conn, "/api/v2/transactions") @@ -1256,13 +1299,13 @@ defmodule BlockScoutWeb.API.V2.TransactionControllerTest do ] } = json_response(request, 200) - request = get(conn, "/api/v2/transactions/#{to_string(tx.hash)}") + request = get(conn, "/api/v2/transactions/#{to_string(transaction.hash)}") assert %{ "celo" => %{"gas_token" => nil} } = json_response(request, 200) - request = get(conn, "/api/v2/addresses/#{to_string(tx.from_address_hash)}/transactions") + request = get(conn, "/api/v2/addresses/#{to_string(transaction.from_address_hash)}/transactions") assert %{ "items" => [ @@ -1283,7 +1326,7 @@ defmodule BlockScoutWeb.API.V2.TransactionControllerTest do end end - describe "/transactions/{tx_hash}/raw-trace" do + describe "/transactions/{transaction_hash}/raw-trace" do test "returns raw trace from node", %{conn: conn} do transaction = :transaction @@ -1337,14 +1380,14 @@ defmodule BlockScoutWeb.API.V2.TransactionControllerTest do describe "stability fees" do test "check stability fees", %{conn: conn} do - tx = insert(:transaction) |> with_block() + transaction = insert(:transaction) |> with_block() _log = insert(:log, - transaction: tx, + transaction: transaction, index: 1, - block: tx.block, - block_number: tx.block_number, + block: transaction.block, + block_number: transaction.block_number, first_topic: topic(@first_topic_hex_string_1), data: "0x000000000000000000000000dc2b93f3291030f3f7a6d9363ac37757f7ad5c4300000000000000000000000000000000000000000000000000002824369a100000000000000000000000000046b555cb3962bf9533c437cbd04a2f702dfdb999000000000000000000000000000000000000000000000000000014121b4d0800000000000000000000000000faf7a981360c2fab3a5ab7b3d6d8d0cf97a91eb9000000000000000000000000000000000000000000000000000014121b4d0800" @@ -1368,7 +1411,7 @@ defmodule BlockScoutWeb.API.V2.TransactionControllerTest do ] } = json_response(request, 200) - request = get(conn, "/api/v2/transactions/#{to_string(tx.hash)}") + request = get(conn, "/api/v2/transactions/#{to_string(transaction.hash)}") assert %{ "stability_fee" => %{ @@ -1381,7 +1424,7 @@ defmodule BlockScoutWeb.API.V2.TransactionControllerTest do } } = json_response(request, 200) - request = get(conn, "/api/v2/addresses/#{to_string(tx.from_address_hash)}/transactions") + request = get(conn, "/api/v2/addresses/#{to_string(transaction.from_address_hash)}/transactions") assert %{ "items" => [ @@ -1400,14 +1443,14 @@ defmodule BlockScoutWeb.API.V2.TransactionControllerTest do end test "check stability if token absent in DB", %{conn: conn} do - tx = insert(:transaction) |> with_block() + transaction = insert(:transaction) |> with_block() _log = insert(:log, - transaction: tx, + transaction: transaction, index: 1, - block: tx.block, - block_number: tx.block_number, + block: transaction.block, + block_number: transaction.block_number, first_topic: topic(@first_topic_hex_string_1), data: "0x000000000000000000000000dc2b93f3291030f3f7a6d9363ac37757f7ad5c4300000000000000000000000000000000000000000000000000002824369a100000000000000000000000000046b555cb3962bf9533c437cbd04a2f702dfdb999000000000000000000000000000000000000000000000000000014121b4d0800000000000000000000000000faf7a981360c2fab3a5ab7b3d6d8d0cf97a91eb9000000000000000000000000000000000000000000000000000014121b4d0800" @@ -1430,7 +1473,7 @@ defmodule BlockScoutWeb.API.V2.TransactionControllerTest do ] } = json_response(request, 200) - request = get(conn, "/api/v2/transactions/#{to_string(tx.hash)}") + request = get(conn, "/api/v2/transactions/#{to_string(transaction.hash)}") assert %{ "stability_fee" => %{ @@ -1443,7 +1486,7 @@ defmodule BlockScoutWeb.API.V2.TransactionControllerTest do } } = json_response(request, 200) - request = get(conn, "/api/v2/addresses/#{to_string(tx.from_address_hash)}/transactions") + request = get(conn, "/api/v2/addresses/#{to_string(transaction.from_address_hash)}/transactions") assert %{ "items" => [ @@ -1465,43 +1508,43 @@ defmodule BlockScoutWeb.API.V2.TransactionControllerTest do defp compare_item(%Transaction{} = transaction, json) do assert to_string(transaction.hash) == json["hash"] - assert transaction.block_number == json["block"] + assert transaction.block_number == json["block_number"] 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(%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"] + defp compare_item(%InternalTransaction{} = internal_transaction, json) do + assert internal_transaction.block_number == json["block_number"] + assert to_string(internal_transaction.gas) == json["gas_limit"] + assert internal_transaction.index == json["index"] + assert to_string(internal_transaction.transaction_hash) == json["transaction_hash"] + assert Address.checksum(internal_transaction.from_address_hash) == json["from"]["hash"] + assert Address.checksum(internal_transaction.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 to_string(log.transaction_hash) == json["transaction_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 to_string(token_transfer.transaction_hash) == json["transaction_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 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"] + assert transaction.block_number == json["block_number"] 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"] @@ -1529,26 +1572,26 @@ defmodule BlockScoutWeb.API.V2.TransactionControllerTest do ) end - defp check_paginated_response(first_page_resp, second_page_resp, txs) do + defp check_paginated_response(first_page_resp, second_page_resp, transactions) do assert Enum.count(first_page_resp["items"]) == 50 assert first_page_resp["next_page_params"] != nil - compare_item(Enum.at(txs, 50), Enum.at(first_page_resp["items"], 0)) - compare_item(Enum.at(txs, 1), Enum.at(first_page_resp["items"], 49)) + compare_item(Enum.at(transactions, 50), Enum.at(first_page_resp["items"], 0)) + compare_item(Enum.at(transactions, 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(txs, 0), Enum.at(second_page_resp["items"], 0)) + compare_item(Enum.at(transactions, 0), Enum.at(second_page_resp["items"], 0)) end - defp check_paginated_response(first_page_resp, second_page_resp, txs, wl_names) do + defp check_paginated_response(first_page_resp, second_page_resp, transactions, wl_names) do assert Enum.count(first_page_resp["items"]) == 50 assert first_page_resp["next_page_params"] != nil - compare_item(Enum.at(txs, 50), Enum.at(first_page_resp["items"], 0), wl_names) - compare_item(Enum.at(txs, 1), Enum.at(first_page_resp["items"], 49), wl_names) + compare_item(Enum.at(transactions, 50), Enum.at(first_page_resp["items"], 0), wl_names) + compare_item(Enum.at(transactions, 1), Enum.at(first_page_resp["items"], 49), wl_names) assert Enum.count(second_page_resp["items"]) == 1 assert second_page_resp["next_page_params"] == nil - compare_item(Enum.at(txs, 0), Enum.at(second_page_resp["items"], 0), wl_names) + compare_item(Enum.at(transactions, 0), Enum.at(second_page_resp["items"], 0), wl_names) end # with the current implementation no transfers should come with list in totals diff --git a/apps/block_scout_web/test/block_scout_web/controllers/metrics_contoller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/metrics_contoller_test.exs new file mode 100644 index 0000000..e9ea7e7 --- /dev/null +++ b/apps/block_scout_web/test/block_scout_web/controllers/metrics_contoller_test.exs @@ -0,0 +1,11 @@ +defmodule BlockScoutWeb.MetricsControllerTest do + use BlockScoutWeb.ConnCase + + describe "/metrics page" do + test "renders /metrics page", %{conn: conn} do + conn = get(conn, "/metrics") + + assert text_response(conn, 200) + end + end +end diff --git a/apps/block_scout_web/test/block_scout_web/controllers/transaction_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/transaction_controller_test.exs index 65d8f89..99d09fc 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/transaction_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/transaction_controller_test.exs @@ -127,7 +127,7 @@ defmodule BlockScoutWeb.TransactionControllerTest do assert html_response(conn, 422) end - test "no redirect from tx page", %{conn: conn} do + test "no redirect from transaction page", %{conn: conn} do transaction = insert(:transaction) conn = get(conn, transaction_path(BlockScoutWeb.Endpoint, :show, transaction)) 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 7e5a91e..0a07b1d 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 @@ -67,7 +67,7 @@ defmodule BlockScoutWeb.VerifiedContractsControllerTest do coin_balance: nil, items_count: "50", smart_contract_id: id, - tx_count: nil + transaction_count: nil ) assert Map.get(json_response(conn, 200), "next_page_path") == expected_path diff --git a/apps/block_scout_web/test/block_scout_web/features/viewing_transactions_test.exs b/apps/block_scout_web/test/block_scout_web/features/viewing_transactions_test.exs index 2d6eb71..9931bd4 100644 --- a/apps/block_scout_web/test/block_scout_web/features/viewing_transactions_test.exs +++ b/apps/block_scout_web/test/block_scout_web/features/viewing_transactions_test.exs @@ -28,7 +28,7 @@ defmodule BlockScoutWeb.ViewingTransactionsTest do taft = insert(:address) # From Lincoln to Taft. - txn_from_lincoln = + transaction_from_lincoln = :transaction |> insert(from_address: lincoln, to_address: taft) |> with_block(block) @@ -66,7 +66,7 @@ defmodule BlockScoutWeb.ViewingTransactionsTest do lincoln: lincoln, taft: taft, transaction: transaction, - txn_from_lincoln: txn_from_lincoln + transaction_from_lincoln: transaction_from_lincoln }} end diff --git a/apps/block_scout_web/test/block_scout_web/views/transaction_view_test.exs b/apps/block_scout_web/test/block_scout_web/views/transaction_view_test.exs index a28f2b5..ea53a2a 100644 --- a/apps/block_scout_web/test/block_scout_web/views/transaction_view_test.exs +++ b/apps/block_scout_web/test/block_scout_web/views/transaction_view_test.exs @@ -366,6 +366,9 @@ defmodule BlockScoutWeb.TransactionViewTest do end ) + init_config = Application.get_env(:ethereum_jsonrpc, EthereumJSONRPC.Geth) + Application.put_env(:ethereum_jsonrpc, EthereumJSONRPC.Geth, tracer: "call_tracer", debug_trace_timeout: "5s") + revert_reason = TransactionView.transaction_revert_reason(transaction, nil) assert revert_reason == @@ -373,6 +376,8 @@ defmodule BlockScoutWeb.TransactionViewTest do [ {"reason", "string", "UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT"} ]} + + Application.put_env(:ethereum_jsonrpc, EthereumJSONRPC.Geth, init_config) end end end diff --git a/apps/block_scout_web/test/test_helper.exs b/apps/block_scout_web/test/test_helper.exs index 1a77c4a..0758a27 100644 --- a/apps/block_scout_web/test/test_helper.exs +++ b/apps/block_scout_web/test/test_helper.exs @@ -42,5 +42,4 @@ Absinthe.Test.prime(BlockScoutWeb.GraphQL.Schema) Mox.defmock(EthereumJSONRPC.Mox, for: EthereumJSONRPC.Transport) -Mox.defmock(BlockScoutWeb.TestCaptchaHelper, for: BlockScoutWeb.CaptchaHelper) Mox.defmock(Explorer.Mox.HTTPoison, for: HTTPoison.Base) diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc.ex index 9540362..5c4d4a3 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc.ex @@ -38,6 +38,7 @@ defmodule EthereumJSONRPC do RequestCoordinator, Subscription, Transport, + Utility.CommonHelper, Utility.EndpointAvailabilityObserver, Utility.RangesHelper, Variant @@ -186,43 +187,77 @@ 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, chunk_size \\ nil) + def fetch_balances(params_list, json_rpc_named_arguments, latest_block_number \\ 0, 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) + latest_block_number_params = + case latest_block_number do + 0 -> fetch_block_number_by_tag("latest", json_rpc_named_arguments) + number -> {:ok, number} + end + + params_in_range = + params_list + |> Enum.filter(fn + %{block_quantity: block_quantity} -> + block_quantity |> quantity_to_integer() |> RangesHelper.traceable_block_number?() + end) + + trace_url_used? = !is_nil(json_rpc_named_arguments[:transport_options][:method_to_url][:eth_getBalance]) + archive_disabled? = Application.get_env(:ethereum_jsonrpc, :disable_archive_balances?) + + {latest_balances_params, archive_balance_params} = + with true <- not trace_url_used? or archive_disabled?, + {:ok, max_block_number} <- latest_block_number_params do window = Application.get_env(:ethereum_jsonrpc, :archive_balances_window) - params_list - |> Enum.filter(fn + Enum.split_with(params_in_range, fn %{block_quantity: "latest"} -> true %{block_quantity: block_quantity} -> quantity_to_integer(block_quantity) > max_block_number - window _ -> false end) else - params_list + _ -> {params_in_range, []} end - 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)} + latest_id_to_params = id_to_params(latest_balances_params) + archive_id_to_params = id_to_params(archive_balance_params) + + with {:ok, latest_responses} <- do_balances_request(latest_id_to_params, chunk_size, json_rpc_named_arguments), + {:ok, archive_responses} <- + maybe_request_archive_balances( + archive_id_to_params, + trace_url_used?, + archive_disabled?, + chunk_size, + json_rpc_named_arguments + ) do + latest_fetched_balances = FetchedBalances.from_responses(latest_responses, latest_id_to_params) + archive_fetched_balances = FetchedBalances.from_responses(archive_responses, archive_id_to_params) + {:ok, FetchedBalances.merge(latest_fetched_balances, archive_fetched_balances)} end end @doc """ - Fetches code for each given `address` at the `block_number`. + Fetches contract code for multiple addresses at specified block numbers. + + This function takes a list of parameters, each containing an address and a + block number, and retrieves the contract code for each address at the + specified block. + + ## Parameters + - `params_list`: A list of maps, each containing: + - `:block_quantity`: The block number (as a quantity string) at which to fetch the code. + - `:address`: The address of the contract to fetch the code for. + - `json_rpc_named_arguments`: A keyword list of JSON-RPC configuration options. + + ## Returns + - `{:ok, fetched_codes}`, where `fetched_codes` is a `FetchedCodes.t()` struct containing: + - `params_list`: A list of successfully fetched code parameters, each containing: + - `address`: The contract address. + - `block_number`: The block number at which the code was fetched. + - `code`: The fetched contract code in hexadecimal format. + - `errors`: A list of errors encountered during the fetch operation. + - `{:error, reason}`: An error occurred during the fetch operation. """ @spec fetch_codes( [%{required(:block_quantity) => quantity, required(:address) => address()}], @@ -270,7 +305,7 @@ defmodule EthereumJSONRPC do Fetches blocks by block number range. """ @spec fetch_blocks_by_range(Range.t(), json_rpc_named_arguments) :: {:ok, Blocks.t()} | {:error, reason :: term} - def fetch_blocks_by_range(_first.._last = range, json_rpc_named_arguments) do + def fetch_blocks_by_range(_first.._last//_ = range, json_rpc_named_arguments) do range |> Enum.map(fn number -> %{number: number} end) |> fetch_blocks_by_params(&Block.ByNumber.request/1, json_rpc_named_arguments) @@ -407,7 +442,21 @@ defmodule EthereumJSONRPC do end @doc """ - Assigns an id to each set of params in `params_list` for batch request-response correlation + Assigns a unique integer ID to each set of parameters in the given list. + + This function is used to prepare parameters for batch request-response + correlation in JSON-RPC calls. + + ## Parameters + - `params_list`: A list of parameter sets, where each set can be of any type. + + ## Returns + A map where the keys are integer IDs (starting from 0) and the values are + the corresponding parameter sets from the input list. + + ## Example + iex> id_to_params([%{block: 1}, %{block: 2}]) + %{0 => %{block: 1}, 1 => %{block: 2}} """ @spec id_to_params([params]) :: %{id => params} when id: non_neg_integer(), params: any() def id_to_params(params_list) do @@ -417,8 +466,27 @@ defmodule EthereumJSONRPC do end @doc """ - Assigns not matched ids between requests and responses to responses with incorrect ids + Sanitizes responses by assigning unmatched IDs to responses with missing IDs. + + This function processes a list of responses and a map of expected IDs to + parameters. It handles cases where responses have missing (nil) IDs by + assigning them unmatched IDs from the id_to_params map. + + ## Parameters + - `responses`: A list of response maps from a batch JSON-RPC call. + - `id_to_params`: A map of request IDs to their corresponding parameters. + + ## Returns + A list of sanitized response maps where each response has a valid ID. + + ## Example + iex> responses = [%{id: 1, result: "ok"}, %{id: nil, result: "error"}] + iex> id_to_params = %{1 => %{}, 2 => %{}, 3 => %{}} + iex> EthereumJSONRPC.sanitize_responses(responses, id_to_params) + [%{id: 1, result: "ok"}, %{id: 2, result: "error"}] """ + @spec sanitize_responses(Transport.batch_response(), %{id => params}) :: Transport.batch_response() + when id: EthereumJSONRPC.request_id(), params: any() def sanitize_responses(responses, id_to_params) do responses |> Enum.reduce( @@ -471,6 +539,31 @@ defmodule EthereumJSONRPC do end end + defp do_balances_request(id_to_params, _chunk_size, _args) when id_to_params == %{}, do: {:ok, []} + + defp do_balances_request(id_to_params, chunk_size, json_rpc_named_arguments) do + id_to_params + |> FetchedBalances.requests() + |> chunk_requests(chunk_size) + |> json_rpc(json_rpc_named_arguments) + end + + defp archive_json_rpc_named_arguments(json_rpc_named_arguments) do + CommonHelper.put_in_keyword_nested( + json_rpc_named_arguments, + [:transport_options, :method_to_url, :eth_getBalance], + System.get_env("ETHEREUM_JSONRPC_TRACE_URL") + ) + end + + defp maybe_request_archive_balances(id_to_params, trace_url_used?, disabled?, chunk_size, json_rpc_named_arguments) do + if not trace_url_used? and not disabled? do + do_balances_request(id_to_params, chunk_size, archive_json_rpc_named_arguments(json_rpc_named_arguments)) + else + {:ok, []} + end + end + defp maybe_replace_url(url, _replace_url, EthereumJSONRPC.HTTP), do: url defp maybe_replace_url(url, replace_url, _), do: EndpointAvailabilityObserver.maybe_replace_url(url, replace_url, :ws) @@ -496,6 +589,24 @@ defmodule EthereumJSONRPC do def quantity_to_integer(_), do: nil + @doc """ + Sanitizes ID in JSON RPC request following JSON RPC [spec](https://www.jsonrpc.org/specification#request_object:~:text=An%20identifier%20established%20by%20the%20Client%20that%20MUST%20contain%20a%20String%2C%20Number%2C%20or%20NULL%20value%20if%20included.%20If%20it%20is%20not%20included%20it%20is%20assumed%20to%20be%20a%20notification.%20The%20value%20SHOULD%20normally%20not%20be%20Null%20%5B1%5D%20and%20Numbers%20SHOULD%20NOT%20contain%20fractional%20parts%20%5B2%5D). + """ + @spec sanitize_id(quantity) :: non_neg_integer() | String.t() | nil + + def sanitize_id(integer) when is_integer(integer), do: integer + + def sanitize_id(string) when is_binary(string) do + # match ID string and ID string without non-ASCII characters + if string == for(<>, c < 128, into: "", do: <>) do + string + else + nil + end + end + + def sanitize_id(_), do: nil + @doc """ Converts `t:non_neg_integer/0` to `t:quantity/0` """ diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/fetched_balances.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/fetched_balances.ex index da769b5..d87bdfb 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/fetched_balances.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/fetched_balances.ex @@ -33,6 +33,17 @@ defmodule EthereumJSONRPC.FetchedBalances do ) end + @doc """ + Merges two `t/0` to one `t/0`. + """ + @spec merge(t(), t()) :: t() + def merge(%{params_list: params_list_1, errors: errors_1}, %{params_list: params_list_2, errors: errors_2}) do + %__MODULE__{ + params_list: params_list_1 ++ params_list_2, + errors: errors_1 ++ errors_2 + } + end + @doc """ `eth_getBalance` requests for `id_to_params`. """ diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/fetched_code.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/fetched_code.ex index 58d5250..2b7211f 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/fetched_code.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/fetched_code.ex @@ -9,9 +9,37 @@ defmodule EthereumJSONRPC.FetchedCode do @type error :: %{code: integer(), message: String.t(), data: %{block_quantity: String.t(), address: String.t()}} @doc """ - Converts `response` to code params or annotated error. - """ + Converts a JSON-RPC response of `eth_getCode` to code params or an annotated error. + + This function handles two types of responses: + 1. Successful responses with fetched code. + 2. Error responses. + + ## Parameters + - `response`: A map containing either a successful result or an error. + - `id_to_params`: A map of request IDs to their corresponding parameters. + ## Returns + - `{:ok, params()}` for successful responses, where `params()` is a map + containing the address, block number, and fetched code. + - `{:error, error()}` for error responses, where `error()` is a map + containing the error code, message, and additional data. + + ## Examples + iex> # Successful response: + iex> response = %{id: 1, result: "0x123"} + iex> id_to_params = %{1 => %{block_quantity: "0x1", address: "0xabc"}} + iex> FetchedCode.from_response(response, id_to_params) + {:ok, %{address: "0xabc", block_number: 1, code: "0x123"}} + iex> # Error response: + iex> response = %{id: 1, error: %{code: 100, message: "Error"}} + iex> id_to_params = %{1 => %{block_quantity: "0x1", address: "0xabc"}} + iex> FetchedCode.from_response(response, id_to_params) + {:error, %{code: 100, message: "Error", data: %{block_quantity: "0x1", address: "0xabc"}}} + """ + @spec from_response(%{id: EthereumJSONRPC.request_id(), result: String.t()}, %{ + non_neg_integer() => %{block_quantity: String.t(), address: String.t()} + }) :: {:ok, params()} def from_response(%{id: id, result: fetched_code}, id_to_params) when is_map(id_to_params) do %{block_quantity: block_quantity, address: address} = Map.fetch!(id_to_params, id) @@ -23,9 +51,9 @@ defmodule EthereumJSONRPC.FetchedCode do }} end - @spec from_response(%{id: id, result: String.t()}, %{id => %{block_quantity: block_quantity, address: address}}) :: - {:ok, params()} - when id: non_neg_integer(), block_quantity: String.t(), address: String.t() + @spec from_response(%{id: EthereumJSONRPC.request_id(), error: %{code: integer(), message: String.t()}}, %{ + non_neg_integer() => %{block_quantity: String.t(), address: String.t()} + }) :: {:error, error()} def from_response(%{id: id, error: %{code: code, message: message} = error}, id_to_params) when is_integer(code) and is_binary(message) and is_map(id_to_params) do %{block_quantity: block_quantity, address: address} = Map.fetch!(id_to_params, id) @@ -35,6 +63,22 @@ defmodule EthereumJSONRPC.FetchedCode do {:error, annotated_error} end + @doc """ + Creates a standardized JSON-RPC request structure to fetch contract code using `eth_getCode`. + + ## Parameters + - `id`: The request identifier. + - `block_quantity`: The block number or tag (e.g., "latest") for which to + fetch the code. + - `address`: The address of the contract whose code is to be fetched. + + ## Returns + A map representing a JSON-RPC request with the following structure: + - `jsonrpc`: The JSON-RPC version (always "2.0"). + - `id`: The request identifier passed in. + - `method`: The RPC method name (always "eth_getCode"). + - `params`: A list containing the contract address and block identifier. + """ @spec request(%{id: id, block_quantity: block_quantity, address: address}) :: %{ jsonrpc: String.t(), id: id, diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/fetched_codes.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/fetched_codes.ex index 2570379..a15ce4f 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/fetched_codes.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/fetched_codes.ex @@ -15,7 +15,22 @@ defmodule EthereumJSONRPC.FetchedCodes do @type t :: %__MODULE__{params_list: [FetchedCode.params()], errors: [FetchedCode.error()]} @doc """ - `eth_getCode` requests for `id_to_params`. + Generates a list of `eth_getCode` JSON-RPC requests for the given parameters. + + This function takes a map of request IDs to parameter maps and transforms them + into a list of JSON-RPC request structures for fetching contract code. + + ## Parameters + - `id_to_params`: A map where keys are request IDs and values are maps + containing `block_quantity` and `address` for each request. + + ## Returns + A list of maps, each representing a JSON-RPC request with the following + structure: + - `jsonrpc`: The JSON-RPC version (always "2.0"). + - `id`: The request identifier. + - `method`: The RPC method name (always "eth_getCode"). + - `params`: A list containing the contract address and block identifier. """ @spec requests(%{id => %{block_quantity: block_quantity, address: address}}) :: [ %{jsonrpc: String.t(), id: id, method: String.t(), params: [address | block_quantity]} @@ -30,8 +45,27 @@ defmodule EthereumJSONRPC.FetchedCodes do end @doc """ - Converts `responses` to `t/0`. + Processes responses from `eth_getCode` JSON-RPC calls and converts them into a structured format. + + This function takes a list of responses from `eth_getCode` calls and a map of + request IDs to their corresponding parameters. It sanitizes the responses, + processes each one, and accumulates the results into a `FetchedCodes` struct. + + ## Parameters + - `responses`: A list of response maps from `eth_getCode` calls. Each map + contains an `:id` key and either a `:result` or `:error` key. + - `id_to_params`: A map where keys are request IDs and values are maps + containing `block_quantity` and `address` for each request. + + ## Returns + A `FetchedCodes` struct containing: + - `params_list`: A list of successfully fetched code parameters. + - `errors`: A list of errors encountered during the process. """ + @spec from_responses( + [%{:id => EthereumJSONRPC.request_id(), optional(:error) => map(), optional(:result) => String.t()}], + %{non_neg_integer() => %{block_quantity: String.t(), address: String.t()}} + ) :: t() def from_responses(responses, id_to_params) do responses |> EthereumJSONRPC.sanitize_responses(id_to_params) diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/filecoin.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/filecoin.ex index c221c71..f9395a1 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/filecoin.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/filecoin.ex @@ -547,4960 +547,7 @@ defmodule EthereumJSONRPC.Filecoin do "transactionHash": "0x86ccda9dc76bd37c7201a6da1e10260bf984590efc6b221635c8dd33cc520067", "transactionPosition": 18 }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000000fce75", - "to": "0xff0000000000000000000000000000000001b5d7", - "gas": "0x2985b7c", - "value": "0x0", - "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000005000000000000000000000000000000000000000000000000000000000000005100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000279850a8382004082014082024081820d5902408fc9978c8c1861e78438a099a06de4d9b807567e3eb578fde1ddbd7c88c23ced96d89887d40e41888f6a359ba0d98f3e89d8003c62be48cb4f7a6d8132329e1e18480a9e89a39da05dbe12d48d027bdeac20d8d46c2e9889ab76a7800cffc27212d72abb73090cc21af180b27add3ee619abcc012a98145bbd466dfeaad245901a7f1106a77ee7bf9818680a028229cbb9c6c1c32b329a01fd9ca68308c92399fac66c67707d3efd760e9b5a0d6a6fad89a425c6ec2bc654e1693c3679059c9cb9543366f77db1c65728b88a98d317b17dc66e3bfe2d30c85cd1e883be09ef4fc6ab3b50a684042e30627cd01b67d964932e9ec0d8608ab66b7da8dc9fd99480e517bc58aa752fd9bddcfaa4aba5f59101d108684a40a8ff3d954d5a58c230fa03ab135a17fe691663d1eceeff68d541a69eb913f0fe3b266dcf66543ee36e374c6596525ade50dd4845347e445773a29476a841d05c79df4ddb1904aaae2473cc9bcba41de75f0072bfe0b7df94aa3231044d2d1f8ca32bb7d4707920918ad9b31854e6791623c69c5cf6bbd57a7589ad39c8cb72459b24099d210fa1b4e11351185315739e2853a81ddb02286907bd9804026f2b59ea68462a4e56263960a2a8f4c20f625f87e13d47cedb499fdf4cc034f010fc170b505540200603103d360231fe050e5d769ce47f97ecf0ee862a6225f91fb57b2c5fdddf11e96f69f68742371714ef808477c8ca21f339ca1273b0aea3c066ad68f49784cc6ec88a7dd3af0408e61e6dcffbd19b9811107d960c137c7225b0c783ccb69194d1915085781a0037e5f8582047ab5fe94c06cdbaec8b74525a8ef23928a1bbb62440ccbcd599b10154404d6900000000000000" - }, - "result": { - "gasUsed": "0x226bdfd", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0xdfe4b81a94cce7b6ca32cc497e32a8447786ceac845e366a9b2fcbcf16971a6d", - "transactionPosition": 19 - }, - { - "type": "call", - "subtraces": 3, - "traceAddress": [], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000002c2afd", - "to": "0xff000000000000000000000000000000002c2c61", - "gas": "0x2ca9cf3", - "value": "0x0", - "input": "0x868e10c4000000000000000000000000000000000000000000000000000000000000001c000000000000000000000000000000000000000000000000000000000000005100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000072818187081a000114cbd82a5829000182e20381e802202a0b230182f9610882b85622ee618ffa5933e6e6fc3e70253837708f7fae630a1a0037df72811a045b19341a004f65a4d82a5828000181e203922020fa592a0514dce829ad6e2bbe0fc789d094183e20304be069b5b9393182ffa6030000000000000000000000000000" - }, - "result": { - "gasUsed": "0x1df238c", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0x76fae58091c030e252484105473c8950ef633dd02b4ac8892b341bf46c77d528", - "transactionPosition": 20 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [ - 0 - ], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000002c2c61", - "to": "0xff00000000000000000000000000000000000002", - "gas": "0x2bf36a3", - "value": "0x0", - "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "result": { - "gasUsed": "0xfabb0", - "output": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000418282581a000286387ff107ef952f3612eb2e2a6606418e51c0f2cceeda5f57011731c2a04034dd33df402838a815a0b69ec1184b8c104a0001c0dfc71cd077c4b900000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0x76fae58091c030e252484105473c8950ef633dd02b4ac8892b341bf46c77d528", - "transactionPosition": 20 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [ - 1 - ], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000002c2c61", - "to": "0xff00000000000000000000000000000000000004", - "gas": "0x2ae715b", - "value": "0x0", - "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000009000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "result": { - "gasUsed": "0x105fb2", - "output": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000578449007d2903b8000000004a000190f76c1adff180004c00907e2dd41a18e7c7a7f2bd82581a0001916cb98a2c3dfb67a389a588fb0e593f762dd6c9195851235601fba7e16707ee65746d4671e80aa2bb15bc7d6ebe3b000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0x76fae58091c030e252484105473c8950ef633dd02b4ac8892b341bf46c77d528", - "transactionPosition": 20 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [ - 2 - ], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000002c2c61", - "to": "0xff00000000000000000000000000000000000005", - "gas": "0x29c5bea", - "value": "0x0", - "input": "0x868e10c4000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000f818183081a004f65a4811a045b19340000000000000000000000000000000000" - }, - "result": { - "gasUsed": "0x477778", - "output": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000002e8181d82a5828000181e203922020fa592a0514dce829ad6e2bbe0fc789d094183e20304be069b5b9393182ffa603000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0x76fae58091c030e252484105473c8950ef633dd02b4ac8892b341bf46c77d528", - "transactionPosition": 20 - }, - { - "type": "call", - "subtraces": 3, - "traceAddress": [], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000002c2afd", - "to": "0xff000000000000000000000000000000002c2c61", - "gas": "0x2e2d67e", - "value": "0x0", - "input": "0x868e10c4000000000000000000000000000000000000000000000000000000000000001c000000000000000000000000000000000000000000000000000000000000005100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000072818187081a0001160fd82a5829000182e20381e80220ed53117c4c68300789b739922ced240b43091dcddfed253062cf17677f9775141a0037e0e5811a045b37331a004f66fcd82a5828000181e203922020c775d47a111888a1ad17dd5989a957c59e7a2e89cb7df8639a15556593cb382f0000000000000000000000000000" - }, - "result": { - "gasUsed": "0x1f29209", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0xbc62a61e0be0e8f6ae09e21ad10f6d79c9a8b8ebc46f8ce076dc0dbe1d6ed4a9", - "transactionPosition": 21 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [ - 0 - ], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000002c2c61", - "to": "0xff00000000000000000000000000000000000002", - "gas": "0x2d7702e", - "value": "0x0", - "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "result": { - "gasUsed": "0xfabb0", - "output": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000418282581a000286387ff107ef952f3612eb2e2a6606418e51c0f2cceeda5f57011731c2a04034dd33df402838a815a0b69ec1184b8c104a0001c0dfc71cd077c4b900000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0xbc62a61e0be0e8f6ae09e21ad10f6d79c9a8b8ebc46f8ce076dc0dbe1d6ed4a9", - "transactionPosition": 21 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [ - 1 - ], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000002c2c61", - "to": "0xff00000000000000000000000000000000000004", - "gas": "0x2c6aae6", - "value": "0x0", - "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000009000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "result": { - "gasUsed": "0x105fb2", - "output": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000578449007d2903b8000000004a000190f76c1adff180004c00907e2dd41a18e7c7a7f2bd82581a0001916cb98a2c3dfb67a389a588fb0e593f762dd6c9195851235601fba7e16707ee65746d4671e80aa2bb15bc7d6ebe3b000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0xbc62a61e0be0e8f6ae09e21ad10f6d79c9a8b8ebc46f8ce076dc0dbe1d6ed4a9", - "transactionPosition": 21 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [ - 2 - ], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000002c2c61", - "to": "0xff00000000000000000000000000000000000005", - "gas": "0x2b49575", - "value": "0x0", - "input": "0x868e10c4000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000f818183081a004f66fc811a045b37330000000000000000000000000000000000" - }, - "result": { - "gasUsed": "0x4769b4", - "output": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000002e8181d82a5828000181e203922020c775d47a111888a1ad17dd5989a957c59e7a2e89cb7df8639a15556593cb382f000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0xbc62a61e0be0e8f6ae09e21ad10f6d79c9a8b8ebc46f8ce076dc0dbe1d6ed4a9", - "transactionPosition": 21 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000001ee034", - "to": "0xff000000000000000000000000000000001ee031", - "gas": "0x1b3a043", - "value": "0x0", - "input": "0x868e10c400000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000f385181e8182004081820d58c0a7a02e8dc005044807e4888bd0823f61ae169669e7a27a3b7e3912b8fba0eea593c30732f4480a1fa0e285867e448306b2a2ddea74ce0511ace188bd92cee9e2352a2f1b70a1903b268fd124d1e8e6f92eefbd3bc7a0fc42a477ace39972b9290963cba107a2a981b7c74c1a3dcb6be6a2b7a01e8224501b0a4371f1a3970c0664b313bfcf7be778165db0e2e4bf634da61561da28de009a0cba108ff65d334ac272e4b3ab6e8e26fc23cc09439290bef48866abadb58407af3dced632acf3a71a0037e5f8582047ab5fe94c06cdbaec8b74525a8ef23928a1bbb62440ccbcd599b10154404d6900000000000000000000000000" - }, - "result": { - "gasUsed": "0x1696c22", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0xcfeff915c7a3e06a7aafbb9f1a20ef023615aecea6cb6b004dc87ead55f63702", - "transactionPosition": 22 - }, - { - "type": "call", - "subtraces": 1, - "traceAddress": [], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000002af885", - "to": "0xff000000000000000000000000000000002afa9a", - "gas": "0x466a958", - "value": "0x1a7b47481750efae", - "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000007000000000000000000000000000000000000000000000000000000000000005100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000789821a0001928c5907808a218c1f6baaa41a2cdb879b1b7bf7cc7a382348970e0520fea435085566ff96b13118cf3c9e35bcac55bee865ff570fa7ec2b266e385a473c7e0e77b1080bb1392cd15fa24f39afd648cbf45b2b99d23aa58e9da9e75476fd4617cee6b3bd4708bf6b5afc9237e1977040f8e0bc63c5d825fbb6c3e471f073a6a96f27f9c30b98658dc8e49f6b7aa65d02aef573b613b579c462df8e45632b979c1baff7445e5645163020e17829f0aa7effd318343387715eb7a7eeed87e366620522a4dbac8e916703773123ad4a1347f8e59285eaeb6db1d236a503cbcb2c19a9ac9c6d2cb110adc7685a38df6873db056779f3a7b06cbac8650260481fdc488b9e2b75a7a3676f237768f3d45dc824ced53e6e34b8b004a457f28eef44cadb4334afea05145b1ea8103363bf1b88db266f14971d72b024f9b7f7b8ed9fa4100c388eabd162af274f019d2ef4bd90d53a903ebd5ca39e0478f6173bbd8a576648f2291f9eede03ce9c41aae4d56489652d0a74e7275f599a8aababb97676b5538ad1960cc8260a4e8a6b218ecbd54c8b5b91457ae56d3cf199e740499ddcd1eca823550817a43c1e53d4e407a7e7443f4f038e337ab1e959c101179efd603f2f9b7e40e6255be393e233ccdf601f2e423e898c286776a0712c4db8ed22e8619923849828d10f7cdddbce7e3bb2d51ecd9c3401ac3762fc44f4848f25fc2e8f22059e43ce8b9d39e628ddee9330ba22cb55da67aab985e6022e877568011d3e3eee84df1cad6cc924a426ae8c338140777f6e592c7872a85357931e586ad3f3684c884994495d20c30c5ec5f6de0244355de4b1fde1117b1068f64372ecdcda183e8215032a2d18157d59eb1aa0e09bb1e85fbcc0ab978e1860f6f457cb821a266bdfd197c53516264feb015c8562144cb4fa2c8a6918c6d5e08d3c050136f2905d373c219149dd6c37662da580148312067cbc440a9adb3ecd5df0cb01eb6ad1d4fcd7399b3fe43bcb7397abb7fe9323a71a2a290ab2b0e955979de28827c98e38fbc7c65d63a22e4f6dcd5077e182801e7821d59408cfb01bf70be7a5d063a48b51fa3c6899da9e902cb9305a68f74f4e32ecc6186130cec8daf78f4d40ec869286130ed41228373e496471376ab5646ee44b6f8ab4c4c9210557b7ec166fb4582fc8bdf2fe12a80e37cba8cabf8c7456b75a305aa0aabb6dc1e040ca088a28672bcad1a17c4b661611cae0b5d968c04df8388a06e69fb302cde54f5a1619c0275b1d7349a41561ce6476a8ba3bf3ab1b4f291dfa72cff5696f4203c733f78f8168ffbb2c97392db60ab160a6f42f1c3b8957e24104283deb670393aa20c5cac99bb54489620f16e0838fdb5b7f92515929745b58c2769d26374cca5d94cfb14e133fd3df40497a1fa9ac70a9e9a295ff659b04aaf81a5436dabde72e10af28a00a8f05cd6b3e6bc75847ca7515fd27d4b2cee97e21594d4a5b659a0504e77ccfcbeb082043f73dbc1484e5504b09c048c8a3a6c2db1278ac2205c78a48263e9ada4646dec7d27f28392667d3fb49dc101a5d1b6aade897bb92c692b956b9a83d7d2abfbdac1707a9d369cfc5260ce380f67c3ae6337e34c272ea646632f2c73465b1a13b358cdad9d4583a59cc3be1c8f2094282560aa83435074686f6abec5b4c895452ae74f92d9bbf6e9ec9728d07c397aef8611d09e75c00dfc297af2d724877127d5abca2f6a530ebc768617ad077d4712b26fba3cab83c6c74a7119a6623cb9a712eddadbd7811fd3948337dbdaa311f9891f96a324c20b56ef6d73450c5d3804dac0e51fc07fc82eddf441c9412e13efb701115362bf844ceb88793a131e136a0ca9935b7025a6682afbb8685c9b058c833dcd81e93e6dac958a31f5b942c4578ddb058ae644dddcb43fd9f785510c7a2bde7532b3f84b48e565cfb0ec1f55284ef355df662f091a401a52122823b46697eb141523b39dd9eeb73e10df2793a4e2107ee299405800df9e95340723b18e0d6c46fd7c4236ceaf34594a4c652b22145361f277fc5b3427d47e5b6c51db78e5b00050bc616c2e6efb54eced079a1a7186c080ccc3bab5989e0df45115d314a95664fff63ec61f73ec34c22033105410d1158e44456ba982984508a8c650d912c1d3fab14f7a12935ea1142eccb0748ddd33f1bcc07cd5614e24257ad9a656cfc4a8589042de2b29aa641b0e59b8fb2d83a0600a72c43d44b0da9f7df90985b27787686d14dfdfba29c895dce4ea9b827f5dea7d12d0536e36a90f98cd8e9078ab343ddc20a35dedf9e46010513eca0141bde7b2ab975abe920e201e7c55375eb7c1fe56b6677eed219a59d85c72e04ca348fae0171c2b4b33dd80e743954b80945cbc97c225e28be27d67c7425a9d0331f74919002b5cc2a120f51725bddf5bd8661f5a6c5909561f579536577473a6c8fadc7ca5b0a415d49e78441c3cc0d3b4e26d089bb515fb1a47d7a54814349b6e1cfebd137c2cfe94d3bd2ddcf7ae976a61f32638e55e0e290b6fdf379fda9428124380187eea5fc2a7ef55c7135b432d95b4916e594c3331dd28419a263202c48deb804ceb2119646cd56f1a907a9b4e723d582d1762c0c667b4ad1fe7887531540f19018896f660d57efbeece42880a36772b04b2b438adfa4b3f8c5c2474cad4d99d2e55a165ae536fd7713f3564464fcc1e193c170a0965bc4f0d1bfa0000000000000000000000000000000000000000000000" - }, - "result": { - "gasUsed": "0x70ac08", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0xa714c6be95aa4ef44699b9098e264c0adead1caeffac75013c413a7a9798dcad", - "transactionPosition": 23 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [ - 0 - ], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000002afa9a", - "to": "0xff00000000000000000000000000000000000004", - "gas": "0x4271824", - "value": "0x0", - "input": "0x868e10c400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000008338808821a002afa9a1a0001928c811a045b25725820ebf616e5e6a85ffef1fe235d974420b8a1c0a70d4fa60d21d2215f7e2aac05ff58207bb5ec49d8afc9b6444096fb05e348b164acb4a9305a4fa411def29c62edc61f5907808a218c1f6baaa41a2cdb879b1b7bf7cc7a382348970e0520fea435085566ff96b13118cf3c9e35bcac55bee865ff570fa7ec2b266e385a473c7e0e77b1080bb1392cd15fa24f39afd648cbf45b2b99d23aa58e9da9e75476fd4617cee6b3bd4708bf6b5afc9237e1977040f8e0bc63c5d825fbb6c3e471f073a6a96f27f9c30b98658dc8e49f6b7aa65d02aef573b613b579c462df8e45632b979c1baff7445e5645163020e17829f0aa7effd318343387715eb7a7eeed87e366620522a4dbac8e916703773123ad4a1347f8e59285eaeb6db1d236a503cbcb2c19a9ac9c6d2cb110adc7685a38df6873db056779f3a7b06cbac8650260481fdc488b9e2b75a7a3676f237768f3d45dc824ced53e6e34b8b004a457f28eef44cadb4334afea05145b1ea8103363bf1b88db266f14971d72b024f9b7f7b8ed9fa4100c388eabd162af274f019d2ef4bd90d53a903ebd5ca39e0478f6173bbd8a576648f2291f9eede03ce9c41aae4d56489652d0a74e7275f599a8aababb97676b5538ad1960cc8260a4e8a6b218ecbd54c8b5b91457ae56d3cf199e740499ddcd1eca823550817a43c1e53d4e407a7e7443f4f038e337ab1e959c101179efd603f2f9b7e40e6255be393e233ccdf601f2e423e898c286776a0712c4db8ed22e8619923849828d10f7cdddbce7e3bb2d51ecd9c3401ac3762fc44f4848f25fc2e8f22059e43ce8b9d39e628ddee9330ba22cb55da67aab985e6022e877568011d3e3eee84df1cad6cc924a426ae8c338140777f6e592c7872a85357931e586ad3f3684c884994495d20c30c5ec5f6de0244355de4b1fde1117b1068f64372ecdcda183e8215032a2d18157d59eb1aa0e09bb1e85fbcc0ab978e1860f6f457cb821a266bdfd197c53516264feb015c8562144cb4fa2c8a6918c6d5e08d3c050136f2905d373c219149dd6c37662da580148312067cbc440a9adb3ecd5df0cb01eb6ad1d4fcd7399b3fe43bcb7397abb7fe9323a71a2a290ab2b0e955979de28827c98e38fbc7c65d63a22e4f6dcd5077e182801e7821d59408cfb01bf70be7a5d063a48b51fa3c6899da9e902cb9305a68f74f4e32ecc6186130cec8daf78f4d40ec869286130ed41228373e496471376ab5646ee44b6f8ab4c4c9210557b7ec166fb4582fc8bdf2fe12a80e37cba8cabf8c7456b75a305aa0aabb6dc1e040ca088a28672bcad1a17c4b661611cae0b5d968c04df8388a06e69fb302cde54f5a1619c0275b1d7349a41561ce6476a8ba3bf3ab1b4f291dfa72cff5696f4203c733f78f8168ffbb2c97392db60ab160a6f42f1c3b8957e24104283deb670393aa20c5cac99bb54489620f16e0838fdb5b7f92515929745b58c2769d26374cca5d94cfb14e133fd3df40497a1fa9ac70a9e9a295ff659b04aaf81a5436dabde72e10af28a00a8f05cd6b3e6bc75847ca7515fd27d4b2cee97e21594d4a5b659a0504e77ccfcbeb082043f73dbc1484e5504b09c048c8a3a6c2db1278ac2205c78a48263e9ada4646dec7d27f28392667d3fb49dc101a5d1b6aade897bb92c692b956b9a83d7d2abfbdac1707a9d369cfc5260ce380f67c3ae6337e34c272ea646632f2c73465b1a13b358cdad9d4583a59cc3be1c8f2094282560aa83435074686f6abec5b4c895452ae74f92d9bbf6e9ec9728d07c397aef8611d09e75c00dfc297af2d724877127d5abca2f6a530ebc768617ad077d4712b26fba3cab83c6c74a7119a6623cb9a712eddadbd7811fd3948337dbdaa311f9891f96a324c20b56ef6d73450c5d3804dac0e51fc07fc82eddf441c9412e13efb701115362bf844ceb88793a131e136a0ca9935b7025a6682afbb8685c9b058c833dcd81e93e6dac958a31f5b942c4578ddb058ae644dddcb43fd9f785510c7a2bde7532b3f84b48e565cfb0ec1f55284ef355df662f091a401a52122823b46697eb141523b39dd9eeb73e10df2793a4e2107ee299405800df9e95340723b18e0d6c46fd7c4236ceaf34594a4c652b22145361f277fc5b3427d47e5b6c51db78e5b00050bc616c2e6efb54eced079a1a7186c080ccc3bab5989e0df45115d314a95664fff63ec61f73ec34c22033105410d1158e44456ba982984508a8c650d912c1d3fab14f7a12935ea1142eccb0748ddd33f1bcc07cd5614e24257ad9a656cfc4a8589042de2b29aa641b0e59b8fb2d83a0600a72c43d44b0da9f7df90985b27787686d14dfdfba29c895dce4ea9b827f5dea7d12d0536e36a90f98cd8e9078ab343ddc20a35dedf9e46010513eca0141bde7b2ab975abe920e201e7c55375eb7c1fe56b6677eed219a59d85c72e04ca348fae0171c2b4b33dd80e743954b80945cbc97c225e28be27d67c7425a9d0331f74919002b5cc2a120f51725bddf5bd8661f5a6c5909561f579536577473a6c8fadc7ca5b0a415d49e78441c3cc0d3b4e26d089bb515fb1a47d7a54814349b6e1cfebd137c2cfe94d3bd2ddcf7ae976a61f32638e55e0e290b6fdf379fda9428124380187eea5fc2a7ef55c7135b432d95b4916e594c3331dd28419a263202c48deb804ceb2119646cd56f1a907a9b4e723d582d1762c0c667b4ad1fe7887531540f19018896f660d57efbeece42880a36772b04b2b438adfa4b3f8c5c2474cad4d99d2e55a165ae536fd7713f3564464fcc1e193c170a0965bc4f0d1bfad82a5829000182e20381e80220301293ec00df8e1cf2d9bf338279beaaa9a9a471701f60f290da4576068acf01d82a5828000181e203922020d56877a200472eb74a6521299547efbfe80cce790d635b75516278eb1731941900000000000000000000000000" - }, - "result": { - "gasUsed": "0x2ea25d1", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0xa714c6be95aa4ef44699b9098e264c0adead1caeffac75013c413a7a9798dcad", - "transactionPosition": 23 - }, - { - "type": "call", - "subtraces": 1, - "traceAddress": [], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000001c17d8", - "to": "0xff00000000000000000000000000000000000005", - "gas": "0x1c5e5bc", - "value": "0x2db18102f13817", - "input": "0x868e10c400000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000054400ebaf70000000000000000000000000000000000000000000000000000000" - }, - "result": { - "gasUsed": "0x160c586", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0x52aaaa21fb1cee8882aef391c310f8a26e6dc9a5d3cb968f2f384477dd6851a9", - "transactionPosition": 24 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [ - 0 - ], - "action": { - "callType": "call", - "from": "0xff00000000000000000000000000000000000005", - "to": "0xff000000000000000000000000000000001c17eb", - "gas": "0x1b32788", - "value": "0x0", - "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "result": { - "gasUsed": "0x13a470", - "output": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000128345008c8ab4014400d8af70814400e3af700000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0x52aaaa21fb1cee8882aef391c310f8a26e6dc9a5d3cb968f2f384477dd6851a9", - "transactionPosition": 24 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000001f3b4d", - "to": "0xff000000000000000000000000000000001ee777", - "gas": "0x199d203", - "value": "0x0", - "input": "0x868e10c400000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000f285108182004081820d58c0962920987d861c8b39327edda8e2adeb06abdb019d3aee70b32a296b43255088b551b860b2047e2362f6a9a8912ab69ca9ba5d09d7bcbfa7d5876af95f05fae5c17c6addc4914812aeb2480cd0fc42494879621ee9917e51828638cbb37f8ac50fb1113b1867527112595aa44ec24261ace85a81af4ca83ad5f3f2cbf1968c7b0172351332ff5b9163b9347f898f329294387bcbb1c4f82a2ac478d1db646a0be71d297d2fe1969e37bc5540e90cd7978d45bd21f3e042c45afd6ee8eb2dab1a1a0037e5f8582047ab5fe94c06cdbaec8b74525a8ef23928a1bbb62440ccbcd599b10154404d690000000000000000000000000000" - }, - "result": { - "gasUsed": "0x154c312", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0x8bfc485414cfc17776059a5ea837757f4df29dde3956b025bffb9dcc9706b3ff", - "transactionPosition": 25 - }, - { - "type": "call", - "subtraces": 1, - "traceAddress": [], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000002ce37d", - "to": "0xff000000000000000000000000000000002ce3ba", - "gas": "0x41f9a33", - "value": "0x0", - "input": "0x868e10c400000000000000000000000000000000000000000000000000000000000000070000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000007878219656359078089b3567e087d9f038414ae554aea4607bc0558961eca169c7a8d57e5bb6408861bedd976ad9c30ac5cba0d0da3af1de68cef5b46465cc730f3ced2fbdb031d392b950ce381b7f12967fc57998aa6f668cc8c5b553fb27ce9dc831e13666d13c007a2516ebe2ab906bd22cbab92d58f874b6f98a4680aa6d8790de18a3090df8b0ae3c90adf836c7b7b3f39830623867ea9c0e007f23be47549243aa4ce36277271a70e527ed4241d8496a27925d45e6898914cf1fbd2d118751b5b6cd292adc18891ab78a9153cacda89d3019f1ca81569cc4570725c70fde9d293bea660ce216b720bf0d27bba7121f33bdb9fb450e385b2d68d890711e47e0e7c98f0e38e0a4cbbaf8137be683ec840c651f4051b00c3ed7e825a6a05fcd81b98502c8c0d280b33fef4c8fe2c0d8dc8bb3d31f96558004f6a437dcd9c4c784ec5f19f9718480de4cbd8c354687bf9c80f0056eca127a37a5a80cd2f268ad24e5e7506b627ef5989354ac09beb5e95e9e202b4c0675b6ea01843afd38ce840a0a97d3083771e956a1040fd7bb21ff1040cd3d025beb0e32ce10166acf3365941e7606b9d021df6ad162f58daa88591e69d94ae13330dafe128e8fc8098ec8011d88e40058cda8de05fc93cbf3b1e81abd599f3ba177f0908627e954fb703a027c72c0bcd4fb9085f59cd9bf2237fb7476e866cc5b9d52fa6269f912f49f7bec55000e8e9166c10f6b0a587b9075cbbb06e9129ee4cf2ad0d222bcff3d3db550ea803bd3d993feb24128f4f27081caf42c65f39bdba76215f32815ed5db1df935bad67101314cade9087e8db56416dda958e20cb1b3ddd5df3739d66d06564f1acf725706aedd6c2a9ec15ec1371690e9581a75f29ff5a09a629e45ae13e6d5d94a5bba0242a2bb4ddabda70b62a3717a942dab67d4112d8b7325aa5cf54233f8c686e9d6b275123dfd791f93db568f8cccb0bea7cf8deaee33696314f092b04b6c8ae4b26e64d3fa436bb49d87205719324c2196db84b758440ee9ebf8b38f60799c26ff8e6237d92f183d94e3213cbe5285b8a4deeb80bc450899c97a0c3383ad17bcdf337599a7461bc3cd7a7b82116d715109cf7564dedc7ff071d502452267776e485740886090a92ebbf802e37ac635966fd8ce8157b93a0fb442f73207996136049fdcd81be00bbb015918683d16427df44576b27ebef8471bbc5fd1bac62006314c1117086f3352c36a7c68abe2054f70d999155445a87431d0cc42df77a1a1d909b92e78cebe4260e86821b1015ab8072b72b255072a7b87b1afbb78f2604c783d84e4a586f248d26720f5402046a48aec8b8999fb958fcd5c1ce873a3b96c95c9508e349d0df5608404fb0a0a9e459c746eb9fb655f4a6d719d3117042c76e202cc3b1d530c8a6f313f2d160d193596244aacf5e704055004579c2a83c5963d8ab595604d10bf98e9d80681c698bf8b9bcde33df3bf3169ab754f5f1fd862adf9250aa422e1611ebac1eba3cc793662894288a724fbd791e89dcdd7a3bc74ff9aee5294be60e991748a2fa432205cf2226cb232967edb4999a3bf0643be318de1296f684b86cceb1dd56b2cdc486c8ec1a6d788568b835318fb355fdac8f9a34cd68725d7cd120372a7f636002fb17f1d10ddb0e674a7e86b98391a77d862325b5195109ebfdb3d8a1e27a7f79d0f7c567cad351826467a63ef149cf5dbfab8399a4012da12af9d4ff6598b0ae54c05775dcf85c0191d38e339e222ead9376c8ee915df379c5b1b42abe15048667290fe02ce9783cc46f10d35bd600d530292992b08ef959940dabc733c9e60fec0ccb388a427a3bf07ded414b478c91d2026df0a52cf2c25aeea9849c86480cfff3600c43174f5d4451df4962a4d9cebb9e2f11797679d4d28533baafa05c253c25a4f62ab3d0190278e8c20fd72662a023e60e0e3eab027090e07716dce1251cf730690b2bf033236d3fe537e153201455032f691e2516a02c1e0a999ceb02c5709dd672c70897838f57a4f6a854ae13c157d5b006677ca44af39133b0663ee2e2beb1fe4a68a846ce3cc2114bf80f91e187da1f0b250fddfdfde59eee0b656da275deb9490dbc7289643c91c94500771e373fdc338251f35567d0bf69a1281300e5f8f5f2df9c3ae790e5d8493c9176ae7048496383654e519118d249be0e47252aeeeec73ce3954722f4ecbfd6dd0a931e35970085692b3c1592022fa557733d6125793516ca85a7b27816862c7db7ecf74a5c01a50caa3997f023a836c410178c402c610b48b97854c8c3b2522a3c73a176c08f86a3d9bcd31c513d54107ed0e87ecbe6a8eef753d7ef748b49ce8e07f0240214d85463e7cb2cede2738ae1ae0ae628abe17f62b3e75cafa9b5065e5469a13bd04e1c7f96924d7fa6464191b9ead0479821d0114ba27f1cdbc1cb935e1fed086c8a53dc38245a373bc68cdd8f01a84d9c5fe093ff831c7ab0606d1179e52877caae79c9112cc5af723d1d6bfaa0515b8313eaa7c65daef498853daccf0345999610f6aa633d04805b14c1a5a34b9c3a75f758f3004e62464fe3f23255148ee02029002108728d91a4fb0f8af2a2c3a44f3d97c810e03a926116f125a42c9d8da8c93da016e97f3e1b50a2fb838b23eab8b279f95fcc21cedbf474b3457ac3a3d791f5d188b694285ec2d1169ef8015203e11a8f28ab520c57b37b9390cecb600000000000000000000000000000000000000000000000000" - }, - "result": { - "gasUsed": "0x695660", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0xc181546e031e0c986a730e2567075d746fa9a148c12c16b15c36e689dba86a87", - "transactionPosition": 26 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [ - 0 - ], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000002ce3ba", - "to": "0xff00000000000000000000000000000000000004", - "gas": "0x3e72861", - "value": "0x0", - "input": "0x868e10c400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000008318808821a002ce3ba196563811a045041cf5820cf92e6b2348466d49f180699709bf0afcc372ea7d8543074c0b5c724a7ecb6a7582039251fcab982839627fe199c46354a2e52a24e2f438c66c568c62466f8aca8ab59078089b3567e087d9f038414ae554aea4607bc0558961eca169c7a8d57e5bb6408861bedd976ad9c30ac5cba0d0da3af1de68cef5b46465cc730f3ced2fbdb031d392b950ce381b7f12967fc57998aa6f668cc8c5b553fb27ce9dc831e13666d13c007a2516ebe2ab906bd22cbab92d58f874b6f98a4680aa6d8790de18a3090df8b0ae3c90adf836c7b7b3f39830623867ea9c0e007f23be47549243aa4ce36277271a70e527ed4241d8496a27925d45e6898914cf1fbd2d118751b5b6cd292adc18891ab78a9153cacda89d3019f1ca81569cc4570725c70fde9d293bea660ce216b720bf0d27bba7121f33bdb9fb450e385b2d68d890711e47e0e7c98f0e38e0a4cbbaf8137be683ec840c651f4051b00c3ed7e825a6a05fcd81b98502c8c0d280b33fef4c8fe2c0d8dc8bb3d31f96558004f6a437dcd9c4c784ec5f19f9718480de4cbd8c354687bf9c80f0056eca127a37a5a80cd2f268ad24e5e7506b627ef5989354ac09beb5e95e9e202b4c0675b6ea01843afd38ce840a0a97d3083771e956a1040fd7bb21ff1040cd3d025beb0e32ce10166acf3365941e7606b9d021df6ad162f58daa88591e69d94ae13330dafe128e8fc8098ec8011d88e40058cda8de05fc93cbf3b1e81abd599f3ba177f0908627e954fb703a027c72c0bcd4fb9085f59cd9bf2237fb7476e866cc5b9d52fa6269f912f49f7bec55000e8e9166c10f6b0a587b9075cbbb06e9129ee4cf2ad0d222bcff3d3db550ea803bd3d993feb24128f4f27081caf42c65f39bdba76215f32815ed5db1df935bad67101314cade9087e8db56416dda958e20cb1b3ddd5df3739d66d06564f1acf725706aedd6c2a9ec15ec1371690e9581a75f29ff5a09a629e45ae13e6d5d94a5bba0242a2bb4ddabda70b62a3717a942dab67d4112d8b7325aa5cf54233f8c686e9d6b275123dfd791f93db568f8cccb0bea7cf8deaee33696314f092b04b6c8ae4b26e64d3fa436bb49d87205719324c2196db84b758440ee9ebf8b38f60799c26ff8e6237d92f183d94e3213cbe5285b8a4deeb80bc450899c97a0c3383ad17bcdf337599a7461bc3cd7a7b82116d715109cf7564dedc7ff071d502452267776e485740886090a92ebbf802e37ac635966fd8ce8157b93a0fb442f73207996136049fdcd81be00bbb015918683d16427df44576b27ebef8471bbc5fd1bac62006314c1117086f3352c36a7c68abe2054f70d999155445a87431d0cc42df77a1a1d909b92e78cebe4260e86821b1015ab8072b72b255072a7b87b1afbb78f2604c783d84e4a586f248d26720f5402046a48aec8b8999fb958fcd5c1ce873a3b96c95c9508e349d0df5608404fb0a0a9e459c746eb9fb655f4a6d719d3117042c76e202cc3b1d530c8a6f313f2d160d193596244aacf5e704055004579c2a83c5963d8ab595604d10bf98e9d80681c698bf8b9bcde33df3bf3169ab754f5f1fd862adf9250aa422e1611ebac1eba3cc793662894288a724fbd791e89dcdd7a3bc74ff9aee5294be60e991748a2fa432205cf2226cb232967edb4999a3bf0643be318de1296f684b86cceb1dd56b2cdc486c8ec1a6d788568b835318fb355fdac8f9a34cd68725d7cd120372a7f636002fb17f1d10ddb0e674a7e86b98391a77d862325b5195109ebfdb3d8a1e27a7f79d0f7c567cad351826467a63ef149cf5dbfab8399a4012da12af9d4ff6598b0ae54c05775dcf85c0191d38e339e222ead9376c8ee915df379c5b1b42abe15048667290fe02ce9783cc46f10d35bd600d530292992b08ef959940dabc733c9e60fec0ccb388a427a3bf07ded414b478c91d2026df0a52cf2c25aeea9849c86480cfff3600c43174f5d4451df4962a4d9cebb9e2f11797679d4d28533baafa05c253c25a4f62ab3d0190278e8c20fd72662a023e60e0e3eab027090e07716dce1251cf730690b2bf033236d3fe537e153201455032f691e2516a02c1e0a999ceb02c5709dd672c70897838f57a4f6a854ae13c157d5b006677ca44af39133b0663ee2e2beb1fe4a68a846ce3cc2114bf80f91e187da1f0b250fddfdfde59eee0b656da275deb9490dbc7289643c91c94500771e373fdc338251f35567d0bf69a1281300e5f8f5f2df9c3ae790e5d8493c9176ae7048496383654e519118d249be0e47252aeeeec73ce3954722f4ecbfd6dd0a931e35970085692b3c1592022fa557733d6125793516ca85a7b27816862c7db7ecf74a5c01a50caa3997f023a836c410178c402c610b48b97854c8c3b2522a3c73a176c08f86a3d9bcd31c513d54107ed0e87ecbe6a8eef753d7ef748b49ce8e07f0240214d85463e7cb2cede2738ae1ae0ae628abe17f62b3e75cafa9b5065e5469a13bd04e1c7f96924d7fa6464191b9ead0479821d0114ba27f1cdbc1cb935e1fed086c8a53dc38245a373bc68cdd8f01a84d9c5fe093ff831c7ab0606d1179e52877caae79c9112cc5af723d1d6bfaa0515b8313eaa7c65daef498853daccf0345999610f6aa633d04805b14c1a5a34b9c3a75f758f3004e62464fe3f23255148ee02029002108728d91a4fb0f8af2a2c3a44f3d97c810e03a926116f125a42c9d8da8c93da016e97f3e1b50a2fb838b23eab8b279f95fcc21cedbf474b3457ac3a3d791f5d188b694285ec2d1169ef8015203e11a8f28ab520c57b37b9390cecb6d82a5829000182e20381e80220da354058660436f1563af7b6a0642c182b1a6fa48602b441b5b47c19c1b0c60fd82a5828000181e203922020185214c7c8cb8fa5f08e57348acba88012ee10f72b1fa2e048d495d152598b08000000000000000000000000000000" - }, - "result": { - "gasUsed": "0x2ebb8b6", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0xc181546e031e0c986a730e2567075d746fa9a148c12c16b15c36e689dba86a87", - "transactionPosition": 26 - }, - { - "type": "call", - "subtraces": 1, - "traceAddress": [], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000002ce37d", - "to": "0xff000000000000000000000000000000002ce3ba", - "gas": "0x4af0e25", - "value": "0x0", - "input": "0x868e10c400000000000000000000000000000000000000000000000000000000000000070000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000007878219656f590780970b4083f06e6772b24fde9600f285c9f542ffb9b93d15ac32a9cfaa002ed367b4d8ee029ac689e0b2a9f797a52bbdd1b01687a9ceb8c9790e00f14c1a9463b0f2caa20f079f8b4aaedd577aa0e21dd971bd3bc56a3eae10200b2e1c2270170508c50c10a32fccc9206146f1d57109fc723b6aef3a814c10ff21d9f9f57e30fb5c88eddaf18dc525847f6ac1519e200e8bfa5521554b9e6519e998b5c41b099e076efdef54142293a4164be42f89a6c006e9ca0a718ebf402aadb270d9b48bbaa30f8cc5d9558d81f61871b4fd006879593d13363fd3ef2d0a8589603037a53d315f2cadbba24eeceee4e6a5b43df62493eec2ba6c2da36e7ef21140e9d7b9f2623466cf2af2d3dac9370b4e7df3439beac4b58b9021ad7c3e8aa5fcd8d76eb21157ee86adaa6ca9fafaae6a986de700eb4446e710eb8a07d95b29f39cff7c35fa5d39f985203e26e0a739506473a53390d4a05c2a890168ae9dde4a0cfc34e7a215e16c83148c671d7f56105ad34ce38de2454a5c6807c422b7d640e4577b718e435b0ee978fdbff427bb8a9aece830bfa5146061849d05b881c9b7212fcdf7798f773347f8b9a0d7a70a2cb83902dd84f882f7245624ba8c44b1d06f98f5159e4f51494105c0b26830321f349f0dd436d1726d00041b0a490a404a689cd9c61949cb8c1624cb83b5ef7ccbbae5fea806b2ff6d9b32c3b4e48ae0046404f2c072676a7d7922467a625948821dbc2c81b8da97d3a187b329f5e86e899fa0a200fb5cb3e083dcf0010a77a3096e3b61afa89d10c834fbd8ec58cf4beef1d1f878aa3ddfdaf747853e9fc63f9f7f2e12e84bc85e453ef19d6b4ed18958fe3405aba887287def1c8dc15777b265c0a42780acf9897858c07cc9b2b9b02b6c90fc3b77a01dd974deb58864d6e31e0ebe2574c9a9283806ea6356b71bd4262fe608740cdfc4631ec56e1c09db0bc46c89698e2eb0e55e81ef73c3c8879b25a1f362a90543c74acd4cbb35cbe9d35765405457b626d5151ee48d91ec4e03ab0429dd9feab2c461f67dfc7ec78211aec693a2815fddd400c8846d224b869565060d2f2c87b753b8df856328e65ddcd3f60638749b6943989ac46cb8e85f33228497c5d2267c75282bc7d3fc69047799cd70f359964a95667d91e13f7d30daebe8bcae1024ace8f8e551f174538b37ca05841dd4a46063b760df8e6f3630953d716c5c410a4d573152fd5ad0360d81d57a051394c739b4e64c0f90679bb3ac490541788cdfc4f6bc54cb95114f05d625530420cdaca32574e660e7f0f9862b12e4ce7efb8543cc1dd745c2a7673f526fd938ab10b82b1a9c725ed7b83573b304ee6d2c13a782368d77f770fd62aec69bf981c3db02fd32b885661ad86de36b04daa7235ca2c7a0ac6f54a43a6af26add753ca2b6991bf2b4450645fff88b7dd39e73437bde00bad496297eb4d7b8522b8c2f2d5b375f43d8b145d5f1eb5c9525f15d8f91095bc1098415496f0570ddbc02173225aeb99cea9b0a4815f7f748043799771a1439be8fbc024c7931242d48262cc08aaa3b158d7fa8a1a53658d6ff4c902b272edcac0ea9650d2956725308dd3b45924f9ac61a1333a09d1ab64b9e3e0d7722b25d972258a46640ad2fd637d28880df353921218d8dc81615f8e43547b9c9759d3c2252e3aae6f07e001483bb5b5bd8a69c0ba553292ad1d8c14af76e7ced6b19a5d2457ae64bf6dba77b17a7bdcd130fdddf49f2f290b8c667b7f2badb370c1277166fb98f700cd6dd05dd895da055a09669e3467ac5d6cfcba2872f834c1834d610436fe6dfeb22f1026a113ea69aa671af6976925cbd5dd0f99babe9b950cf89a275ef1a28c541c2f342222326b9fce88df14f5e2cc597bd120cd518c7228d6c8a8c6f42db9078b54bfbdb3d6985a5fff3878608b15d4c5edeeff9657f7ae9bb515da83879a97566a1de5ea47c8f8db7fa1bdbb0ad3e8a89806e028a8a54c61ca914dc001ffa741069ac048a0c6afdf37b308c514bdc40556ba4351b1d340cb76431035265fd4bab3cf003b2fe67f019815f28d1402e4380a89dfab1e0b90274c861b288fd381ac83f48cdc4b5218d12dc7e41c6fcd86ab860d59c960db092d456e19d0d2ccfe7233fc7baea842a410c1bfaab6fb587d6fc26880ee860b6aef390991b1b242301e28680c6d65a39d49d7c7e25a8c1411571a66010a74f87851a7314b07731bbf30c6d786ecf42709360b3fc1b12c450115f35e707b41f258073d287a602b6fd57389348b8fec9366c80350ac96b03a10b0f8ad4c09455c906b681a9a1f9405d32fd8f08fd5fa9e9ff70b2b4a0fca4cb1dd659d39e9e99c630f2d3aa056f8f3acc239497f45643e0a7b6d86e7922129429caa5d71b00a88e3aefea943d8efc66f97ac160d570d2834c8114aeb1232010ca35826faa4cf786aeda3204f74c456407ae72a09bad8eb9261510bfabe03ef8c206645f36edd78572babe7ab11f7d26cd0bbd07c868eaad8e659ff329cfa502aa61081506313f76a4f81d100fd693b37c8877c9a6b543a8ee45e826669d7326b15681f5f83af7fb13b323cc649316cbccfa93e76756718bbb0a0e11ccfd5335af216c8ea21ba41976cfd65caa7c0072bd2f2df78fe32bbfa5612057cbf2c059d946ca4f5480043ca4d79ec3f1af8d36c47b0e1cb7a01e897485b1a76c00aad500e378da9ef789b500000000000000000000000000000000000000000000000000" - }, - "result": { - "gasUsed": "0x661c29", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0x2388a8d8981d196c52b6b731e06991d9a502910cbaeaa5396a156b882a6f6097", - "transactionPosition": 27 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [ - 0 - ], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000002ce3ba", - "to": "0xff00000000000000000000000000000000000004", - "gas": "0x479cbfe", - "value": "0x0", - "input": "0x868e10c400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000008318808821a002ce3ba19656f811a045041325820cf92e6b2348466d49f180699709bf0afcc372ea7d8543074c0b5c724a7ecb6a7582030f7dea42fec628a29ba26a0dab17df120f5f9b2740e0ca4ba1c332d86c1eea2590780970b4083f06e6772b24fde9600f285c9f542ffb9b93d15ac32a9cfaa002ed367b4d8ee029ac689e0b2a9f797a52bbdd1b01687a9ceb8c9790e00f14c1a9463b0f2caa20f079f8b4aaedd577aa0e21dd971bd3bc56a3eae10200b2e1c2270170508c50c10a32fccc9206146f1d57109fc723b6aef3a814c10ff21d9f9f57e30fb5c88eddaf18dc525847f6ac1519e200e8bfa5521554b9e6519e998b5c41b099e076efdef54142293a4164be42f89a6c006e9ca0a718ebf402aadb270d9b48bbaa30f8cc5d9558d81f61871b4fd006879593d13363fd3ef2d0a8589603037a53d315f2cadbba24eeceee4e6a5b43df62493eec2ba6c2da36e7ef21140e9d7b9f2623466cf2af2d3dac9370b4e7df3439beac4b58b9021ad7c3e8aa5fcd8d76eb21157ee86adaa6ca9fafaae6a986de700eb4446e710eb8a07d95b29f39cff7c35fa5d39f985203e26e0a739506473a53390d4a05c2a890168ae9dde4a0cfc34e7a215e16c83148c671d7f56105ad34ce38de2454a5c6807c422b7d640e4577b718e435b0ee978fdbff427bb8a9aece830bfa5146061849d05b881c9b7212fcdf7798f773347f8b9a0d7a70a2cb83902dd84f882f7245624ba8c44b1d06f98f5159e4f51494105c0b26830321f349f0dd436d1726d00041b0a490a404a689cd9c61949cb8c1624cb83b5ef7ccbbae5fea806b2ff6d9b32c3b4e48ae0046404f2c072676a7d7922467a625948821dbc2c81b8da97d3a187b329f5e86e899fa0a200fb5cb3e083dcf0010a77a3096e3b61afa89d10c834fbd8ec58cf4beef1d1f878aa3ddfdaf747853e9fc63f9f7f2e12e84bc85e453ef19d6b4ed18958fe3405aba887287def1c8dc15777b265c0a42780acf9897858c07cc9b2b9b02b6c90fc3b77a01dd974deb58864d6e31e0ebe2574c9a9283806ea6356b71bd4262fe608740cdfc4631ec56e1c09db0bc46c89698e2eb0e55e81ef73c3c8879b25a1f362a90543c74acd4cbb35cbe9d35765405457b626d5151ee48d91ec4e03ab0429dd9feab2c461f67dfc7ec78211aec693a2815fddd400c8846d224b869565060d2f2c87b753b8df856328e65ddcd3f60638749b6943989ac46cb8e85f33228497c5d2267c75282bc7d3fc69047799cd70f359964a95667d91e13f7d30daebe8bcae1024ace8f8e551f174538b37ca05841dd4a46063b760df8e6f3630953d716c5c410a4d573152fd5ad0360d81d57a051394c739b4e64c0f90679bb3ac490541788cdfc4f6bc54cb95114f05d625530420cdaca32574e660e7f0f9862b12e4ce7efb8543cc1dd745c2a7673f526fd938ab10b82b1a9c725ed7b83573b304ee6d2c13a782368d77f770fd62aec69bf981c3db02fd32b885661ad86de36b04daa7235ca2c7a0ac6f54a43a6af26add753ca2b6991bf2b4450645fff88b7dd39e73437bde00bad496297eb4d7b8522b8c2f2d5b375f43d8b145d5f1eb5c9525f15d8f91095bc1098415496f0570ddbc02173225aeb99cea9b0a4815f7f748043799771a1439be8fbc024c7931242d48262cc08aaa3b158d7fa8a1a53658d6ff4c902b272edcac0ea9650d2956725308dd3b45924f9ac61a1333a09d1ab64b9e3e0d7722b25d972258a46640ad2fd637d28880df353921218d8dc81615f8e43547b9c9759d3c2252e3aae6f07e001483bb5b5bd8a69c0ba553292ad1d8c14af76e7ced6b19a5d2457ae64bf6dba77b17a7bdcd130fdddf49f2f290b8c667b7f2badb370c1277166fb98f700cd6dd05dd895da055a09669e3467ac5d6cfcba2872f834c1834d610436fe6dfeb22f1026a113ea69aa671af6976925cbd5dd0f99babe9b950cf89a275ef1a28c541c2f342222326b9fce88df14f5e2cc597bd120cd518c7228d6c8a8c6f42db9078b54bfbdb3d6985a5fff3878608b15d4c5edeeff9657f7ae9bb515da83879a97566a1de5ea47c8f8db7fa1bdbb0ad3e8a89806e028a8a54c61ca914dc001ffa741069ac048a0c6afdf37b308c514bdc40556ba4351b1d340cb76431035265fd4bab3cf003b2fe67f019815f28d1402e4380a89dfab1e0b90274c861b288fd381ac83f48cdc4b5218d12dc7e41c6fcd86ab860d59c960db092d456e19d0d2ccfe7233fc7baea842a410c1bfaab6fb587d6fc26880ee860b6aef390991b1b242301e28680c6d65a39d49d7c7e25a8c1411571a66010a74f87851a7314b07731bbf30c6d786ecf42709360b3fc1b12c450115f35e707b41f258073d287a602b6fd57389348b8fec9366c80350ac96b03a10b0f8ad4c09455c906b681a9a1f9405d32fd8f08fd5fa9e9ff70b2b4a0fca4cb1dd659d39e9e99c630f2d3aa056f8f3acc239497f45643e0a7b6d86e7922129429caa5d71b00a88e3aefea943d8efc66f97ac160d570d2834c8114aeb1232010ca35826faa4cf786aeda3204f74c456407ae72a09bad8eb9261510bfabe03ef8c206645f36edd78572babe7ab11f7d26cd0bbd07c868eaad8e659ff329cfa502aa61081506313f76a4f81d100fd693b37c8877c9a6b543a8ee45e826669d7326b15681f5f83af7fb13b323cc649316cbccfa93e76756718bbb0a0e11ccfd5335af216c8ea21ba41976cfd65caa7c0072bd2f2df78fe32bbfa5612057cbf2c059d946ca4f5480043ca4d79ec3f1af8d36c47b0e1cb7a01e897485b1a76c00aad500e378da9ef789b5d82a5829000182e20381e802209ce6925c121712eda1a12f4df52b5a3d3105f320dc6b45f24c785776a953de4cd82a5828000181e2039220207ff6f6be1300fb4908c0daf51e3d7d0425283612424a721db42365b52ea8cc06000000000000000000000000000000" - }, - "result": { - "gasUsed": "0x361d537", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0x2388a8d8981d196c52b6b731e06991d9a502910cbaeaa5396a156b882a6f6097", - "transactionPosition": 27 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [], - "action": { - "callType": "call", - "from": "0xff0000000000000000000000000000000021ef69", - "to": "0xff0000000000000000000000000000000021f07d", - "gas": "0x1afc9e2", - "value": "0x0", - "input": "0x868e10c400000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000f285068182004081820e58c0b13a7cf6ffb355576e128fb8aee41b36616a208be66d052ac70553d944dbe0f1b0acc2540132e6e73d2768ef30811b87a0fa8aafd877070dba2c069689f4ee35e6bfbaa13cbfaabf1fb4993ce394f46d869176762f568fd424489bd4f30f1393039b7e812f687b929ad1239207ac71e60dbd48891e13f11eeaa0ca96e8552f2dd050d9717c63e3a1d9e101baf351083baf7e27633b6eab89e3fbeabd50cb6e355d36fc8a22137e9687355072fefc0d1c4291d9122bb90d719155ddf81db1dd2b1a0037e5f8582047ab5fe94c06cdbaec8b74525a8ef23928a1bbb62440ccbcd599b10154404d690000000000000000000000000000" - }, - "result": { - "gasUsed": "0x1665f4e", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0x3cf0131db87f6dfaeea3932633507f9dff31e2c8f7679e0eb5205035cf910373", - "transactionPosition": 28 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [], - "action": { - "callType": "call", - "from": "0xff0000000000000000000000000000000021ac60", - "to": "0xff000000000000000000000000000000002174cc", - "gas": "0x180d49d", - "value": "0x0", - "input": "0x868e10c400000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000f285078182004081820d58c08b865932f85aa47714a2504c2449ae644e1e707b02cfa87f65050ead44fffdf335551663226b76a111eb07fa6f925cc1a2a3e2e0adc244bfe617b684dfa7e7e3b3b50156599a634395500da0cb12066f3376acd9ede2debe33e4c947a7c1aa710af15c15c5af34cddf7e3eb2dba604767d0ff52d42c2f96d16bb7ca5212ed5d43383279eea49778963a1fe2ef07ffd5fa66de8142d0af0bb17aaa80803d95dff6feff0b487353f7879bf9f090949d38260a4fbf6e5557bd5714ce8d4b0410bbd1a0037e5f8582047ab5fe94c06cdbaec8b74525a8ef23928a1bbb62440ccbcd599b10154404d690000000000000000000000000000" - }, - "result": { - "gasUsed": "0x140ce4b", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0x256951a7f0727529f3eac30506f6961b9bb393ef925d1f2d92ae118855bc62aa", - "transactionPosition": 29 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [], - "action": { - "callType": "call", - "from": "0xff00000000000000000000000000000000129271", - "to": "0xff00000000000000000000000000000000129273", - "gas": "0x1c8bfc9", - "value": "0x0", - "input": "0x868e10c400000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000f2850e8182004081820d58c08f79c809bb284bc44d5dbea91400a0fa579c80d3a67a42aa097fd6d87f87ad57618a343124258c40eb83694be6b6fd1c8fbb50bc744e3bb07263fcf016c2b01cca117a25eb6fe38e8502f6d4f5eeb61df3e96e085f2fdf8aa1b67cc6b28de7b7165f3bb0a76cd3f86416410c209b10c43eea9cefd8dcf6da457e6d51e3921920cefc97215219538eab13d665987d0bf0856d7030ab5b71078c7b3ee750aabfba4f8a95711aedde2e313594761bdcf6e44332b66beeb9f956386f9c820a1eed401a0037e5f8582047ab5fe94c06cdbaec8b74525a8ef23928a1bbb62440ccbcd599b10154404d690000000000000000000000000000" - }, - "result": { - "gasUsed": "0x17a611b", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0xc6547d24edca37263cea22782639104718c6ab47802a579a7c593b50c03c5897", - "transactionPosition": 30 - }, - { - "type": "call", - "subtraces": 1, - "traceAddress": [], - "action": { - "callType": "call", - "from": "0xff0000000000000000000000000000000024071d", - "to": "0xff00000000000000000000000000000000240720", - "gas": "0x4dea67f", - "value": "0x249707e1ba2b041", - "input": "0x868e10c4000000000000000000000000000000000000000000000000000000000000000700000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000078782197b81590780b1f9d380ba870fa437208ac15878ab89d9f34543419b3e34eb4c0ffcab1dd0992538a2d9071e62187bb0ff89a3e2af7e80975593af8b408966223d147eedca680aebdd1e5bab0e28ef8339ffb595adbaf18b2d96f245274e15cfdd799db6e3a4034db1b7497bd552b7f38cd4b215e241c9988f3640325e3e4001cc21d2cd8bfcc05f2e21f8845d235d866232c134342f99eaf59c7ebc23005f57b27254bb8d7d7648a4364ad5521203158f4a2aca6046c19beb35782af2b2f3b05e4054cc5a5982fc8035f2b2e5bad7763f27a2ff9045af7149ec036d283304f6d2f24e63438b862089e498d000357170027730c997aa9882ac929ff2054cafece481a559624985d64edbf24eba8e157e7b98d14f2bf9a39a53e88862cdc4c613ccebcfb497600591a0a9018199adc75cd6d4b3c20e910d8e20aea5c1eb09609581e8ae4ab6fdeb211c974a031d6e54ce46df51beecbf879b41106c7414eb1cc5b04fda2c51a3531c7f04709c9e22f613c3d3b7ad4f5e7e781f3b42545baa78bc2493253841bcad24670ba011f5ba63fcc93e2d067f4fd0f1b614244aa5ef3841c10b7cc1a74cc81c9782d0ce1ac66177215374e7a1aea72a75e2e2bb9da9a6192b5e83f3bf67d6d965cc6a71aa5d830712576370ab75f0b7b6fc30ffb3cf538d1f9ee8447b1612b6a9c367ed3f20311c673c413c51a5a4b17bcabf9387231a5c2114b7b53f8aee48ac87a5ad6c6423c5ee5b14c54a4aa25edc9889e8246fc3b1d57dcbec40489d316220fc885f960f9a0f1b726b170a0bb338f6025eddd39e8dda03087a123f8932ffe6f36555ef77d5fac549d71a600f443ec3ad1f148037da3e1a8174cafe06935278299e757623eb47f0135f0b75b68438c64e93676fca8fdb911d7fa4768c03a37ad1301eccf1590446ab692118005081a29cfb985c9d924f5bd657bfac0e68a82a191a5ffc09122189493329c1ef4e363e9bce42884fac89c5b1e253d6ae3ca4d306ec9e2ec762950a9752d00291c5c57417006d9aba659a30d9b2b331ad8b860adc960e7597697e7c5bbf6995629d11e3c48b7b260af7a0505eada5628158b9e64913ad94aed332e8102eddad06c33064c68eb46662ea33655673f00ed13a5d5ee4f59943c4cfb1cbef7773ab83d50e98c6098e431b0a5ecd0e1a48d8740818809c0bda28094d6cd0999cf27c3166eba8f0842e81b4fc28f3e3340b3c0a321942f6d75cdd91ed3fba60b2cb7b2d5278c2f4e7a5ee24def2a35054b8d3091d554c985a2d53301b7558373ef8ce80bca45cd25e1a5357811d816a6ebc50fe673d5273453407f058311bfb9bcdd0d7d08d58a6da2ab1bcd5916ce7b2bb5ca34d875957c53712a9b1950220e8ce327d3c8a1d308dae2830620e9dc5e1f1d385fba934ff856b4627fa732ac1ddaa43b572089da4a6bee77be2cc1326fd910edc12ec05eeea0930b31806eb6143e89e024d51e1f90829f4d0189d29c414b84e0efc6a6cb9aef21257c6652c05ee743127c77d5946e48aedcb71a8ab3bb5ad4e815d882e9d397cc6d3b39644939d3085afb44969e77ff50629a356bf1caba25d9eca119afb0db5c351018d98b6b7de8d9ccea9fdd725295f738c2c8e9522d95ea2171db0c927976321c3651b67da188e509b4c64368ba8989efff9eecbaa2dd4b3e24fb0181adae49c248f6f523a5ff98a4c2d53ecc8042a8ff6863f41174cd04958fa488513cb38afb23a50f922cf247701b63ed1bf3b76d22eb64b38f39280195c59321bca0f4988251f886e16e9d6cb28eb13b1bcb33a5defe754ad4fa7554e54b9e3d1bb419f69b8e8aae47ed6e090541503f8c5ba540fc8eecd09755f1304266ebe5add110b0c10ecd0b6cc31313fb341c4beb9ff6b6801baa267f8be3f99545966ea7d36df8d78d8a7b764e2f142e7143043ba3a3fe3ad4e1172975e7e5a16920a21ed3e173b0fd4a2d178f124829c312bb34b8a086c661db0cf539643f06f9321f0ddab3443a54041dc26fef93072b60025182b4c319ceeb27490c3fb1895be83f081812a7e442f041c803636dc7bd7af7f91772485de16a3a70a6423441af2d578060152c6978b7f5eaf265f96aa593c6b8988ef6abd7f0c211685ebd15f4691d81a9b61458123e8f3222dae3d7eec64d3d793f8bcb89ff0957ca775a89e2143167d7cb39ebd4ced7f5ff6d5777e07feb6be110c0236f316ac919c2e4fc2bbab76ea38720a16a5e5b2d4bf40a8578daf93a7b78394ca172c71be8eb9d9f7872a31e25ce6ae6905b2761cc870eac7122db2f6a2462a0f6d230c3ea41b162f17567030cef113fdcde321e3b5d33a77caf6b4429ba578f598c7a76dc604661233e1d55b99359bfc5f19d67a64b6b04d36f99502fdb41c7946ac73b44c9ec05bbdcba1f76f1ecc660226fbdc1e225854866849e918e849736f04ac6c084294a822d73059f9fe80af1b41762b2fc84e140674c1864599dacf5ed6ad074e1095366a4918b0dad7abba9b24263bbf5e995ced17f759c6978d54af02a9b9c78d057896ee884d8df1fc163ef03b38d449e7f45cc41d831d132f75032c8665ce7b04b88c28b1ea61f1ace20ff87735ceac8ebff21ce2769aefd6e462c71a55150b775178016c4367d3a0772c6074ccababa6af341859176f0899d58047d4e95913a944acdca04f14c985d11118f0e9c07da419d22a1b3b249c00c61d0016ebd48c00000000000000000000000000000000000000000000000000" - }, - "result": { - "gasUsed": "0x924748", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0xbac6d7be578578bc26e152bd6d6daa4a9e87f38bdf6b42eb34a1fa0436188dbf", - "transactionPosition": 31 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [ - 0 - ], - "action": { - "callType": "call", - "from": "0xff00000000000000000000000000000000240720", - "to": "0xff00000000000000000000000000000000000004", - "gas": "0x47d5eee", - "value": "0x0", - "input": "0x868e10c4000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000082c8808821a00240720197b818058204fb7698ad0ac99ea2b79cfa2feaa9b606fb14a18877cfabe989d204b529fa8b958201e32feaddd3941fa6bc24f9608e4aaf72e91fb8c668ab31bd76c8a22f8c57243590780b1f9d380ba870fa437208ac15878ab89d9f34543419b3e34eb4c0ffcab1dd0992538a2d9071e62187bb0ff89a3e2af7e80975593af8b408966223d147eedca680aebdd1e5bab0e28ef8339ffb595adbaf18b2d96f245274e15cfdd799db6e3a4034db1b7497bd552b7f38cd4b215e241c9988f3640325e3e4001cc21d2cd8bfcc05f2e21f8845d235d866232c134342f99eaf59c7ebc23005f57b27254bb8d7d7648a4364ad5521203158f4a2aca6046c19beb35782af2b2f3b05e4054cc5a5982fc8035f2b2e5bad7763f27a2ff9045af7149ec036d283304f6d2f24e63438b862089e498d000357170027730c997aa9882ac929ff2054cafece481a559624985d64edbf24eba8e157e7b98d14f2bf9a39a53e88862cdc4c613ccebcfb497600591a0a9018199adc75cd6d4b3c20e910d8e20aea5c1eb09609581e8ae4ab6fdeb211c974a031d6e54ce46df51beecbf879b41106c7414eb1cc5b04fda2c51a3531c7f04709c9e22f613c3d3b7ad4f5e7e781f3b42545baa78bc2493253841bcad24670ba011f5ba63fcc93e2d067f4fd0f1b614244aa5ef3841c10b7cc1a74cc81c9782d0ce1ac66177215374e7a1aea72a75e2e2bb9da9a6192b5e83f3bf67d6d965cc6a71aa5d830712576370ab75f0b7b6fc30ffb3cf538d1f9ee8447b1612b6a9c367ed3f20311c673c413c51a5a4b17bcabf9387231a5c2114b7b53f8aee48ac87a5ad6c6423c5ee5b14c54a4aa25edc9889e8246fc3b1d57dcbec40489d316220fc885f960f9a0f1b726b170a0bb338f6025eddd39e8dda03087a123f8932ffe6f36555ef77d5fac549d71a600f443ec3ad1f148037da3e1a8174cafe06935278299e757623eb47f0135f0b75b68438c64e93676fca8fdb911d7fa4768c03a37ad1301eccf1590446ab692118005081a29cfb985c9d924f5bd657bfac0e68a82a191a5ffc09122189493329c1ef4e363e9bce42884fac89c5b1e253d6ae3ca4d306ec9e2ec762950a9752d00291c5c57417006d9aba659a30d9b2b331ad8b860adc960e7597697e7c5bbf6995629d11e3c48b7b260af7a0505eada5628158b9e64913ad94aed332e8102eddad06c33064c68eb46662ea33655673f00ed13a5d5ee4f59943c4cfb1cbef7773ab83d50e98c6098e431b0a5ecd0e1a48d8740818809c0bda28094d6cd0999cf27c3166eba8f0842e81b4fc28f3e3340b3c0a321942f6d75cdd91ed3fba60b2cb7b2d5278c2f4e7a5ee24def2a35054b8d3091d554c985a2d53301b7558373ef8ce80bca45cd25e1a5357811d816a6ebc50fe673d5273453407f058311bfb9bcdd0d7d08d58a6da2ab1bcd5916ce7b2bb5ca34d875957c53712a9b1950220e8ce327d3c8a1d308dae2830620e9dc5e1f1d385fba934ff856b4627fa732ac1ddaa43b572089da4a6bee77be2cc1326fd910edc12ec05eeea0930b31806eb6143e89e024d51e1f90829f4d0189d29c414b84e0efc6a6cb9aef21257c6652c05ee743127c77d5946e48aedcb71a8ab3bb5ad4e815d882e9d397cc6d3b39644939d3085afb44969e77ff50629a356bf1caba25d9eca119afb0db5c351018d98b6b7de8d9ccea9fdd725295f738c2c8e9522d95ea2171db0c927976321c3651b67da188e509b4c64368ba8989efff9eecbaa2dd4b3e24fb0181adae49c248f6f523a5ff98a4c2d53ecc8042a8ff6863f41174cd04958fa488513cb38afb23a50f922cf247701b63ed1bf3b76d22eb64b38f39280195c59321bca0f4988251f886e16e9d6cb28eb13b1bcb33a5defe754ad4fa7554e54b9e3d1bb419f69b8e8aae47ed6e090541503f8c5ba540fc8eecd09755f1304266ebe5add110b0c10ecd0b6cc31313fb341c4beb9ff6b6801baa267f8be3f99545966ea7d36df8d78d8a7b764e2f142e7143043ba3a3fe3ad4e1172975e7e5a16920a21ed3e173b0fd4a2d178f124829c312bb34b8a086c661db0cf539643f06f9321f0ddab3443a54041dc26fef93072b60025182b4c319ceeb27490c3fb1895be83f081812a7e442f041c803636dc7bd7af7f91772485de16a3a70a6423441af2d578060152c6978b7f5eaf265f96aa593c6b8988ef6abd7f0c211685ebd15f4691d81a9b61458123e8f3222dae3d7eec64d3d793f8bcb89ff0957ca775a89e2143167d7cb39ebd4ced7f5ff6d5777e07feb6be110c0236f316ac919c2e4fc2bbab76ea38720a16a5e5b2d4bf40a8578daf93a7b78394ca172c71be8eb9d9f7872a31e25ce6ae6905b2761cc870eac7122db2f6a2462a0f6d230c3ea41b162f17567030cef113fdcde321e3b5d33a77caf6b4429ba578f598c7a76dc604661233e1d55b99359bfc5f19d67a64b6b04d36f99502fdb41c7946ac73b44c9ec05bbdcba1f76f1ecc660226fbdc1e225854866849e918e849736f04ac6c084294a822d73059f9fe80af1b41762b2fc84e140674c1864599dacf5ed6ad074e1095366a4918b0dad7abba9b24263bbf5e995ced17f759c6978d54af02a9b9c78d057896ee884d8df1fc163ef03b38d449e7f45cc41d831d132f75032c8665ce7b04b88c28b1ea61f1ace20ff87735ceac8ebff21ce2769aefd6e462c71a55150b775178016c4367d3a0772c6074ccababa6af341859176f0899d58047d4e95913a944acdca04f14c985d11118f0e9c07da419d22a1b3b249c00c61d0016ebd48cd82a5829000182e20381e8022004dd1fca6c24f41664d592050ba2076cd6039cda423fda286e09f819521aab4dd82a5828000181e203922020077e5fde35c50a9303a55009e3498a4ebedff39c42b710b730d8ec7ac7afa63e0000000000000000000000000000000000000000" - }, - "result": { - "gasUsed": "0x2ed1ebd", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0xbac6d7be578578bc26e152bd6d6daa4a9e87f38bdf6b42eb34a1fa0436188dbf", - "transactionPosition": 31 - }, - { - "type": "call", - "subtraces": 1, - "traceAddress": [], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000002d0792", - "to": "0xff000000000000000000000000000000002d0798", - "gas": "0x4925b8d", - "value": "0x0", - "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000007000000000000000000000000000000000000000000000000000000000000005100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000787821962a95907808265626909a4ea50de70b5e4dfaeeb5b403aab71f3409491deb25fb3c4449f50fe2bc1a5537e4b2608e03546eaa9d29687514caf1220dcb30c1e835079e713548f6740668c61e52d683dc8b67070abd42b15a531ad8b40175096f86a2c8b2ec702e738164a93493f95163c8c21e7e52868bf0fe9c5b00f8990f3f7ceada05f43383b91396b367551323e598bbdab72e18520442cea081a512f194206860173141bde8125f3e07bb1cf4d5fd549d25a0d4c88ef18b36556d3bc244e193bdd09d2875d98ede100bede4df18c21c96fb3cd453a1f3f93a0fdef627cca54d991ca62939ab2da07ae3aedc77fc02f7adf0bc2ab8fc7a2dbdbbe43822a6614121339caa742d899a8cec7b24bbecb74223453a797fb13d2a02fa5d9e64bea767015e9b204e07f623f6a3fa48d963e1862f6b3621aace85bcf136cfefbd90d7da18acd89a80095c8c5d69e81fb87509d591346c2b64a4b00034d547ae2a2434e2581d941e66b13ee33c24e4c6855b89e714903fa8144a973bb15845b611cc4b35a31b9af98a9dc2008efd63b66010cce307a8d0e0cb4ba546696799bc982100eb2a084d8ba0d2298be4031ee9be274ac56c4a54180db55f3c7d2a08ef23d62f32b77464cff82a20d207dc7d77088d48ac3be042b89b5474e5d3e01707df11c3fdace110f092303e87dbbf1fb68525eff8e9c6e3d2c707b1607df2825f9185347c8ea30bee636482051aac9a3186db603d47dc197b7f6b6641f8f04dc677a706ef7612b0319e64fc6054c264d352df72a8d0e5215e01b5e8df18a5c57f858ee2551ed283394b8a9ba8af2f2e5e2893af0b1eac66f7392bc5c03db6dd4ca92b79a14376f4708a9e02d2754869a5fad8c2053d04c23870d894b1f544bc24c52317b96c2cb3c48819dcef816df07139213c497e0ab81161b3edf503f2b0cadfff3a50ea9dc330014d31667bcaed035df0baf2e5f61d030c0990cb0632297193fd1388b3e1e92e14dfc50a3ba223014083e1b5b0dc441895eb44fb49fe1c05d0fae11eff6345c78a0f5ec196853cd23491bce4f19d4ca991243608a435e5237a4f42e2809912c934374d56394f5f43be0ac3849a53b5469ceed1885089c11cb32249426f9783e01a233d8bb9214b2156bd17e7e2bee7bb38520aad22aee9b7c1632703135717bba0f2f96df3af123c738c90bd19d8965bb21530f87ee8e8e0d3d1b080c456f910c499416cf83efa02a2f15f1a8fb49abc46f09456a809d1cb602dd97c45c8f35b1cd44c41a030d883d30764fd55953aa8c9d01a706cffea8a758bde4b13eb25338eafced8bfd2cdfe50d008a55849dc87554ad043e4598700c1c8909f1fe87ada76c4928a51c4a1cc4a70d4ffce0b1f0ab0aa1a71c8d8f59d3b1fb23a3293b55984d2f0bc0a9fbd1b5c2aecd8c50bd298d17f3940f287d128e62e09785a742f37ea7dca45bdb4e4aeb1885f4492f87799a30600051f8246b7b51fa9c67b2862019515e8bfc96177d185c55c69650e9fa4109ca4118f54cba2d2c425b4a59b60af44bcc0d183103185d7018f4e7835311a615e4162e6988e8722c752a221a87a4821fc2b7347cd18953e62a394972f913aea13a7af23a080800aee625613c3e82a45b1b788e023e6954c2f01d1cf0e6036c9a2ccf3e62c76eaf4795ff9f2546ae0347681aada949ac906bd3a1de1e07a9a62edba95110aeefbc36681894891a4a834826ff1a83cef05e707616fc89690fff9968798fbda3a53d84ef6c8a1a14e215ba888759a872c6ce00a5574c6de5eaabf613c83cf931a259230c371f0d99ec4716b8ef8901a254b12960c073f3dbdda8f9cab5a901cd8c0ee25127a43497f48e7d137d9113e3e3e6703db8aa3ddf0259121a747e3af4eaa99bd81bc89e8f01a9ec7ffef3d16003bfa686999882c2f04945dc1a23af1ee3454e20af05849310a51820069ad852446beb0315bc621a37a20db633b68659b4efc5e9dfcf9633ecc623d691dbccea398ec18581df9172eb1111f374974f1f455833dea61076684404d22b455a0cda3dd693170362e5709df55aabc08e74bd72bc667864f7d1e43d8ae4ba7131cc95584d0b0fd50e4468be82a9ecdbd22e196d47626ddc3aab7ed44f075e6b75123877609e5ccb0f06e880927d1d7ef767abb799e76335a6731719a0c45b970919652ec1345c6aeb3cba067490778aa13114dc3ace9e9c344059e00a41138130670c8aaa8e9caeb84bfd93ad31f17c8f8adf749f9952dc66992720ed9a7c418b7997c6b7333d17d625156c3401379625a57b221291f34b70d860cf19f7ab19fd6f368e5f8a5b6ae9cde87e77036e3a3419868e9da66c4586a15124dfb129a843552fb4ee6b547a330860e4853c1e6756166583bad1cbc0f1237b2cb83b3360b0e4cc99f573fa80b8ff45553e90ebf37e81d7e5e607e0588938e7688c35c074b450f658b799876b7e747f916e4b34e5c528ead2f62bd8b5fed53a3109018120998b9ed887ecff9f3b4f9bafb07aad375e2932572cc4dbfc74eb914a9312da02e733d82a0ee714c267539a6b098d2b4b3d5c72a78b94cdab5bfc68c211d416527daf7ed999906bb2ba6d6a12f721ac588d2ef19aa32ff39a3911e16934a67666bca132a0e2cef9145053becfa4cb0998ec5a101d15276d79029516d3091078d61fc8f4ac3a6329bf79474af2680c599507a9682f2416968e14b2d39b00000000000000000000000000000000000000000000000000" - }, - "result": { - "gasUsed": "0x97deb7", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0x1c497ac8ca436d3bdf10d78a0b639383046a81cbe079c6af5939c4e235a8cfa0", - "transactionPosition": 32 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [ - 0 - ], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000002d0798", - "to": "0xff00000000000000000000000000000000000004", - "gas": "0x42b55c1", - "value": "0x0", - "input": "0x868e10c400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000008318808821a002d07981962a9811a045b28e258205e3d639571a49f742b43f2f1c00218f4564577b58a3472c5e3d98a8e0d7838ec582019705c311b3d19f529f3c2161e7b3a08ffba8b20e0a434e7dcf4c04ea43f79345907808265626909a4ea50de70b5e4dfaeeb5b403aab71f3409491deb25fb3c4449f50fe2bc1a5537e4b2608e03546eaa9d29687514caf1220dcb30c1e835079e713548f6740668c61e52d683dc8b67070abd42b15a531ad8b40175096f86a2c8b2ec702e738164a93493f95163c8c21e7e52868bf0fe9c5b00f8990f3f7ceada05f43383b91396b367551323e598bbdab72e18520442cea081a512f194206860173141bde8125f3e07bb1cf4d5fd549d25a0d4c88ef18b36556d3bc244e193bdd09d2875d98ede100bede4df18c21c96fb3cd453a1f3f93a0fdef627cca54d991ca62939ab2da07ae3aedc77fc02f7adf0bc2ab8fc7a2dbdbbe43822a6614121339caa742d899a8cec7b24bbecb74223453a797fb13d2a02fa5d9e64bea767015e9b204e07f623f6a3fa48d963e1862f6b3621aace85bcf136cfefbd90d7da18acd89a80095c8c5d69e81fb87509d591346c2b64a4b00034d547ae2a2434e2581d941e66b13ee33c24e4c6855b89e714903fa8144a973bb15845b611cc4b35a31b9af98a9dc2008efd63b66010cce307a8d0e0cb4ba546696799bc982100eb2a084d8ba0d2298be4031ee9be274ac56c4a54180db55f3c7d2a08ef23d62f32b77464cff82a20d207dc7d77088d48ac3be042b89b5474e5d3e01707df11c3fdace110f092303e87dbbf1fb68525eff8e9c6e3d2c707b1607df2825f9185347c8ea30bee636482051aac9a3186db603d47dc197b7f6b6641f8f04dc677a706ef7612b0319e64fc6054c264d352df72a8d0e5215e01b5e8df18a5c57f858ee2551ed283394b8a9ba8af2f2e5e2893af0b1eac66f7392bc5c03db6dd4ca92b79a14376f4708a9e02d2754869a5fad8c2053d04c23870d894b1f544bc24c52317b96c2cb3c48819dcef816df07139213c497e0ab81161b3edf503f2b0cadfff3a50ea9dc330014d31667bcaed035df0baf2e5f61d030c0990cb0632297193fd1388b3e1e92e14dfc50a3ba223014083e1b5b0dc441895eb44fb49fe1c05d0fae11eff6345c78a0f5ec196853cd23491bce4f19d4ca991243608a435e5237a4f42e2809912c934374d56394f5f43be0ac3849a53b5469ceed1885089c11cb32249426f9783e01a233d8bb9214b2156bd17e7e2bee7bb38520aad22aee9b7c1632703135717bba0f2f96df3af123c738c90bd19d8965bb21530f87ee8e8e0d3d1b080c456f910c499416cf83efa02a2f15f1a8fb49abc46f09456a809d1cb602dd97c45c8f35b1cd44c41a030d883d30764fd55953aa8c9d01a706cffea8a758bde4b13eb25338eafced8bfd2cdfe50d008a55849dc87554ad043e4598700c1c8909f1fe87ada76c4928a51c4a1cc4a70d4ffce0b1f0ab0aa1a71c8d8f59d3b1fb23a3293b55984d2f0bc0a9fbd1b5c2aecd8c50bd298d17f3940f287d128e62e09785a742f37ea7dca45bdb4e4aeb1885f4492f87799a30600051f8246b7b51fa9c67b2862019515e8bfc96177d185c55c69650e9fa4109ca4118f54cba2d2c425b4a59b60af44bcc0d183103185d7018f4e7835311a615e4162e6988e8722c752a221a87a4821fc2b7347cd18953e62a394972f913aea13a7af23a080800aee625613c3e82a45b1b788e023e6954c2f01d1cf0e6036c9a2ccf3e62c76eaf4795ff9f2546ae0347681aada949ac906bd3a1de1e07a9a62edba95110aeefbc36681894891a4a834826ff1a83cef05e707616fc89690fff9968798fbda3a53d84ef6c8a1a14e215ba888759a872c6ce00a5574c6de5eaabf613c83cf931a259230c371f0d99ec4716b8ef8901a254b12960c073f3dbdda8f9cab5a901cd8c0ee25127a43497f48e7d137d9113e3e3e6703db8aa3ddf0259121a747e3af4eaa99bd81bc89e8f01a9ec7ffef3d16003bfa686999882c2f04945dc1a23af1ee3454e20af05849310a51820069ad852446beb0315bc621a37a20db633b68659b4efc5e9dfcf9633ecc623d691dbccea398ec18581df9172eb1111f374974f1f455833dea61076684404d22b455a0cda3dd693170362e5709df55aabc08e74bd72bc667864f7d1e43d8ae4ba7131cc95584d0b0fd50e4468be82a9ecdbd22e196d47626ddc3aab7ed44f075e6b75123877609e5ccb0f06e880927d1d7ef767abb799e76335a6731719a0c45b970919652ec1345c6aeb3cba067490778aa13114dc3ace9e9c344059e00a41138130670c8aaa8e9caeb84bfd93ad31f17c8f8adf749f9952dc66992720ed9a7c418b7997c6b7333d17d625156c3401379625a57b221291f34b70d860cf19f7ab19fd6f368e5f8a5b6ae9cde87e77036e3a3419868e9da66c4586a15124dfb129a843552fb4ee6b547a330860e4853c1e6756166583bad1cbc0f1237b2cb83b3360b0e4cc99f573fa80b8ff45553e90ebf37e81d7e5e607e0588938e7688c35c074b450f658b799876b7e747f916e4b34e5c528ead2f62bd8b5fed53a3109018120998b9ed887ecff9f3b4f9bafb07aad375e2932572cc4dbfc74eb914a9312da02e733d82a0ee714c267539a6b098d2b4b3d5c72a78b94cdab5bfc68c211d416527daf7ed999906bb2ba6d6a12f721ac588d2ef19aa32ff39a3911e16934a67666bca132a0e2cef9145053becfa4cb0998ec5a101d15276d79029516d3091078d61fc8f4ac3a6329bf79474af2680c599507a9682f2416968e14b2d39bd82a5829000182e20381e802204c7ab7b39b702b0f85ccccf1ab3be3cecfbcca6270f318e87139e54034536273d82a5828000181e2039220203e1051280d09281ef100474edd98804234de5c26e1f9b4f2fe44fc6b0f764b04000000000000000000000000000000" - }, - "result": { - "gasUsed": "0x2f02cf2", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0x1c497ac8ca436d3bdf10d78a0b639383046a81cbe079c6af5939c4e235a8cfa0", - "transactionPosition": 32 - }, - { - "type": "call", - "subtraces": 1, - "traceAddress": [], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000002d0792", - "to": "0xff000000000000000000000000000000002d0798", - "gas": "0x526a0ff", - "value": "0x0", - "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000007000000000000000000000000000000000000000000000000000000000000005100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000787821962ba590780ad996281f9ca4a29d9500e079f0250a570058f543c381fd8ff5207dec9eb29a21cf732e00a9eb5fc8ce8b92c68151f3ea5a8dcee1ce14acbc24ae6bf934d64e15815bc9f77f315e05c00432ebbadd65e756658ac51f723ab9fbc7f18d4ddef6b17d65d1d29f5c20bf130c8961f470eae522d4d7d4dc28fd743b26f887c9a9839228f2aa8617759e82bbf05b981dd0c2b8445502cb8b61250cb5b8d01c79b033712bd91da570437900ca7f3162e50b1761db4a01a33a1772e8e7650754c3f6a3ba979537cdf00b7754517d53fc5e18d882ae7117ba605ccf1d721e3ea5140a73690ee33308bf5a4fe2cd26c63ab034bfeb90f7df81b484f729a56493faf1040f97c2c85ec1020fd161cf9e0c8c1c065bfe31807b8784d5fd0f68c698f91229f700987a0ce6a73bd01fc4d460a72cfdd968493b612fa80e40a57b149e549d1efd97db9c8d1536af9b1e70fba2d395c7d50a514b2278efb333df538fe508f53d4cc292d2af34a41caf2eaa162222154d676730b10b10f9d118a064b8315418de4dfaaa8676c0d845e271c4f6d2f0a39db3f400434354a85d5fc8bc2bf7809da7623eef18779dfafa0e608c00871be55b43891059115ebbaddecb8b8d2862999e8b8803111718f8a23effe8ea2d666bed91ee98a3c36d6b89c9c90faf1576703b1e90b9b4164c1ab5c4daee51b26f1e556b4c2fac68125c293a77808e780c08bcfff3939ebd5eec5effa30f49d5860ca1bc890f610404430af070a43862d00ffad5de51d8701138d0cc46b856104fd792a08ba470d441992e282877b7ccf31844480ae2b679357e12c96b95620b64bcf38b27944f9ee4bf8768c9966973d3f40f6575e45a2dec3509de997e50445251a16608f299e2d8b3e71618942a15fc35ea5eb48da6a6600f1fffbf7e5b953b27f857e9773ca7ffc9dbc14ec275e60471c2bd60d07b74585bc5a34b93f36658600b57bcd4901d436374891e76826790619d555ca2dd736aedc7150f3f237345e77508e8dc3dc4310b36cad0af9e65a7a105c4b688eeca266d381d70f589348de350d0f19e7560a321282e17f954a6196322945b60efc03e30afef8d77d3fc1dd2b37f0d412d070edeed492eceabc80e650839f7b0e35ca378c689644dcc4245e08fc2fb1b6005fcce45165107ef7595ac4238af11527076d10e0fe0d0f09e7cacf37a60d9f7ed0259573911c85967b143e472b1079e14ce53f8bc32c7379a667792baca4475d11ffca642f2d780b1d14fef9e9334679d2a12e88b20870a1b7c6320ea9825b4e43cb92c048d3e2380f28232eaceac0872bc1f7a105fa8791d91832003b25e6cc7a9c7b4aeb3110a2a5ee2c973eb5a80b91a73fa6f229913b4105fba4130b66fee5180beca7baa641a2c4e5c2e6cf634bc48d90ac29df2255b199ed35668aaf0da3623882bc8205f2ca636ca57d421362ecb5ef30116a0116cd11ed69d8370738c0189ffdc4643ccda19121bd491092f5ae8a4fc41011c632a77ccd63ce37ce06ea0b6f1fe85e8708752916d3e4452933112c24c0cfdfd277d6f8380faba68b7895890a935554aa91a5b36e1530c9cf70dcece8ae88e0c5a6a57393b525df9776e959c1e2c3dde4a29bf9fb75fd97c09aeffbe6db0470e4a2643e67a3b853a8b316a486746132475b086318fb4544aa997ea1b029277e735e1b5594c143811c2b516042ac4dc03ca4fbe35b8ebaa09a29077e3781855a0c8d2145fc8265ace6acd485b54d45637233c4b802c20909601743f4ec91a511dd1c9a40151c1cee99ace0e96fc9899a95167bf257a296870409f20db7cf21d196c17d3b129a77ac87501ed12703bd1f3099f7c1a13522148184103faeac28ab678d0f0a147ecf4df1f500be9247273f7509964b9030579626584f64f121fa58c2553f3f9b4ce40f222fd6f0e6ce54474094db8dedd92f9fd120ab7e9ac7cd8b5c54380cecbf1aaed48e2168c1d6103c663abbadc136128e5ea5d92a9f20b9829c623d416a7bf3fafefacb698389f86beb29622dcca8a1196f72bfd67519413bbd81d5301435d8a591acbb2ad327ecec80ddda5c4a5ab366565db6b8c5fe50e2149a23d54c3969b397ca76981b100d57242378214addd615783e11d4be7c9581bdedafe3ea2d75960bb1f45337e4a8468ad1392e72b77bb73f9f72878441834c67488d6d094731df676f4f51ec0deed3c2d0669a02accd31a7bedd6fa9832cc62038aaf19253b18e9fbdacd86b15cfef36e9a6d23de4ff82b8ef728903b755295073b6c082b64793278b973a0c3059ace8e6e374fd0a4c0c81e8c40acfb96043d56e361c1c51542d0eb119cb806c19291c07e70e5ec730c251c32dfde23d16db87e8abc640fe068234cd9cc8327c68102028218fa8e804694dce870992a7a0b93763c76da0dc8973872c1bec2eacf36350d6fc6ce3e83380539c5c28ff24eb52827157e585468cc0a89a934dbb6b15c5bc889979f6ebddc9fb7e1fc68d5ddeacf15de8fa67f741acb10f452e47d04592810872d8d48ca85c4aea240fe825d6ccaec9faac8ba8a8b91d60a645e0656da4c0dc0cde1bcd1218c301e16881f782d91a57aa5e063c03762184110973924c9ecf4ce91c56691500f9251d3e528dd75bdde769d4f8e04aa7330362cf1bf2f1029ef25f8eb27059fb9d30d438d77cf63f364baf4744d243acb5966b79c5c35f74e8695c5b58469d00000000000000000000000000000000000000000000000000" - }, - "result": { - "gasUsed": "0x97d42b", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0x8ade285038462b2ece31e2ee0ff1af1448e9cf8e07520f04618b5b26436a73af", - "transactionPosition": 33 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [ - 0 - ], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000002d0798", - "to": "0xff00000000000000000000000000000000000004", - "gas": "0x4bfa5bf", - "value": "0x0", - "input": "0x868e10c400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000008318808821a002d07981962ba811a045b2bd658200614498463ce77b23bbf18bfd324b0fd4d5b102634806020ccff3f8c78f955b158208eb133c33fa726942d694d350b629cf534788e290162780485829b4a70160907590780ad996281f9ca4a29d9500e079f0250a570058f543c381fd8ff5207dec9eb29a21cf732e00a9eb5fc8ce8b92c68151f3ea5a8dcee1ce14acbc24ae6bf934d64e15815bc9f77f315e05c00432ebbadd65e756658ac51f723ab9fbc7f18d4ddef6b17d65d1d29f5c20bf130c8961f470eae522d4d7d4dc28fd743b26f887c9a9839228f2aa8617759e82bbf05b981dd0c2b8445502cb8b61250cb5b8d01c79b033712bd91da570437900ca7f3162e50b1761db4a01a33a1772e8e7650754c3f6a3ba979537cdf00b7754517d53fc5e18d882ae7117ba605ccf1d721e3ea5140a73690ee33308bf5a4fe2cd26c63ab034bfeb90f7df81b484f729a56493faf1040f97c2c85ec1020fd161cf9e0c8c1c065bfe31807b8784d5fd0f68c698f91229f700987a0ce6a73bd01fc4d460a72cfdd968493b612fa80e40a57b149e549d1efd97db9c8d1536af9b1e70fba2d395c7d50a514b2278efb333df538fe508f53d4cc292d2af34a41caf2eaa162222154d676730b10b10f9d118a064b8315418de4dfaaa8676c0d845e271c4f6d2f0a39db3f400434354a85d5fc8bc2bf7809da7623eef18779dfafa0e608c00871be55b43891059115ebbaddecb8b8d2862999e8b8803111718f8a23effe8ea2d666bed91ee98a3c36d6b89c9c90faf1576703b1e90b9b4164c1ab5c4daee51b26f1e556b4c2fac68125c293a77808e780c08bcfff3939ebd5eec5effa30f49d5860ca1bc890f610404430af070a43862d00ffad5de51d8701138d0cc46b856104fd792a08ba470d441992e282877b7ccf31844480ae2b679357e12c96b95620b64bcf38b27944f9ee4bf8768c9966973d3f40f6575e45a2dec3509de997e50445251a16608f299e2d8b3e71618942a15fc35ea5eb48da6a6600f1fffbf7e5b953b27f857e9773ca7ffc9dbc14ec275e60471c2bd60d07b74585bc5a34b93f36658600b57bcd4901d436374891e76826790619d555ca2dd736aedc7150f3f237345e77508e8dc3dc4310b36cad0af9e65a7a105c4b688eeca266d381d70f589348de350d0f19e7560a321282e17f954a6196322945b60efc03e30afef8d77d3fc1dd2b37f0d412d070edeed492eceabc80e650839f7b0e35ca378c689644dcc4245e08fc2fb1b6005fcce45165107ef7595ac4238af11527076d10e0fe0d0f09e7cacf37a60d9f7ed0259573911c85967b143e472b1079e14ce53f8bc32c7379a667792baca4475d11ffca642f2d780b1d14fef9e9334679d2a12e88b20870a1b7c6320ea9825b4e43cb92c048d3e2380f28232eaceac0872bc1f7a105fa8791d91832003b25e6cc7a9c7b4aeb3110a2a5ee2c973eb5a80b91a73fa6f229913b4105fba4130b66fee5180beca7baa641a2c4e5c2e6cf634bc48d90ac29df2255b199ed35668aaf0da3623882bc8205f2ca636ca57d421362ecb5ef30116a0116cd11ed69d8370738c0189ffdc4643ccda19121bd491092f5ae8a4fc41011c632a77ccd63ce37ce06ea0b6f1fe85e8708752916d3e4452933112c24c0cfdfd277d6f8380faba68b7895890a935554aa91a5b36e1530c9cf70dcece8ae88e0c5a6a57393b525df9776e959c1e2c3dde4a29bf9fb75fd97c09aeffbe6db0470e4a2643e67a3b853a8b316a486746132475b086318fb4544aa997ea1b029277e735e1b5594c143811c2b516042ac4dc03ca4fbe35b8ebaa09a29077e3781855a0c8d2145fc8265ace6acd485b54d45637233c4b802c20909601743f4ec91a511dd1c9a40151c1cee99ace0e96fc9899a95167bf257a296870409f20db7cf21d196c17d3b129a77ac87501ed12703bd1f3099f7c1a13522148184103faeac28ab678d0f0a147ecf4df1f500be9247273f7509964b9030579626584f64f121fa58c2553f3f9b4ce40f222fd6f0e6ce54474094db8dedd92f9fd120ab7e9ac7cd8b5c54380cecbf1aaed48e2168c1d6103c663abbadc136128e5ea5d92a9f20b9829c623d416a7bf3fafefacb698389f86beb29622dcca8a1196f72bfd67519413bbd81d5301435d8a591acbb2ad327ecec80ddda5c4a5ab366565db6b8c5fe50e2149a23d54c3969b397ca76981b100d57242378214addd615783e11d4be7c9581bdedafe3ea2d75960bb1f45337e4a8468ad1392e72b77bb73f9f72878441834c67488d6d094731df676f4f51ec0deed3c2d0669a02accd31a7bedd6fa9832cc62038aaf19253b18e9fbdacd86b15cfef36e9a6d23de4ff82b8ef728903b755295073b6c082b64793278b973a0c3059ace8e6e374fd0a4c0c81e8c40acfb96043d56e361c1c51542d0eb119cb806c19291c07e70e5ec730c251c32dfde23d16db87e8abc640fe068234cd9cc8327c68102028218fa8e804694dce870992a7a0b93763c76da0dc8973872c1bec2eacf36350d6fc6ce3e83380539c5c28ff24eb52827157e585468cc0a89a934dbb6b15c5bc889979f6ebddc9fb7e1fc68d5ddeacf15de8fa67f741acb10f452e47d04592810872d8d48ca85c4aea240fe825d6ccaec9faac8ba8a8b91d60a645e0656da4c0dc0cde1bcd1218c301e16881f782d91a57aa5e063c03762184110973924c9ecf4ce91c56691500f9251d3e528dd75bdde769d4f8e04aa7330362cf1bf2f1029ef25f8eb27059fb9d30d438d77cf63f364baf4744d243acb5966b79c5c35f74e8695c5b58469dd82a5829000182e20381e80220de75946bac8d0de4cc507110766a5f17cc19b30c8d0b0a55cf1b2b9afa65c939d82a5828000181e203922020d3ed4d594376bea8a01dd6414cca63e8f5165f21ae5d3ce86714ccca0f03f504000000000000000000000000000000" - }, - "result": { - "gasUsed": "0x3662a3c", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0x8ade285038462b2ece31e2ee0ff1af1448e9cf8e07520f04618b5b26436a73af", - "transactionPosition": 33 - }, - { - "type": "call", - "subtraces": 3, - "traceAddress": [], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000002ce375", - "to": "0xff000000000000000000000000000000002ce3b6", - "gas": "0x32202fd", - "value": "0x0", - "input": "0x868e10c4000000000000000000000000000000000000000000000000000000000000001c00000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000007081818708195c92d82a5829000182e20381e802209d8f62a071341cd01d80033faa7c9ef07f7e1d8fbe534b0f811772b3fda0b23f1a0037e10b811a0454b28f1a0047eb4ad82a5828000181e2039220203c71b16224218e29bc29672e8db69ba0c012113a8b5d8cf2220c261f2a0b4a3d00000000000000000000000000000000" - }, - "result": { - "gasUsed": "0x222e801", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0x0ab1e62d5a67bd84621c1419f136c59115731ca47ba1da49e589760d38c95b84", - "transactionPosition": 34 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [ - 0 - ], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000002ce3b6", - "to": "0xff00000000000000000000000000000000000002", - "gas": "0x3169cd6", - "value": "0x0", - "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "result": { - "gasUsed": "0xfabb0", - "output": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000418282581a000286387ff107ef952f3612eb2e2a6606418e51c0f2cceeda5f57011731c2a04034dd33df402838a815a0b69ec1184b8c104a0001c0dfc71cd077c4b900000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0x0ab1e62d5a67bd84621c1419f136c59115731ca47ba1da49e589760d38c95b84", - "transactionPosition": 34 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [ - 1 - ], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000002ce3b6", - "to": "0xff00000000000000000000000000000000000004", - "gas": "0x305d78e", - "value": "0x0", - "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000009000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "result": { - "gasUsed": "0x105fb2", - "output": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000578449007d2903b8000000004a000190f76c1adff180004c00907e2dd41a18e7c7a7f2bd82581a0001916cb98a2c3dfb67a389a588fb0e593f762dd6c9195851235601fba7e16707ee65746d4671e80aa2bb15bc7d6ebe3b000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0x0ab1e62d5a67bd84621c1419f136c59115731ca47ba1da49e589760d38c95b84", - "transactionPosition": 34 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [ - 2 - ], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000002ce3b6", - "to": "0xff00000000000000000000000000000000000005", - "gas": "0x2f3c21d", - "value": "0x0", - "input": "0x868e10c4000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000f818183081a0047eb4a811a0454b28f0000000000000000000000000000000000" - }, - "result": { - "gasUsed": "0x489dcf", - "output": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000002e8181d82a5828000181e2039220203c71b16224218e29bc29672e8db69ba0c012113a8b5d8cf2220c261f2a0b4a3d000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0x0ab1e62d5a67bd84621c1419f136c59115731ca47ba1da49e589760d38c95b84", - "transactionPosition": 34 - }, - { - "type": "call", - "subtraces": 1, - "traceAddress": [], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000002ce375", - "to": "0xff000000000000000000000000000000002ce3b6", - "gas": "0x4abaf3a", - "value": "0x0", - "input": "0x868e10c4000000000000000000000000000000000000000000000000000000000000000700000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000078782195b555907809884d69eeaf1026b549be04d95a7ba703a48494ebdf41d17bc31dab9d7c1a4eb9b8cb0b88cd2e55617338b8c5a58d8feb896df6dfbac66c6fbc56fcc0f17a40fb03339bdbeaa70f0c1de65480e3ddddf1ae71d64dedfd200c263962df45b20761265f747786fac48c4e890695c371113ec4175b2234fc81296ac9116c9601332b99d0206321e58b16bb0d7a69e49a26a96ee1f0b811031d79fd51dd6d0e4498cc7f65e385df4287d72ed0a4a4dbe019a545abb5ebb5f170058a18293f41ed1228286e4110fdf37e6e04aaa156bcff40c7038ed380fd8eaddc864faf7813ea3349785566d1f911b529990df437c2a4f929622ce6571923fb7087485996562785a046a44d48847dd949522a274d3fbef4bf000b9c9979d64df5d77c0efa6c5e4680e457e02379f7d9da2a35de7dce57aecdbc696d4f2cc02d802117a371bd456af945be6d3e9e2146b25c30b7da50ce4dea9055fd1c429ed8ff3b6a7d14f16fe20d8f9ec8802abd3b8f61083c1f05cf3c31369d29938ab21e1a0aefc1f5f2eef4c8ccf3cd9d89a930697c9c9e0042ac63288dc70b9bd5d86e36fee93d5e2eef36a89e3db13cd6be53c37649a0e287ad71caaa0cd08dc46b5c93952d6a9540408017688facad9f4e7c94a24f80a7e8abe7352222a51a82c80a5cf3b0d11f1b071d20b5c67f2c120b8a7f722abc21fab82893fbc206501e4a5b8fd3e881ab39b89ec931bbf3dfc2f9ffa139c9a518552ffa4b9547d88a53cc7947f6c8145c33ae6feb9bfa3d7ed81a7df09827e16033ef88d6990fa158a56750d1f554de9786bb2fa83fb593f11b7747633ac9978c6991256e5fb0ca9a7a70b26fad4d27baa338e8eb3ef2cc3321361a7b44b84b5a5093dc6a04883c5843d2125797cf4be06297cf765eab5b11a2d2c5f3ade90a787ea41b236e1c4291bd547de7c16ed4046576ba515ba1786d16a9d7e55ba802c43702e1a7f4eb842b4a0a96772ed3ccf9a8d53283d5f2a31fde784532cfe8cff41802a72a3255f6998ab70c87afe02271b5ca1ac137350f68636af6a540f0bfaccf2074b0e3b562d7aa01af153d118c2375f053e916152723a1dc3bbc16c0d8fcf9cd2e494f71540f0610e12a6f871a3848b4b96fb73d259e4108ce519dc0fb10ebf6a83aa9fb407a9578cb12ae2eebc43362358255c093c66dfc22d6a2340d292b41a6a303c116579537134ee370eeec1ba522f131d702130b5f3a49defa3941176ad0789abeee3becc738ecea0140ef69f574cf0bfa214a9a4a9bae8fc78831207cd8784c917b967b502ab4a36fa814aa55e50915ce6adf77fba178c69d94ccd5b949a7af66e74de04232563e2fdef2338b83cad860e8b29259888c35159f8e64bfa34ddd87b32450c04770f20e6db09c137fb9016382987673cd00c284cffc207b47c83f6d6e290c287d4a1567d94a012043c4a51d3403467b4a21a7c6276f7239dde0966df4c194be5951b3c2f99480ab17f11463d9625bf27656d0b8285d3dadbdd73ecddaae62c825f621bdafcc7af4370d13be788e472b2975d7d55689341f9baa6e149e46b6826ca829a349172faa749f97f3d6ecbb7af40de6b28129aed0ff96febce04bb4ee7be532bda99fbede436a027b1c665bfa7675607fb57a5372ad9152ce057aa429ddf38fa3c9bdd8fd6206c49ffa21fe8f295eba5f54ab9fa4d609075877e8502fca77deaacc884564262bb091c2673a1ec8799887adcfb4e704127113eafa4c3d9ff3e65999049c92d2807c3799be3393ab73d781f5b8e7c20ec0c06428765120f54fd38e51b4d1e1c20db2e34f516ff69206330cb7acbca9eacb1ca8150b1cdc5389e70ffdc7d51957acef211a7eaa79fd9f77386681dc5e38f49dfc57f3825574b15b15656fb0945c7a681fa170a9710061fd7caf7abaa47cae606ac96cb7f1fbe0068c2e8f921be971d95596059a93fb8deeba5d4a62cb47e8dc1dce320e84038526c7c060e9d417f76f0f73bcb76f75c645230e5a90700c828274e2014c7518d2e1ddd46aa16c747153524b5e96cdebcb298c811e54753b3f43334537d058ae0dd56e20305133cfa46a5cc7006fbbea66a8b984cc8544ea5a76f0c2ed9791bd26ea64647d4eb00661f3e95693b61f2bc7ff947076ede2bb5091c147ab4909cb5621327875ba61e68975ee5e03d45e8ddbaae869db24ff4f70adda4dbe7172e4b36c6964a0ace19a86e08fb21568608cd852cab0449596ce4b9e49db0cd51a50f2b1ca224b9c96b8c1cd106aeb48c98ef00715f28fada0f7ba49177a3d335b7d009cbd88d6efefa71054423c1ac2c035ccea176b1042ee93168ca564503be614e44cabe8e122f183ee2cd6f49ad5413a4baa3213bc12c22518c6e87dcc93894b68ae158744f9f8e6d8e85a2bd79d8ffec811994db429726802997f02547c9adc375c68b458b373e9b8f78a7929fe22894ac92778dbc2efca07d79ff57b299a85fe25de4a6d01408bfe07dfef673cee97649565b439306e36bafc9baa89916b7ddb6dee4fc1912aae243fa0aef0c2b63e1850f22049737a5f8d2dfd273da4036dcfb1e9bcca5afa48401ac3823b00b4e5b16793a859abdf9da123b1ede0a42cb2c9005550a95039882814e58f6baa6311999faab45021217ab8b95e78306b6a172ec0c8ede1d7ce86879ff48851d8f8b03d000d04532155db191b8368833e7917bcb257ffcf64f821b00000000000000000000000000000000000000000000000000" - }, - "result": { - "gasUsed": "0x699ac9", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0x5391369997f0d1335d275d9f74b26e1b6633b7949375e065d75ecf3e9ee1b478", - "transactionPosition": 35 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [ - 0 - ], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000002ce3b6", - "to": "0xff00000000000000000000000000000000000004", - "gas": "0x472f623", - "value": "0x0", - "input": "0x868e10c400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000008318808821a002ce3b6195b55811a0454b2585820ce4b423b5779ab9911bd426632a925b6a83466ce6e076590b106211eaffebb5a582047a51e4e53644247c4ad14e95dcf51070bc8748691de862db56543447a9993eb5907809884d69eeaf1026b549be04d95a7ba703a48494ebdf41d17bc31dab9d7c1a4eb9b8cb0b88cd2e55617338b8c5a58d8feb896df6dfbac66c6fbc56fcc0f17a40fb03339bdbeaa70f0c1de65480e3ddddf1ae71d64dedfd200c263962df45b20761265f747786fac48c4e890695c371113ec4175b2234fc81296ac9116c9601332b99d0206321e58b16bb0d7a69e49a26a96ee1f0b811031d79fd51dd6d0e4498cc7f65e385df4287d72ed0a4a4dbe019a545abb5ebb5f170058a18293f41ed1228286e4110fdf37e6e04aaa156bcff40c7038ed380fd8eaddc864faf7813ea3349785566d1f911b529990df437c2a4f929622ce6571923fb7087485996562785a046a44d48847dd949522a274d3fbef4bf000b9c9979d64df5d77c0efa6c5e4680e457e02379f7d9da2a35de7dce57aecdbc696d4f2cc02d802117a371bd456af945be6d3e9e2146b25c30b7da50ce4dea9055fd1c429ed8ff3b6a7d14f16fe20d8f9ec8802abd3b8f61083c1f05cf3c31369d29938ab21e1a0aefc1f5f2eef4c8ccf3cd9d89a930697c9c9e0042ac63288dc70b9bd5d86e36fee93d5e2eef36a89e3db13cd6be53c37649a0e287ad71caaa0cd08dc46b5c93952d6a9540408017688facad9f4e7c94a24f80a7e8abe7352222a51a82c80a5cf3b0d11f1b071d20b5c67f2c120b8a7f722abc21fab82893fbc206501e4a5b8fd3e881ab39b89ec931bbf3dfc2f9ffa139c9a518552ffa4b9547d88a53cc7947f6c8145c33ae6feb9bfa3d7ed81a7df09827e16033ef88d6990fa158a56750d1f554de9786bb2fa83fb593f11b7747633ac9978c6991256e5fb0ca9a7a70b26fad4d27baa338e8eb3ef2cc3321361a7b44b84b5a5093dc6a04883c5843d2125797cf4be06297cf765eab5b11a2d2c5f3ade90a787ea41b236e1c4291bd547de7c16ed4046576ba515ba1786d16a9d7e55ba802c43702e1a7f4eb842b4a0a96772ed3ccf9a8d53283d5f2a31fde784532cfe8cff41802a72a3255f6998ab70c87afe02271b5ca1ac137350f68636af6a540f0bfaccf2074b0e3b562d7aa01af153d118c2375f053e916152723a1dc3bbc16c0d8fcf9cd2e494f71540f0610e12a6f871a3848b4b96fb73d259e4108ce519dc0fb10ebf6a83aa9fb407a9578cb12ae2eebc43362358255c093c66dfc22d6a2340d292b41a6a303c116579537134ee370eeec1ba522f131d702130b5f3a49defa3941176ad0789abeee3becc738ecea0140ef69f574cf0bfa214a9a4a9bae8fc78831207cd8784c917b967b502ab4a36fa814aa55e50915ce6adf77fba178c69d94ccd5b949a7af66e74de04232563e2fdef2338b83cad860e8b29259888c35159f8e64bfa34ddd87b32450c04770f20e6db09c137fb9016382987673cd00c284cffc207b47c83f6d6e290c287d4a1567d94a012043c4a51d3403467b4a21a7c6276f7239dde0966df4c194be5951b3c2f99480ab17f11463d9625bf27656d0b8285d3dadbdd73ecddaae62c825f621bdafcc7af4370d13be788e472b2975d7d55689341f9baa6e149e46b6826ca829a349172faa749f97f3d6ecbb7af40de6b28129aed0ff96febce04bb4ee7be532bda99fbede436a027b1c665bfa7675607fb57a5372ad9152ce057aa429ddf38fa3c9bdd8fd6206c49ffa21fe8f295eba5f54ab9fa4d609075877e8502fca77deaacc884564262bb091c2673a1ec8799887adcfb4e704127113eafa4c3d9ff3e65999049c92d2807c3799be3393ab73d781f5b8e7c20ec0c06428765120f54fd38e51b4d1e1c20db2e34f516ff69206330cb7acbca9eacb1ca8150b1cdc5389e70ffdc7d51957acef211a7eaa79fd9f77386681dc5e38f49dfc57f3825574b15b15656fb0945c7a681fa170a9710061fd7caf7abaa47cae606ac96cb7f1fbe0068c2e8f921be971d95596059a93fb8deeba5d4a62cb47e8dc1dce320e84038526c7c060e9d417f76f0f73bcb76f75c645230e5a90700c828274e2014c7518d2e1ddd46aa16c747153524b5e96cdebcb298c811e54753b3f43334537d058ae0dd56e20305133cfa46a5cc7006fbbea66a8b984cc8544ea5a76f0c2ed9791bd26ea64647d4eb00661f3e95693b61f2bc7ff947076ede2bb5091c147ab4909cb5621327875ba61e68975ee5e03d45e8ddbaae869db24ff4f70adda4dbe7172e4b36c6964a0ace19a86e08fb21568608cd852cab0449596ce4b9e49db0cd51a50f2b1ca224b9c96b8c1cd106aeb48c98ef00715f28fada0f7ba49177a3d335b7d009cbd88d6efefa71054423c1ac2c035ccea176b1042ee93168ca564503be614e44cabe8e122f183ee2cd6f49ad5413a4baa3213bc12c22518c6e87dcc93894b68ae158744f9f8e6d8e85a2bd79d8ffec811994db429726802997f02547c9adc375c68b458b373e9b8f78a7929fe22894ac92778dbc2efca07d79ff57b299a85fe25de4a6d01408bfe07dfef673cee97649565b439306e36bafc9baa89916b7ddb6dee4fc1912aae243fa0aef0c2b63e1850f22049737a5f8d2dfd273da4036dcfb1e9bcca5afa48401ac3823b00b4e5b16793a859abdf9da123b1ede0a42cb2c9005550a95039882814e58f6baa6311999faab45021217ab8b95e78306b6a172ec0c8ede1d7ce86879ff48851d8f8b03d000d04532155db191b8368833e7917bcb257ffcf64f821bd82a5829000182e20381e8022034ec77824ca12b84726221d0f555f3f2259556b034481de7444a5c4aa8bf1c50d82a5828000181e2039220206228c3dd993b0b2ae7a15517e751ab3c3eccbdbf66fae8352ca91edbd307b200000000000000000000000000000000" - }, - "result": { - "gasUsed": "0x2f32441", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0x5391369997f0d1335d275d9f74b26e1b6633b7949375e065d75ecf3e9ee1b478", - "transactionPosition": 35 - }, - { - "type": "call", - "subtraces": 1, - "traceAddress": [], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000002d0717", - "to": "0xff000000000000000000000000000000002d071d", - "gas": "0x465036e", - "value": "0x0", - "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000007000000000000000000000000000000000000000000000000000000000000005100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000787821915ed590780855ab01ab68d7f7911622a692d9f5e153cd7fa0c876e34bdc0e919b9dad348d36a5ca7da172d7861dc9d374691ff78598f681fb630f618d5ec6dd97e4d8a4e0fdf3f470e0a270b498470e6077d62ee727c3befcea4bd75f78fd5e2baaef1154100a718f8cdf3f51606b032345be315e76db25515c551449554df8986c672cc635509291c62e177d32302c0e4786c892890efc83438b702777075fbefdf555bed7bb2247ccdbf5180e39269a95e03be6be89dc54665ef8739adbd1deb19b654a2a38cbfbe57591f81c1270883e1cf67fb9aa629166cc561ba617833a693a205500021a0cd11a4819df33dbab56d16b9c78963c2673b2c7a4ac8de6d05ef318cf273434c9a2abe2e0f052e1862d2fc1e6e61f3edb2402094f81449ce1dc7be7e320a53970a2da7e93ce09803d02b5d281d2a903e95d032a668d2b1f841f6ba1f6929e8ba6c1bc2b5529fa2ae454358fd6a8ae66ebcfae4fad3caf51d07ccac6b7eb6e0cce37e5f33b934b9f3804572d36e8890793e24685e0824732a9303cae07aa3cc5acf931554fa5a27cbad77cbd038aa052d7f480450aefd205ced4eb9061e7284158d0885a359c90ec391a13b7dcdaf1813dbfdd47dc891733aba9cac4783f3ddcdc48cb6b1921273cd611b6fad160a3fe55116777fcc891e6889bfe15dbc00a040dfd74a3d22cb5e3be517f90ff919d15788b29c50d80bf410e92c7ec237f0612f93184c4e0da45f0b200ab37719b02a6d62d04d153c2db8a667dd2dedfc82351d9337db03a47cc8725600b06867a98a469798098624a596ceedc82cd48f86a62dc8eb7f7e99826b9631a85d6fd16d82cd6d71a4688eee0423c4bb7b3edf2486896cff4d406aa42fcc63f0c43aafa8ec1861ba4d5e518d898f9952017faa76b718ae54e2a09bd55e3ddc6330d3e01496b8e16284d5af1c22adb0869974620dc3e4391b2fb94e86c25a724d51fea1fd2eaddcdb455eabfd8243e1e5433802bc68b8aa42651290ede7c143442f2dda975d26c0d4826866e1087526d18a250b297cea211cb48f13ad95b7d2d2f8828bdd07ca84124c296536e2cd5cb4dd61a1ac2f544e079c716bf706216cb708dbc5fb8230e8898b1bf17e822e198a78d7ea9d7d704bf8f92c0190075422deb51286b5f282571527ba28e18abe3ccc5bebccba440c9fb1de24b5c1104584082f2109f5e499cba9d3dae4f57c0302d38b41d10f6f0f108678011f025affb7a754c400b85cc54aace92ae21baea706d8d76580c57199f7a64cfdf087a38982597b3199af6ca2900ee6652fc62340cc1d60157e7fc31b2c474ae9938c0db0c31a3edb7ad52f086c299551fd5c60b1b6231281eda99598c5a8a9fab4c6b523c25d03415c071d9cfc252129d2fa2319cb9af8525aeff2501cfb25d443952f1f3147bfc1fda017550abb58ebeb17b9327c2c86d059cf04cb2d29966fa689d9e871c45e32f16f322655f5f5556e55bc1a4ae33d68ce15235816886fe7ce919935dd497bfb829c8fef3b22c8e5a1e0bb3e446bff9d0b458dd7531778d144dfd38de1a220e6cda4da4708930f8265d4db9a3fb8a73ae369fa128d49181a04908fc9aed5cb5c6e55caf4786d878ba601cbf32820939679af0c8a99b4fc33d5b343dc3d428258e9a802ed92d6ae7bd6b874198cfbe168738536975ed78666b6cdf8d3155990c913aa782e39d9d6feb1229b4b743e32a2ba22cbaf681945b9eaf70e3e49ec02a3caaf478c3cee83a96c775823d02976408c0633c100b27f8cca832ba01e4b8f9a50d3eb8295abdec2d1a06e4aac519e0ba2c5c16779f01c4dbf6217a55ac9125b4fa5e940fb50eff805cf6aeca0435a52fdf674808e088670c7efbd9c8cf59480dad35b7279c25e0b4a7441e169d78ed79aae7a48f29c98669cdefc6ebb71f66fcdbe1f1132adb2ae7dc3a714a2cb4a4f5865d1ec9ae4c2930511b9f9a16e220ee2afc51d056c7b69e8bcdb3a7ef8e9c264a499c6d1839f4c6161859ab00e06795b03d33a13cbe6e025a7c5ed1b3d6044b714b25ef4228841cb1326e9a198fec9eaae849f1571d7a988f832ab45d6a05106344eb0529b07b4649bdd0592e602e1b2a263ef675e2fb5250d0fc12bcc1482a9102568c9379611cf7ced996cd9a575836ead5474162a2a93c23888edde35d8fe83d4d4c1306feee83f8c39c4f9ad788e5687f67228715d99f512b40ded4a799d620d81c858dd5126a69dfac4864496cc980004071a578e9004bdce44ea1371f6acfadd0b3e1afa6a293e81eb3ab08a58958a5be5d0d5a9347e0b7968fb694d200078e991c2666bc35c2d27000427c6d9d3390ab4c1db1a92554f2891a06e196438556a18b8e861d815a8f1c1bb5d854fb8a06b983efff0ce867571d9d6d2c57315407adbd16ef8874b27f8813a3f8f8729705fce0084eaf6387ecc87a23bac38b25eabdd4067558fa08305e4a349aa2549f25217d5aa7f4246ca354196e56d51032b673f351121a217fbea67c47215c397ad20729eb402e188e616eecfbd8397f67a33c14eaa247741331d54e4d3de3e8755a09ae65f1e1b5e83779f03ade05a0076fa1272324c4ddd2caa3ef289038d11e55d5d6f8f47262d5b441c21930e5d718556d13350cb0153688ca40083ce5d8a6cfbda9f072f0a096806d16c89e721b86e7f8b0a7a26b91aef4164a2581db740dd88536c967ed71cf12f22794ad66e00000000000000000000000000000000000000000000000000" - }, - "result": { - "gasUsed": "0xa3a5a2", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0xe01589cddd43f6bceff20e5ae7715922ddb41b97e88b5e4520fa355684aea943", - "transactionPosition": 36 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [ - 0 - ], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000002d071d", - "to": "0xff00000000000000000000000000000000000004", - "gas": "0x3f23511", - "value": "0x0", - "input": "0x868e10c400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000008318808821a002d071d1915ed811a045aacad5820daf497f015b8511702381fed6091635b036cdaf22a594e43cd2d97f95eb90f6b5820cdfd7ccb0815a58ba76c8db18a2801a5fdf6e4f7bffebc8a48086b5769d838f4590780855ab01ab68d7f7911622a692d9f5e153cd7fa0c876e34bdc0e919b9dad348d36a5ca7da172d7861dc9d374691ff78598f681fb630f618d5ec6dd97e4d8a4e0fdf3f470e0a270b498470e6077d62ee727c3befcea4bd75f78fd5e2baaef1154100a718f8cdf3f51606b032345be315e76db25515c551449554df8986c672cc635509291c62e177d32302c0e4786c892890efc83438b702777075fbefdf555bed7bb2247ccdbf5180e39269a95e03be6be89dc54665ef8739adbd1deb19b654a2a38cbfbe57591f81c1270883e1cf67fb9aa629166cc561ba617833a693a205500021a0cd11a4819df33dbab56d16b9c78963c2673b2c7a4ac8de6d05ef318cf273434c9a2abe2e0f052e1862d2fc1e6e61f3edb2402094f81449ce1dc7be7e320a53970a2da7e93ce09803d02b5d281d2a903e95d032a668d2b1f841f6ba1f6929e8ba6c1bc2b5529fa2ae454358fd6a8ae66ebcfae4fad3caf51d07ccac6b7eb6e0cce37e5f33b934b9f3804572d36e8890793e24685e0824732a9303cae07aa3cc5acf931554fa5a27cbad77cbd038aa052d7f480450aefd205ced4eb9061e7284158d0885a359c90ec391a13b7dcdaf1813dbfdd47dc891733aba9cac4783f3ddcdc48cb6b1921273cd611b6fad160a3fe55116777fcc891e6889bfe15dbc00a040dfd74a3d22cb5e3be517f90ff919d15788b29c50d80bf410e92c7ec237f0612f93184c4e0da45f0b200ab37719b02a6d62d04d153c2db8a667dd2dedfc82351d9337db03a47cc8725600b06867a98a469798098624a596ceedc82cd48f86a62dc8eb7f7e99826b9631a85d6fd16d82cd6d71a4688eee0423c4bb7b3edf2486896cff4d406aa42fcc63f0c43aafa8ec1861ba4d5e518d898f9952017faa76b718ae54e2a09bd55e3ddc6330d3e01496b8e16284d5af1c22adb0869974620dc3e4391b2fb94e86c25a724d51fea1fd2eaddcdb455eabfd8243e1e5433802bc68b8aa42651290ede7c143442f2dda975d26c0d4826866e1087526d18a250b297cea211cb48f13ad95b7d2d2f8828bdd07ca84124c296536e2cd5cb4dd61a1ac2f544e079c716bf706216cb708dbc5fb8230e8898b1bf17e822e198a78d7ea9d7d704bf8f92c0190075422deb51286b5f282571527ba28e18abe3ccc5bebccba440c9fb1de24b5c1104584082f2109f5e499cba9d3dae4f57c0302d38b41d10f6f0f108678011f025affb7a754c400b85cc54aace92ae21baea706d8d76580c57199f7a64cfdf087a38982597b3199af6ca2900ee6652fc62340cc1d60157e7fc31b2c474ae9938c0db0c31a3edb7ad52f086c299551fd5c60b1b6231281eda99598c5a8a9fab4c6b523c25d03415c071d9cfc252129d2fa2319cb9af8525aeff2501cfb25d443952f1f3147bfc1fda017550abb58ebeb17b9327c2c86d059cf04cb2d29966fa689d9e871c45e32f16f322655f5f5556e55bc1a4ae33d68ce15235816886fe7ce919935dd497bfb829c8fef3b22c8e5a1e0bb3e446bff9d0b458dd7531778d144dfd38de1a220e6cda4da4708930f8265d4db9a3fb8a73ae369fa128d49181a04908fc9aed5cb5c6e55caf4786d878ba601cbf32820939679af0c8a99b4fc33d5b343dc3d428258e9a802ed92d6ae7bd6b874198cfbe168738536975ed78666b6cdf8d3155990c913aa782e39d9d6feb1229b4b743e32a2ba22cbaf681945b9eaf70e3e49ec02a3caaf478c3cee83a96c775823d02976408c0633c100b27f8cca832ba01e4b8f9a50d3eb8295abdec2d1a06e4aac519e0ba2c5c16779f01c4dbf6217a55ac9125b4fa5e940fb50eff805cf6aeca0435a52fdf674808e088670c7efbd9c8cf59480dad35b7279c25e0b4a7441e169d78ed79aae7a48f29c98669cdefc6ebb71f66fcdbe1f1132adb2ae7dc3a714a2cb4a4f5865d1ec9ae4c2930511b9f9a16e220ee2afc51d056c7b69e8bcdb3a7ef8e9c264a499c6d1839f4c6161859ab00e06795b03d33a13cbe6e025a7c5ed1b3d6044b714b25ef4228841cb1326e9a198fec9eaae849f1571d7a988f832ab45d6a05106344eb0529b07b4649bdd0592e602e1b2a263ef675e2fb5250d0fc12bcc1482a9102568c9379611cf7ced996cd9a575836ead5474162a2a93c23888edde35d8fe83d4d4c1306feee83f8c39c4f9ad788e5687f67228715d99f512b40ded4a799d620d81c858dd5126a69dfac4864496cc980004071a578e9004bdce44ea1371f6acfadd0b3e1afa6a293e81eb3ab08a58958a5be5d0d5a9347e0b7968fb694d200078e991c2666bc35c2d27000427c6d9d3390ab4c1db1a92554f2891a06e196438556a18b8e861d815a8f1c1bb5d854fb8a06b983efff0ce867571d9d6d2c57315407adbd16ef8874b27f8813a3f8f8729705fce0084eaf6387ecc87a23bac38b25eabdd4067558fa08305e4a349aa2549f25217d5aa7f4246ca354196e56d51032b673f351121a217fbea67c47215c397ad20729eb402e188e616eecfbd8397f67a33c14eaa247741331d54e4d3de3e8755a09ae65f1e1b5e83779f03ade05a0076fa1272324c4ddd2caa3ef289038d11e55d5d6f8f47262d5b441c21930e5d718556d13350cb0153688ca40083ce5d8a6cfbda9f072f0a096806d16c89e721b86e7f8b0a7a26b91aef4164a2581db740dd88536c967ed71cf12f22794ad66ed82a5829000182e20381e8022007bba645c0115a8f7d6f67c1f9d2460f73d641ceb84997bb54afe36ed11a9c72d82a5828000181e203922020756cc556bd2c6db9ffad0a86b29297860d63869337c1d5fd0921e71afa592a29000000000000000000000000000000" - }, - "result": { - "gasUsed": "0x2f75775", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0xe01589cddd43f6bceff20e5ae7715922ddb41b97e88b5e4520fa355684aea943", - "transactionPosition": 36 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [], - "action": { - "callType": "call", - "from": "0xff0000000000000000000000000000000015c8bc", - "to": "0xff00000000000000000000000000000000018a9c", - "gas": "0x3603c9c", - "value": "0x0", - "input": "0x868e10c400000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000001b685168282044082054081820d590180b9e0650e2791bd2e265cf4ff157de041c454b9813e09d21651b6b132609604b77b8a54feec5a9596f79a7aaa2e96fb50ade24f30190a0cb5fb7fa0d77780812aff6c95f4d15990f648a57fc4b3af6ca33ce17b1b0ba17c59198975f7dc79c231180918651145f631f00234c7697c8ad40d293cb34edd09af17f4f2a1f950aed915d6e16a18540542154198516872226599ef6d287379e87c82cfcc40bb6f1c4226d1876235d0e4c2cf7e5c830e7ecc81df37f42fbf714e393a501b80496549d1b52ce5ca02ba411c32ae4b89878363c6434ef47dc39a36027e01f3ad4c60427cd9e9d65595421a557624fb76b236702e94d4400f3bfe6814a14a0fdf34e98ccce46c551a77eea8722ff0b61baa3de7566e17d752e01d4c6ba769be27e9d4dbbf0364002ac4a50774c06353273215ba88c4a820cb9e4a2e70e7f0f4f12f95977b66fba1bc3d5a9a1549514fc23d4d39d9a6233d894761862d0763d7602eb764f05818d872e2ed5c10f0b9dac5e2540c825b6beef6049f00056ee5eaa72bca70be1a0037e5f8582047ab5fe94c06cdbaec8b74525a8ef23928a1bbb62440ccbcd599b10154404d6900000000000000000000" - }, - "result": { - "gasUsed": "0x2c387bc", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0xf62bf0708f9536497bc516be3b6d547abc74b04e46598b8d428d1bb989175294", - "transactionPosition": 37 - }, - { - "type": "call", - "subtraces": 1, - "traceAddress": [], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000002b3964", - "to": "0xff000000000000000000000000000000001db56f", - "gas": "0x5d6ade4", - "value": "0x0", - "input": "0x868e10c4000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000010185181a8182004e40587e20e402c98ae04030a2159e81820d58c0810a1d1d92f527d5c666cacddb7c8da0c4319d0b04c56b232ffaf060c49cba4ca620bb1159347958d2681a5727efd7e090ed851ae5a9236758cd90dbacd940cc7ee8e431727a501fdf14809cb9d343520a11b67feb31f49da7cf1401fff4a04c10fb9d02be4807a8c83a812bf690ea6a3e7bf377884f8ac908c8e02f4d10bd7adc51480159b52ca97c5e9b9d3ee9c4e98d2b48aba3ea7de3710ef8f20a85aa25feff154a5d62baa62f446ddeebb8ca723c9df71e7b8f4021c48871459b65cc721a0037e5f75820a3a81d9f7df6ebf387d44874769074545c6eaafa2e7d3e3728a8b70a048a411800000000000000000000000000000000000000000000000000000000000000" - }, - "result": { - "gasUsed": "0x35c5604", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0x36071df471f65c45aa13469b5e27b464ca5467f7a0c32a24b7e35e02ea17953e", - "transactionPosition": 38 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [ - 0 - ], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000001db56f", - "to": "0xff00000000000000000000000000000000000004", - "gas": "0x28f29a3", - "value": "0x0", - "input": "0x868e10c4000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000f8246013800000000460138000000000000000000000000000000000000000000" - }, - "result": { - "gasUsed": "0x15c9767", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0x36071df471f65c45aa13469b5e27b464ca5467f7a0c32a24b7e35e02ea17953e", - "transactionPosition": 38 - }, - { - "type": "call", - "subtraces": 3, - "traceAddress": [], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000002ce379", - "to": "0xff000000000000000000000000000000002ce3c0", - "gas": "0x326ffc0", - "value": "0x0", - "input": "0x868e10c4000000000000000000000000000000000000000000000000000000000000001c0000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000708181870819436cd82a5829000182e20381e8022028d8fa5716e16415633900cf944c3a7c13c89b4b7998357f96b93e52033ab7101a0037e107811a0454802e1a00480c92d82a5828000181e20392202026901d7150173a8f624326ea93f6aa265f7d4bea28071817876b420b6bb1763d00000000000000000000000000000000" - }, - "result": { - "gasUsed": "0x2270b3e", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0xa105b44c0b659b3818b548210b3ac8c78f9d1320c94d359df1a499b8e92b1d69", - "transactionPosition": 39 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [ - 0 - ], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000002ce3c0", - "to": "0xff00000000000000000000000000000000000002", - "gas": "0x31b9999", - "value": "0x0", - "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "result": { - "gasUsed": "0xfabb0", - "output": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000418282581a000286387ff107ef952f3612eb2e2a6606418e51c0f2cceeda5f57011731c2a04034dd33df402838a815a0b69ec1184b8c104a0001c0dfc71cd077c4b900000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0xa105b44c0b659b3818b548210b3ac8c78f9d1320c94d359df1a499b8e92b1d69", - "transactionPosition": 39 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [ - 1 - ], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000002ce3c0", - "to": "0xff00000000000000000000000000000000000004", - "gas": "0x30ad451", - "value": "0x0", - "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000009000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "result": { - "gasUsed": "0x105fb2", - "output": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000578449007d2903b8000000004a000190f76c1adff180004c00907e2dd41a18e7c7a7f2bd82581a0001916cb98a2c3dfb67a389a588fb0e593f762dd6c9195851235601fba7e16707ee65746d4671e80aa2bb15bc7d6ebe3b000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0xa105b44c0b659b3818b548210b3ac8c78f9d1320c94d359df1a499b8e92b1d69", - "transactionPosition": 39 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [ - 2 - ], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000002ce3c0", - "to": "0xff00000000000000000000000000000000000005", - "gas": "0x2f8bee0", - "value": "0x0", - "input": "0x868e10c4000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000f818183081a00480c92811a0454802e0000000000000000000000000000000000" - }, - "result": { - "gasUsed": "0x488c45", - "output": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000002e8181d82a5828000181e20392202026901d7150173a8f624326ea93f6aa265f7d4bea28071817876b420b6bb1763d000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0xa105b44c0b659b3818b548210b3ac8c78f9d1320c94d359df1a499b8e92b1d69", - "transactionPosition": 39 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000001614ac", - "to": "0xff00000000000000000000000000000000018a9d", - "gas": "0x290f9d0", - "value": "0x0", - "input": "0x868e10c400000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000f285028182024081820d58c0a59157072c783a23fdad72543a912757d0234de28719fa60979b8cfbf9cf4eb5f7819b9e24f2aad13efb0e64f69f20b0b1fb96008c44c1ee0695eb0740aee9ba1106fb832481216b53b6929e9da8f3ece77b6685bedd0ac314a12dc9838f7b4612c874b708ca9d190d31d22f5c362625a6993f9055ca9078f040aedbd1195823a13b92e4f83234cdbd69f7df84741fd89704df8fb5d8455f4c1590aa377ebf35ea6918e47951d1ad4c22f64cec35a81bda8e2063148a98259e3689b711747ff81a0037e5f6582080334418ee2e59dadfdb8d33bf996c9c35f36dbe740e214e308a34ebd796ddc90000000000000000000000000000" - }, - "result": { - "gasUsed": "0x21a9811", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0x5baf436ea76a5a6938726489a2b0ac90d8a62225ee0a67bf9440d9b66c8ab007", - "transactionPosition": 40 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000001c3bf7", - "to": "0xff000000000000000000000000000000001c3c0e", - "gas": "0x6a72102", - "value": "0x0", - "input": "0x868e10c4000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000027a8518258382004082014082024081820d590240915b93366d1917d73ed68ec773c15b05dcfabe838b6aef263760ca3e6db2c6173be57b35130f6fbe36edc2f923b97f74a48c1b20703f76fece172c2f10ee57dcbc723f46b02d971a1407358f025066b292140a32797174b57406603f80bb85f70ab3669c9a27f96f03808f47de7fe08a12149ca62ee727ebe4de79e3f7e547781fc5b6a86d3974c842f134f388418f39b7f528104398df97d94c20d49793e55105399d0e591cab05e828f98ef04f48796e367696e4ecd7c9cf48fd989199905d891f08abd8c6ab58bae7d76cb0a142e875e259f7cfeca88ca0c1cc42798c516abfefd08366536984294f0463ddcb287bac2b2305440a2d11b80bcde36942dbc0a900e8a8edd3f74bd8d63479a011a9cbffbe20eae183469f8a093bcec950ff0a0dffe0a0d2039dd24fbdef242e0d7a81e5d375443ca995272da2733c8f1087552a2572aad7049c15d007e59a5f19befab15b2c01a196d4c131ebf118c4beb33bf1ae866b5f1d89906de32cad27e4c9c9e824108d07fee9d5938c62214a10fbfcad0efef90ae507b4b9b8f20786dfe26ebdb215adda0d3530bef4619c99011340a3f6e294391e5a70cd8f3507e046bf54a27b703a49354359363d301723d6eceb1760bf220ecd15753058e87d3b489fdcf6bdf391a6ceb5c932e9cf9e3e83fe53087f8c9ba3f369567dbe5d597c133534dc3d5961f71fa1cca16614e2871a102aa59af9bc65a9f69e6babb703e41fa3cdb2599140ebd81769ae17febc5700d59e556be7bcc6ab0f245dac5cab7d98f0e0565d1b4d91505ff1f147f1a91aa80d9d1a0037e5de58201bc00903e90ca71cf8a383db0b4d3a038e6c65f02639a026af83a66ff71862a5000000000000" - }, - "result": { - "gasUsed": "0x565a543", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0xd88734b87797aaa8445444670a2d6bdd3bbd84d4ec90d3b7b78acae141f7c2a1", - "transactionPosition": 41 - }, - { - "type": "call", - "subtraces": 1, - "traceAddress": [], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000002ce375", - "to": "0xff000000000000000000000000000000002ce3b6", - "gas": "0x538030d", - "value": "0x0", - "input": "0x868e10c4000000000000000000000000000000000000000000000000000000000000000700000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000078782195b765907808776753106795c7ae26573ae19505c7fc8a145840257c67736fc00ed90d79d770f0bcbb1d914234ffc6f522a4aba7ae8893e8fb235797e8c7258a8fcbfedf14a90fe28bbfa9834fdc4e5030caaf9ef6313a756804a9c01e2a4ef6cbee65d16281897d1285160d0504c5e42d119880b6c1967694b390b219079086f2ebb8012e17f3866f16f7be2224bceb0327cf80150ae72b36802a974b70541a9613da97ff18342f052a8a505b89b83f948fc6ae6e467bfdb19b5c9ef2c392bd54052bb1f13877b77cb9ad7cdc31bdfbd2ea960a82b6196e6c6d4fe8dddc50fe40c6f2bb4ceb0ebb1802548bf4f1f16ad3890285c00b89aa1d36745ab91d5ef87d043f15a1d8d1dc392832c578b40f456e6e1e4adbf21363cf9f0905ef9f32a1cb10ba0ac9010acdde70778af71b6054348355503a303951d23be9f4c559390da5e1708098e34f37bb0dcd3033549e902c4c1f3d747b2212f19d3c0f10a10e7176022832c521afc8f6b58407de713dc3cc35940762cfabf4ae2cb11b6d730529f4f37dcf94faac4bf6007443e3d7fa2fc3ed9207926345d2135759822096874eed1156a4040244ca30c55e64954531d1e0ac583b6128ec62a2d5adc76dca441a36884bafc29937bd304187e2c75f3ffc04b522a86e381fe672dc5299f9e53632f01d52c78a81495daee861015440697d3d49cfb4ec14c4371b5931e550168703a4e0cb329cfbf0bfccddb6d4a686b961a5910176a3ab16e4b57edfd8dfc7217bd51836bc9fba749534a635d2ce58b50563087e3a59a0a7e0cc897bfe3444792f04a3f52e375a4c7de5cf5eb1077b9aa09fd2ad2adec73036686e29548174b6515e0845d5c5a32635c25253ee3a4afb699f00ab6c720a3f82349679c5ed59ab01a70c68e4ddf395cc14cb70ed8a1645b9007226c4b733e6136d8493430420a155caaaef3e9f40869873f0d1816baa43388948f8ce129405a8ff08d514ebfd190c519871ee66af8f599911f6f10d0b727a09ad16621d1b804c32e6d906152e8651e754d58dcd2ec572696175f743b426bf5f9bd96996f53083f871fa9628eb93fe223438e39448bf72ad9facc6c2649e29345f3aaa6a26140bed5c6f524137e9789ed17efde3b814e2b16eafe8785fe83fa7d66e53c76932f20cf89053e4651060552134686a70a8faa289a0baab5d8bdf5903621347c6b9d30bf6d6d1c227148cbd69da134d20baf7597b0314737a8c30a00e13362f12a7d49b2b8c673346ed47d70e0c11423cc97f7b62af88afccc4afeaeed0f8059b726785b7e459c4a620145ab96b6ee7d683420761b1f5a96757676fc33b528d5182f7b8e84fb84ab36eba1f39db0d551b59af3258e48317d70a612e79afa2302cdfbc89d99905aa6026d24cdee78ea2d5f3145e9cad4531b2550472914cc5ff89780669bf5b7b90db9f518801c3fc317721469dcdb5ab2aa22df8a55f3db7e7f62119252fda66bb5d1201088b68faccc1403ae074c6f8085f436eacedd09a9286396ad0779ac01c50dfb60ded4892fde0ec55c56b702ec5ec3c886f9ff62e84b88f0252460118d411ca853216ee009b948b07a60cd3f4689135bc857663fe4ed71309e7babe6d0750f15e9225045b506a8dd78ba11138d131c30e7175c9bcf461c1ae51cb232d1ee4361757d729d0cea9676f1e6de815fb305e390c0cc402f1481fffbd8bcf49ae6ab8965941bcad082a712c97975217f189c434cbadd60bce79381f9f1b42de417d9e0ab162027f4c00c09bf0d01d84fbc28a74079d94462bead8c877a09bf978b8e8ec49816123cc244613c0fe60426977348bbe2d75b18f59840e4cba2688992e5713493514c6f2ebbecd2c3650eed7c3018614bec7030211b9638bf78d1b74f2adc51d3c588eb05a58f6faeab355e620c0091ad67fa3b5a861ec0fb700c637680fff5468e99bea0257f74e38ba8fbcdd08ddea960403db1af19ba1df53e371b0ae6af65fc3996effe4e8451b49fdc81c873ba134d06d38ccfc19a589d135ac1501cf693fdcc897b15b044736a99a47f8f69a48d8f58297ab20bb43f986209c0c3d8862b055c66611d040c50a281bb349c82884f39f3d9258febfcfcd6b359a03dcc0455ee5e9fe97e4c89350871a21cb0564fb0ecfb45a3263f10fb0d74bcf8c5920dd677472c45a519f63ef0b1c71344d6f2f4a95c2be6a344b20a8470a0b4eb3b2aea6a1c94c9ebc0fa8ac142432f81361b87e2d73e768b6d636fedf68e02a0481173987dd0f3d0e6707cb517c8e9364bfe2b24521459e1e9ab2fa00932c0ea67ab512532b3870e6c7f5b885911ad4d96308568461d5386d370d9a081573585105bcff4b83867b466b8248a626d41c8dc0e88345145ae881c0a03b9a157e002e822ec3b6ae93e1e35dc7e40a5e9f4f7b828eded0a715ae79d61941cff6bd265efd6cedac3c5db83c50a59ce24ac3779a5055627bc52d2ff33b335cc0e47d517e06f3861bb08d10135516097ec0cb573f3299b58e37cf3a431627b8ddcf3b0e894cab48bc3d16ac21b17ad6488977a2991c31936b6ae1d83b2988de15eb4996850978dd2853d9608b4d7d90e6d3b6198fbd064403012f7918829b806669194b55eead50487d9a89ab7c3e808cb6b6f04613df480eea6bb837b8e94790d5af979fad39284d609c1fc40bcf347466bc24434f617465178d7f89998da9da550cc49d54b18807f6eff00000000000000000000000000000000000000000000000000" - }, - "result": { - "gasUsed": "0x6badd2", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0xa96e51f481ad7fa03bf8bb21077c475f6104053a8d553f7be0c7ec6bdbc5f4ac", - "transactionPosition": 42 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [ - 0 - ], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000002ce3b6", - "to": "0xff00000000000000000000000000000000000004", - "gas": "0x4fd333a", - "value": "0x0", - "input": "0x868e10c400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000008318808821a002ce3b6195b76811a045356e5582091f30bc594d5d33c5fdead7467a5c9a5f5d3493a04d21054be0fd86e552edb33582059f127e49b8051719946f330b970a0c841fa53270c5b6e5453ddfa01e9db20155907808776753106795c7ae26573ae19505c7fc8a145840257c67736fc00ed90d79d770f0bcbb1d914234ffc6f522a4aba7ae8893e8fb235797e8c7258a8fcbfedf14a90fe28bbfa9834fdc4e5030caaf9ef6313a756804a9c01e2a4ef6cbee65d16281897d1285160d0504c5e42d119880b6c1967694b390b219079086f2ebb8012e17f3866f16f7be2224bceb0327cf80150ae72b36802a974b70541a9613da97ff18342f052a8a505b89b83f948fc6ae6e467bfdb19b5c9ef2c392bd54052bb1f13877b77cb9ad7cdc31bdfbd2ea960a82b6196e6c6d4fe8dddc50fe40c6f2bb4ceb0ebb1802548bf4f1f16ad3890285c00b89aa1d36745ab91d5ef87d043f15a1d8d1dc392832c578b40f456e6e1e4adbf21363cf9f0905ef9f32a1cb10ba0ac9010acdde70778af71b6054348355503a303951d23be9f4c559390da5e1708098e34f37bb0dcd3033549e902c4c1f3d747b2212f19d3c0f10a10e7176022832c521afc8f6b58407de713dc3cc35940762cfabf4ae2cb11b6d730529f4f37dcf94faac4bf6007443e3d7fa2fc3ed9207926345d2135759822096874eed1156a4040244ca30c55e64954531d1e0ac583b6128ec62a2d5adc76dca441a36884bafc29937bd304187e2c75f3ffc04b522a86e381fe672dc5299f9e53632f01d52c78a81495daee861015440697d3d49cfb4ec14c4371b5931e550168703a4e0cb329cfbf0bfccddb6d4a686b961a5910176a3ab16e4b57edfd8dfc7217bd51836bc9fba749534a635d2ce58b50563087e3a59a0a7e0cc897bfe3444792f04a3f52e375a4c7de5cf5eb1077b9aa09fd2ad2adec73036686e29548174b6515e0845d5c5a32635c25253ee3a4afb699f00ab6c720a3f82349679c5ed59ab01a70c68e4ddf395cc14cb70ed8a1645b9007226c4b733e6136d8493430420a155caaaef3e9f40869873f0d1816baa43388948f8ce129405a8ff08d514ebfd190c519871ee66af8f599911f6f10d0b727a09ad16621d1b804c32e6d906152e8651e754d58dcd2ec572696175f743b426bf5f9bd96996f53083f871fa9628eb93fe223438e39448bf72ad9facc6c2649e29345f3aaa6a26140bed5c6f524137e9789ed17efde3b814e2b16eafe8785fe83fa7d66e53c76932f20cf89053e4651060552134686a70a8faa289a0baab5d8bdf5903621347c6b9d30bf6d6d1c227148cbd69da134d20baf7597b0314737a8c30a00e13362f12a7d49b2b8c673346ed47d70e0c11423cc97f7b62af88afccc4afeaeed0f8059b726785b7e459c4a620145ab96b6ee7d683420761b1f5a96757676fc33b528d5182f7b8e84fb84ab36eba1f39db0d551b59af3258e48317d70a612e79afa2302cdfbc89d99905aa6026d24cdee78ea2d5f3145e9cad4531b2550472914cc5ff89780669bf5b7b90db9f518801c3fc317721469dcdb5ab2aa22df8a55f3db7e7f62119252fda66bb5d1201088b68faccc1403ae074c6f8085f436eacedd09a9286396ad0779ac01c50dfb60ded4892fde0ec55c56b702ec5ec3c886f9ff62e84b88f0252460118d411ca853216ee009b948b07a60cd3f4689135bc857663fe4ed71309e7babe6d0750f15e9225045b506a8dd78ba11138d131c30e7175c9bcf461c1ae51cb232d1ee4361757d729d0cea9676f1e6de815fb305e390c0cc402f1481fffbd8bcf49ae6ab8965941bcad082a712c97975217f189c434cbadd60bce79381f9f1b42de417d9e0ab162027f4c00c09bf0d01d84fbc28a74079d94462bead8c877a09bf978b8e8ec49816123cc244613c0fe60426977348bbe2d75b18f59840e4cba2688992e5713493514c6f2ebbecd2c3650eed7c3018614bec7030211b9638bf78d1b74f2adc51d3c588eb05a58f6faeab355e620c0091ad67fa3b5a861ec0fb700c637680fff5468e99bea0257f74e38ba8fbcdd08ddea960403db1af19ba1df53e371b0ae6af65fc3996effe4e8451b49fdc81c873ba134d06d38ccfc19a589d135ac1501cf693fdcc897b15b044736a99a47f8f69a48d8f58297ab20bb43f986209c0c3d8862b055c66611d040c50a281bb349c82884f39f3d9258febfcfcd6b359a03dcc0455ee5e9fe97e4c89350871a21cb0564fb0ecfb45a3263f10fb0d74bcf8c5920dd677472c45a519f63ef0b1c71344d6f2f4a95c2be6a344b20a8470a0b4eb3b2aea6a1c94c9ebc0fa8ac142432f81361b87e2d73e768b6d636fedf68e02a0481173987dd0f3d0e6707cb517c8e9364bfe2b24521459e1e9ab2fa00932c0ea67ab512532b3870e6c7f5b885911ad4d96308568461d5386d370d9a081573585105bcff4b83867b466b8248a626d41c8dc0e88345145ae881c0a03b9a157e002e822ec3b6ae93e1e35dc7e40a5e9f4f7b828eded0a715ae79d61941cff6bd265efd6cedac3c5db83c50a59ce24ac3779a5055627bc52d2ff33b335cc0e47d517e06f3861bb08d10135516097ec0cb573f3299b58e37cf3a431627b8ddcf3b0e894cab48bc3d16ac21b17ad6488977a2991c31936b6ae1d83b2988de15eb4996850978dd2853d9608b4d7d90e6d3b6198fbd064403012f7918829b806669194b55eead50487d9a89ab7c3e808cb6b6f04613df480eea6bb837b8e94790d5af979fad39284d609c1fc40bcf347466bc24434f617465178d7f89998da9da550cc49d54b18807f6effd82a5829000182e20381e80220653bb701762a54aec57374c36979ae603e219bc00ef3be63554bf4a537c9be5fd82a5828000181e203922020dedeefe8f2842d894b8ef09f16bdb01583ef59df9249aeec829366d436be382e000000000000000000000000000000" - }, - "result": { - "gasUsed": "0x36c73ec", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0xa96e51f481ad7fa03bf8bb21077c475f6104053a8d553f7be0c7ec6bdbc5f4ac", - "transactionPosition": 42 - }, - { - "type": "call", - "subtraces": 1, - "traceAddress": [], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000002ce375", - "to": "0xff000000000000000000000000000000002ce3b6", - "gas": "0x5b8f940", - "value": "0x0", - "input": "0x868e10c4000000000000000000000000000000000000000000000000000000000000000700000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000078782195b65590780b0d5b0ae31a98ddcc416abef382cebb9497b1f392ad01eea0da893b1830817c4363a3d8553f7f49433d65d2a2b36460ab3e02973695e6a0dcf8a7226c2de837a3c7780f2cba244e1eca00be2d1e4aaa1be39b9bb68dbaf151ee90bc7f5a499310f690858ed287ee53d27ce21a485938f847db28229e6c4c078d5a0f8dea6bc7303c891bd668e6208356efa1c527805399282f916b8ba3082034aa4b89be8eff9007efa934a7302aea6d85c2008276e7fd39ead1fe418e5671db68e328182462baa5f085e99ea93a2081f63683629c8451beb4a55fde9204d19dcfcda05b9024bfbe4c99cf42bbf21a61ab5a89a431256a122169cf3ea980326ccd701dbb37a04c6d97a850e0d7da22b08a7e4f6ccc717ba53b5950b95abdf294ffbccada1068611c9cf16516df5df034b397b8f55c3411024b16cf1f6a83ff6e1ba079c2aa300dc232c96d2bca93e3c75bf0db07e6bb8886c95035566b4c35e8ffb0730ff4a3f42d38dde63aaa711cced92c376d56f2d848c29b0a979a83acf3c46bb5d123c6cac402b87a4c24d59310f44af1bcc61316c8d778a2da26a6e0cc3c1f0e3053431dcc3bb29021e11ce9bd74bf44676734c8ab00ac0156ae09e3ab82f82f2ca467d3d5ac6153dd388faa6ea8384692559f65ed9ef1948e400c8908f335d494aa8f611fd52acdb72c913f7b0ab498b5dcf487e666499c3507b4a418934b4c26c60da8d2d53b4d17a1497db33596126ff1bc2ab4fccbba06ce6c2b83027d82ef31a7f796464d996d4085bf7da53a2e260c8dca9024f28ae70cce80a23482f4aa19aff838a77344229c7b6884ca2d319e6bfe987e8c025c1c71d892d480c7a55bb46e5c0498fcea2e7f5d06f5ad4caee57fd3da417ee1cdeb660a00519fd939feedf6eef6a2ae2cf57ec6a588dd0415debd6edf03ff259b70b7217c394e0c0b1504eb819c1731560cc7011367e650862035c1eff36d61358aef1cd66bfb345c5c349d986afa0bc31ce62f53d7746b5bbdb027db28352f745320768b01619a152b9f766689c7573bd1770fe7704faa8eff9b8c83c064681e7bf2b345d9def6b7d57a219841a2ed3b613adb9c13ac6ec9595f59691ed05db2274062868c42451cb669e048b3cb9a5929448822ada2029ffa2a42ca2a7b97e7e1cf652314ef151242d982d0204ab49bb8900d45fe57e07484cb008454aa8cf1bbef3b797c6c8bb100c7d7b000f3c347bd0c6ee5f225ab5c579c24ca23d4358f5349423c93e9c6f9056d3faf80241b9c0a3c8b77f18bd364f400a08a50c55cc989646d1de3b8b0c2b243f4529b72c0d1d5cb316b2068588848aa784c2c1b047b63d30507e9cd58cbcd0034390e5ec61ff2990b0b07867011387e9aad8e6b38c236bb93bd7eefd961d696bd64169784ad55e9c5196a0792e070a33dd95fee21939f59b7fd4a7bdb0c3c8c56135e7f8e621ba578e59eb328426864527394bfd080e8e2c06e317693bfb7f9c970dbeddcb5e6ea07f44c1cc364a0d864a645fab0422c0e27363dbc7256ad44eb265b00c671a7c0f20b0bf8904b008bc2ea07490db2fd954cfbe48d137963c1e286d79a8e60f1285003f476ab16b5b28a79ebfa78fd689c276203f4aea3b3f43f9999c81e03a13e9c8faede90f991440dd39a34fe4763db1652a4b7dc274412ee9b836f8aab5c7e02328a2897f8f331a378324e1d2a84cf998435fd6ad631f16b1ff3566def97b6b319c95038d95135c8f1edddd7ceee4083cd9fbfd48539db28007ae00c350bae09355a3d3cd68bc7c91ec4616dff3dd300e8da3861c78cd67b58a49bb4a5c98c4d29b72f3e0fe32193e9047ff4f62b207623f0da9eb315415bb153347b4d7e7989107a2fe1f2c25572e6e7d757b09c5e382d046e5a72934d378ac9298db613281a15611b522725f058106276dd70689b65ccb27b0fbccda99c7b502af389fcc4c441738462efd87439cafb0aab34b7f33d0f44da59b90ea03d7d8e7b7a96b8b78f34ba126936a08a5c915ea8c679aadf08a72b1ac79738ca72f157cadc8246b3332d20bf1c4319c4d0b4a0fc643710084c05a44ee3c6d6316f26b3d5c05afde5b660d6534dc0bb51647a9b980dca0aa2b9430ff689dd3b9fa871d5cf594df3b25776cf0bbfd9a4644bb53c083c7b3845064ae1046b0420475b0a5cd3d316ff15ed581d62456d117021a52e8adacda6ab8365c94578a84139adcdabe3d32694258b687202452117ba61bacab047517a323b2f36607052fa48ffd55dd3ef4a78c65a1441b4f628666b623f76ef4576136ac1ef16d6f98bbaeb14d1428790969175eee0968e9c76aaedacdb1b9d77f898e5aa8bfbf4d053a7cba468090a749f80ed668cb7f4dc7af33af98935de85ea462451634378eebb58acb78f6a1021a4b023ce212d83d60402f28dde712722f72d6438142148231e69e55a1ab5fa192f5a2e068a40d60327422e302b8a560b66f8eb0eb0a94c36bbaba86f38310bc2e8a8c06d9ab87c8af8edb971581de9ac84b8c7371560c9d4471fec715f622b92bc0439c1a3b2ff4a50e2e5db260beda585142eb60851c9b31e0e4254d010fecbf7af7ce9921e5dad628acb23f5406a2f3e9b647944de96e8d6b2e91aadbdf8cd27af2a8800af5f450015eaf88b47a858b5123f718c6378c9c085da31519fc41dfcaca195a0b2de33dfd216dac4b348e4cb4b2811853cdfab8e11ed55a00000000000000000000000000000000000000000000000000" - }, - "result": { - "gasUsed": "0x64fb6e", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0x8de36a2a721ac62d59185fc78daabfe4f045335c963be30edd33170cc7d2ca47", - "transactionPosition": 43 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [ - 0 - ], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000002ce3b6", - "to": "0xff00000000000000000000000000000000000004", - "gas": "0x584d9b1", - "value": "0x0", - "input": "0x868e10c400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000008318808821a002ce3b6195b65811a045353365820e8191611dfc1e40b36fb948cae0d899ee686b3cd8900eab1e5d4c9f8502a022a582047a51e4e53644247c4ad14e95dcf51070bc8748691de862db56543447a9993eb590780b0d5b0ae31a98ddcc416abef382cebb9497b1f392ad01eea0da893b1830817c4363a3d8553f7f49433d65d2a2b36460ab3e02973695e6a0dcf8a7226c2de837a3c7780f2cba244e1eca00be2d1e4aaa1be39b9bb68dbaf151ee90bc7f5a499310f690858ed287ee53d27ce21a485938f847db28229e6c4c078d5a0f8dea6bc7303c891bd668e6208356efa1c527805399282f916b8ba3082034aa4b89be8eff9007efa934a7302aea6d85c2008276e7fd39ead1fe418e5671db68e328182462baa5f085e99ea93a2081f63683629c8451beb4a55fde9204d19dcfcda05b9024bfbe4c99cf42bbf21a61ab5a89a431256a122169cf3ea980326ccd701dbb37a04c6d97a850e0d7da22b08a7e4f6ccc717ba53b5950b95abdf294ffbccada1068611c9cf16516df5df034b397b8f55c3411024b16cf1f6a83ff6e1ba079c2aa300dc232c96d2bca93e3c75bf0db07e6bb8886c95035566b4c35e8ffb0730ff4a3f42d38dde63aaa711cced92c376d56f2d848c29b0a979a83acf3c46bb5d123c6cac402b87a4c24d59310f44af1bcc61316c8d778a2da26a6e0cc3c1f0e3053431dcc3bb29021e11ce9bd74bf44676734c8ab00ac0156ae09e3ab82f82f2ca467d3d5ac6153dd388faa6ea8384692559f65ed9ef1948e400c8908f335d494aa8f611fd52acdb72c913f7b0ab498b5dcf487e666499c3507b4a418934b4c26c60da8d2d53b4d17a1497db33596126ff1bc2ab4fccbba06ce6c2b83027d82ef31a7f796464d996d4085bf7da53a2e260c8dca9024f28ae70cce80a23482f4aa19aff838a77344229c7b6884ca2d319e6bfe987e8c025c1c71d892d480c7a55bb46e5c0498fcea2e7f5d06f5ad4caee57fd3da417ee1cdeb660a00519fd939feedf6eef6a2ae2cf57ec6a588dd0415debd6edf03ff259b70b7217c394e0c0b1504eb819c1731560cc7011367e650862035c1eff36d61358aef1cd66bfb345c5c349d986afa0bc31ce62f53d7746b5bbdb027db28352f745320768b01619a152b9f766689c7573bd1770fe7704faa8eff9b8c83c064681e7bf2b345d9def6b7d57a219841a2ed3b613adb9c13ac6ec9595f59691ed05db2274062868c42451cb669e048b3cb9a5929448822ada2029ffa2a42ca2a7b97e7e1cf652314ef151242d982d0204ab49bb8900d45fe57e07484cb008454aa8cf1bbef3b797c6c8bb100c7d7b000f3c347bd0c6ee5f225ab5c579c24ca23d4358f5349423c93e9c6f9056d3faf80241b9c0a3c8b77f18bd364f400a08a50c55cc989646d1de3b8b0c2b243f4529b72c0d1d5cb316b2068588848aa784c2c1b047b63d30507e9cd58cbcd0034390e5ec61ff2990b0b07867011387e9aad8e6b38c236bb93bd7eefd961d696bd64169784ad55e9c5196a0792e070a33dd95fee21939f59b7fd4a7bdb0c3c8c56135e7f8e621ba578e59eb328426864527394bfd080e8e2c06e317693bfb7f9c970dbeddcb5e6ea07f44c1cc364a0d864a645fab0422c0e27363dbc7256ad44eb265b00c671a7c0f20b0bf8904b008bc2ea07490db2fd954cfbe48d137963c1e286d79a8e60f1285003f476ab16b5b28a79ebfa78fd689c276203f4aea3b3f43f9999c81e03a13e9c8faede90f991440dd39a34fe4763db1652a4b7dc274412ee9b836f8aab5c7e02328a2897f8f331a378324e1d2a84cf998435fd6ad631f16b1ff3566def97b6b319c95038d95135c8f1edddd7ceee4083cd9fbfd48539db28007ae00c350bae09355a3d3cd68bc7c91ec4616dff3dd300e8da3861c78cd67b58a49bb4a5c98c4d29b72f3e0fe32193e9047ff4f62b207623f0da9eb315415bb153347b4d7e7989107a2fe1f2c25572e6e7d757b09c5e382d046e5a72934d378ac9298db613281a15611b522725f058106276dd70689b65ccb27b0fbccda99c7b502af389fcc4c441738462efd87439cafb0aab34b7f33d0f44da59b90ea03d7d8e7b7a96b8b78f34ba126936a08a5c915ea8c679aadf08a72b1ac79738ca72f157cadc8246b3332d20bf1c4319c4d0b4a0fc643710084c05a44ee3c6d6316f26b3d5c05afde5b660d6534dc0bb51647a9b980dca0aa2b9430ff689dd3b9fa871d5cf594df3b25776cf0bbfd9a4644bb53c083c7b3845064ae1046b0420475b0a5cd3d316ff15ed581d62456d117021a52e8adacda6ab8365c94578a84139adcdabe3d32694258b687202452117ba61bacab047517a323b2f36607052fa48ffd55dd3ef4a78c65a1441b4f628666b623f76ef4576136ac1ef16d6f98bbaeb14d1428790969175eee0968e9c76aaedacdb1b9d77f898e5aa8bfbf4d053a7cba468090a749f80ed668cb7f4dc7af33af98935de85ea462451634378eebb58acb78f6a1021a4b023ce212d83d60402f28dde712722f72d6438142148231e69e55a1ab5fa192f5a2e068a40d60327422e302b8a560b66f8eb0eb0a94c36bbaba86f38310bc2e8a8c06d9ab87c8af8edb971581de9ac84b8c7371560c9d4471fec715f622b92bc0439c1a3b2ff4a50e2e5db260beda585142eb60851c9b31e0e4254d010fecbf7af7ce9921e5dad628acb23f5406a2f3e9b647944de96e8d6b2e91aadbdf8cd27af2a8800af5f450015eaf88b47a858b5123f718c6378c9c085da31519fc41dfcaca195a0b2de33dfd216dac4b348e4cb4b2811853cdfab8e11ed55ad82a5829000182e20381e802204d0dbb2063ca111c6cf0067d447609ce7c03d44c821d8fbb2c417e96ea7b5a18d82a5828000181e203922020c5455567d46fbaa53337932443d470d07b100bed66689795774541eac6bfd92e000000000000000000000000000000" - }, - "result": { - "gasUsed": "0x3dab44b", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0x8de36a2a721ac62d59185fc78daabfe4f045335c963be30edd33170cc7d2ca47", - "transactionPosition": 43 - }, - { - "type": "call", - "subtraces": 1, - "traceAddress": [], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000002af885", - "to": "0xff000000000000000000000000000000002afa9a", - "gas": "0x4ffdb6b", - "value": "0x1a7f287357826fcb", - "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000007000000000000000000000000000000000000000000000000000000000000005100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000789821a000190f1590780aa6dd1b6cde27d943f35448a588dda962b90bb5c2d83fb2fc6b05c8879362c7e817ed132379a5d26eedc593a3c606d21b1ad6dce5e114d940ed6339ad15a75f332154afdfcf7b12d95582ee4a14202d965a951d4188d7c229f08116f19110bf10ffe6f3131684f4b81a83d8f134ba8b934c9fbba9de485da6314a6e2b4506ce66faabd643095fc29368353eccc4666ccabd8aa5e770a7a983fc357e401b33944c70ad9ca78bc9cfc28c89557f36c9211906843f318dc25c061094b004bd12cba8e086520ffd326f3770543b454bc2dde31928e03115ae3ec7827456b9d1686d4221d1bbd908594ae4765699cd49f5d35b683c356421c3b3d84831c9f848ede29dd011f0b7b2b19cd4247ba549fc81ba77a935573ecb2a8e51fe4e0cbcf360a3a19cbca7006ede49b7b6520c744849e6b4daa1f79d178a9247fce67547d3b07d2e19762f3bf18dba2a6f5889aa872a843948cd7ef996705951974d2335c4b8f5162f93695a1babd67b5452e4bc9ec673031a23f3e19238e1ce953df0d6932a2b5a9a4cfdf9ec7023cb3a3532211daee7979736978bf3dfaba75b44e9890fc1e324a6ff5740bb5d5a91b57d21795a62843af5e1fdc25d90e2b087d36c3d646f526a2e9336f937d80f9e03a855a08878f80da10ea8167836870d3b8abf9ebf2b5b60f001ad41bf0e78dd27b48569c587335e59482402570c61740c114af91f1b163699498bf6b0cefd1d1d211328ce8f0cd831770938798626b188d47787e3169c103fd8acf60aafbad26c69259c269e55aef8ef27ce606e69cace5bed0e8570864881b582727c096fab89d33c0e6d2d22ac2b3d02974110bd104a36be0f1d20bf3ed4d3c4c3fbdbb4165f534eb2adf466587effe414558bd05d694c75eb071cacaf21d67d7782d4bc7b42deb129286a96d00de11b5ffbe2f07502224b99541637018c19fd37d110a3636addac50bc20bfd8c408d014e4f41c3f6caaccdeb47057447de642bfd04b7a240d526879cd484ecafb45f07e4c4728a6df049534316e76ab3b751101a606b8f7741c30508c86d68075d24bde13a68276dd9c64f2bdc20798f8d3ff9052273f3407b8a255a5de6303c2a2a2501a1f0271a3996dbad35959ce3f69169329593faea319dd23d1aaa66adb32fc8be99377176e47de9538a55b5fc6f02ed74a494dd4546953b7cc772b649ed17a4c728ecb4d63ea885293926751387f8e6f1779acbc0c8a4aebbea14b51dd4e651f8b2055c6b307c041fca08e590f50bfd1066f7e074b7f98fd4000fc8a10872fd115e4c74a21c9129fb0230ae27f4af10bdbcd02cfe96792a10628b0018ef8cf7984ab0b2fa34a9464404500c8e2d6945935a22a534962ea8ea448da54d79b8e53d56b9c2c7ad7de5b9003476d530f1bc1f7a3371a4e1dddffe93974eb03383e315632977426589e4a13022b55ad361150a48c42c35ba475848dd8a6ec957c7c1a6525830eea4ca05175c3cfb0a5c1faff0338023961101aa2cf5458b1ec14e1c958ee0a8048acac6cdde743043847c2c8951d708a00247e19a8fcc1f90325533a773ffbe795e78498ffc3d81267c2ca13eb7b9fe362ac30518537653ec47bf3bf172ec0acd9a65d9e8c16022a6ced9030033964a3505894f0621c9be0268328a4826c7596ea3f21a6f404126dcdfda7063e9e70175d012424b890043a619d74f99734052b262f7983646b7c273061c898341b4ad3a46e0c825790b4f8b43181669311936f36b7595c13f46f111c1b49bc1b18c9fc55bedd45667483a7effc18e808f5159a09530f142669318d5ad90c9c2c768351373af5cb789cd938c40735aee33285036aadc2f692dc762caeedf5f870d215c7c1b5fecd512b83dce65badc32776ee0ae289eef405ad38683de41cf605690a078d504cea2c2e7436e3b87f2bf9a89f06ca8a9a79b91cc449dabb10818c178334db4ca84d41a17d390d07ac237423365864ca394acd3a1c17dec6bea0542ef51aca5929e4ce6cc4a422d79008b060ee30734f93bfd0d2b5114f4a49c2479f2d2a2761bd7f60fa028a66c3206a393a6025cb26c546c1244b6ddb13e7bfde0de9441da50655ba2d00085ec2d9ccdb2d58068b8e55d04aa4c4283b7b8626ea6e7458cdcf6ba6100be0dda7d2eb19d2b9afc2bb6ad54a1f444e388e5e43d5160fd81ac0fd1235f8479fdc8594e65b5e45e2227348206fa42fa56fe1449825b054c1ad68f2b8420b5e669af6f79d3d9f6c36e5d1d7e9dd9a04377fc49a38e8dbc54d5a45e50a9f5504911666d08c6720967d47e27b6b680731a22100bd337afccac15368ae597a8ac55a8c548fe999c5c2c1767d04c91e547032e4fdcefde964a4901c34e6f374c0a77d1afe3a5ba1bc85a66ca17f3bb12192f0029ca4e402547b2f9b60f67925f14c7ea6f30eab4d4a75110969326dc7631ae6090e665147ff29b6b0aaa472ec0fd57937813cb55ff2dc4b47d73775022ed80e10e2aac5bbd886f1c91a09dad3ae74f3aa71db848302c08145f59d5e56a476f9ad63326fd249bb7083b4a77e5e576685ef161c65bfc3c2beb424ecf1b320c2471171cec1f8c27b04c1f685823d84a3aaf4f74b2f1a613064a75500b84f7c3635836b17380ef09f24ab3dd708af2705201af775ea0422215d6ff0a616ed0e6d31a94aad6f55cc65fd60336380f28870b93cc861d9b2ee43293063c27620892065a0000000000000000000000000000000000000000000000" - }, - "result": { - "gasUsed": "0x7544fb", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0xc0408073d9e8c991bfcb7a2e0205c700a68359ed40dcf6912fbc02d2623971ce", - "transactionPosition": 44 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [ - 0 - ], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000002afa9a", - "to": "0xff00000000000000000000000000000000000004", - "gas": "0x4bbae48", - "value": "0x0", - "input": "0x868e10c400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000008338808821a002afa9a1a000190f1811a045acda85820b24b3e1b897f42b136eab69c226a2ae7647adf103dc5fe05e67ef22daa0e65f8582074fea458846979d6f5d8383b77701ee74e32ded56a348a283c139f3019f64153590780aa6dd1b6cde27d943f35448a588dda962b90bb5c2d83fb2fc6b05c8879362c7e817ed132379a5d26eedc593a3c606d21b1ad6dce5e114d940ed6339ad15a75f332154afdfcf7b12d95582ee4a14202d965a951d4188d7c229f08116f19110bf10ffe6f3131684f4b81a83d8f134ba8b934c9fbba9de485da6314a6e2b4506ce66faabd643095fc29368353eccc4666ccabd8aa5e770a7a983fc357e401b33944c70ad9ca78bc9cfc28c89557f36c9211906843f318dc25c061094b004bd12cba8e086520ffd326f3770543b454bc2dde31928e03115ae3ec7827456b9d1686d4221d1bbd908594ae4765699cd49f5d35b683c356421c3b3d84831c9f848ede29dd011f0b7b2b19cd4247ba549fc81ba77a935573ecb2a8e51fe4e0cbcf360a3a19cbca7006ede49b7b6520c744849e6b4daa1f79d178a9247fce67547d3b07d2e19762f3bf18dba2a6f5889aa872a843948cd7ef996705951974d2335c4b8f5162f93695a1babd67b5452e4bc9ec673031a23f3e19238e1ce953df0d6932a2b5a9a4cfdf9ec7023cb3a3532211daee7979736978bf3dfaba75b44e9890fc1e324a6ff5740bb5d5a91b57d21795a62843af5e1fdc25d90e2b087d36c3d646f526a2e9336f937d80f9e03a855a08878f80da10ea8167836870d3b8abf9ebf2b5b60f001ad41bf0e78dd27b48569c587335e59482402570c61740c114af91f1b163699498bf6b0cefd1d1d211328ce8f0cd831770938798626b188d47787e3169c103fd8acf60aafbad26c69259c269e55aef8ef27ce606e69cace5bed0e8570864881b582727c096fab89d33c0e6d2d22ac2b3d02974110bd104a36be0f1d20bf3ed4d3c4c3fbdbb4165f534eb2adf466587effe414558bd05d694c75eb071cacaf21d67d7782d4bc7b42deb129286a96d00de11b5ffbe2f07502224b99541637018c19fd37d110a3636addac50bc20bfd8c408d014e4f41c3f6caaccdeb47057447de642bfd04b7a240d526879cd484ecafb45f07e4c4728a6df049534316e76ab3b751101a606b8f7741c30508c86d68075d24bde13a68276dd9c64f2bdc20798f8d3ff9052273f3407b8a255a5de6303c2a2a2501a1f0271a3996dbad35959ce3f69169329593faea319dd23d1aaa66adb32fc8be99377176e47de9538a55b5fc6f02ed74a494dd4546953b7cc772b649ed17a4c728ecb4d63ea885293926751387f8e6f1779acbc0c8a4aebbea14b51dd4e651f8b2055c6b307c041fca08e590f50bfd1066f7e074b7f98fd4000fc8a10872fd115e4c74a21c9129fb0230ae27f4af10bdbcd02cfe96792a10628b0018ef8cf7984ab0b2fa34a9464404500c8e2d6945935a22a534962ea8ea448da54d79b8e53d56b9c2c7ad7de5b9003476d530f1bc1f7a3371a4e1dddffe93974eb03383e315632977426589e4a13022b55ad361150a48c42c35ba475848dd8a6ec957c7c1a6525830eea4ca05175c3cfb0a5c1faff0338023961101aa2cf5458b1ec14e1c958ee0a8048acac6cdde743043847c2c8951d708a00247e19a8fcc1f90325533a773ffbe795e78498ffc3d81267c2ca13eb7b9fe362ac30518537653ec47bf3bf172ec0acd9a65d9e8c16022a6ced9030033964a3505894f0621c9be0268328a4826c7596ea3f21a6f404126dcdfda7063e9e70175d012424b890043a619d74f99734052b262f7983646b7c273061c898341b4ad3a46e0c825790b4f8b43181669311936f36b7595c13f46f111c1b49bc1b18c9fc55bedd45667483a7effc18e808f5159a09530f142669318d5ad90c9c2c768351373af5cb789cd938c40735aee33285036aadc2f692dc762caeedf5f870d215c7c1b5fecd512b83dce65badc32776ee0ae289eef405ad38683de41cf605690a078d504cea2c2e7436e3b87f2bf9a89f06ca8a9a79b91cc449dabb10818c178334db4ca84d41a17d390d07ac237423365864ca394acd3a1c17dec6bea0542ef51aca5929e4ce6cc4a422d79008b060ee30734f93bfd0d2b5114f4a49c2479f2d2a2761bd7f60fa028a66c3206a393a6025cb26c546c1244b6ddb13e7bfde0de9441da50655ba2d00085ec2d9ccdb2d58068b8e55d04aa4c4283b7b8626ea6e7458cdcf6ba6100be0dda7d2eb19d2b9afc2bb6ad54a1f444e388e5e43d5160fd81ac0fd1235f8479fdc8594e65b5e45e2227348206fa42fa56fe1449825b054c1ad68f2b8420b5e669af6f79d3d9f6c36e5d1d7e9dd9a04377fc49a38e8dbc54d5a45e50a9f5504911666d08c6720967d47e27b6b680731a22100bd337afccac15368ae597a8ac55a8c548fe999c5c2c1767d04c91e547032e4fdcefde964a4901c34e6f374c0a77d1afe3a5ba1bc85a66ca17f3bb12192f0029ca4e402547b2f9b60f67925f14c7ea6f30eab4d4a75110969326dc7631ae6090e665147ff29b6b0aaa472ec0fd57937813cb55ff2dc4b47d73775022ed80e10e2aac5bbd886f1c91a09dad3ae74f3aa71db848302c08145f59d5e56a476f9ad63326fd249bb7083b4a77e5e576685ef161c65bfc3c2beb424ecf1b320c2471171cec1f8c27b04c1f685823d84a3aaf4f74b2f1a613064a75500b84f7c3635836b17380ef09f24ab3dd708af2705201af775ea0422215d6ff0a616ed0e6d31a94aad6f55cc65fd60336380f28870b93cc861d9b2ee43293063c27620892065ad82a5829000182e20381e802206742e56094a25e9cb8ccfd548669001e885fdb05e5a3a32e414f33401d1ece41d82a5828000181e20392202072f2ba4a22ea83fd98e49f73658227ca7d4026d6c093aefc824f23e5a5acc71c00000000000000000000000000" - }, - "result": { - "gasUsed": "0x371397e", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0xc0408073d9e8c991bfcb7a2e0205c700a68359ed40dcf6912fbc02d2623971ce", - "transactionPosition": 44 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000001282b2", - "to": "0xff00000000000000000000000000000000127dd3", - "gas": "0x1d3a22b", - "value": "0x0", - "input": "0x868e10c400000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000f285078182004081820d58c0941484e4cc646ac06322666cfc99a0ed9ad905db567a1cc54211cdce6f42ee3dee60352b2be6a23aba9bba8e794ff0698928e0a8c7f0bd2ad8eadff2ce5e9750579598a700988bd54af7e4667fdd1ae926725280acf36e1ab80cc1de1601e8da010c876138e492f250edc21b8b59635fe8a8f7c7337b6c142ef29b76757b5145e1e9882e664b43e8a10ec295667d9cb2a6b2de7f8c3b15035cf8c7865767b31d6e2f2dd243b61e8abbc333640b050bb8524f7c22950fcf01306c1fdcef61e1541a0037e5f8582047ab5fe94c06cdbaec8b74525a8ef23928a1bbb62440ccbcd599b10154404d690000000000000000000000000000" - }, - "result": { - "gasUsed": "0x183032f", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0x8643bb35c2d74917f7c4ccc8b797edc5c71e1d27e5cf731220ffe82a3533e984", - "transactionPosition": 45 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [], - "action": { - "callType": "call", - "from": "0xff0000000000000000000000000000000015c8bc", - "to": "0xff00000000000000000000000000000000018a9c", - "gas": "0x2eafd6d", - "value": "0x0", - "input": "0x868e10c400000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000001b6851682820a40820b4081820d590180b2eb828eedbe33cad6f19452c4a64757c82c436f25cd9eaf8e04e32b77ad8322faf0b5c2faa9b927cb1f9c8e0bf7e943883c08d938f92ab436ad40321f8cea79f55f2011801a09c8728506c0d208f370617e3559354b5cbccc6e9e37493999a604e8c9020ac8f8ae7ec6318c8f907748b717f990f97a5f3bdefb656bd8e1ab058e90e1ae54d200c60da283573c6462db83164589345e177a4f12c229c60965ce0345ca9f4f375464dd4c4b22e5fe3c4b93c4a566e55f4ae5d2f888c22cbdfb3aac072a09b8d53f522a24aaef5d50a5cd405d81980c744454908cc3198eae1549d77e8f2f038dbc7600bae3709738870985f222ce07642e324042fd34bac9d89c17e327a10f5e6f1b1e15af4e85682eb5737255793ac3502890b72403550aa5440c8560e2634fc9d63ed0dac44bf3d1162666cc01adea437adc6ad2b5bf16c3e16c850c6d914054f5eff597510445ccd98600f5f6e75e683e13a942bc4c3ff3d26f7dce9ea5ad9281809f01018ea7c7a93991336e2358ad44f5094686cb6ea4441a0037e5f8582047ab5fe94c06cdbaec8b74525a8ef23928a1bbb62440ccbcd599b10154404d6900000000000000000000" - }, - "result": { - "gasUsed": "0x27952ea", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0x0cfb366083cce141ec4cbcb7fbc844164a6d689001b4fa1a5233ba49ac2a60b8", - "transactionPosition": 46 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [], - "action": { - "callType": "call", - "from": "0xff0000000000000000000000000000000015c8bc", - "to": "0xff00000000000000000000000000000000018a9c", - "gas": "0x367a5bf", - "value": "0x0", - "input": "0x868e10c400000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000001b685168282004082014081820d59018083cfb7899f78e9abf3b68fb49960e041030f4a1111d343aaf21b64aff5cb100e76bd22d19c9b785283ebfe8678bc4df1b758afeba700e80c3821f5cdcbcdcb4203a09c02283c38eed8602a4fd85cf2b7ace368e0870aae6f09269d4e2f84861b1737f0af1660fd760a0bccf250bc131510d151ee15ce9ca7949bea9f981dca6bfb9f1c1201a464982345ea31d5188afa8eea3801c8edacef9734027b0f51aeb2abb58399a43c27189a368f4baf04c151cec24704723ef65ec928f465d7673501b5e895e3968a207457fa85d74a28b0254d13d8ac435c1413fba0e64155ab4344403690d1191e9240699cd5798d27d7cd95e88ad5077892b556df1102216b13fcbd8b10a2225da004b1fd134292441bf70a721f2a846a15d6850866f59017271e016aa391968b25a1ba3ad81fd844601bfd2b57d7a40a2fc036f457dba80b4ebb2399677fe3419026d308eb9ab3e49ba08e0087e610c6f3f02f14f180549c697687f4bd3f0ea6d51ad0f238a320adc310b8a86f661a3ec01b30664a771471383d1a0037e5f8582047ab5fe94c06cdbaec8b74525a8ef23928a1bbb62440ccbcd599b10154404d6900000000000000000000" - }, - "result": { - "gasUsed": "0x2f3564d", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0x6e808c2937deb1c22c515ef2023ee58c101a4576db5d5e6ac159ac61ba036e6d", - "transactionPosition": 47 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [], - "action": { - "callType": "call", - "from": "0xff0000000000000000000000000000000015c8bc", - "to": "0xff00000000000000000000000000000000018a9c", - "gas": "0x39ef4a2", - "value": "0x0", - "input": "0x868e10c400000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000001b685168282064082074081820d590180afa14917d4144b56496b6cc74319d2098d173c50b0da49bb9512bed6dd7595601cfa80897e804817c0d2478207f71d2587dd46bfcf6b13cf6521add3b7652f47e4639b19027f26aeda2f1b3722110d54cd360c30150a3bbee48dc463e3e663cd0c21b12ee7ca34be107976d7b795d7dd4112d82dc88b762fe79698d0125e512e1a44c16134cc62e91459213c2fd61bad9562f6f79e1886637d4643ffdb1c6b73d81fa1c9340db4bd820e616bb6f6b95ab0a3ef8ad4a66493ee9a309a9e0a7809a0f7cbf76c61839805069b4e4877f198f05b1635a20c99068ab282c61cc8f3bc39f7ee82a63bf4b31edee162fd63aa4491f4a1f7ca6315e8d9821e782ce077ebc413911b951bf6d428541ac0953fa6ad6b0b6ea69baf7385e9c6c3aa4a649d4d09cd4b65224f6e84399d0833ecd50cc64e2ce13f38eb38e9988d7f1191f67e85e80460abb16b217cd2d086157df72c71875a36560a72f5fa0e8bf4575d9de99122fb62eefa0dcfaaedbff79ef30a2a9d9406468359ee9481cd403de8fd3de2321a0037e5f8582047ab5fe94c06cdbaec8b74525a8ef23928a1bbb62440ccbcd599b10154404d6900000000000000000000" - }, - "result": { - "gasUsed": "0x333798d", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0x3208378ba92f068351f798d2e18b7515acc2cb3706ad08828a25fa04bd8d5176", - "transactionPosition": 48 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [], - "action": { - "callType": "call", - "from": "0xff0000000000000000000000000000000015c8bc", - "to": "0xff00000000000000000000000000000000018a9c", - "gas": "0x2df8316", - "value": "0x0", - "input": "0x868e10c400000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000001b6851682820c40820d4081820d5901808b127976370cee8e21810fef9e01d393ed9157f129f195e89183a3b3986dd47d6b1fc50411f55fa62e3e8a21b87432f5b4083668c65d107e89a712a69228bc8f9b8434cfb6cce55292911f7259ad33dc27b52ce79130347f460b6cd9af4a1fc201fa009bfdec83897255da344d1f87b4549066a6d1480d2e45d78817a48496d9170fea27163562954584ad441d094b90b70ad4eda0a538df1aa2c98ad11c53df1527b388ba6d985f184749d68c6755f589aa44158cb62b25a216a65a6d34707c8e1964f519655f563b7d8176595a9962081e4080e7cc1222eb61b8fbbef1148bea9dc54cfccf8ad8a8219e4743f42d1cb8d068aee2c128a2e7078d273ceb80b1b5d0be48e05c89220f166e09f24a92d4fc1653d899044f27801cd7be7b60516b082a25bf8e584af5d2ab269526b640e2a9b15d8bef2746851aa32ff49440a5d422503b60e4891e4406dca42d2df507f6a69222360c3a8d4974bd664bbb84996665e0e006c354d5270219b23a91b13f61b74352e5c977ef9a334ca6e8dfef7c681a0037e5f8582047ab5fe94c06cdbaec8b74525a8ef23928a1bbb62440ccbcd599b10154404d6900000000000000000000" - }, - "result": { - "gasUsed": "0x2c63cfa", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0xa92fbc3e483686a903d03dad832850c63d219c087a9560d8dd11846aba05d051", - "transactionPosition": 49 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [], - "action": { - "callType": "call", - "from": "0xff0000000000000000000000000000000015c8bc", - "to": "0xff00000000000000000000000000000000018a9c", - "gas": "0x277b6fa", - "value": "0x0", - "input": "0x868e10c400000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000f285168182084081820d58c0a00fc058f003e9ce71e5c7f0110c27df98ac84029077d1a9b2744f7e0151cf7f37003c1eecdaca3938bdbd868a115882aff1ebd7e304411ef8bae7829e72f002b092325c39e5b2e81b27daa449d1e68e653f07b8bedfe8280056265a1f69de540f5bbb84169d1bc2066279ff20e66708357d446408e3da7c4b690b0f87ab40dce7a3f32066eccc99e74612d6ece3570caef37ce3559cfdde7e2415c47ee44bcc21d4f14974bbb91207ee9bc4597be61fea4b52f2f210f58bd1ee7ca856fc56461a0037e5f8582047ab5fe94c06cdbaec8b74525a8ef23928a1bbb62440ccbcd599b10154404d690000000000000000000000000000" - }, - "result": { - "gasUsed": "0x22bd49a", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0xac6b89882cdc6aee6b38cd3e2c2ddaa63c2d4f48c431cf8298e24c5d95af081a", - "transactionPosition": 50 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [], - "action": { - "callType": "call", - "from": "0xff0000000000000000000000000000000015c8bc", - "to": "0xff00000000000000000000000000000000018a9c", - "gas": "0x279471a", - "value": "0x0", - "input": "0x868e10c400000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000f285168182094081820d58c0a16986848d181bf1de1caebee43c1eccd53a589850e2a21b63e3fd4c7157be80c013abb17ffeb957d5064bb729f24be8a8a1f9bc526100fde11a14cb850d2773204d90747c7eeec3943bb2ab043b1c7427245198329a88920423807b1d378a1d05ecc5761e6fe291dc6f1a826f8af0d213406ffa54176aa3954fbdc6875bf14dab7c3b3e37973d09e7c2501bd3b8a3dc8a9f1a14e911342e61f958afb3765d537f4442254d53cf384e210b8f22e897d2fb2da104c1cb7167f1b1dcddc216105b1a0037e5f8582047ab5fe94c06cdbaec8b74525a8ef23928a1bbb62440ccbcd599b10154404d690000000000000000000000000000" - }, - "result": { - "gasUsed": "0x2381302", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0x66969ead995791added4e5ea61637c9f338e94808ab77f703ff227e63cd158a9", - "transactionPosition": 51 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000000c4d75", - "to": "0xff000000000000000000000000000000001536f3", - "gas": "0x210e371", - "value": "0x0", - "input": "0x868e10c400000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000001b78518228282004082014081820d590180ab8892f0b4c2c9054daf7a9ed12d1b5a3a216bf4c540c2d12ff2f19b40e14a2aaee5a22012b97888bf5e150a69e6561f850dbdc6a8bce7a292800a139bddff40a363999f600294b6b7490984d83af7f92bb98c27df4490661b5bfb662ace46f20c62a9fc0db2401dc84f0548f8cb6c2bb8d57ec3d520d49aea2dc2a6416dff3072b2b901948d6474c794484e2e0fc90b95856412e33b02c965f56065eaf18a042545cf1094f16c16f0a70f671d11276a73633fd4aa4e0ff4d261d07298950c25b3b9dd698ea90ef14758e2d760a8339b6e893c97bb5a0797c9b0a199bb6dc7fd82dcdb58c3f4e8e7b525823cc28430d280d85fd1c4cf59019d8387b0edaf3c136d04bdceba76e8942efb9ec1f642d2db2f61e50e5c6917ef2485e272d4ef047712b5b7e6ae6c4621ea2cff3dabab4cd71c0bd559460e39d5f6e571006c3286bf72ec724e9bd5d9f8ccb0abd1557ef7408c3cc690e1e7ba8f4b0fb87d447b6e66cb34b90c3150ba3a2bd623fcc09ebefcabf62aa3bd01c63308c2428272ace8b01a0037e5f8582047ab5fe94c06cdbaec8b74525a8ef23928a1bbb62440ccbcd599b10154404d69000000000000000000" - }, - "result": { - "gasUsed": "0x1b7284d", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0x957227ab77c764b14f4df2b5f2a60da060bd4da8f8f9f8ae87a67f2188f134a6", - "transactionPosition": 52 - }, - { - "type": "call", - "subtraces": 1, - "traceAddress": [], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000002d0792", - "to": "0xff000000000000000000000000000000002d0798", - "gas": "0x5a9136f", - "value": "0x0", - "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000007000000000000000000000000000000000000000000000000000000000000005100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000787821962b1590780a3e5852016727b45737aa29963044c46e1db6470429aacf34f13901c0f14737eefdbd5397827482d83c172d144687b9a88ea422d78dbbd983dfc149f18e56512cc94e94c81b34d5dab65edf4f2162c96a23f9a7e64389a6d5687930180c5777116996e4921375c5c20f43a201b95a204a461e4641476649e5cca5e164c74a8bd4a6598ded80a57ef8b1fba959de531e8a27a73a56ac06909e5a71852b04441cabb5675d2012b93285232c8e631d8bd76659134bcceabedadeca603df88a21c3db6640598a40b0df2473d6fc3869229a85efc0636dfedb4d34eca5b74904bbd5ed3af1cbb1f1a87643ed8414835ee9eb8a4825f62c74d7e750e5ec9fc5578548b78103536969c97d1f58ddf1acf39527e335827f8a78dd2d0a25419032819f99b03b66aa0fb3abb3f03068f34f03f0a8659bb6f1c16b80534eeacd0eaf88493a0ae91dfb25e5f06a4dd152b29a2c6498fb22755da0e514ab417656a676ca94d3b621810634ac162decd849c9488542d6d2fc73f293cd670884391b243947c42e6a310fca6b6f11683aea97b10d7040cbd42cd18c3c7b1f74ea9465037a82639898baf55c881719afd3b515668eaa6d42b8cf88178ec5656af49760c56f3384ca50305479afeb610aa3c73637f1b5f454a8eb775bee088a34fbf1008a303afd43d114b09ae243b41895bd07cba783646fc19a54f9e1026fc5ec214c6147795dcc334b6836bed237f75c03f70f81bea01c386742a25f408582d0cd4feee4b1d842aa45169e841cf4047bbb3774cf4e94989255905aad75a10ed978c9643994b39deaebb37a3a9d085a5161d592c536992b66e7ed7652b41676254a339e4cf630f7c1f8d067d558ea51a712e19f02829003589c84b321cd305f2b22dbf4777c6a04df233f59e49794948b97539b73bb1e801cd4c6d55b2462cee225939279de6b5d509a6c1ae5eec57cd9ef5eae92ecb9a6d83c1afce2371e1bb740975f4aeb53b57e6c8c3306d35541cd8bdb0e4ded06ff1aafa1fda68a352fa08ac3f2d3d878030af72503dc5edafa040e3f86d4dd393c84b75622c2579867f2aacf2b0fcc871d6a29ea710940a052fa9fcd7b88e61b629b41569616cf37756928a313e6c6e22e4e49ee9d5ff55bdae517a645d5e4ae3258b97737f906aa1f033d50e76e7d7994e9e2472ee6bfdb2b87ba54d0d85d81417674264deb6d747a2de5d3e8719ce7e6f0c510da24e1e3bb2c83810e9dfe19c5d69908183132dc368ecf12f57515b980bbf22bcf06fb059167910897ab200517795dcb31ffdb04466e78d000310859edb2e5cc76e85f6bbf79f1aba4667429b24b25e698ac37f20e24ac9fea4e7ebac408106c36d4939a70a027a838f3f561e41f3b084a773980f2803943d3145a5a5742fb373dd991e3b29f2746ee4820e19a1b52dbe9dac79b36204f52a85368cb507d93b7ec6a4935258ffacd2111459d57ed8b959e19627453384708da76ec1176404ad527184daee6523f76681a6958aa405e33d7335204474a9cc5ba2a7dbf1d556629c54ef419af663dc6c87b3166b1db21db1a772a94e119da2d83a9923bf6d08e50660df19f2ea19ba1408458ad5163911d0d5053993569f7c2feb04379c4db9cb6dc5bc2304138b9aafb4ae1165651a181bf252936af0c1450d9906bac56a379f27153b12424a645b5dc22f646e9483d5e9212adc5ef5755c491ee76be107d2cd0b0945a7d10aa5433be44fc36d806a83e064d88f9b1ffa85ca9428f1483c08ee4519fc35692df29f69fead901ba40966c150d96c54e7bfac3bd02a543d881d3609fb7d3956b942df9da3fe417ae9a6dcf2454eb7987ff5437f12cd8d77e15cdd292bd02d7480edf5b28dd6667ab3b0fbad18f5081b4171b6b3c92d8c3090b6d7f244272825fef7d2c3ba44c68ede30cdf681567a43a18d24d65948f09884933aa0eef904bb940b3dd1a92bfd67e6abc7eeb148aad962836b622007653043443474e44c2042c2e353bf99b556853d26a4d314e715bbbdc9073ffb98ec6cc40fa010c562c6b123ffff8cabfb1038a29a489899ef8d6dbe2031f073ab9f4d82582df6660dacd347dd6d207174f18db4977c669d3ae04f6d9fc5e1b190358c7155c1afb1f18e022d0207aaa724aaafc2484478ba1cd7abfb18046cf04a59eb36a313ab0e6cc23799943432d828ff486adcd3a6cd078cc8a931fcd0bf2c392e70daa6ac9c1def1e3a249fc6db7c451beab38b40738eba8e40bfc26b0b9580aacce29745135024154074bcd74774c8173f388b9a41ea5bbf36f8aec182dff1506f097f5a7aeb71749767b80b1ad9898d977c9f250439c8baf8114f94a7ec61f4ccd958f4680144ad1558c0e075d3b0d8a4aa38307191765bdadf1c62fb19b0e5b995bed7898195d30ddc393b2cd516976ea358e10882af8013947be9d0a82803e18d12c5f3d90e382a0d336413382e47014735c63bc04063941fdf5a8a5d1b57738a501ee1b50b185033926bc90480e0b8a991a693a610fa869245301c023fd291efb61b60d1af0f0f2e2080e1c17ee21ed1a4c6aa53c232949a1a0e2d325d0d3309c7b7de30811c7b1e2872918b3c2cc4f055e2b9afa7ade34b7462b95a8ed657248bd379407c57e0c0243aefdaa54b0597cc4206a85b6db894d534e00b91c07d7942e29107bb4dd5db5749e790e7a57bd23d50af35116d1c15f38e7ade6baa7000000000000000000000000000000000000000000000000000" - }, - "result": { - "gasUsed": "0x91d33d", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0x4de7b42d21f0a27c46254aa5536ddc1ba165322f56ad47655d256a8fdbcc0839", - "transactionPosition": 53 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [ - 0 - ], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000002d0798", - "to": "0xff00000000000000000000000000000000000004", - "gas": "0x54822a8", - "value": "0x0", - "input": "0x868e10c400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000008318808821a002d07981962b1811a045b2a6358203fd3e68cc4519573cd488191e2be8fb213cc957966f7ab315c24afd373103850582058eff1bd5d4366dbe11ce747891d3e24826b623b4459903b6ab586fda7f31209590780a3e5852016727b45737aa29963044c46e1db6470429aacf34f13901c0f14737eefdbd5397827482d83c172d144687b9a88ea422d78dbbd983dfc149f18e56512cc94e94c81b34d5dab65edf4f2162c96a23f9a7e64389a6d5687930180c5777116996e4921375c5c20f43a201b95a204a461e4641476649e5cca5e164c74a8bd4a6598ded80a57ef8b1fba959de531e8a27a73a56ac06909e5a71852b04441cabb5675d2012b93285232c8e631d8bd76659134bcceabedadeca603df88a21c3db6640598a40b0df2473d6fc3869229a85efc0636dfedb4d34eca5b74904bbd5ed3af1cbb1f1a87643ed8414835ee9eb8a4825f62c74d7e750e5ec9fc5578548b78103536969c97d1f58ddf1acf39527e335827f8a78dd2d0a25419032819f99b03b66aa0fb3abb3f03068f34f03f0a8659bb6f1c16b80534eeacd0eaf88493a0ae91dfb25e5f06a4dd152b29a2c6498fb22755da0e514ab417656a676ca94d3b621810634ac162decd849c9488542d6d2fc73f293cd670884391b243947c42e6a310fca6b6f11683aea97b10d7040cbd42cd18c3c7b1f74ea9465037a82639898baf55c881719afd3b515668eaa6d42b8cf88178ec5656af49760c56f3384ca50305479afeb610aa3c73637f1b5f454a8eb775bee088a34fbf1008a303afd43d114b09ae243b41895bd07cba783646fc19a54f9e1026fc5ec214c6147795dcc334b6836bed237f75c03f70f81bea01c386742a25f408582d0cd4feee4b1d842aa45169e841cf4047bbb3774cf4e94989255905aad75a10ed978c9643994b39deaebb37a3a9d085a5161d592c536992b66e7ed7652b41676254a339e4cf630f7c1f8d067d558ea51a712e19f02829003589c84b321cd305f2b22dbf4777c6a04df233f59e49794948b97539b73bb1e801cd4c6d55b2462cee225939279de6b5d509a6c1ae5eec57cd9ef5eae92ecb9a6d83c1afce2371e1bb740975f4aeb53b57e6c8c3306d35541cd8bdb0e4ded06ff1aafa1fda68a352fa08ac3f2d3d878030af72503dc5edafa040e3f86d4dd393c84b75622c2579867f2aacf2b0fcc871d6a29ea710940a052fa9fcd7b88e61b629b41569616cf37756928a313e6c6e22e4e49ee9d5ff55bdae517a645d5e4ae3258b97737f906aa1f033d50e76e7d7994e9e2472ee6bfdb2b87ba54d0d85d81417674264deb6d747a2de5d3e8719ce7e6f0c510da24e1e3bb2c83810e9dfe19c5d69908183132dc368ecf12f57515b980bbf22bcf06fb059167910897ab200517795dcb31ffdb04466e78d000310859edb2e5cc76e85f6bbf79f1aba4667429b24b25e698ac37f20e24ac9fea4e7ebac408106c36d4939a70a027a838f3f561e41f3b084a773980f2803943d3145a5a5742fb373dd991e3b29f2746ee4820e19a1b52dbe9dac79b36204f52a85368cb507d93b7ec6a4935258ffacd2111459d57ed8b959e19627453384708da76ec1176404ad527184daee6523f76681a6958aa405e33d7335204474a9cc5ba2a7dbf1d556629c54ef419af663dc6c87b3166b1db21db1a772a94e119da2d83a9923bf6d08e50660df19f2ea19ba1408458ad5163911d0d5053993569f7c2feb04379c4db9cb6dc5bc2304138b9aafb4ae1165651a181bf252936af0c1450d9906bac56a379f27153b12424a645b5dc22f646e9483d5e9212adc5ef5755c491ee76be107d2cd0b0945a7d10aa5433be44fc36d806a83e064d88f9b1ffa85ca9428f1483c08ee4519fc35692df29f69fead901ba40966c150d96c54e7bfac3bd02a543d881d3609fb7d3956b942df9da3fe417ae9a6dcf2454eb7987ff5437f12cd8d77e15cdd292bd02d7480edf5b28dd6667ab3b0fbad18f5081b4171b6b3c92d8c3090b6d7f244272825fef7d2c3ba44c68ede30cdf681567a43a18d24d65948f09884933aa0eef904bb940b3dd1a92bfd67e6abc7eeb148aad962836b622007653043443474e44c2042c2e353bf99b556853d26a4d314e715bbbdc9073ffb98ec6cc40fa010c562c6b123ffff8cabfb1038a29a489899ef8d6dbe2031f073ab9f4d82582df6660dacd347dd6d207174f18db4977c669d3ae04f6d9fc5e1b190358c7155c1afb1f18e022d0207aaa724aaafc2484478ba1cd7abfb18046cf04a59eb36a313ab0e6cc23799943432d828ff486adcd3a6cd078cc8a931fcd0bf2c392e70daa6ac9c1def1e3a249fc6db7c451beab38b40738eba8e40bfc26b0b9580aacce29745135024154074bcd74774c8173f388b9a41ea5bbf36f8aec182dff1506f097f5a7aeb71749767b80b1ad9898d977c9f250439c8baf8114f94a7ec61f4ccd958f4680144ad1558c0e075d3b0d8a4aa38307191765bdadf1c62fb19b0e5b995bed7898195d30ddc393b2cd516976ea358e10882af8013947be9d0a82803e18d12c5f3d90e382a0d336413382e47014735c63bc04063941fdf5a8a5d1b57738a501ee1b50b185033926bc90480e0b8a991a693a610fa869245301c023fd291efb61b60d1af0f0f2e2080e1c17ee21ed1a4c6aa53c232949a1a0e2d325d0d3309c7b7de30811c7b1e2872918b3c2cc4f055e2b9afa7ade34b7462b95a8ed657248bd379407c57e0c0243aefdaa54b0597cc4206a85b6db894d534e00b91c07d7942e29107bb4dd5db5749e790e7a57bd23d50af35116d1c15f38e7ade6baa70d82a5829000182e20381e802200c82b78d2956e18f54a7f16aeb3aad9d317d478d5b3212fefcc83d184999b859d82a5828000181e20392202096242ea129b2d38f1a44eb5dda71f9cd9f697abd137478959f807b834680c202000000000000000000000000000000" - }, - "result": { - "gasUsed": "0x3db1b4c", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0x4de7b42d21f0a27c46254aa5536ddc1ba165322f56ad47655d256a8fdbcc0839", - "transactionPosition": 53 - }, - { - "type": "call", - "subtraces": 3, - "traceAddress": [], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000002ce389", - "to": "0xff000000000000000000000000000000002ce3be", - "gas": "0x2fb7db3", - "value": "0x0", - "input": "0x868e10c4000000000000000000000000000000000000000000000000000000000000001c00000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000007081818708193efdd82a5829000182e20381e802207f4d07b116b6300ab7afb497429e9b845c6b81d1a4aa8b3f7dee0fb9c2d5ad5a1a0037e10e811a045365671a00480bc5d82a5828000181e203922020a2e68e2e80ebeaca7ed536bb450bb1c4a8577e2209135546f2287009cc45671b00000000000000000000000000000000" - }, - "result": { - "gasUsed": "0x2042bc7", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0x8912c515d71922148eec6cd24d28673f3920b413bf2ced0ae6847f79c001d58e", - "transactionPosition": 54 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [ - 0 - ], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000002ce3be", - "to": "0xff00000000000000000000000000000000000002", - "gas": "0x2f0178c", - "value": "0x0", - "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "result": { - "gasUsed": "0xfabb0", - "output": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000418282581a000286387ff107ef952f3612eb2e2a6606418e51c0f2cceeda5f57011731c2a04034dd33df402838a815a0b69ec1184b8c104a0001c0dfc71cd077c4b900000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0x8912c515d71922148eec6cd24d28673f3920b413bf2ced0ae6847f79c001d58e", - "transactionPosition": 54 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [ - 1 - ], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000002ce3be", - "to": "0xff00000000000000000000000000000000000004", - "gas": "0x2df5244", - "value": "0x0", - "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000009000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "result": { - "gasUsed": "0x105fb2", - "output": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000578449007d2903b8000000004a000190f76c1adff180004c00907e2dd41a18e7c7a7f2bd82581a0001916cb98a2c3dfb67a389a588fb0e593f762dd6c9195851235601fba7e16707ee65746d4671e80aa2bb15bc7d6ebe3b000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0x8912c515d71922148eec6cd24d28673f3920b413bf2ced0ae6847f79c001d58e", - "transactionPosition": 54 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [ - 2 - ], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000002ce3be", - "to": "0xff00000000000000000000000000000000000005", - "gas": "0x2cd3cd3", - "value": "0x0", - "input": "0x868e10c4000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000f818183081a00480bc5811a045365670000000000000000000000000000000000" - }, - "result": { - "gasUsed": "0x4888fe", - "output": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000002e8181d82a5828000181e203922020a2e68e2e80ebeaca7ed536bb450bb1c4a8577e2209135546f2287009cc45671b000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0x8912c515d71922148eec6cd24d28673f3920b413bf2ced0ae6847f79c001d58e", - "transactionPosition": 54 - }, - { - "type": "call", - "subtraces": 3, - "traceAddress": [], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000002cf0e9", - "to": "0xff000000000000000000000000000000002cf0eb", - "gas": "0x3f8e7b6", - "value": "0x0", - "input": "0x868e10c4000000000000000000000000000000000000000000000000000000000000001c00000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000007081818708194760d82a5829000182e20381e802205bd2105f384ad53a57500cfda364fb6d48557fd75a9da12488bd97950b8e22031a0037e0db811a0453be491a00480bc7d82a5828000181e203922020133508dc0c6fc1cdcb063ef40e4cb11f4165e5d52f9bf41ca2f9ceb1f1e5701800000000000000000000000000000000" - }, - "result": { - "gasUsed": "0x2cee852", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0xf698e990a7ea335ddf046c92759be8d87ced39bae42e1295308137ce727281d5", - "transactionPosition": 55 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [ - 0 - ], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000002cf0eb", - "to": "0xff00000000000000000000000000000000000002", - "gas": "0x3ed818f", - "value": "0x0", - "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "result": { - "gasUsed": "0xfabb0", - "output": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000418282581a000286387ff107ef952f3612eb2e2a6606418e51c0f2cceeda5f57011731c2a04034dd33df402838a815a0b69ec1184b8c104a0001c0dfc71cd077c4b900000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0xf698e990a7ea335ddf046c92759be8d87ced39bae42e1295308137ce727281d5", - "transactionPosition": 55 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [ - 1 - ], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000002cf0eb", - "to": "0xff00000000000000000000000000000000000004", - "gas": "0x3dcbc47", - "value": "0x0", - "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000009000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "result": { - "gasUsed": "0x105fb2", - "output": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000578449007d2903b8000000004a000190f76c1adff180004c00907e2dd41a18e7c7a7f2bd82581a0001916cb98a2c3dfb67a389a588fb0e593f762dd6c9195851235601fba7e16707ee65746d4671e80aa2bb15bc7d6ebe3b000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0xf698e990a7ea335ddf046c92759be8d87ced39bae42e1295308137ce727281d5", - "transactionPosition": 55 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [ - 2 - ], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000002cf0eb", - "to": "0xff00000000000000000000000000000000000005", - "gas": "0x3caa6d6", - "value": "0x0", - "input": "0x868e10c4000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000f818183081a00480bc7811a0453be490000000000000000000000000000000000" - }, - "result": { - "gasUsed": "0x4887a6", - "output": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000002e8181d82a5828000181e203922020133508dc0c6fc1cdcb063ef40e4cb11f4165e5d52f9bf41ca2f9ceb1f1e57018000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0xf698e990a7ea335ddf046c92759be8d87ced39bae42e1295308137ce727281d5", - "transactionPosition": 55 - }, - { - "type": "call", - "subtraces": 7, - "traceAddress": [], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000001bac42", - "to": "0xff00000000000000000000000000000000000005", - "gas": "0x13c70235", - "value": "0x0", - "input": "0x868e10c400000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000eb8181828bd82a5828000181e203922020edaa33053d8a5f916356a3ff18ad24fbda73e61e7d6bdd4486427e9bb61f180e1b0000000800000000f555010f29c20971f6e29cead00b57277fb5975fd83f914400a29f74783b6261666b7265696234616d7a696b6578787072753432747579626c7a35777736776636776a6236737935367a747863636b62696d647374627966711a003814d61a004f81164048001b6a51c29fe8e040584201667d08b321901df7ed569ed7f84c82d3b69c25b4bdee7b916eb19319500c778b06b89684eefd98e18bf8f5086c272b4c5fc53ac344a1b17e9d226612077b346f00000000000000000000000000000000000000000000" - }, - "result": { - "gasUsed": "0x8c4173b", - "output": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000982811a045b5762410c0000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0xe62a29767a0e1030fd633df1090900a6ca3b2ccfde69682b8b4850a4faa385ad", - "transactionPosition": 56 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [ - 0 - ], - "action": { - "callType": "call", - "from": "0xff00000000000000000000000000000000000005", - "to": "0xff000000000000000000000000000000001d0fa2", - "gas": "0x13b36e85", - "value": "0x0", - "input": "0x868e10c40000000000000000000000000000000000000000000000000000000014c1cb970000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000054400c2d86e000000000000000000000000000000000000000000000000000000" - }, - "result": { - "gasUsed": "0x13301e", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000001f500000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0xe62a29767a0e1030fd633df1090900a6ca3b2ccfde69682b8b4850a4faa385ad", - "transactionPosition": 56 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [ - 1 - ], - "action": { - "callType": "call", - "from": "0xff00000000000000000000000000000000000005", - "to": "0xff00000000000000000000000000000000000002", - "gas": "0x139f99ae", - "value": "0x0", - "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "result": { - "gasUsed": "0xfabb0", - "output": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000418282581a000286387ff107ef952f3612eb2e2a6606418e51c0f2cceeda5f57011731c2a04034dd33df402838a815a0b69ec1184b8c104a0001c0dfc71cd077c4b900000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0xe62a29767a0e1030fd633df1090900a6ca3b2ccfde69682b8b4850a4faa385ad", - "transactionPosition": 56 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [ - 2 - ], - "action": { - "callType": "call", - "from": "0xff00000000000000000000000000000000000005", - "to": "0xff00000000000000000000000000000000000004", - "gas": "0x138ecdce", - "value": "0x0", - "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000009000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "result": { - "gasUsed": "0x105fb2", - "output": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000578449007d2903b8000000004a000190f76c1adff180004c00907e2dd41a18e7c7a7f2bd82581a0001916cb98a2c3dfb67a389a588fb0e593f762dd6c9195851235601fba7e16707ee65746d4671e80aa2bb15bc7d6ebe3b000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0xe62a29767a0e1030fd633df1090900a6ca3b2ccfde69682b8b4850a4faa385ad", - "transactionPosition": 56 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [ - 3 - ], - "action": { - "callType": "staticcall", - "from": "0xff00000000000000000000000000000000000005", - "to": "0xff0000000000000000000000000000000021f2a6", - "gas": "0x137bc7fd", - "value": "0x0", - "input": "0x868e10c4000000000000000000000000000000000000000000000000000000009d8b06780000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000ea825841667d08b321901df7ed569ed7f84c82d3b69c25b4bdee7b916eb19319500c778b06b89684eefd98e18bf8f5086c272b4c5fc53ac344a1b17e9d226612077b346f0058a48bd82a5828000181e203922020edaa33053d8a5f916356a3ff18ad24fbda73e61e7d6bdd4486427e9bb61f180e1b0000000800000000f555010f29c20971f6e29cead00b57277fb5975fd83f914400a29f74783b6261666b7265696234616d7a696b6578787072753432747579626c7a35777736776636776a6236737935367a747863636b62696d647374627966711a003814d61a004f81164048001b6a51c29fe8e04000000000000000000000000000000000000000000000" - }, - "result": { - "gasUsed": "0x2e9853", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000001f500000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0xe62a29767a0e1030fd633df1090900a6ca3b2ccfde69682b8b4850a4faa385ad", - "transactionPosition": 56 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [ - 4 - ], - "action": { - "callType": "call", - "from": "0xff00000000000000000000000000000000000005", - "to": "0xff00000000000000000000000000000000000007", - "gas": "0x126d96d9", - "value": "0x0", - "input": "0x868e10c400000000000000000000000000000000000000000000000000000000c26ddbd50000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000064500a6e587010000000000000000000000000000000000000000000000000000" - }, - "result": { - "gasUsed": "0x2ce23f", - "output": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000104f001206c3fe6bb46656ea1e89d8000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0xe62a29767a0e1030fd633df1090900a6ca3b2ccfde69682b8b4850a4faa385ad", - "transactionPosition": 56 - }, - { - "type": "call", - "subtraces": 1, - "traceAddress": [ - 5 - ], - "action": { - "callType": "call", - "from": "0xff00000000000000000000000000000000000005", - "to": "0xff00000000000000000000000000000000000007", - "gas": "0x123e221e", - "value": "0x0", - "input": "0x868e10c400000000000000000000000000000000000000000000000000000000d7d4deed000000000000000000000000000000000000000000000000000000000000005100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000067844500a6e587014200064d006f05b59d3b20000000000000584d8281861a001d0fa2d82a5828000181e203922020edaa33053d8a5f916356a3ff18ad24fbda73e61e7d6bdd4486427e9bb61f180e1b00000008000000001a00176c401a001b60c01a003814d68000000000000000000000000000000000000000000000000000" - }, - "result": { - "gasUsed": "0x378d0ea", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000043844f00120654f8b6172b36ea1e89d80000500006a8d15c1acd58b4e5110000000000520002f050b9c93842f9b9dcb15680000000004d83820180820080811a034d18b40000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0xe62a29767a0e1030fd633df1090900a6ca3b2ccfde69682b8b4850a4faa385ad", - "transactionPosition": 56 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [ - 5, - 0 - ], - "action": { - "callType": "call", - "from": "0xff00000000000000000000000000000000000007", - "to": "0xff00000000000000000000000000000000000006", - "gas": "0xf2d40e0", - "value": "0x0", - "input": "0x868e10c400000000000000000000000000000000000000000000000000000000de180de300000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000006e821a85223bdf5866861a0021f2a606054d006f05b59d3b20000000000000584d8281861a001d0fa2d82a5828000181e203922020edaa33053d8a5f916356a3ff18ad24fbda73e61e7d6bdd4486427e9bb61f180e1b00000008000000001a00176c401a001b60c01a003814d68040000000000000000000000000000000000000" - }, - "result": { - "gasUsed": "0x2439f4c", - "output": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000d83820180820080811a034d18b400000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0xe62a29767a0e1030fd633df1090900a6ca3b2ccfde69682b8b4850a4faa385ad", - "transactionPosition": 56 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [ - 6 - ], - "action": { - "callType": "call", - "from": "0xff00000000000000000000000000000000000005", - "to": "0xff0000000000000000000000000000000021f2a6", - "gas": "0x4c7ce06", - "value": "0x0", - "input": "0x868e10c400000000000000000000000000000000000000000000000000000000f98c996600000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000009c8258948bd82a5828000181e203922020edaa33053d8a5f916356a3ff18ad24fbda73e61e7d6bdd4486427e9bb61f180e1b0000000800000000f54500a6e587014400a29f74783b6261666b7265696234616d7a696b6578787072753432747579626c7a35777736776636776a6236737935367a747863636b62696d647374627966711a003814d61a004f81164048001b6a51c29fe8e0401a045b576200000000" - }, - "result": { - "gasUsed": "0x9858a", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0xe62a29767a0e1030fd633df1090900a6ca3b2ccfde69682b8b4850a4faa385ad", - "transactionPosition": 56 - }, - { - "type": "call", - "subtraces": 1, - "traceAddress": [], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000002cbdce", - "to": "0xff000000000000000000000000000000002cbe59", - "gas": "0x5b6dcdd", - "value": "0x0", - "input": "0x868e10c4000000000000000000000000000000000000000000000000000000000000000700000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000078782196cb85907808da266f0d295889a05be62ca726ac93cae2bf1976516514387a04f6c161b95f727f97fe04494e7eed2533dfa309ff933835dfcf8a37564e3ebf5e4f2a7c0048aa707c303814028b15d44a34184d6e88f422defb35d333642a4592eee09398954097c2bb5290d2d989947f61ffcfbb57ff4efd12c5f0bdbe44e97487e45aeca92cdfd5678303f0b20617dc9fb65ecc734986207f6e60aeb1426702cebcba9b1e3e99b1f05d922b877818dcea601b115ee1794c04f76b666de9bf218f78f3bcb44b1c2f38368f86d53ce36b72bf6517d1a5f289da6799cbc2619403ea09b6f2fe13d598586938debb8a2e28762e65540b2b5401175c6e0533f20065b308444fcdd2462733270fba4adff7c4f1e9ac2c311b0c4e2fc6304f26ad4f1700a849eecaa0778bc7133031c1de707450c8fb1dc14f13789ed53844ca17939d2bc948ec550d0c18282b578e1228c7fa698c47611c28933cc0ae919ea14bb1aa6f75a01e7c474660eb686047eba0742f543acce9640bb67b163874d4528c7ea10b78fbd2a9180d69cb3e6477e8076fbfc1331194b04165be5e67f5161067e3b462edae40b9696d0d5217a1c20752df202384733c238a2a0b1ed16038f3cc7cbe51848d47d9031302870450538e8dd0022e41fb115ef3a4d9c4581a9229ab5385dce0753fcde17e8ed5bd77e3d3f865bc9f2da86ddff535786f72c93627459d5e4953019b9e71a0a13ff2fb7745b9a770c2ff2147edc8bb388730ff2c7b60645eb02a3dd96c6987d5cfe6f2fc254916d37c4589807faccdeed6595c5ed36d3bee947f5600f74b7737eaf6da28e3ba27ef9bd8b2075819620049af7b0034a55355a032fd807838dde66f7e9fee2aafa916f8c481bc8fca7fd868e1b39433a55bb96819eade540d63a2541f6de1ec9bca1c9f5545ab9b87481738c6868c3173daa50e42d6ca2c70172e512f1a7716ad8fcaa273d14d22466de3b18d9ac596b99fbfe1b50dc78a43042057415f19d79a9fb0243639303eeaccd138b5e8ffd0eaa1afa6418b7acab26e09458079a866b5cb655e0e2c9193a3be567ebdb8be7aa7b7ec4ca5726490798ff39a69bc1d15c8b4db2258173bb90c15ccdd3a38f0761a67a2893b7a176478345af99a4f151d89bf47addc9c19dc0a1fc7b8cfc2777cf6f98690feb3024ee5e393f63430d96d88efd01077fb4793b1a1369c529f05ccb8631a86eda1496c700095d52127504428f372d5dfd5b0c62aeed47698c64685eabd5ca056c3cd59f1c352de9b6aaa4ad2e3cfc3ea8ca119db8b24685432c4b2e6904c8b2d9ebc40585f8e37d77d585bc45b24448685f85b9061254e159b4d19ea81a33a3ee180e1ead0492e74fc2f4223ab2fda859b7c5e5abeb763194281c9b80ec89d0e1ef85bb4cbeb34dd238cc85f4f4b916ff8a3b47a9a18925f84adbf8b425970f6b2fa58789473fa2092f619365699266bdf2c84d80fa2f580f2135ea744abe1d6522a98b018b0c48e390f635a4e53c8df2c17b981d8e9b9ecf20681cbe9b727ec422a107486effcc2eb1255b4b16dd9ffda0d317834a3dcaba0647ffff274bbafb78225ed55d8a91e5fd5593425ec96db174708fae8ef62c34bc529fbe0ce13b52fb99d09288a63914fa099634fcc6af402b29dec3579afaa5136ae438ad32576e0a401d9e9da4dd79dd76992b02dc924d7b83c8b1f01ca4409fe937525b7772962430e8169a122afe5535e4c05af5001c96c548ddcdfbf2e313846f4422efc1b7d6399f022926f0a45475d6a1fc2e8dc9e1da4ceeb78ad725f324a5a495e4af104b244d3aecad990a3bf0538bc13afd284af720833bf6e0be96192104f181450c28bb8da27e81dda480c36dc01f13a33989f2cbeb16b295fe2b2fcc6f964fec34622e66a1ce4cd850d06e17ba099287e812603fdc936768fc2f0d13256fb56cd98b546906dd67c8f9c3724429cd8df899588c71a08404ad8e7443b3d19d3614fc7be29a6c7ed83efbf70d3d642c4673ef2622f45fa6ca7cfba8d0c1c49b40caf850a218158cceea53ed7379476f03114197df49e352e97677764c0d050c24155edfc4d1089bafcd0666161acc6f2b53bad7648f9564a9cdc969b22d769aa037c00b4b27aae14db8d6085c48ec89f16ef310c532d483aa1754e0e4b1d3f50cc91ef92bb8af19f71eeead2c2a1b32f5f8554204d22d23c1e861b8d3b72b38391abbd4c9a468c75f9f55425b1ccdc7d75fcd8f0730aa27d634fb3347a5a79bd73bbc4d8f0c3b2ad4900a618b54a9fe84738413ae34ccadd19059d68c869976f3b1342b6d53197b798f65977ad905386fca03705d04e6e826b343229a267164b2068d851c69f560abbdbb426aa9ebfd2e79d1e5ea05b8b78869b7722b5245a91021cec3673336befd9074f10aa72cb164cc46be41573d5f482e0495a8287951c9e85dde7e9494044ad93f6981796257b8b6f4095dbd21d9dd853506dd43bc225116cdb4b019d74a49ccd953656d12162103019cdc4ab77f2248b477c0584848da3e7f70c61eb4e66e01f49b97ec92190ca44cf1f45753ddb1d135f00074b70ca728b939379102b396420ce758f0bbae79b2809293d9161621b3a3ead8574652a49f857aedc874aafa48269d666f46efd63c17100812b9a79a0500309e82d3bbe467996dda0aac3afc8d391c6cd3a55d7cf2c7daad4da2072c6800b7d1fc9273921bb6ca97a400000000000000000000000000000000000000000000000000" - }, - "result": { - "gasUsed": "0x9e78d2", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0x19d4af3d4de075052a7e46d1e79cdac48c06b20a637af57edfaa39add1b7ebbf", - "transactionPosition": 57 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [ - 0 - ], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000002cbe59", - "to": "0xff00000000000000000000000000000000000004", - "gas": "0x5493bc3", - "value": "0x0", - "input": "0x868e10c400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000008318808821a002cbe59196cb8811a0458d4515820c733511a1ee67c4b6354648a0aad557a7d6103db91ba7ec3605d0e0d0a7dd727582070588192c2cfcb22bd6934b66eb4b782332859eca0353d7a6a6de5ae787e23ff5907808da266f0d295889a05be62ca726ac93cae2bf1976516514387a04f6c161b95f727f97fe04494e7eed2533dfa309ff933835dfcf8a37564e3ebf5e4f2a7c0048aa707c303814028b15d44a34184d6e88f422defb35d333642a4592eee09398954097c2bb5290d2d989947f61ffcfbb57ff4efd12c5f0bdbe44e97487e45aeca92cdfd5678303f0b20617dc9fb65ecc734986207f6e60aeb1426702cebcba9b1e3e99b1f05d922b877818dcea601b115ee1794c04f76b666de9bf218f78f3bcb44b1c2f38368f86d53ce36b72bf6517d1a5f289da6799cbc2619403ea09b6f2fe13d598586938debb8a2e28762e65540b2b5401175c6e0533f20065b308444fcdd2462733270fba4adff7c4f1e9ac2c311b0c4e2fc6304f26ad4f1700a849eecaa0778bc7133031c1de707450c8fb1dc14f13789ed53844ca17939d2bc948ec550d0c18282b578e1228c7fa698c47611c28933cc0ae919ea14bb1aa6f75a01e7c474660eb686047eba0742f543acce9640bb67b163874d4528c7ea10b78fbd2a9180d69cb3e6477e8076fbfc1331194b04165be5e67f5161067e3b462edae40b9696d0d5217a1c20752df202384733c238a2a0b1ed16038f3cc7cbe51848d47d9031302870450538e8dd0022e41fb115ef3a4d9c4581a9229ab5385dce0753fcde17e8ed5bd77e3d3f865bc9f2da86ddff535786f72c93627459d5e4953019b9e71a0a13ff2fb7745b9a770c2ff2147edc8bb388730ff2c7b60645eb02a3dd96c6987d5cfe6f2fc254916d37c4589807faccdeed6595c5ed36d3bee947f5600f74b7737eaf6da28e3ba27ef9bd8b2075819620049af7b0034a55355a032fd807838dde66f7e9fee2aafa916f8c481bc8fca7fd868e1b39433a55bb96819eade540d63a2541f6de1ec9bca1c9f5545ab9b87481738c6868c3173daa50e42d6ca2c70172e512f1a7716ad8fcaa273d14d22466de3b18d9ac596b99fbfe1b50dc78a43042057415f19d79a9fb0243639303eeaccd138b5e8ffd0eaa1afa6418b7acab26e09458079a866b5cb655e0e2c9193a3be567ebdb8be7aa7b7ec4ca5726490798ff39a69bc1d15c8b4db2258173bb90c15ccdd3a38f0761a67a2893b7a176478345af99a4f151d89bf47addc9c19dc0a1fc7b8cfc2777cf6f98690feb3024ee5e393f63430d96d88efd01077fb4793b1a1369c529f05ccb8631a86eda1496c700095d52127504428f372d5dfd5b0c62aeed47698c64685eabd5ca056c3cd59f1c352de9b6aaa4ad2e3cfc3ea8ca119db8b24685432c4b2e6904c8b2d9ebc40585f8e37d77d585bc45b24448685f85b9061254e159b4d19ea81a33a3ee180e1ead0492e74fc2f4223ab2fda859b7c5e5abeb763194281c9b80ec89d0e1ef85bb4cbeb34dd238cc85f4f4b916ff8a3b47a9a18925f84adbf8b425970f6b2fa58789473fa2092f619365699266bdf2c84d80fa2f580f2135ea744abe1d6522a98b018b0c48e390f635a4e53c8df2c17b981d8e9b9ecf20681cbe9b727ec422a107486effcc2eb1255b4b16dd9ffda0d317834a3dcaba0647ffff274bbafb78225ed55d8a91e5fd5593425ec96db174708fae8ef62c34bc529fbe0ce13b52fb99d09288a63914fa099634fcc6af402b29dec3579afaa5136ae438ad32576e0a401d9e9da4dd79dd76992b02dc924d7b83c8b1f01ca4409fe937525b7772962430e8169a122afe5535e4c05af5001c96c548ddcdfbf2e313846f4422efc1b7d6399f022926f0a45475d6a1fc2e8dc9e1da4ceeb78ad725f324a5a495e4af104b244d3aecad990a3bf0538bc13afd284af720833bf6e0be96192104f181450c28bb8da27e81dda480c36dc01f13a33989f2cbeb16b295fe2b2fcc6f964fec34622e66a1ce4cd850d06e17ba099287e812603fdc936768fc2f0d13256fb56cd98b546906dd67c8f9c3724429cd8df899588c71a08404ad8e7443b3d19d3614fc7be29a6c7ed83efbf70d3d642c4673ef2622f45fa6ca7cfba8d0c1c49b40caf850a218158cceea53ed7379476f03114197df49e352e97677764c0d050c24155edfc4d1089bafcd0666161acc6f2b53bad7648f9564a9cdc969b22d769aa037c00b4b27aae14db8d6085c48ec89f16ef310c532d483aa1754e0e4b1d3f50cc91ef92bb8af19f71eeead2c2a1b32f5f8554204d22d23c1e861b8d3b72b38391abbd4c9a468c75f9f55425b1ccdc7d75fcd8f0730aa27d634fb3347a5a79bd73bbc4d8f0c3b2ad4900a618b54a9fe84738413ae34ccadd19059d68c869976f3b1342b6d53197b798f65977ad905386fca03705d04e6e826b343229a267164b2068d851c69f560abbdbb426aa9ebfd2e79d1e5ea05b8b78869b7722b5245a91021cec3673336befd9074f10aa72cb164cc46be41573d5f482e0495a8287951c9e85dde7e9494044ad93f6981796257b8b6f4095dbd21d9dd853506dd43bc225116cdb4b019d74a49ccd953656d12162103019cdc4ab77f2248b477c0584848da3e7f70c61eb4e66e01f49b97ec92190ca44cf1f45753ddb1d135f00074b70ca728b939379102b396420ce758f0bbae79b2809293d9161621b3a3ead8574652a49f857aedc874aafa48269d666f46efd63c17100812b9a79a0500309e82d3bbe467996dda0aac3afc8d391c6cd3a55d7cf2c7daad4da2072c6800b7d1fc9273921bb6ca97a4d82a5829000182e20381e80220c655d56cf0ef0742b187ef633a957ffbd32c3defade8f2594ec34910ae8ef12cd82a5828000181e20392202097b123d3710a5dd973f857a5dc4d0514f0a97e1d975eb01e42aaa1cd87b10e3f000000000000000000000000000000" - }, - "result": { - "gasUsed": "0x2fbb841", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0x19d4af3d4de075052a7e46d1e79cdac48c06b20a637af57edfaa39add1b7ebbf", - "transactionPosition": 57 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000001a0f6b", - "to": "0xff000000000000000000000000000000001a0aaa", - "gas": "0x1c23858", - "value": "0x0", - "input": "0x868e10c400000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000f285098182004081820d58c095c1f3fcb3c1579274a763a69efe68df562832d6d07902f5bd0d92df6c5f3633242ab04b3bf33cd6c1dc4e53e6348e57b64a9c8af60ecc295357de84447d6eec698cf27d62e260272bb587d433ca022f29db4399a700813df8341aafefde3eac07783ba253600998029028668b87cd91de868c4a1e0485ecdce958493fb7b03de06060e93b360705af0f49e300d06c90acdf3e6e8d6de2933463909e42a9f1b6a96a465cab34d14c762e113b2ab20797a9101fa6321983e962c74fdab9fe9eda1a0037e5f8582047ab5fe94c06cdbaec8b74525a8ef23928a1bbb62440ccbcd599b10154404d690000000000000000000000000000" - }, - "result": { - "gasUsed": "0x17514ec", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0xe85fd747a88049a644b187321afe6fc108862c99c3e42fb076fe35e266905bcf", - "transactionPosition": 58 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000001c3bf7", - "to": "0xff000000000000000000000000000000001c3c0e", - "gas": "0x6e1b2cc", - "value": "0x0", - "input": "0x868e10c4000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000027a8518258382034082044082054081820d590240a06d5c12f5ddfcf4805175f47f5711e5e0eda838d6ccc4423f9426fa01451a3037af6100b3ea8913e16caffeac1f8308805c1898f39f306d98d01c0cde75312cd842c524eabd33bd58aa15b50f9d0b1f2e068c36ce8fb4db6cd66e4f11ebb93c01d973c053d13e1b070b9ffe554b0ff62e602e7940bd9904618ab4bdac3dadcccd10183165128f991bc97d20924c719e8b483140672040df267d9c5ec364f04c5df0b2134c18673cb86feeae16bad31fc27d68282f9a2158b0d91718020cf98383682293553b37a85872eaff3c7b34cd107853c65f24bcbbee7c0962080abd615c7568f4f84a795e9c43e21bfd10d3008efcab45e9f96671fa38198fbd4d4f9e50e9285ab59092b4c9f7f34936dab4996949b05ecc76a489b2e2cc02943f12fb143cbbfada5ddb42821e823f5c8acb9686de0e3ef85fc41d39719dbe8702ae9a3662a834f68c200c79f6d9e7c7032124b438ab8660ac3de175f80c0c7fefd992e83534f7b1f7e7c3afb4974cebde8238ffd9f3a738047ee515d483b2f91fd8fc8630777082c23784590b3d8a817a963f10a39e83a5740f7b6954a8930dd713c6fa332ba0da90a8e2b3f71e645f6876ac8a2b980be79ec5fc0d39fec3ac0a6cbcb99e661b4d62d5a46d6feebe18280aeb0e2ff21001b3bdcf4faaa677f16dbd9a03d0dd3fbec38d38403f21fe7795a4612bfec76bb2c945456637b84d2929e0d962a79a161de51d0ff523b91c342c37e5b170338c7678717aecc762d5935ffd168257763650220d5f38f6a269d2c39160a243cd163f8446607bbba2f2d5e61aa91a0037e5de58201bc00903e90ca71cf8a383db0b4d3a038e6c65f02639a026af83a66ff71862a5000000000000" - }, - "result": { - "gasUsed": "0x5b37ee5", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0x468720813c31079df1c1d52f6bdc98c5b55dd3c3cf0c0f5377d692e2030c0ed8", - "transactionPosition": 59 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000001c3bf7", - "to": "0xff000000000000000000000000000000001c3c0e", - "gas": "0x548fbe8", - "value": "0x0", - "input": "0x868e10c400000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000f38518258182064081820d58c0b4a54e9a1ab64f94e7977377bea362e540a83376e284bf86dc426fdde3652582267f7508e553394e0317f636155ad659b04b297544b50bc0ddb85e23b7657fb6851233d15ed8c422341192b767e9627396f6b4c1380db51647bda0570fc0c8f51016b9cf8092e073beb73a451a93e08c309762bdd09ab92af5c738661bfdd8e86eb24c5ddbe399bf534221d26bd3ac0aa247b059202ec208dc4b57488ed12e0144b3ab60c54912333580b0b5e2c20cf0f14908dfb11b7514cfd0624a6ccb4c591a0037e5de58201bc00903e90ca71cf8a383db0b4d3a038e6c65f02639a026af83a66ff71862a500000000000000000000000000" - }, - "result": { - "gasUsed": "0x4840f87", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0x6ae23d16858a9de909457b27a4a7272b0b7bee42f8d5f12c10f0cdc55118b459", - "transactionPosition": 60 - }, - { - "type": "call", - "subtraces": 3, - "traceAddress": [], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000002b3338", - "to": "0xff000000000000000000000000000000002b3363", - "gas": "0x4ae7ef5", - "value": "0x0", - "input": "0x868e10c4000000000000000000000000000000000000000000000000000000000000001c000000000000000000000000000000000000000000000000000000000000005100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000072818187081a00011b51d82a5829000182e20381e80220f29dcf0ce726c36a3a9dec02c7975bc7d4fbe9058f088a5f66d827231404b12d1a0037de15811a045af8041a004f7082d82a5828000181e20392202091de727d20058c198e55b1a08967352ff72168b2b70f0c597e7e907b3778df3a0000000000000000000000000000" - }, - "result": { - "gasUsed": "0x3613dfc", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0x558e1f235e7b5c9ee2373bb7c39b2c236f584979cb6d3b0898e1e283e3c05e07", - "transactionPosition": 61 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [ - 0 - ], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000002b3363", - "to": "0xff00000000000000000000000000000000000002", - "gas": "0x4a318a5", - "value": "0x0", - "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "result": { - "gasUsed": "0xfabb0", - "output": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000418282581a000286387ff107ef952f3612eb2e2a6606418e51c0f2cceeda5f57011731c2a04034dd33df402838a815a0b69ec1184b8c104a0001c0dfc71cd077c4b900000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0x558e1f235e7b5c9ee2373bb7c39b2c236f584979cb6d3b0898e1e283e3c05e07", - "transactionPosition": 61 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [ - 1 - ], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000002b3363", - "to": "0xff00000000000000000000000000000000000004", - "gas": "0x492535d", - "value": "0x0", - "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000009000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "result": { - "gasUsed": "0x105fb2", - "output": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000578449007d2903b8000000004a000190f76c1adff180004c00907e2dd41a18e7c7a7f2bd82581a0001916cb98a2c3dfb67a389a588fb0e593f762dd6c9195851235601fba7e16707ee65746d4671e80aa2bb15bc7d6ebe3b000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0x558e1f235e7b5c9ee2373bb7c39b2c236f584979cb6d3b0898e1e283e3c05e07", - "transactionPosition": 61 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [ - 2 - ], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000002b3363", - "to": "0xff00000000000000000000000000000000000005", - "gas": "0x4803dec", - "value": "0x0", - "input": "0x868e10c4000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000f818183081a004f7082811a045af8040000000000000000000000000000000000" - }, - "result": { - "gasUsed": "0x489183", - "output": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000002e8181d82a5828000181e20392202091de727d20058c198e55b1a08967352ff72168b2b70f0c597e7e907b3778df3a000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0x558e1f235e7b5c9ee2373bb7c39b2c236f584979cb6d3b0898e1e283e3c05e07", - "transactionPosition": 61 - }, - { - "type": "call", - "subtraces": 4, - "traceAddress": [], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000002b199b", - "to": "0xff000000000000000000000000000000002b19a2", - "gas": "0x4ed0b83", - "value": "0x0", - "input": "0x868e10c4000000000000000000000000000000000000000000000000000000000000001c00000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000022881858708195f5bd82a5829000182e20381e80220f9acb798b6fe4fb2b334af3d3e0ee522004ea693556ed6e7ead3d68306d75b151a0037e089811a0457ca271a004f598cd82a5828000181e2039220202e5970c0e581497fbb90cc3f526c0d9d553c5fbcdb2aaadcb64149ad5f22b0098708195f37d82a5829000182e20381e802208fe0b57c9c0dff7538fdeedef16dca53661ce854dc7a06a296a6adc3491ce7361a0037ddee811a0457c65a1a004f598dd82a5828000181e203922020607d2d181747f8a1df3b139e06f52588adf196097d38f13f8160aed6d437e1188708195f5cd82a5829000182e20381e80220c31fbd6bb96206aaf825a12337ddcac48808b5b835ef8d2b1c6b82255f1b0e081a0037e0a6811a0457f0331a004f5983d82a5828000181e20392202036078ad115da0b077798bbffb9abdf4756c5fb3eba49d917f608f4e93791b13b8708195f38d82a5829000182e20381e802206a13d744c58c3c3194185fc76b711ddd793451a6bc1c1b0d2593391fa4b9aa271a0037ddf7811a045921771a004f5980d82a5828000181e20392202085574c49ce6d1f8149b4631ce8d502e8764c0e1a949f05287f34553420e6ab228708195f5dd82a5829000182e20381e8022037ea5178bc9933cd3e272574beb4420d23ba7fd1141d27ef728aa2a03cb78d1d1a0037e0aa811a0457e4e41a004f5987d82a5828000181e203922020f4abd76e214c8125bd6b5470b3d111cc049db132b3e80cf0d9e64beb13cd4129000000000000000000000000000000000000000000000000" - }, - "result": { - "gasUsed": "0x307c5da", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0xef58a04d45dda0091572a6ae13c1c043e2397ab851d94fb07f108d5bc34beac5", - "transactionPosition": 62 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [ - 0 - ], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000002b19a2", - "to": "0xff00000000000000000000000000000000000002", - "gas": "0x4e01c98", - "value": "0x0", - "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "result": { - "gasUsed": "0xfabb0", - "output": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000418282581a000286387ff107ef952f3612eb2e2a6606418e51c0f2cceeda5f57011731c2a04034dd33df402838a815a0b69ec1184b8c104a0001c0dfc71cd077c4b900000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0xef58a04d45dda0091572a6ae13c1c043e2397ab851d94fb07f108d5bc34beac5", - "transactionPosition": 62 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [ - 1 - ], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000002b19a2", - "to": "0xff00000000000000000000000000000000000004", - "gas": "0x4cf5750", - "value": "0x0", - "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000009000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "result": { - "gasUsed": "0x105fb2", - "output": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000578449007d2903b8000000004a000190f76c1adff180004c00907e2dd41a18e7c7a7f2bd82581a0001916cb98a2c3dfb67a389a588fb0e593f762dd6c9195851235601fba7e16707ee65746d4671e80aa2bb15bc7d6ebe3b000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0xef58a04d45dda0091572a6ae13c1c043e2397ab851d94fb07f108d5bc34beac5", - "transactionPosition": 62 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [ - 2 - ], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000002b19a2", - "to": "0xff00000000000000000000000000000000000005", - "gas": "0x4bce48f", - "value": "0x0", - "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000005000000000000000000000000000000000000000000000000000000000000005100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000043818583081a004f598c811a0457ca2783081a004f598d811a0457c65a83081a004f5983811a0457f03383081a004f5980811a0459217783081a004f5987811a0457e4e40000000000000000000000000000000000000000000000000000000000" - }, - "result": { - "gasUsed": "0xdad3b5", - "output": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000de8185d82a5828000181e2039220202e5970c0e581497fbb90cc3f526c0d9d553c5fbcdb2aaadcb64149ad5f22b009d82a5828000181e203922020607d2d181747f8a1df3b139e06f52588adf196097d38f13f8160aed6d437e118d82a5828000181e20392202036078ad115da0b077798bbffb9abdf4756c5fb3eba49d917f608f4e93791b13bd82a5828000181e20392202085574c49ce6d1f8149b4631ce8d502e8764c0e1a949f05287f34553420e6ab22d82a5828000181e203922020f4abd76e214c8125bd6b5470b3d111cc049db132b3e80cf0d9e64beb13cd41290000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0xef58a04d45dda0091572a6ae13c1c043e2397ab851d94fb07f108d5bc34beac5", - "transactionPosition": 62 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [ - 3 - ], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000002b19a2", - "to": "0xff00000000000000000000000000000000000063", - "gas": "0x1053f8b", - "value": "0x48fa86c15fa600", - "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "result": { - "gasUsed": "0x1770", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0xef58a04d45dda0091572a6ae13c1c043e2397ab851d94fb07f108d5bc34beac5", - "transactionPosition": 62 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000001614ac", - "to": "0xff00000000000000000000000000000000018a9d", - "gas": "0x2950360", - "value": "0x0", - "input": "0x868e10c400000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000f285028182034081820d58c0a24c0e37761bfe50b11dd0fd147cc58de9c706966e58ebf1c59bc68d826c3aa1c8ce21e214b65146eebb6599c7e9b13e9771b9703259e98da1c6be2581c7a3d32de9391b77cba94683e4a488a85b7677f7c4b24ee043e5b74328c64b2ab3548e168389d0eae2771b996bd858db8331aea66eacc1dab69bd7488c341a69d1a77c19373ab2cd2e0a213a4dd87e3a65b47ab6ebe365a6b834dd6d0f508e3cf61f4d534aafcc397017d2858c44e26b493131f8db7feb49cdc4df3f916b2681d5a8701a0037e5f6582080334418ee2e59dadfdb8d33bf996c9c35f36dbe740e214e308a34ebd796ddc90000000000000000000000000000" - }, - "result": { - "gasUsed": "0x2288efe", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0x9821a2e7145a9a0635ef1ef3b683392817d43c61caafbfbff0ec5422c3ada2bd", - "transactionPosition": 63 - }, - { - "type": "call", - "subtraces": 1, - "traceAddress": [], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000002b199b", - "to": "0xff000000000000000000000000000000002b19a2", - "gas": "0x43cf604", - "value": "0x0", - "input": "0x868e10c4000000000000000000000000000000000000000000000000000000000000000700000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000078782195f2a590780af0a4181a3dd50f9927739353d495ca29562a915a1ac79b0a82ac9fc0a2f6cf9a54d632c8c9f0198f2837f66507ce46eadce1f4c166c6256647d8973789a927937e5e74d9a27305ce146b549af4be391a9ae468e2a579b2864a66e7f641ffa38019d7736b70736af2d3747d2b5baef4a5d594ff32fec3d286dc115f7af556287d19df302659af6dbc84444e43eb9afe1a42de2f425ff8d15f75809e36e3d559197df381cbaf4cab36ac23c51ae9fc19c0e9dd92ce497c3757784cdde3d11c766a8a3142e9a8ddf1c5c03f6cb797b60a407d6b70395c40be27bca492c50f56b990468a1a436d4d84109afed2b7a8ace86b7c4376ac7e99bf30ef9360b4f84cfe243c8808e68fdc48d6a4780c225d78fe033eadfd2194bff4b72dd07e0c763ce8d13687ceddb0fe5ddcbf00a8d3ef2795a04928c1cd2c8ab9883d8b027d6b9d2e7130732f84d2e7677e8a66fc09bf7677fa8e5a83b4318d7991fa6e9c7e4ed85d92f4d0aa0421ca497c5248e7740b133558b3f348254b784271ea1cba40c570c6ba6dffaf4a6aa9c7f7f0c32d3175cd4b3ecacc8ec238a0b2dcd12decb7d6915f04cd722fe3c39385a51e4fd5a62663027a8e0e9d833d18e95f375aaecac522193cd514123eb9e351fc001127196a777d6cf7a47e490d38bcc435539a21bb781770ebef5972953b9793169212f89ebf0e717d9e37985df6313884a24442391e52cec8e668ffa90cc2f87e4a0ed3d4358d3b8d1ab4702b58df778b1710de8d416f6249f10524144721304e415dcb2b41599553e111f80262229c3e3ef6d2cd9c0a0a34efc99920676c84381dcc23180cc9094bbd90c29caa08077a6dca013e081c38db3db3d69dc436fe177d940ae47f566a2326d7b901bf823aae9ada3d119291b1d75eb7e327b19b4bec1217b732fad8c4c0a68bb8238fc3b9ee2ebc78d295773030cb0c8526ed6426f0765b5b1f1e89d0d80dc399f99ad18b4fb29e1236babcf994e75f7f60a68cd364bada4a5765af2b50f4b3a4695b3fdae7c48133f80fa6148b12478a1b46810d31258b50e3a694d6817548a39d664ba0d5fdda0d32f458b91119ff404818d4163345ac7fcb4dbff87b2c20d407c829c200236f9453a43a8bc9cde4da3d3a6763d306e86f97cf996b875e446091b221926e6a3574a64e8eed09a03e8450707c097c5059b357df5d2625446ebb495637f2137565d97a6e919006a576f55c4eccae9d0919dcc15984d908f970d6988fdd9df8c505c0f1c078069a659c07c420063bd90c71d4b9b4d16908921063c78e8b88fcd606b8505e4cb4c0a23983090df8fa3b14176c08c293c9404a2f3844a16eb4c3d2542398cf90db898fdf234259a06f52e445e2ef8755db263a09c97abb53b6c9044fb2214fbbeeaff34c0888b8ae6977a082b521a106aacaab8acd1faa0422a83a4fd799d3f3bd384bc017b5c2e2d00cd16ebdc2b41dcf1f5f41886ae1455fe0cb8769066a4f114205ed50579936c6ad6ee817a47080865f04c33bf029acbedeedf5474f90e9e556cfebb52bb44b745ac3d1119980a2998282e3384b4b4b72e44a2ce7715853f9f8b4289d83c5a524d0fde04ae762a1e6b9a25bf1a6ea57eded7737afca6b61e83639136b89ce030c6c54cedb8392db55895af9ad866bde97db08fb12f9f235e296c86fa007954cce91f615ff01ad1d7aa0ec788362313392bd4d47921872d821571d42689c2d10e9f27e30e065b0f1c03643f419b5ca7aa5157373e6c0b8ae60b5a56daa8932782bcb72bd7da154719fa551a84af6e3f7b5e30f263944c6c1f47fa510d037b90e01b1095df28baf2ef9885b7af50aaab01ccd913b9f1dccfec40b7fe6c4aefd442654aaa5442abd774d41d55583656a53d5bc9f3ff08d87478894a9ddecd39efa5f2119bfb25415970408fcf3265a8faf706bf6841953e44371630048ab9b604354fa279314cd9dd19af0822942179f32bcd9f9d1b19e4ec3ad7308372d8b5bf03908178e5d4c5e87a3eb40c0120fccad46d4d0f91c484416e0132a4cc9dde03bd5d4c492aaa06967014c5b414c8fedb95c4553f69643e6bdcf3076a67a85b8c32ae6a0e72ce8d2737a8724688121662677175c2ac618a2c481cd4043ce6c0fcd6a1d9bb39e1ac0caf63d27b1a01366de0f5f93d007f480efc95b00263a391ba807aed88a3e421ab2d62b9e604456026434da79a0949a7b12f9c791ddf565ea9f107b4c1b8050d563ba5a633528f068dc30cf7a440cfe418de1f54c571ac670cc2a2fb17ee44e9d57f9c7cdadf0b7772bba6edb8a6b260631a01532da47bf3fc27fd5b137a4a449e0a25a4adb6b60eadd2ec7abec7e32f970fe50696932a324eb8a81297a23f6e27d082d3e7bb299625f591c887bcef5587fdfaad00d5e1faf2c1c17dd48b0504993f224285f3c40e8965bc39422455ff1e248a1ba8859079c361dd332b2246b733cf51cb21b38f704127a36cf08f92b9200a3f951b90979e061b5b3b3c7163fbb03eab9ec644a1f14b05c3e4d90ec541006d90c315a69a4bcbebba3bfb5a1be8814bf3006cdbfc7436673e967e1d2d0be00f12c2ec5b7fc3d05ae6d23cd318c77f9ba86f31dafd6c886a3bd0e05dbbaf6dd052c29e10c13355768314c14c1077f3e8ada461e49d3bb8e0d9270b25f1e35bc638e1c732188ff5a7e082cfd91183afdc79ecc78878e1e0122abf3b767051b93600000000000000000000000000000000000000000000000000" - }, - "result": { - "gasUsed": "0x841db2", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0x352d011f8bc53595968e076db6f8bd657486a8fea38439ae699980096c2dc7e9", - "transactionPosition": 64 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [ - 0 - ], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000002b19a2", - "to": "0xff00000000000000000000000000000000000004", - "gas": "0x3e9b138", - "value": "0x0", - "input": "0x868e10c400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000008318808821a002b19a2195f2a811a0458bf95582019d87ad3ccfc90bcec5bdd6e8270ed89d169ef967b5f727bae65de6ef176ae9b582045d1743b2baf96952374e2de1c0f2ef15e7cf0e678ebb9d158d2572d304a8530590780af0a4181a3dd50f9927739353d495ca29562a915a1ac79b0a82ac9fc0a2f6cf9a54d632c8c9f0198f2837f66507ce46eadce1f4c166c6256647d8973789a927937e5e74d9a27305ce146b549af4be391a9ae468e2a579b2864a66e7f641ffa38019d7736b70736af2d3747d2b5baef4a5d594ff32fec3d286dc115f7af556287d19df302659af6dbc84444e43eb9afe1a42de2f425ff8d15f75809e36e3d559197df381cbaf4cab36ac23c51ae9fc19c0e9dd92ce497c3757784cdde3d11c766a8a3142e9a8ddf1c5c03f6cb797b60a407d6b70395c40be27bca492c50f56b990468a1a436d4d84109afed2b7a8ace86b7c4376ac7e99bf30ef9360b4f84cfe243c8808e68fdc48d6a4780c225d78fe033eadfd2194bff4b72dd07e0c763ce8d13687ceddb0fe5ddcbf00a8d3ef2795a04928c1cd2c8ab9883d8b027d6b9d2e7130732f84d2e7677e8a66fc09bf7677fa8e5a83b4318d7991fa6e9c7e4ed85d92f4d0aa0421ca497c5248e7740b133558b3f348254b784271ea1cba40c570c6ba6dffaf4a6aa9c7f7f0c32d3175cd4b3ecacc8ec238a0b2dcd12decb7d6915f04cd722fe3c39385a51e4fd5a62663027a8e0e9d833d18e95f375aaecac522193cd514123eb9e351fc001127196a777d6cf7a47e490d38bcc435539a21bb781770ebef5972953b9793169212f89ebf0e717d9e37985df6313884a24442391e52cec8e668ffa90cc2f87e4a0ed3d4358d3b8d1ab4702b58df778b1710de8d416f6249f10524144721304e415dcb2b41599553e111f80262229c3e3ef6d2cd9c0a0a34efc99920676c84381dcc23180cc9094bbd90c29caa08077a6dca013e081c38db3db3d69dc436fe177d940ae47f566a2326d7b901bf823aae9ada3d119291b1d75eb7e327b19b4bec1217b732fad8c4c0a68bb8238fc3b9ee2ebc78d295773030cb0c8526ed6426f0765b5b1f1e89d0d80dc399f99ad18b4fb29e1236babcf994e75f7f60a68cd364bada4a5765af2b50f4b3a4695b3fdae7c48133f80fa6148b12478a1b46810d31258b50e3a694d6817548a39d664ba0d5fdda0d32f458b91119ff404818d4163345ac7fcb4dbff87b2c20d407c829c200236f9453a43a8bc9cde4da3d3a6763d306e86f97cf996b875e446091b221926e6a3574a64e8eed09a03e8450707c097c5059b357df5d2625446ebb495637f2137565d97a6e919006a576f55c4eccae9d0919dcc15984d908f970d6988fdd9df8c505c0f1c078069a659c07c420063bd90c71d4b9b4d16908921063c78e8b88fcd606b8505e4cb4c0a23983090df8fa3b14176c08c293c9404a2f3844a16eb4c3d2542398cf90db898fdf234259a06f52e445e2ef8755db263a09c97abb53b6c9044fb2214fbbeeaff34c0888b8ae6977a082b521a106aacaab8acd1faa0422a83a4fd799d3f3bd384bc017b5c2e2d00cd16ebdc2b41dcf1f5f41886ae1455fe0cb8769066a4f114205ed50579936c6ad6ee817a47080865f04c33bf029acbedeedf5474f90e9e556cfebb52bb44b745ac3d1119980a2998282e3384b4b4b72e44a2ce7715853f9f8b4289d83c5a524d0fde04ae762a1e6b9a25bf1a6ea57eded7737afca6b61e83639136b89ce030c6c54cedb8392db55895af9ad866bde97db08fb12f9f235e296c86fa007954cce91f615ff01ad1d7aa0ec788362313392bd4d47921872d821571d42689c2d10e9f27e30e065b0f1c03643f419b5ca7aa5157373e6c0b8ae60b5a56daa8932782bcb72bd7da154719fa551a84af6e3f7b5e30f263944c6c1f47fa510d037b90e01b1095df28baf2ef9885b7af50aaab01ccd913b9f1dccfec40b7fe6c4aefd442654aaa5442abd774d41d55583656a53d5bc9f3ff08d87478894a9ddecd39efa5f2119bfb25415970408fcf3265a8faf706bf6841953e44371630048ab9b604354fa279314cd9dd19af0822942179f32bcd9f9d1b19e4ec3ad7308372d8b5bf03908178e5d4c5e87a3eb40c0120fccad46d4d0f91c484416e0132a4cc9dde03bd5d4c492aaa06967014c5b414c8fedb95c4553f69643e6bdcf3076a67a85b8c32ae6a0e72ce8d2737a8724688121662677175c2ac618a2c481cd4043ce6c0fcd6a1d9bb39e1ac0caf63d27b1a01366de0f5f93d007f480efc95b00263a391ba807aed88a3e421ab2d62b9e604456026434da79a0949a7b12f9c791ddf565ea9f107b4c1b8050d563ba5a633528f068dc30cf7a440cfe418de1f54c571ac670cc2a2fb17ee44e9d57f9c7cdadf0b7772bba6edb8a6b260631a01532da47bf3fc27fd5b137a4a449e0a25a4adb6b60eadd2ec7abec7e32f970fe50696932a324eb8a81297a23f6e27d082d3e7bb299625f591c887bcef5587fdfaad00d5e1faf2c1c17dd48b0504993f224285f3c40e8965bc39422455ff1e248a1ba8859079c361dd332b2246b733cf51cb21b38f704127a36cf08f92b9200a3f951b90979e061b5b3b3c7163fbb03eab9ec644a1f14b05c3e4d90ec541006d90c315a69a4bcbebba3bfb5a1be8814bf3006cdbfc7436673e967e1d2d0be00f12c2ec5b7fc3d05ae6d23cd318c77f9ba86f31dafd6c886a3bd0e05dbbaf6dd052c29e10c13355768314c14c1077f3e8ada461e49d3bb8e0d9270b25f1e35bc638e1c732188ff5a7e082cfd91183afdc79ecc78878e1e0122abf3b767051b936d82a5829000182e20381e802205236012662fbb977c807728310b40aebc827a02db9ee6a6eb618093047f3f416d82a5828000181e20392202003feb391e43dda2b8d156216efff3abad9143e71882e38ff2f78ed23db51e537000000000000000000000000000000" - }, - "result": { - "gasUsed": "0x2fdd661", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0x352d011f8bc53595968e076db6f8bd657486a8fea38439ae699980096c2dc7e9", - "transactionPosition": 64 - }, - { - "type": "call", - "subtraces": 3, - "traceAddress": [], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000002d1d1d", - "to": "0xff000000000000000000000000000000002b1e7f", - "gas": "0x3735056", - "value": "0x0", - "input": "0x868e10c4000000000000000000000000000000000000000000000000000000000000001c0000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000708181870819599dd82a5829000182e20381e80220d2d632c58c38d9ec51c905873d86022179e01c1508c4cc851f5f6a1ab3e4c4241a0037e0fc811a045b33ab1a004f64ead82a5828000181e2039220202f50056598e93a1acef58e7f51d3ea8bb0c7a50758cda43c77f43cd2eb09140100000000000000000000000000000000" - }, - "result": { - "gasUsed": "0x2660a8d", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0x905d04dc590112e6e1c646b036395ef60c40a0dd3d1692e5d7aa645b72206993", - "transactionPosition": 65 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [ - 0 - ], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000002b1e7f", - "to": "0xff00000000000000000000000000000000000002", - "gas": "0x367ea2f", - "value": "0x0", - "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "result": { - "gasUsed": "0xfabb0", - "output": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000418282581a000286387ff107ef952f3612eb2e2a6606418e51c0f2cceeda5f57011731c2a04034dd33df402838a815a0b69ec1184b8c104a0001c0dfc71cd077c4b900000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0x905d04dc590112e6e1c646b036395ef60c40a0dd3d1692e5d7aa645b72206993", - "transactionPosition": 65 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [ - 1 - ], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000002b1e7f", - "to": "0xff00000000000000000000000000000000000004", - "gas": "0x35724e7", - "value": "0x0", - "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000009000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "result": { - "gasUsed": "0x105fb2", - "output": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000578449007d2903b8000000004a000190f76c1adff180004c00907e2dd41a18e7c7a7f2bd82581a0001916cb98a2c3dfb67a389a588fb0e593f762dd6c9195851235601fba7e16707ee65746d4671e80aa2bb15bc7d6ebe3b000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0x905d04dc590112e6e1c646b036395ef60c40a0dd3d1692e5d7aa645b72206993", - "transactionPosition": 65 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [ - 2 - ], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000002b1e7f", - "to": "0xff00000000000000000000000000000000000005", - "gas": "0x3450f76", - "value": "0x0", - "input": "0x868e10c4000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000f818183081a004f64ea811a045b33ab0000000000000000000000000000000000" - }, - "result": { - "gasUsed": "0x478f65", - "output": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000002e8181d82a5828000181e2039220202f50056598e93a1acef58e7f51d3ea8bb0c7a50758cda43c77f43cd2eb091401000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0x905d04dc590112e6e1c646b036395ef60c40a0dd3d1692e5d7aa645b72206993", - "transactionPosition": 65 - }, - { - "type": "call", - "subtraces": 1, - "traceAddress": [], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000001db1ef", - "to": "0xff000000000000000000000000000000001db1f8", - "gas": "0x4ff0eb8", - "value": "0x0", - "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000007000000000000000000000000000000000000000000000000000000000000005100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000789821a0001933e59078085a6a05ba075038c36680da91e35dc5e0342a4b0321542423416ef2cd460c0c4ff58ffa9ef1fa9cc25b2552a34c615dba6b9e7239402bf8e7c1d6c5c4310df78676f565d7a5cc7d86d9b5167dd1ab39f5ebae6bfdd2ce39f0121e4da37372620167a8403f8b0559db674be819fd9fd0fbf56c3d2380e69d7b24ac4ec5f15a984f2a347f7ba4369e2b4138a929933721085746f24dde157e3505c368b5f2da8f0f771be99312f7036e1104a8be9110ea577633f13ed70b3af39063c3421cbd673b3794651781e4b02612ac51ad3d045241c3f529ad25440301aea3e523b862ae800ae314e9a7d3c56c10df3febe2c2745a8431b0e400b9e78a151d50861417006dc356daa9dad6de15e4f62c1f3755e326fa69c95ef5bb94447912b0420eaf16b1034069ea566340dcaaad6b3c0e3230f0e24e829deba4880f6b14b069bc1fad638fda5443c98c7a23d733f50316b26ee887cda3dc12b890ebbfe8cd06f0a5e932937e994d3632086e26068050e0eb5e38788eb9917f96d995f9378d0705a71a682a4ea8657a5dc5b301e650f0ec27a5061b72f1b0378c5ccad2af6f73c802f87c56497ca82134b0b33ff48c211664128b2568daed030ab8a9395c7aa3bcd9e4f45570cfadcead43c36274ec16ceaf030b79cc110bb4526ebf51872b2420c025b0dd6eea34b9433bba6343fc6423ef42f6ff8ffd66e1f221d3385138a03155dad6d88e341d059a211606a7b23626046a5b11dcb4b1a36f783f863fe0d98a152c6a776498bb1ce5c9d36782f7ef973439585c50410ae820cdde1e5e78b7ed8f503b499e35f83958098c0d48896c0dc5e8fefe05a8cf42a28bec48f80dd2ac9de1fc09b409bb326b90a84b935c3867530b08df919bee3190d349e9416a8da77a23769d41f94990c342a042024389c6d6cd76994836fa3460ced9122476ec30848370c21e632ceef9ec9c06809562c90d008591f3005d92638efd1c3a4f6f8d86df6e0d39b30cf8f6ec91afc479db4e73016818d72c72e7ea7269eb2baa704b384e39e852103d92696cb10223e1262b3bcfb2f555ed19fbe9adc4cb16e50964be2f9aebcc0cc146466f72ec0c226e4d4e0d9846b99c646bfd5113884ae519ce08dfd6b08eb22a1af25b6d1815c7758bea333b9a410db11c75981e3c8fd36c663ee2fe62da024f01d684333317f1c8c075af0baff1dd9e1e647a295679900455cb16601e70b453351eda37402cfd61566ff38ae99f209e94a03976744a02b3a65f7b09d39bea0437cabc80d3971d0846d565d8bb1534add4e34f906b971460c23fd49f7ceb43db3647e8b4fc88417462477782a5dad0e67a4784c54490c5b21f971d192cb642a66c41d3c68a10b3655552b007fda2576a8cbb01ee3ff85021d26ddfd652a04942a586794ea3ba963ac24509ba0e8ee686f62f67c7d15d0efe99f86500f7f1c109939619a594b7ac7465179d1aebe00ea6a4fcb81ff9d3df670995d2202214db0a3d5eb371dc45fe23c3e82e7ec22f0d61b0d79714dcec70249fb447cbc96e50c387210bae5a4307d24dde932906bc825c16499f98c1837d6cf3561dd5fdb49618f6cfb5a1ca730566923a7836b769548ef077db369969121768624f1a7504590fe55a2dae2053b7b3f3f5301934abe9e804eddd301547c8d3aaa947caf2d21053d659786fbd959e2658777b9aa2808f3319a1189bad15b5f735bb6bc8e068acddb64e9131733ba193b38d9039f5646024ee3eeb5b952191dde3fd98019471737f6e6dc983d57a9fdee96aaf60598335fab83da3328b7f208d0731458e59377832573e74e6de7dc2337eb381fa37e4a08c79a65c754655d6af439d8ad54b347b98323dfe758e27178a40d63208fc1fcd3df61b2d7160757947d46c696a8843f7ce187c6656ac0008407a7b5f634405ea440bc3447ec91910599f4edd8def67045b4200778e4c9757c8e868bc08366fffedc3ce00dbff563b173552ae31d197a949f3d18bb143d460a0e02d3b4aaad2779c0820b2853477964deff42a9067033402aafefbd28dcae30cadf7e68a92b5499c47faf0a6cb25d3225dbbe1debb6a7f8be1692e1ed61ca929fa7b6d88a13905e6992dbf0e9a2102c50b8e346c9fa52e67516a226b4a6ed0a5175c2a4432c22882acb727a284191fb0207329a873381bdbfae72dc2a2f1e6be1c0a8bf77e090d056a7689b5b6763f128f8b76639404cc14d396d6604d2a8c5145ccb1bb72a3d899550b93b132cfff03f3b941c92677f1bc1d0d0bc9819ca0467643eaacd6eb7923124b15cd5cbefd24647152e11e3865c1e17bb1a35f7cd7a57d50998cd91f03efc0f5dc9b5658b8cc739f46745801fd9dedf3829a7ce44ce25ef3a0cb8b49b8c18c89247f4d5095c877d71302e66ccc2ac9debf3c6fb511bad40eae508965dfdf66ed8d45e218f8eb3fc13e98644b50ddd91dbc11d6944ec32801ce9761e585a69930468de3b262956f72e250eb0bb4711b0387509df05732feced968a04b24fe6805ecf93ea6d85d178f4af97ccc05811f56288f28972b3fb873a1b626056be4f4aabe5b3b8bcbfdcdc277e0854c1c3eace993ef36304bb9453cd91e76b60bfbca1bf1613426cdddabd05e689ba64cdbc8c4ffaf177e1a9731b38dca1fb28934b8140d54f6e0a636cdee02e18c31b79871bb61d4a1d171798460859b76e358af75eac0ea8c55d3dd93b584a0000000000000000000000000000000000000000000000" - }, - "result": { - "gasUsed": "0x7adabe", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0x6393b0c1a03fcddab9d197626ee380c8dce03ac552781061a25ce03ed4a4b293", - "transactionPosition": 66 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [ - 0 - ], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000001db1f8", - "to": "0xff00000000000000000000000000000000000004", - "gas": "0x4b51aed", - "value": "0x0", - "input": "0x868e10c400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000008338808821a001db1f81a0001933e811a045b2300582097527d534b9bb0b5c38ed86fd4baf2c362b69fc2cb99f053439fd0d75ef7fa5e5820064b2b8946c028299181336eb551f58ccaa48f3f51029555218e5b6eb443910b59078085a6a05ba075038c36680da91e35dc5e0342a4b0321542423416ef2cd460c0c4ff58ffa9ef1fa9cc25b2552a34c615dba6b9e7239402bf8e7c1d6c5c4310df78676f565d7a5cc7d86d9b5167dd1ab39f5ebae6bfdd2ce39f0121e4da37372620167a8403f8b0559db674be819fd9fd0fbf56c3d2380e69d7b24ac4ec5f15a984f2a347f7ba4369e2b4138a929933721085746f24dde157e3505c368b5f2da8f0f771be99312f7036e1104a8be9110ea577633f13ed70b3af39063c3421cbd673b3794651781e4b02612ac51ad3d045241c3f529ad25440301aea3e523b862ae800ae314e9a7d3c56c10df3febe2c2745a8431b0e400b9e78a151d50861417006dc356daa9dad6de15e4f62c1f3755e326fa69c95ef5bb94447912b0420eaf16b1034069ea566340dcaaad6b3c0e3230f0e24e829deba4880f6b14b069bc1fad638fda5443c98c7a23d733f50316b26ee887cda3dc12b890ebbfe8cd06f0a5e932937e994d3632086e26068050e0eb5e38788eb9917f96d995f9378d0705a71a682a4ea8657a5dc5b301e650f0ec27a5061b72f1b0378c5ccad2af6f73c802f87c56497ca82134b0b33ff48c211664128b2568daed030ab8a9395c7aa3bcd9e4f45570cfadcead43c36274ec16ceaf030b79cc110bb4526ebf51872b2420c025b0dd6eea34b9433bba6343fc6423ef42f6ff8ffd66e1f221d3385138a03155dad6d88e341d059a211606a7b23626046a5b11dcb4b1a36f783f863fe0d98a152c6a776498bb1ce5c9d36782f7ef973439585c50410ae820cdde1e5e78b7ed8f503b499e35f83958098c0d48896c0dc5e8fefe05a8cf42a28bec48f80dd2ac9de1fc09b409bb326b90a84b935c3867530b08df919bee3190d349e9416a8da77a23769d41f94990c342a042024389c6d6cd76994836fa3460ced9122476ec30848370c21e632ceef9ec9c06809562c90d008591f3005d92638efd1c3a4f6f8d86df6e0d39b30cf8f6ec91afc479db4e73016818d72c72e7ea7269eb2baa704b384e39e852103d92696cb10223e1262b3bcfb2f555ed19fbe9adc4cb16e50964be2f9aebcc0cc146466f72ec0c226e4d4e0d9846b99c646bfd5113884ae519ce08dfd6b08eb22a1af25b6d1815c7758bea333b9a410db11c75981e3c8fd36c663ee2fe62da024f01d684333317f1c8c075af0baff1dd9e1e647a295679900455cb16601e70b453351eda37402cfd61566ff38ae99f209e94a03976744a02b3a65f7b09d39bea0437cabc80d3971d0846d565d8bb1534add4e34f906b971460c23fd49f7ceb43db3647e8b4fc88417462477782a5dad0e67a4784c54490c5b21f971d192cb642a66c41d3c68a10b3655552b007fda2576a8cbb01ee3ff85021d26ddfd652a04942a586794ea3ba963ac24509ba0e8ee686f62f67c7d15d0efe99f86500f7f1c109939619a594b7ac7465179d1aebe00ea6a4fcb81ff9d3df670995d2202214db0a3d5eb371dc45fe23c3e82e7ec22f0d61b0d79714dcec70249fb447cbc96e50c387210bae5a4307d24dde932906bc825c16499f98c1837d6cf3561dd5fdb49618f6cfb5a1ca730566923a7836b769548ef077db369969121768624f1a7504590fe55a2dae2053b7b3f3f5301934abe9e804eddd301547c8d3aaa947caf2d21053d659786fbd959e2658777b9aa2808f3319a1189bad15b5f735bb6bc8e068acddb64e9131733ba193b38d9039f5646024ee3eeb5b952191dde3fd98019471737f6e6dc983d57a9fdee96aaf60598335fab83da3328b7f208d0731458e59377832573e74e6de7dc2337eb381fa37e4a08c79a65c754655d6af439d8ad54b347b98323dfe758e27178a40d63208fc1fcd3df61b2d7160757947d46c696a8843f7ce187c6656ac0008407a7b5f634405ea440bc3447ec91910599f4edd8def67045b4200778e4c9757c8e868bc08366fffedc3ce00dbff563b173552ae31d197a949f3d18bb143d460a0e02d3b4aaad2779c0820b2853477964deff42a9067033402aafefbd28dcae30cadf7e68a92b5499c47faf0a6cb25d3225dbbe1debb6a7f8be1692e1ed61ca929fa7b6d88a13905e6992dbf0e9a2102c50b8e346c9fa52e67516a226b4a6ed0a5175c2a4432c22882acb727a284191fb0207329a873381bdbfae72dc2a2f1e6be1c0a8bf77e090d056a7689b5b6763f128f8b76639404cc14d396d6604d2a8c5145ccb1bb72a3d899550b93b132cfff03f3b941c92677f1bc1d0d0bc9819ca0467643eaacd6eb7923124b15cd5cbefd24647152e11e3865c1e17bb1a35f7cd7a57d50998cd91f03efc0f5dc9b5658b8cc739f46745801fd9dedf3829a7ce44ce25ef3a0cb8b49b8c18c89247f4d5095c877d71302e66ccc2ac9debf3c6fb511bad40eae508965dfdf66ed8d45e218f8eb3fc13e98644b50ddd91dbc11d6944ec32801ce9761e585a69930468de3b262956f72e250eb0bb4711b0387509df05732feced968a04b24fe6805ecf93ea6d85d178f4af97ccc05811f56288f28972b3fb873a1b626056be4f4aabe5b3b8bcbfdcdc277e0854c1c3eace993ef36304bb9453cd91e76b60bfbca1bf1613426cdddabd05e689ba64cdbc8c4ffaf177e1a9731b38dca1fb28934b8140d54f6e0a636cdee02e18c31b79871bb61d4a1d171798460859b76e358af75eac0ea8c55d3dd93b584ad82a5829000182e20381e802201c82068b925940ebe16ec679b86840d59d53a5afbc0ac8a51cf35e448f994a68d82a5828000181e203922020f96346a454a264d426a574e670033337c65e38a464a9fe95b5347d845167f73200000000000000000000000000" - }, - "result": { - "gasUsed": "0x3016322", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0x6393b0c1a03fcddab9d197626ee380c8dce03ac552781061a25ce03ed4a4b293", - "transactionPosition": 66 - }, - { - "type": "call", - "subtraces": 1, - "traceAddress": [], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000001db1ef", - "to": "0xff000000000000000000000000000000001db1f8", - "gas": "0x4c53635", - "value": "0x0", - "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000007000000000000000000000000000000000000000000000000000000000000005100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000789821a0001936a590780979cc13480c6881871acf163c67f7a1a2bc1f03cc354f55dcf9dde15e48cc8a68e98e105f7e7616ad71c627e3d8f6e9b8943c90960eefd4a81a64f2fbdff01e1898df27da876dbda7c1e563a46fbfd254c52605b2add4b45236708ffa56c2fbc07908bb0d02575f826cdcb31c359c02aabad55583196cb94a22d4f17192f8b154a0e1a1f82153db370e15254e9f2976fadeb82b9f9dabed2109febad359bf7f04ea2468d908f72fd558a3e6f38c71e7e85bc1ba5111b542f05ccedd6e05b5599b348dcc5e5c24397b8bca23768f81ce95793fc3bc58d35ea8a77edcdf8dd3bb8bb527033e09f1f013bf3a4c14e050d4892ce22664802477bb67714e5ab3554f6f533905dcc74a467a55daad996dc9e7ba973238b0bc61cc5fc0dcf6f8867746b07a3026ffd5871c015495177899006daebe855e8b582b4141f55a94b06c07535589c5b736aef5faee110569bdebc43f5a97fb56e9378ada1f40298d28f98b3601e22b8029a62d1787652dada0e8b2fd1be40f8371bf9f2622ac367f91e3a4ff6a5d2b2c648eb2265eafc4e1c7a970ca09cfe7b3155bf10b42596bd5ff02f7d4e89010e9c4ac3a27aef7e8a543ca01310a89951f79a2f2429618dae9ae72baf2ebd4252816841e095b84d0d7abc399ef3d9435803749fa2bdf5ba8da6091e0e6c03b171affa5d8a3994ba6e269e2626318fa75808e6962ec2ce525b6ba5c3a395c90698aba9dd1f775527109dda8f3923a1b29dc929d02c9944f123f6b5a406b981ac7d94fdae623b963dabb44965a2ec39dbc3c350e060859f09b33c33b7183ca3507d467b152b9adefffb424b0411309bdb55882d0ed0284bd5891e81a2cf43243ea28da1815900beae3b1241952ff0989730da43746acef8bd5f5180291fdd707b206b7c5e3db17434cd1cf0c9951b147d9de68552128857e9867410bf03a50399230d371fcad03c528ddd8820305db233fb5f807f7268621f43b7bee3d44435ceb00b959a9298343ab73af240d2c6adc91fd617ee83aab7d65cf53248f30699745db31f4727a5a8f4137efc258d63621fd8326c90c26195e85686bea4c9eb8694df56f88732df7e7112b27259326c8d151410b922a23103b10ee6d81d325be256a6760c5b700fc4c21a99ae270f1fb4b6d0cc60948801c9d408bc6223eafcbe52dce0cc9f1ed2f2f2f52b3ba691418fda181a27417fbf753e14a8680b3ff4000579547b6c37c0d72e6d2e8b48280f96da26b838373d5a242342a9a46daefd8341852f2f53f5dfac1d8d40a3402ab891a0a8da280978d7803d88f4c52ab376d6db65c3cf315ee5f185e0f8b30d72f7da427654ead6a1790ff750bb41113313a32d9e81be3849240ec12417e7f16eca03a9a381b90352649b5ccfbd8a1ccaedaf858ef2cb1b31f85bce89d86e471984b30e029c13859ecc88301ade00414c5140b0a1ba262677a9d1f082287db7bdf4ab1f5d6ff313dbdbe62ac8faa283471019bcdb8670033166fc082d37812abe3a30284a67f7bf95c2ed53b1b521baa8d1069578cf5c01c8d09351fb27fe25a8d7b542582846c6b4c8c7f3decd48c0e21d6c00f6106e27a64e8b8a0cb6acf08c1d3fc566229e9317b81c372c53e0b4d61fb477b6c786b0bc58ee7a794b988fe8de466d93665535e19b98b909537d133aa4f4a995a89631da6007e4f698a0a2e529a0ece6534782c158195fa292657956c07d76c1668deb0ed453603c35a3bda1da85b5e29d766a17d87f1f995367f3801c066428d52d2c18a9dbb3934b69aa3c5cd6b20987df92d5cd1bbac10c805e33a9c6ec48162385eb4d2d9cd56dee21dc48ae2bb998b343be328590e079d8bf64b367c8169543df713d89ab398b9856c4c7437abd4ecb799cc0f5e56c7a165d1e8dababa0b8ee63474c0d31fa50e59274bdd73db1d0a6ba2fb590a523234fe5026bd276526293fe12ef6ead189f7d967a858a3243befb772036d5763730b6fd1d7d2515630653a23d63558ec0ef68428987bb3c6beb925703ca08c8333cc5aa742d0afc2a7fe498d6cc5f22f13d993a0b5023e6712c547c825a7b0adf77e9a88d93fc539e4f708e5c0fb9f6f44925ba5e2e97ebba350c59965d74652b17c3707440c8c7bb0604f4a0623a34425f37f8e03f50a8bfba1054ad50942a3f4e9c76486ca9a92219cbea8de506ca908a751ad9c51c1e9296f809b2b02f2f66a5659f5d8bd30426c484507723380cc1ab250482eda674e329f191abc9caa647f6ba3d54449e9969fc2a42f340b5440660da775488e5364d49de6a8a4fa8c81b836abc90ba13a01dedc03e28113d7f5d655b7071e665b9f13772632a5cc8df38593b76b6afe348878d07ef2cfb78c1210c26fbe21ea60564376f1b1770a732dd01a39af2f9ebd3b802686dc271b6046bcf3b1c0c8468506cfc654bb04aebe9d67d8814d0eeb9b1aab31c1f67ca73528ca48743992ac0ed70b4536b46ca73bfeb84e6678f5c730a05e6b02be30a8620830554179018980dff44d9bed0578b162191608205f506d2f427edd72d38e0a3b750aab0a7dc896a5cf8339f685ab8573e94728ced5a0a738844c3d8d64750c547383f99a227dd13138db4712808f367392979cd6227a3ce5930eeb8b2ec388b6fb7c0a2fd9da86ddbc94f7f71e87694781c69f428158a923c3c7b5199f7f9dd6d71eb50b8035b53e8bfeaa2ca0c7aaea9930c2df83b0000000000000000000000000000000000000000000000" - }, - "result": { - "gasUsed": "0x7ac08c", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0x49ef42aace56fbd0d5779635f363a2b37eb171aa85aefcf5218da7d495abf901", - "transactionPosition": 67 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [ - 0 - ], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000001db1f8", - "to": "0xff00000000000000000000000000000000000004", - "gas": "0x47b5c9c", - "value": "0x0", - "input": "0x868e10c400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000008338808821a001db1f81a0001936a811a045b2a7758203e20b2105a6aae7da18e80f56751382eb6c4750b41c516ded263e3cd722d81f65820096c96c4cac9b18cb607419f842911557776091216b2c3a38014e9484ff0bb3a590780979cc13480c6881871acf163c67f7a1a2bc1f03cc354f55dcf9dde15e48cc8a68e98e105f7e7616ad71c627e3d8f6e9b8943c90960eefd4a81a64f2fbdff01e1898df27da876dbda7c1e563a46fbfd254c52605b2add4b45236708ffa56c2fbc07908bb0d02575f826cdcb31c359c02aabad55583196cb94a22d4f17192f8b154a0e1a1f82153db370e15254e9f2976fadeb82b9f9dabed2109febad359bf7f04ea2468d908f72fd558a3e6f38c71e7e85bc1ba5111b542f05ccedd6e05b5599b348dcc5e5c24397b8bca23768f81ce95793fc3bc58d35ea8a77edcdf8dd3bb8bb527033e09f1f013bf3a4c14e050d4892ce22664802477bb67714e5ab3554f6f533905dcc74a467a55daad996dc9e7ba973238b0bc61cc5fc0dcf6f8867746b07a3026ffd5871c015495177899006daebe855e8b582b4141f55a94b06c07535589c5b736aef5faee110569bdebc43f5a97fb56e9378ada1f40298d28f98b3601e22b8029a62d1787652dada0e8b2fd1be40f8371bf9f2622ac367f91e3a4ff6a5d2b2c648eb2265eafc4e1c7a970ca09cfe7b3155bf10b42596bd5ff02f7d4e89010e9c4ac3a27aef7e8a543ca01310a89951f79a2f2429618dae9ae72baf2ebd4252816841e095b84d0d7abc399ef3d9435803749fa2bdf5ba8da6091e0e6c03b171affa5d8a3994ba6e269e2626318fa75808e6962ec2ce525b6ba5c3a395c90698aba9dd1f775527109dda8f3923a1b29dc929d02c9944f123f6b5a406b981ac7d94fdae623b963dabb44965a2ec39dbc3c350e060859f09b33c33b7183ca3507d467b152b9adefffb424b0411309bdb55882d0ed0284bd5891e81a2cf43243ea28da1815900beae3b1241952ff0989730da43746acef8bd5f5180291fdd707b206b7c5e3db17434cd1cf0c9951b147d9de68552128857e9867410bf03a50399230d371fcad03c528ddd8820305db233fb5f807f7268621f43b7bee3d44435ceb00b959a9298343ab73af240d2c6adc91fd617ee83aab7d65cf53248f30699745db31f4727a5a8f4137efc258d63621fd8326c90c26195e85686bea4c9eb8694df56f88732df7e7112b27259326c8d151410b922a23103b10ee6d81d325be256a6760c5b700fc4c21a99ae270f1fb4b6d0cc60948801c9d408bc6223eafcbe52dce0cc9f1ed2f2f2f52b3ba691418fda181a27417fbf753e14a8680b3ff4000579547b6c37c0d72e6d2e8b48280f96da26b838373d5a242342a9a46daefd8341852f2f53f5dfac1d8d40a3402ab891a0a8da280978d7803d88f4c52ab376d6db65c3cf315ee5f185e0f8b30d72f7da427654ead6a1790ff750bb41113313a32d9e81be3849240ec12417e7f16eca03a9a381b90352649b5ccfbd8a1ccaedaf858ef2cb1b31f85bce89d86e471984b30e029c13859ecc88301ade00414c5140b0a1ba262677a9d1f082287db7bdf4ab1f5d6ff313dbdbe62ac8faa283471019bcdb8670033166fc082d37812abe3a30284a67f7bf95c2ed53b1b521baa8d1069578cf5c01c8d09351fb27fe25a8d7b542582846c6b4c8c7f3decd48c0e21d6c00f6106e27a64e8b8a0cb6acf08c1d3fc566229e9317b81c372c53e0b4d61fb477b6c786b0bc58ee7a794b988fe8de466d93665535e19b98b909537d133aa4f4a995a89631da6007e4f698a0a2e529a0ece6534782c158195fa292657956c07d76c1668deb0ed453603c35a3bda1da85b5e29d766a17d87f1f995367f3801c066428d52d2c18a9dbb3934b69aa3c5cd6b20987df92d5cd1bbac10c805e33a9c6ec48162385eb4d2d9cd56dee21dc48ae2bb998b343be328590e079d8bf64b367c8169543df713d89ab398b9856c4c7437abd4ecb799cc0f5e56c7a165d1e8dababa0b8ee63474c0d31fa50e59274bdd73db1d0a6ba2fb590a523234fe5026bd276526293fe12ef6ead189f7d967a858a3243befb772036d5763730b6fd1d7d2515630653a23d63558ec0ef68428987bb3c6beb925703ca08c8333cc5aa742d0afc2a7fe498d6cc5f22f13d993a0b5023e6712c547c825a7b0adf77e9a88d93fc539e4f708e5c0fb9f6f44925ba5e2e97ebba350c59965d74652b17c3707440c8c7bb0604f4a0623a34425f37f8e03f50a8bfba1054ad50942a3f4e9c76486ca9a92219cbea8de506ca908a751ad9c51c1e9296f809b2b02f2f66a5659f5d8bd30426c484507723380cc1ab250482eda674e329f191abc9caa647f6ba3d54449e9969fc2a42f340b5440660da775488e5364d49de6a8a4fa8c81b836abc90ba13a01dedc03e28113d7f5d655b7071e665b9f13772632a5cc8df38593b76b6afe348878d07ef2cfb78c1210c26fbe21ea60564376f1b1770a732dd01a39af2f9ebd3b802686dc271b6046bcf3b1c0c8468506cfc654bb04aebe9d67d8814d0eeb9b1aab31c1f67ca73528ca48743992ac0ed70b4536b46ca73bfeb84e6678f5c730a05e6b02be30a8620830554179018980dff44d9bed0578b162191608205f506d2f427edd72d38e0a3b750aab0a7dc896a5cf8339f685ab8573e94728ced5a0a738844c3d8d64750c547383f99a227dd13138db4712808f367392979cd6227a3ce5930eeb8b2ec388b6fb7c0a2fd9da86ddbc94f7f71e87694781c69f428158a923c3c7b5199f7f9dd6d71eb50b8035b53e8bfeaa2ca0c7aaea9930c2df83bd82a5829000182e20381e802209731f18aa4fce0cef187b30f84c05ce42eb42b7321ead72441898d5abdeea412d82a5828000181e20392202039f254d4d3235a7d50f05f37d5d73cdc32d12a9c989840c1108a54fb9a15523500000000000000000000000000" - }, - "result": { - "gasUsed": "0x3778f53", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0x49ef42aace56fbd0d5779635f363a2b37eb171aa85aefcf5218da7d495abf901", - "transactionPosition": 67 - }, - { - "type": "call", - "subtraces": 3, - "traceAddress": [], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000002cf0e5", - "to": "0xff000000000000000000000000000000002cf0ea", - "gas": "0x338cc13", - "value": "0x0", - "input": "0x868e10c4000000000000000000000000000000000000000000000000000000000000001c00000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000007081818708192d05d82a5829000182e20381e80220351469a2b481b840bd9743a6fcf7513753a3937e572c7cb25433031de7ec15651a0037e103811a045669031a004808a5d82a5828000181e20392202046998118b3148e63f207a911c0224a4eac12b07b6743415b6bff7524aac8023500000000000000000000000000000000" - }, - "result": { - "gasUsed": "0x2354b00", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0x0f12d60f9268de78ac12e64be7f99d6dd88c7d865d8ce91f32c11652baf62c16", - "transactionPosition": 68 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [ - 0 - ], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000002cf0ea", - "to": "0xff00000000000000000000000000000000000002", - "gas": "0x32d65ec", - "value": "0x0", - "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "result": { - "gasUsed": "0xfabb0", - "output": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000418282581a000286387ff107ef952f3612eb2e2a6606418e51c0f2cceeda5f57011731c2a04034dd33df402838a815a0b69ec1184b8c104a0001c0dfc71cd077c4b900000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0x0f12d60f9268de78ac12e64be7f99d6dd88c7d865d8ce91f32c11652baf62c16", - "transactionPosition": 68 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [ - 1 - ], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000002cf0ea", - "to": "0xff00000000000000000000000000000000000004", - "gas": "0x31ca0a4", - "value": "0x0", - "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000009000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "result": { - "gasUsed": "0x105fb2", - "output": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000578449007d2903b8000000004a000190f76c1adff180004c00907e2dd41a18e7c7a7f2bd82581a0001916cb98a2c3dfb67a389a588fb0e593f762dd6c9195851235601fba7e16707ee65746d4671e80aa2bb15bc7d6ebe3b000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0x0f12d60f9268de78ac12e64be7f99d6dd88c7d865d8ce91f32c11652baf62c16", - "transactionPosition": 68 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [ - 2 - ], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000002cf0ea", - "to": "0xff00000000000000000000000000000000000005", - "gas": "0x30a8b33", - "value": "0x0", - "input": "0x868e10c4000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000f818183081a004808a5811a045669030000000000000000000000000000000000" - }, - "result": { - "gasUsed": "0x488993", - "output": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000002e8181d82a5828000181e20392202046998118b3148e63f207a911c0224a4eac12b07b6743415b6bff7524aac80235000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0x0f12d60f9268de78ac12e64be7f99d6dd88c7d865d8ce91f32c11652baf62c16", - "transactionPosition": 68 - }, - { - "type": "call", - "subtraces": 3, - "traceAddress": [], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000002ce379", - "to": "0xff000000000000000000000000000000002ce3c0", - "gas": "0x305c21c", - "value": "0x0", - "input": "0x868e10c4000000000000000000000000000000000000000000000000000000000000001c00000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000007081818708194375d82a5829000182e20381e8022019ea299b91070fce9a4ed47462469afe6593ac2a70357ce4d26758a7334278591a0037e112811a04548e851a00480c92d82a5828000181e203922020690da56aaad92166ebff80ea0daf666c3c7e1a59eb690b95367f47097cd4363c00000000000000000000000000000000" - }, - "result": { - "gasUsed": "0x20c763a", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0x949c7c1e151376037350ef882355c9b80173632c806d5ca6164148b7f51ea7cf", - "transactionPosition": 69 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [ - 0 - ], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000002ce3c0", - "to": "0xff00000000000000000000000000000000000002", - "gas": "0x2fa5bf5", - "value": "0x0", - "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "result": { - "gasUsed": "0xfabb0", - "output": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000418282581a000286387ff107ef952f3612eb2e2a6606418e51c0f2cceeda5f57011731c2a04034dd33df402838a815a0b69ec1184b8c104a0001c0dfc71cd077c4b900000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0x949c7c1e151376037350ef882355c9b80173632c806d5ca6164148b7f51ea7cf", - "transactionPosition": 69 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [ - 1 - ], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000002ce3c0", - "to": "0xff00000000000000000000000000000000000004", - "gas": "0x2e996ad", - "value": "0x0", - "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000009000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "result": { - "gasUsed": "0x105fb2", - "output": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000578449007d2903b8000000004a000190f76c1adff180004c00907e2dd41a18e7c7a7f2bd82581a0001916cb98a2c3dfb67a389a588fb0e593f762dd6c9195851235601fba7e16707ee65746d4671e80aa2bb15bc7d6ebe3b000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0x949c7c1e151376037350ef882355c9b80173632c806d5ca6164148b7f51ea7cf", - "transactionPosition": 69 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [ - 2 - ], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000002ce3c0", - "to": "0xff00000000000000000000000000000000000005", - "gas": "0x2d7813c", - "value": "0x0", - "input": "0x868e10c4000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000f818183081a00480c92811a04548e850000000000000000000000000000000000" - }, - "result": { - "gasUsed": "0x488993", - "output": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000002e8181d82a5828000181e203922020690da56aaad92166ebff80ea0daf666c3c7e1a59eb690b95367f47097cd4363c000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0x949c7c1e151376037350ef882355c9b80173632c806d5ca6164148b7f51ea7cf", - "transactionPosition": 69 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000002b3543", - "to": "0xff0000000000000000000000000000000021b478", - "gas": "0x1995446", - "value": "0x0", - "input": "0x868e10c400000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000f285068182004081820d58c08a3c01bd1c9be515f6217c646d9fec13e541dc1445178d0fee2bc33183b23dc4dc5b7d7d06493bb131a44566a5e1eecf8fb68474b5448d132996e4cc35db93b6e13b32f59f2d6a02eccc2f128271e171c09ce20cedfa3b7a57f6905d553af8e403305f072dadfa782b2e673feb8bebe6e75d8875a0af330a3ba0b404463b46ada804bde0677c9e44659a1973e82a243e88d1f18d823d6849eff98fab0e9c585246f8fc4a897916d823c91780159e530fd732352ad88e2474f2cf815c388dc3111a0037e5f8582047ab5fe94c06cdbaec8b74525a8ef23928a1bbb62440ccbcd599b10154404d690000000000000000000000000000" - }, - "result": { - "gasUsed": "0x1545d77", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0x7d27a0315e51d96e06bc4699d5b5efcebf60f838a9db79f1c1ca6183ae3b86c5", - "transactionPosition": 70 - }, - { - "type": "call", - "subtraces": 1, - "traceAddress": [], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000002cbdce", - "to": "0xff000000000000000000000000000000002cbe59", - "gas": "0x4f47d1a", - "value": "0x0", - "input": "0x868e10c4000000000000000000000000000000000000000000000000000000000000000700000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000078782196c66590780906c8270f7a49125a567510c97404d726700444a974c52abaa369eacad17de494b817f70ae9f60f7e4b4fd7bb7325a17b8124630857a51652e94ee9a590f76dd479998cfaabbb74fc4dbdebe8be928083719ba2bc514dbf3723335072095ac5110206d71e43f6acb4c8d4a5f5c4489c2adfbacf8b54f44eeb958ccee349a7bd587c8ad64af6cbe885df7a04af2a4e1cda51388e3d459cb9efcc9905ffc53d4de4a7aed05a0537de2ce8594c672b5c3876bba195e095302b88d73f91b4d1716fab0eca1e729706fee665d539960ef9a4719ec4edc6bc0a4e6b52bb0e76ad484d5eeb2c1650ef9bc821d67524aa82f3bf4825548eb5d9b8a7f1638c747f219649c75241dbc9d1b47edf6baa29e7d85e667f9c96703e9ff09e3e5ee2be72a10a3e30f2e506c74bff2b581521fceb697c2a7b83db32f3c665e213c0eba5a8505d2415fb60ddebf509bbfd1a1dec3df01d480b23bdc16c8268bc0462d477101742b11bb5f31dfc037608610ad3489b78bd776e3e66e7655f5b55f98ef01edbe1c1ad0938b6883764bca24d33e2dae96c467ddb242b153f1bbb41df5d0842d496f1271e4cc9e212ab58369126cd56c30c602708f6b62ce8c1dd20b7eb5de15447c683536faee20399a2f23a619b2069546b279077a5b73c050962447c05997bb9c5831146230ac4f09cbb01eb7dc5abc87e185eb44da96695fd18f6047831d0276710e7aac3e850507ca142bbf4b036b72b872a3c351eff4e017b664d965c4c3a5b3745fb4dbd401f57ec16b0fcff536d8b705757f369e16ae1ea5b59e4f37d45b15d699257bdf5e01ea1be77e3dbb45a5204108e0009e93197e601b7da1d6bc29dab0c8126b99aef1ada965ea0a4c999b335c81095313b3d576be3ae66e4157a86a985285b64eff9380da6f2b4fcb5f6966ade1c97b4c293efc753316ad19d3e542600ef0539c6b1fa0ebed10edd8b6da5f276fc7d07075e1953120543573a4263641e9cf2c1e73df2d803339b29f7d9b14e08811c3f0d631c08f61853a59cffb9f585e2f409b1149602f4861bfafcbeabb4a8f6a0095e5b5cc20c3c7080e93563d4988839181355d52ec6c8e141890a662460b5d8e4b7a53f2abd3922221b848943b6aa79b10421431d8cf5e5973e7b28f218ab7578011d031c9f1ad86fba0f4915aa01e1dc34465c8e39caadf80d8e312e96b4e1ee885bafa1f2d0346461ea8939f08cd15cfa035611642aff82dede8f823d84aa267db9e9a439dddc9eb81ac4855a4746e844aa385b797c674f952b56ecc8a31180a1478fcb46486892bcd7f546b03546a6a2beb93c5412814a1ec8bf9c8cb8c098afbaac58bbfc45e1db9df070188c93ab52ec845d019ec1fc01f4a2e4efeb9930b71905de02d44736a3d07a7b7c34c4f4480dc8f8b974532afb2bb06ea8165a113710d5e429ba435c552fe7aa34183643a0e1b716447f71dc12ac4668f9c67a3f43195f9f4f9ee28451ac2445403c208be6a02cc5ac06012fd1209663aae19460d2afdff338e18f77a3cc12d9439ba513ddfe94c2f15754f39def5eec8b731642c48e08b4edb7c015578f02f69c4d8ba79b746a0caaee744715d94c139a6c9aa489a3ce3a343edac841181136c833f65a9dad3b1dca25f31f729fcb7dbebb47a11eaaf5a0f7904690b5b31a0bdcb88559d34ea252862b0a399dfcc57e283c0ea11412ab19438e439926f6dc9db6086c68ea329984dff59b636137eb83eb0a0c43b4e3d81c5e0e45bf305f0dc720d092086254dfc294477e5e5ff47c793feaa1866c94e251d5511229f1e95c05540b591984e73a1959b4094e62e71affd916daa99eabffc7ce1881e081be632ae2c09d61ad1b69e1b2bf064eae889182e86e0d65ec4667f75e2d277ee17b0cb85aabe7c6b7169011ed82f831758553ae49d4ea6e41b7e578c6e354c33fe528217a0f331ea1ba1350165b3098015cb58d38340811ff27e5e20a1368081fe140bd1ebc77dfdcdb28bc567e28b6584a7c68b68f6dec93e9cc385135038ee735322260f7c90044be8a19bf18d99e09f70607ff2a6a3a4ad29e0a291202eb601186b75e69835d2258e74bdbd04ec8f9c75981eb6f4dc7f09c01763aa3a3de91dd16c0b3f61b97802a4dd6dfccbc1da28fa09a5f8ee64b41ba12580d4a4163f84ec9f81a4edc5fba7fec105aeeb32dcb68d757cbd276c13a5e09af8302a11e5b503e7911d058ae7c1dcaa44b5927c9ab4f02aa796a478b2442b91f21877c46d91b41b07ba9b2b8514bd1adb787a9a3c27efa27fdeb8371e1d27c66ab80da9ec7c2c1f43145e73dd2483e3a93ea363c28131b8aadcbe8c7fd536caa63a79df8cd3a2e432fb449c6df33bc07a4a85328c5f0d7f1c89d5920bb7272f5b320df888a23877838b9bfb9a53217b09c71b5c5e0ccf18684ecfe43093a8586c2ca5567b99b52dde92398b19af9eaa852639e726d14c261610f64bb225b4d598b3e99f27fd427ccff640bf57173cd0ea640c4fd4662d9c14914d93457b202f3c020c4aec749ee965d824ce864b837797fdb365c5c20bfce3d5f3410308901bb2e6bbd2979af999c909d9f6b240a50559868ade0f8d1b22b8cf1fed7e084c000e3229b36b06c12f9eef090281bbc125f78415f6a9382b6646b5f3741ee2c39ca4a070e3fa322f138ae27f56f7bfecff510cd82b170b2d9c20bcf4133ab2cfb82278268f6a998e07cd00000000000000000000000000000000000000000000000000" - }, - "result": { - "gasUsed": "0x9f6264", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0x09256a67c71f386327c50728db231ac787c819340a27da9c52bf5338efdc1b8c", - "transactionPosition": 71 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [ - 0 - ], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000002cbe59", - "to": "0xff00000000000000000000000000000000000004", - "gas": "0x485fdf9", - "value": "0x0", - "input": "0x868e10c400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000008318808821a002cbe59196c66811a0458d8675820c733511a1ee67c4b6354648a0aad557a7d6103db91ba7ec3605d0e0d0a7dd727582070588192c2cfcb22bd6934b66eb4b782332859eca0353d7a6a6de5ae787e23ff590780906c8270f7a49125a567510c97404d726700444a974c52abaa369eacad17de494b817f70ae9f60f7e4b4fd7bb7325a17b8124630857a51652e94ee9a590f76dd479998cfaabbb74fc4dbdebe8be928083719ba2bc514dbf3723335072095ac5110206d71e43f6acb4c8d4a5f5c4489c2adfbacf8b54f44eeb958ccee349a7bd587c8ad64af6cbe885df7a04af2a4e1cda51388e3d459cb9efcc9905ffc53d4de4a7aed05a0537de2ce8594c672b5c3876bba195e095302b88d73f91b4d1716fab0eca1e729706fee665d539960ef9a4719ec4edc6bc0a4e6b52bb0e76ad484d5eeb2c1650ef9bc821d67524aa82f3bf4825548eb5d9b8a7f1638c747f219649c75241dbc9d1b47edf6baa29e7d85e667f9c96703e9ff09e3e5ee2be72a10a3e30f2e506c74bff2b581521fceb697c2a7b83db32f3c665e213c0eba5a8505d2415fb60ddebf509bbfd1a1dec3df01d480b23bdc16c8268bc0462d477101742b11bb5f31dfc037608610ad3489b78bd776e3e66e7655f5b55f98ef01edbe1c1ad0938b6883764bca24d33e2dae96c467ddb242b153f1bbb41df5d0842d496f1271e4cc9e212ab58369126cd56c30c602708f6b62ce8c1dd20b7eb5de15447c683536faee20399a2f23a619b2069546b279077a5b73c050962447c05997bb9c5831146230ac4f09cbb01eb7dc5abc87e185eb44da96695fd18f6047831d0276710e7aac3e850507ca142bbf4b036b72b872a3c351eff4e017b664d965c4c3a5b3745fb4dbd401f57ec16b0fcff536d8b705757f369e16ae1ea5b59e4f37d45b15d699257bdf5e01ea1be77e3dbb45a5204108e0009e93197e601b7da1d6bc29dab0c8126b99aef1ada965ea0a4c999b335c81095313b3d576be3ae66e4157a86a985285b64eff9380da6f2b4fcb5f6966ade1c97b4c293efc753316ad19d3e542600ef0539c6b1fa0ebed10edd8b6da5f276fc7d07075e1953120543573a4263641e9cf2c1e73df2d803339b29f7d9b14e08811c3f0d631c08f61853a59cffb9f585e2f409b1149602f4861bfafcbeabb4a8f6a0095e5b5cc20c3c7080e93563d4988839181355d52ec6c8e141890a662460b5d8e4b7a53f2abd3922221b848943b6aa79b10421431d8cf5e5973e7b28f218ab7578011d031c9f1ad86fba0f4915aa01e1dc34465c8e39caadf80d8e312e96b4e1ee885bafa1f2d0346461ea8939f08cd15cfa035611642aff82dede8f823d84aa267db9e9a439dddc9eb81ac4855a4746e844aa385b797c674f952b56ecc8a31180a1478fcb46486892bcd7f546b03546a6a2beb93c5412814a1ec8bf9c8cb8c098afbaac58bbfc45e1db9df070188c93ab52ec845d019ec1fc01f4a2e4efeb9930b71905de02d44736a3d07a7b7c34c4f4480dc8f8b974532afb2bb06ea8165a113710d5e429ba435c552fe7aa34183643a0e1b716447f71dc12ac4668f9c67a3f43195f9f4f9ee28451ac2445403c208be6a02cc5ac06012fd1209663aae19460d2afdff338e18f77a3cc12d9439ba513ddfe94c2f15754f39def5eec8b731642c48e08b4edb7c015578f02f69c4d8ba79b746a0caaee744715d94c139a6c9aa489a3ce3a343edac841181136c833f65a9dad3b1dca25f31f729fcb7dbebb47a11eaaf5a0f7904690b5b31a0bdcb88559d34ea252862b0a399dfcc57e283c0ea11412ab19438e439926f6dc9db6086c68ea329984dff59b636137eb83eb0a0c43b4e3d81c5e0e45bf305f0dc720d092086254dfc294477e5e5ff47c793feaa1866c94e251d5511229f1e95c05540b591984e73a1959b4094e62e71affd916daa99eabffc7ce1881e081be632ae2c09d61ad1b69e1b2bf064eae889182e86e0d65ec4667f75e2d277ee17b0cb85aabe7c6b7169011ed82f831758553ae49d4ea6e41b7e578c6e354c33fe528217a0f331ea1ba1350165b3098015cb58d38340811ff27e5e20a1368081fe140bd1ebc77dfdcdb28bc567e28b6584a7c68b68f6dec93e9cc385135038ee735322260f7c90044be8a19bf18d99e09f70607ff2a6a3a4ad29e0a291202eb601186b75e69835d2258e74bdbd04ec8f9c75981eb6f4dc7f09c01763aa3a3de91dd16c0b3f61b97802a4dd6dfccbc1da28fa09a5f8ee64b41ba12580d4a4163f84ec9f81a4edc5fba7fec105aeeb32dcb68d757cbd276c13a5e09af8302a11e5b503e7911d058ae7c1dcaa44b5927c9ab4f02aa796a478b2442b91f21877c46d91b41b07ba9b2b8514bd1adb787a9a3c27efa27fdeb8371e1d27c66ab80da9ec7c2c1f43145e73dd2483e3a93ea363c28131b8aadcbe8c7fd536caa63a79df8cd3a2e432fb449c6df33bc07a4a85328c5f0d7f1c89d5920bb7272f5b320df888a23877838b9bfb9a53217b09c71b5c5e0ccf18684ecfe43093a8586c2ca5567b99b52dde92398b19af9eaa852639e726d14c261610f64bb225b4d598b3e99f27fd427ccff640bf57173cd0ea640c4fd4662d9c14914d93457b202f3c020c4aec749ee965d824ce864b837797fdb365c5c20bfce3d5f3410308901bb2e6bbd2979af999c909d9f6b240a50559868ade0f8d1b22b8cf1fed7e084c000e3229b36b06c12f9eef090281bbc125f78415f6a9382b6646b5f3741ee2c39ca4a070e3fa322f138ae27f56f7bfecff510cd82b170b2d9c20bcf4133ab2cfb82278268f6a998e07cdd82a5829000182e20381e80220650c58df573197bd182a131c412403032df10dd4be914cad3fee6a8f9f50490dd82a5828000181e203922020bbfd25d9bb672844a5597ab0c099ddd592766e9cc6bfbc1aabcbdeb78b5af603000000000000000000000000000000" - }, - "result": { - "gasUsed": "0x378821c", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0x09256a67c71f386327c50728db231ac787c819340a27da9c52bf5338efdc1b8c", - "transactionPosition": 71 - }, - { - "type": "call", - "subtraces": 1, - "traceAddress": [], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000002cbdce", - "to": "0xff000000000000000000000000000000002cbe59", - "gas": "0x5842604", - "value": "0x0", - "input": "0x868e10c4000000000000000000000000000000000000000000000000000000000000000700000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000078782196cb759078088616c745421a5e0810320c2d4127fb35257616beecae3a3857f10eef0cbe560c70555cf106e7df9ff45475bd7e7f097ada94a97f56afacb8367bae0a7c9b9e07bd27f4372024646cb53abdc2f6be656423de773c1a708381a3424184cf315e80ea091cecc1005df11c33d569f0d275d97d25892f931e33d3817f556f964d44bf425022a7d06d67442c2ad9304f5e6b9b42497880067eaf6df1d5b6d405f1a7296142ea97427040300ece471973832fa6b0dc8cfbd4dbb4b747475ac001497f980a801e5d9ed67095e8253cd38643181f78ed6d1fd39f9d5969faa493b5136c40536e7f464cf02d8b67b682e34825f8d9755f05de55886dc90e1058deab0272e02ecfbbca1f12e12e56123cc2c5ea67f8631520d5691b5a78ddfe1af7e40bebd163fe9a61c4f8a9d02a3939bd06bc3665a97fc75ba9bb0febcfff1b0339d6a88df855e5a15c9a6693164bd5d3e9220869867de3399f97ab90ae31232176481decab82cae43a9c82944ce462cc38e68eb7a112acd831286494b5842f2afd857dda77c58d2134652c533d730e6b5ae06f7d3437c4489c590215f5c8371b355ff2d934d166a48a586e02e8e7f48730810d4b591b5cfad7ebdb03553a70af3c23a214b7b06392f902cbbc6766ca703c25e9122e46cf6b698c6b7b615f8cc1cae3a3f1253f5fadca7be1d9548af2ea031ee8aea3cea3be303178c1d01c9b3b2d5e99daf1546921fb56aaeda89be4f70a11343a4c6a8459454783024ab4aeabba105a969b453cb83ad6dd19bbd8bee6dfe04de7bac58fb49abc7ab6f63831a904a0b66b209f3326c4e21854aff6c2be4f393a145290a9574ae7ab0d6c90ab17da9c5e1c027f06ad6c19797e8f411f5499a6c62b1d8cfa70b6f61fd3f6100b93d6b53141ac5598568e9357449ee5c2b1d444ea251a41956b52d35e2b7cd26dc33c3eaa5067ff1da021e9901f02fa9e7a5a5a55b67c336655a31407ba2899156fc7dc35f221422cdf332c3fd52978b644c6e73a2aff102ff518f8d3ecacdbc64cc07a70f4d62fd677c740f3470e41bf0e28288f3bf8c615054fd2a853c0f80fd284b979999db697097029be9b7a5a0ef6c8abc89ac176180a208cd524679f10c6a9403472f9ff960cebec3e33b9912d62f455ef0adb936e5f88e24127e4f4dd46fe7ff9ae1317c217a630a0760bc84ff5fba0b48b047e5eb25975e00572c6b6dbe561ade01ea50f345d95c91c866537d86c0720e7396dff985a191fd554f838289ae148fb45a6db5ebb752f2690ffcb42e75c29aa74b3f566789ad96d7eda31320391c160dbe295d4a8102d4b331d8a1e1290e41bff958c5e0155f333e1846b21d1a14e0871bacecd05b4a16370aacf1319016359ac2d1d9d3911dd41b1936cc9f0d8d0a33196d4805e869e801df13e1046487c5a0fe53944aa55a6737cc6e0a84fce49f514d76c68e9be3c782c64b788d92c6c04f4c158d5c63670ca19e1a85024017d2160f591c8ac226ffdf96733ccb6bda368b266f6b326ff8b01227d1f4fcbe2850a57c9b71ea234817a198d111f8f81b59b12a4bc48614ec5364db7ef0205315c3530e418f5a039e05883b1401c9d9de917e25a1324bdd5f693cb93a8bd86db1d797bd6be5f9a4078c6710690da48a2b5058939427c91b9184bfba9199a5ce398f3e851e0b2299a1138e3ac216f64a9d8981f312c74419106595e549d9dfcbe7bd6f803995f9f36d20685e0c771c6b73e73c602bb0e18c7c1b71a2b18cababbd1a1891bb3b4b927e58bc8a7b5d6c3433640985609b3e3baf4c178f3a469a52a2697a185fa6b57c2a6d6371cf02f18cdba2ae3b7467a3663962e333726df013118dd4e5f91b258df39a3870e252712e87cc44f2e9e3a7b6e4c503088efa5ce363e7b10d2ce33a87a85629482cb5d64c01ec4f97d0b7f704a8fd30590bcefa2b3699a133a39e0a383cf5db3fcaa299b0154eb3dccdecbd83c3daf38216cd7ce9c9b436ef33decba8a32a2f1a64e5539ebe5b5432d5a438a24d2d6e8ca07f33519cc601dfe0551faeb8514b005c79ad7ed2c7a1f8c9cc2804ca4170bd1ab04e00c0d07e5bef9895515ad0485dcc4b00cdba458655f3818dcdf7757ba76fff722b9856c3925ff17e0213075adcccd677fc0794c92930da09419fbe0fc78c98d466c550a6541aa4e5a01689d9634d384d8b7f77e678676555b342d1f54ab3f7719191c4bc98577a0ead9b05b86e16bd1bdc018a98f467472d8b8e54ca29642b2020d11595a282b3cabd7689bc79d0c5822fa6d3b539a962bdd17b3eb3adf7c34ca9eb2c00a7e18d11e5290d11878e2437876e4d306e2fab457fab2c52deccf7219830e2308d87441b33906d7334feab7c6e9259b8136502f237344b954ab861132dc40769b10ac6f4e4933fa08422e2f499a7e588caec50bb4912e85df69e97c6a148a6961d683b631c82a5e96080368d6800c9908f5e36f93d2d4b3ebcbdeff3e492b26faec713df3f5e74536a313effcb12e1b0c3d2c3e290d34ec267b895f23ca7511cdc0be7dfb6648163f8731e9833b73b77697ee24bb2012cf36986470798b1d00fa384d2e0eeb172ea82d705c6b55785c0c1fc64b0f85081dea8f9b9242961e1251f0d3bc474b6db1977efd1c0d217fbaa15682c62fd9d386fe37b710fb7a3e86018b629cf0c15cb32509633f555f7750c4f553798b97b008b8347c3dedc9d4900000000000000000000000000000000000000000000000000" - }, - "result": { - "gasUsed": "0xa41eb9", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0xfe2a899422ce49a8aa92413b3b30dfeef07f4601e1f817a9be1e2681e0c6a3c5", - "transactionPosition": 72 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [ - 0 - ], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000002cbe59", - "to": "0xff00000000000000000000000000000000000004", - "gas": "0x510df01", - "value": "0x0", - "input": "0x868e10c400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000008318808821a002cbe59196cb7811a0458d1025820bdda037c22b1041a1ab65a83f366c6dda203b1a83f8a11f7e6d76b019411a960582070588192c2cfcb22bd6934b66eb4b782332859eca0353d7a6a6de5ae787e23ff59078088616c745421a5e0810320c2d4127fb35257616beecae3a3857f10eef0cbe560c70555cf106e7df9ff45475bd7e7f097ada94a97f56afacb8367bae0a7c9b9e07bd27f4372024646cb53abdc2f6be656423de773c1a708381a3424184cf315e80ea091cecc1005df11c33d569f0d275d97d25892f931e33d3817f556f964d44bf425022a7d06d67442c2ad9304f5e6b9b42497880067eaf6df1d5b6d405f1a7296142ea97427040300ece471973832fa6b0dc8cfbd4dbb4b747475ac001497f980a801e5d9ed67095e8253cd38643181f78ed6d1fd39f9d5969faa493b5136c40536e7f464cf02d8b67b682e34825f8d9755f05de55886dc90e1058deab0272e02ecfbbca1f12e12e56123cc2c5ea67f8631520d5691b5a78ddfe1af7e40bebd163fe9a61c4f8a9d02a3939bd06bc3665a97fc75ba9bb0febcfff1b0339d6a88df855e5a15c9a6693164bd5d3e9220869867de3399f97ab90ae31232176481decab82cae43a9c82944ce462cc38e68eb7a112acd831286494b5842f2afd857dda77c58d2134652c533d730e6b5ae06f7d3437c4489c590215f5c8371b355ff2d934d166a48a586e02e8e7f48730810d4b591b5cfad7ebdb03553a70af3c23a214b7b06392f902cbbc6766ca703c25e9122e46cf6b698c6b7b615f8cc1cae3a3f1253f5fadca7be1d9548af2ea031ee8aea3cea3be303178c1d01c9b3b2d5e99daf1546921fb56aaeda89be4f70a11343a4c6a8459454783024ab4aeabba105a969b453cb83ad6dd19bbd8bee6dfe04de7bac58fb49abc7ab6f63831a904a0b66b209f3326c4e21854aff6c2be4f393a145290a9574ae7ab0d6c90ab17da9c5e1c027f06ad6c19797e8f411f5499a6c62b1d8cfa70b6f61fd3f6100b93d6b53141ac5598568e9357449ee5c2b1d444ea251a41956b52d35e2b7cd26dc33c3eaa5067ff1da021e9901f02fa9e7a5a5a55b67c336655a31407ba2899156fc7dc35f221422cdf332c3fd52978b644c6e73a2aff102ff518f8d3ecacdbc64cc07a70f4d62fd677c740f3470e41bf0e28288f3bf8c615054fd2a853c0f80fd284b979999db697097029be9b7a5a0ef6c8abc89ac176180a208cd524679f10c6a9403472f9ff960cebec3e33b9912d62f455ef0adb936e5f88e24127e4f4dd46fe7ff9ae1317c217a630a0760bc84ff5fba0b48b047e5eb25975e00572c6b6dbe561ade01ea50f345d95c91c866537d86c0720e7396dff985a191fd554f838289ae148fb45a6db5ebb752f2690ffcb42e75c29aa74b3f566789ad96d7eda31320391c160dbe295d4a8102d4b331d8a1e1290e41bff958c5e0155f333e1846b21d1a14e0871bacecd05b4a16370aacf1319016359ac2d1d9d3911dd41b1936cc9f0d8d0a33196d4805e869e801df13e1046487c5a0fe53944aa55a6737cc6e0a84fce49f514d76c68e9be3c782c64b788d92c6c04f4c158d5c63670ca19e1a85024017d2160f591c8ac226ffdf96733ccb6bda368b266f6b326ff8b01227d1f4fcbe2850a57c9b71ea234817a198d111f8f81b59b12a4bc48614ec5364db7ef0205315c3530e418f5a039e05883b1401c9d9de917e25a1324bdd5f693cb93a8bd86db1d797bd6be5f9a4078c6710690da48a2b5058939427c91b9184bfba9199a5ce398f3e851e0b2299a1138e3ac216f64a9d8981f312c74419106595e549d9dfcbe7bd6f803995f9f36d20685e0c771c6b73e73c602bb0e18c7c1b71a2b18cababbd1a1891bb3b4b927e58bc8a7b5d6c3433640985609b3e3baf4c178f3a469a52a2697a185fa6b57c2a6d6371cf02f18cdba2ae3b7467a3663962e333726df013118dd4e5f91b258df39a3870e252712e87cc44f2e9e3a7b6e4c503088efa5ce363e7b10d2ce33a87a85629482cb5d64c01ec4f97d0b7f704a8fd30590bcefa2b3699a133a39e0a383cf5db3fcaa299b0154eb3dccdecbd83c3daf38216cd7ce9c9b436ef33decba8a32a2f1a64e5539ebe5b5432d5a438a24d2d6e8ca07f33519cc601dfe0551faeb8514b005c79ad7ed2c7a1f8c9cc2804ca4170bd1ab04e00c0d07e5bef9895515ad0485dcc4b00cdba458655f3818dcdf7757ba76fff722b9856c3925ff17e0213075adcccd677fc0794c92930da09419fbe0fc78c98d466c550a6541aa4e5a01689d9634d384d8b7f77e678676555b342d1f54ab3f7719191c4bc98577a0ead9b05b86e16bd1bdc018a98f467472d8b8e54ca29642b2020d11595a282b3cabd7689bc79d0c5822fa6d3b539a962bdd17b3eb3adf7c34ca9eb2c00a7e18d11e5290d11878e2437876e4d306e2fab457fab2c52deccf7219830e2308d87441b33906d7334feab7c6e9259b8136502f237344b954ab861132dc40769b10ac6f4e4933fa08422e2f499a7e588caec50bb4912e85df69e97c6a148a6961d683b631c82a5e96080368d6800c9908f5e36f93d2d4b3ebcbdeff3e492b26faec713df3f5e74536a313effcb12e1b0c3d2c3e290d34ec267b895f23ca7511cdc0be7dfb6648163f8731e9833b73b77697ee24bb2012cf36986470798b1d00fa384d2e0eeb172ea82d705c6b55785c0c1fc64b0f85081dea8f9b9242961e1251f0d3bc474b6db1977efd1c0d217fbaa15682c62fd9d386fe37b710fb7a3e86018b629cf0c15cb32509633f555f7750c4f553798b97b008b8347c3dedc9d49d82a5829000182e20381e80220ad21a8b5617018e5872ad54d2fb83e094fb5178f7ac7530bc2a82c91c9b0e473d82a5828000181e203922020bc3b85c5479f349cc048347aa46fcd4a1364eb6ec3b191d86bfd70ae490cdc32000000000000000000000000000000" - }, - "result": { - "gasUsed": "0x3e69aeb", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0xfe2a899422ce49a8aa92413b3b30dfeef07f4601e1f817a9be1e2681e0c6a3c5", - "transactionPosition": 72 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [], - "action": { - "callType": "call", - "from": "0xff00000000000000000000000000000000181d8a", - "to": "0xff00000000000000000000000000000000181e6b", - "gas": "0x1959fb3", - "value": "0x0", - "input": "0x868e10c400000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000f385181a8182004081820e58c0a829718db09f68797f2d9be15809d2e1387a94bd8b1a51d53013dab9b9611d70aece45b4771bd878d93db7f151e76e9a812d513d34dd30afd572af6adb1763bf9583245961b2e14558086e2eb0da8a1fbf572d042dc1f0d6f0ac2bdab7ed340306aa8db0e432b57aca8579449eab439fc87f84c441133383e504824927027f70f722acfaebc2ebe1ab004f5989794a51b59d9f1ac7749ae9d4cf9b0860b9b1f8b9bd11b0e7f183bf20e9d1ad2278791c6c438c3c768c6e9395786a463bd9ba4f1a0037e5f8582047ab5fe94c06cdbaec8b74525a8ef23928a1bbb62440ccbcd599b10154404d6900000000000000000000000000" - }, - "result": { - "gasUsed": "0x1517eb4", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0x1b93e4163b1bd35cb6342233c84dcda34d3347e5f5ea996f90e63b2d0e71f98e", - "transactionPosition": 73 - }, - { - "type": "call", - "subtraces": 1, - "traceAddress": [], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000002c2abc", - "to": "0xff000000000000000000000000000000002c2ade", - "gas": "0x4dea6fd", - "value": "0x0", - "input": "0x868e10c400000000000000000000000000000000000000000000000000000000000000070000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000007878219911e590780960f552b98a30ad3840c6da73d540213f5723c320b231419709ba5b7381feff81e38de449fdf783e55f3f411c1baa728854228f264cb115325348d007cb5922c59e926caf1a0d78fe1b4ec9841883cfd34d7a6b2eaf574097ca32f079d4a862a12245f8aec0237e66ee5b29da0ab67f6d5da06f5311dc40f205aef2c09826d84283d6c951d802f3a4dbba31b38abf15fa07625c908e2c2797578c1431ee2894e7d954bf134439530ae612ebcf87deb1f354bc3bb96bfa4f4ca53c79112772671891803ee5c6dc2bebf2beea9bed50c947b731c5faf9369b3c42fb5cfe9d0236f4f559a05a7a8a584cf26637e6713ad1a80520fb7f0d3e6f3c70866b6371c17ee1ae76dbbcc809a0c2a70a2e6241ed2b04d9f35f5e9d215e346266321856099c81550a8d7b7e6a82bb31f5002c5545e9e853ae79cbe26d5d9fbdca504de06b48dbec1614875ead88b1aacc0a76a6e1123b91092b53b92d6507d2c4b90b950406e3706e22fd16ba83ef4474f93edf8e908ca824aa875175425b757365e0d94af4d8f054278185210db77b78c66d44ead9ad621b2080b8a96d41aece81e05638984440954d538f31a8d90847550cfc56846a51c83ec9630241066785b926985a90ab0d30f815a1a365adadf5560b911a33336e278dcd77eceef98eaaa2acc96c03806004221de8e1e43c097d2ddd72870cf493361094483976e1cf852362c17559004265230b9f6582e572b3f359a2242948c49b4273efce8cf7e2ae233ab96db76f22b2214c2c2b58edc3faa2fc67e589bad0a2eb05e0830be889f439d27a7c1dab781d301ac88790943ade6fd61679b08f70bfdde55c4f98f8d41a9ff2ad730a8221eacb29d4b26ddcf813bffa913bf5ab88e3f73a9a3b6b8b8e433ecb6ba009384e088519a4422bb250aacf4cc81d39be173eb79b52b2991171310067a240b6507a0b344cecd6896062ed7dc28022567c5236f2ad5e97fbd6c37621fbfe727b11536ee5e6ba31030f252ce8a05224bc69617ba5aa7a5a8561d68c97b7532181c77df9804feb4853fdd3efbff953eb9d8e826ea01b27919b33911bbec2f9041b2aa05c84419df21b1f91515ec4467a3abcb30aab03f04018b351ec6684a1fb67e7f67f356ac375c34f2a181d718318f92b97acd7328907279031a4b93eebc88b4fc70b4e831be82deef3e724b3af5608f1f311723a3a3c7dfa2be97407358469e04304456ee864fc2b212eaa3adff6d89c1991ca2fa1e988eb2769178e935a996ac0c1efa44000328ff4667474f79536499392534a06cd4638cfabbddaab5028f202a3c7364fc01f147902c9bdbc2bfd750498927e9a0769330daada7cb605619ad35a6f016f2405e9978b6df94c5226f1c796328027b9be8f87dfd536ffd4a3cba032084902817ef3b5a014a29d81db98cf0f0ffcc696a4408a40097d0e809237cab607f0ec005572c41cc3a7829a2ecbd9df4280d283f23882fbd6d159e208f0447ef60c9c20ed40115ca2631ebac7a8b5920dfeb64c5354fc1a6a3e489b33e9fe5c7dbf8139d06ed75b648aeaa7995b013265228524981c8f09cc1268bb0a626ab89d3d829fefafddd2a7734787f1e691f862ded29aec3478520af69fda96493b04ba75b38ed974f0ed859bf8703f7b9579c2bc9358994a71481b1450f791996ddd13b9faee4684551059290a96485a537d58087d1e857eb09bf9e2896542631756c5bab31ee5c4a2b4bee767d62945ae42aed7edb1d294df7698b64f1b603179a12f1efd28980a98ba42890c2afcafebf1a970f660ab38ec4120192e3087a438c1860274a65e5928726c4e42bfd07a8adf53878b8aef598229d957591aa63b0e9e3e21a1ad7df441c5292a24f4e6f47e6dd12310a9037da22169cdcb1e46fb20f63afc4133ccb82a4ec7e21af6670e565090ab805d9a93b30648b57e472f24deb499426392b6134b2de005119751aadbdb7535e60042f0f8d549522bacdaa1c55572d267294fd3f2c98230004b05d2f2b2315acf1e4e9d46d71e3811b25690382e88dedf7046af4d4439aaedc1e8bdbed282aeed7d88e451e393bcabb3e8c7e6343aceddaf51dceaaa4041231b5008bdee7e20d551bc2003d54518a240afc80bee8c709256971ce9ae1ed882c2f080ffcc7e9cf0b5a15a678e81ed782fe8ab12df8aac05305bac6864eeb2112012703cc57fa1c7c09845cab94dad1eb3562aeeb75dee6b56fb23f915865f0fa577bae17fbba3d8dab489dc750e20d4bade21b94fa009563993932a5f67cadd5904177a457ede06a0bb3e41f64af699d4b2e0f11368d7c9bd7a490a34b334766df92964dec91fd5cc7e1768319c3b761a16ce91b980ec58ae990b2d2828a0e780a8e84967fa7c03cc052d842cdc645b47f3b000286cc8f28fee0542bd0a715c04816572412a78e07a595f646354de4e8959a80713401b9bae9624f6fd9828d1f36c440417ab6c6e4f7d3ce8f165529983ab31f1bf09eb67f7b19d6f192548cdb5f34accfdfd6a69f081cf9ffa9994cb5824ff1f89bd9915ceb08eb935c7d1181aba7da21f540b7351098fcfb708e91d65d7b091cb0b17dd60f241e87cd8e6635b0eba3b015185d25de049b63a7c32ce752989dac83f03a2af6664f6eb072202100a5a6d10a8ea84d2cc10872acafd6e2989c32baf41e3c4f3d5d55ec352f30517ebd74b96a10b80454e52009898eab5a699900000000000000000000000000000000000000000000000000" - }, - "result": { - "gasUsed": "0x96b2d7", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0xf14f04259ac556d1381965bd9d12c803e7e1ec83656ef48e3d57130963a8fd10", - "transactionPosition": 74 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [ - 0 - ], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000002c2ade", - "to": "0xff00000000000000000000000000000000000004", - "gas": "0x478d47b", - "value": "0x0", - "input": "0x868e10c400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000008318808821a002c2ade19911e811a0459c3f65820be2d62bd4039006ec2601e9b85f158e3facb09f64592cf07d68ade65df8ce7345820a821855a854abe1c7ada3b128358010c99e1da5bea3dd573733e8978edfe3483590780960f552b98a30ad3840c6da73d540213f5723c320b231419709ba5b7381feff81e38de449fdf783e55f3f411c1baa728854228f264cb115325348d007cb5922c59e926caf1a0d78fe1b4ec9841883cfd34d7a6b2eaf574097ca32f079d4a862a12245f8aec0237e66ee5b29da0ab67f6d5da06f5311dc40f205aef2c09826d84283d6c951d802f3a4dbba31b38abf15fa07625c908e2c2797578c1431ee2894e7d954bf134439530ae612ebcf87deb1f354bc3bb96bfa4f4ca53c79112772671891803ee5c6dc2bebf2beea9bed50c947b731c5faf9369b3c42fb5cfe9d0236f4f559a05a7a8a584cf26637e6713ad1a80520fb7f0d3e6f3c70866b6371c17ee1ae76dbbcc809a0c2a70a2e6241ed2b04d9f35f5e9d215e346266321856099c81550a8d7b7e6a82bb31f5002c5545e9e853ae79cbe26d5d9fbdca504de06b48dbec1614875ead88b1aacc0a76a6e1123b91092b53b92d6507d2c4b90b950406e3706e22fd16ba83ef4474f93edf8e908ca824aa875175425b757365e0d94af4d8f054278185210db77b78c66d44ead9ad621b2080b8a96d41aece81e05638984440954d538f31a8d90847550cfc56846a51c83ec9630241066785b926985a90ab0d30f815a1a365adadf5560b911a33336e278dcd77eceef98eaaa2acc96c03806004221de8e1e43c097d2ddd72870cf493361094483976e1cf852362c17559004265230b9f6582e572b3f359a2242948c49b4273efce8cf7e2ae233ab96db76f22b2214c2c2b58edc3faa2fc67e589bad0a2eb05e0830be889f439d27a7c1dab781d301ac88790943ade6fd61679b08f70bfdde55c4f98f8d41a9ff2ad730a8221eacb29d4b26ddcf813bffa913bf5ab88e3f73a9a3b6b8b8e433ecb6ba009384e088519a4422bb250aacf4cc81d39be173eb79b52b2991171310067a240b6507a0b344cecd6896062ed7dc28022567c5236f2ad5e97fbd6c37621fbfe727b11536ee5e6ba31030f252ce8a05224bc69617ba5aa7a5a8561d68c97b7532181c77df9804feb4853fdd3efbff953eb9d8e826ea01b27919b33911bbec2f9041b2aa05c84419df21b1f91515ec4467a3abcb30aab03f04018b351ec6684a1fb67e7f67f356ac375c34f2a181d718318f92b97acd7328907279031a4b93eebc88b4fc70b4e831be82deef3e724b3af5608f1f311723a3a3c7dfa2be97407358469e04304456ee864fc2b212eaa3adff6d89c1991ca2fa1e988eb2769178e935a996ac0c1efa44000328ff4667474f79536499392534a06cd4638cfabbddaab5028f202a3c7364fc01f147902c9bdbc2bfd750498927e9a0769330daada7cb605619ad35a6f016f2405e9978b6df94c5226f1c796328027b9be8f87dfd536ffd4a3cba032084902817ef3b5a014a29d81db98cf0f0ffcc696a4408a40097d0e809237cab607f0ec005572c41cc3a7829a2ecbd9df4280d283f23882fbd6d159e208f0447ef60c9c20ed40115ca2631ebac7a8b5920dfeb64c5354fc1a6a3e489b33e9fe5c7dbf8139d06ed75b648aeaa7995b013265228524981c8f09cc1268bb0a626ab89d3d829fefafddd2a7734787f1e691f862ded29aec3478520af69fda96493b04ba75b38ed974f0ed859bf8703f7b9579c2bc9358994a71481b1450f791996ddd13b9faee4684551059290a96485a537d58087d1e857eb09bf9e2896542631756c5bab31ee5c4a2b4bee767d62945ae42aed7edb1d294df7698b64f1b603179a12f1efd28980a98ba42890c2afcafebf1a970f660ab38ec4120192e3087a438c1860274a65e5928726c4e42bfd07a8adf53878b8aef598229d957591aa63b0e9e3e21a1ad7df441c5292a24f4e6f47e6dd12310a9037da22169cdcb1e46fb20f63afc4133ccb82a4ec7e21af6670e565090ab805d9a93b30648b57e472f24deb499426392b6134b2de005119751aadbdb7535e60042f0f8d549522bacdaa1c55572d267294fd3f2c98230004b05d2f2b2315acf1e4e9d46d71e3811b25690382e88dedf7046af4d4439aaedc1e8bdbed282aeed7d88e451e393bcabb3e8c7e6343aceddaf51dceaaa4041231b5008bdee7e20d551bc2003d54518a240afc80bee8c709256971ce9ae1ed882c2f080ffcc7e9cf0b5a15a678e81ed782fe8ab12df8aac05305bac6864eeb2112012703cc57fa1c7c09845cab94dad1eb3562aeeb75dee6b56fb23f915865f0fa577bae17fbba3d8dab489dc750e20d4bade21b94fa009563993932a5f67cadd5904177a457ede06a0bb3e41f64af699d4b2e0f11368d7c9bd7a490a34b334766df92964dec91fd5cc7e1768319c3b761a16ce91b980ec58ae990b2d2828a0e780a8e84967fa7c03cc052d842cdc645b47f3b000286cc8f28fee0542bd0a715c04816572412a78e07a595f646354de4e8959a80713401b9bae9624f6fd9828d1f36c440417ab6c6e4f7d3ce8f165529983ab31f1bf09eb67f7b19d6f192548cdb5f34accfdfd6a69f081cf9ffa9994cb5824ff1f89bd9915ceb08eb935c7d1181aba7da21f540b7351098fcfb708e91d65d7b091cb0b17dd60f241e87cd8e6635b0eba3b015185d25de049b63a7c32ce752989dac83f03a2af6664f6eb072202100a5a6d10a8ea84d2cc10872acafd6e2989c32baf41e3c4f3d5d55ec352f30517ebd74b96a10b80454e52009898eab5a6999d82a5829000182e20381e80220c128d6724fd7c641a959983b978d9fa693eb45ebfa44d0befd58193ac5f96c55d82a5828000181e2039220201026e6f2fadbf4f6d050d669a0d7171f9b602d906e9c51a6a311c86b5273dd2b000000000000000000000000000000" - }, - "result": { - "gasUsed": "0x30520d4", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0xf14f04259ac556d1381965bd9d12c803e7e1ec83656ef48e3d57130963a8fd10", - "transactionPosition": 74 - }, - { - "type": "call", - "subtraces": 21, - "traceAddress": [], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000002cffc4", - "to": "0xff00000000000000000000000000000000000005", - "gas": "0x530c26f6", - "value": "0x0", - "input": "0x868e10c400000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000007128188828bd82a5828000181e203922020638379e47a3916bae849a7d2eb688c4c79f2a16dc32813f0fbc2799237183c251b0000000800000000f55501d886fa8ef922a4be477ef30d932184be05323f2244008a944278346d41584367354149677a69414f5733616d6d6f2f2f342b6f4e77425546714c787a57712b5365565143506f79524842494f4c46451a00381e4f1a004fa10f40480016de9bafa1a688405842018c87b3a0c61f501ebe01004d2c3989e74e8d3409e66ea7cf0485433f61ea84b92cab8863c916181ce0de0547b883855699afb28d70d8ed96c4fa49608949373100828bd82a5828000181e203922020a646954f2e700a51f149491da320e5065d7266a505b4013f3fe2b95a410487341b0000000800000000f55501d886fa8ef922a4be477ef30d932184be05323f2244008a944278346d415843673541496777613542697561305a34474d65625947757336787a484e5a6e47726c744b456d7535744b3536676e366e551a00381e4f1a004fa10f40480016de9bafa1a6884058420144ad58edfe5d5d30d7b9789535d6c44c6f0014a05b3cb8e1141c1bf9a506d34c68d42e1f2b90ad6b13e231d84358535d36eb329f4e126dc5667eeebe8f87d40701828bd82a5828000181e20392202019553461c49b73c9ac6661d0bcbad1abd0b5747cb3b2c2cdbaaf2f0ee22e4f381b0000000800000000f55501d886fa8ef922a4be477ef30d932184be05323f2244008a944278346d41584367354149676d43454d37766c6163766b78657351395a6e785554564433696473695259644c613641636b4d6266514b381a00381e4f1a004fa10f40480016de9bafa1a6884058420111c340ca79b4ec152f975556bdca273716cd512588b630a12ac48109c4719e00406e504a97193fd228a1fa0492381acf41c3929fdc8f5a2d0cec1eef7216c79d01828bd82a5828000181e20392202078ef0796f6fb842665ab9c1896535d700bea5b1f70189272d8bea7a61b36f43b1b0000000800000000f55501d886fa8ef922a4be477ef30d932184be05323f2244008a944278346d415843673541496749617553335659766c5578394e7575384c42776d507a55326f6d6e6a4c724e2b534474686439535656776b1a00381e4f1a004fa10f40480016de9bafa1a68840584201634845b6b07346e86e066a38f58506581f2c0edc279607b5d55f0245a2b0713046385efd92bd31fc2553d484d6067a699f1b9997dac16cd7f6467904f029f25501828bd82a5828000181e203922020997081e8865104540ec72c6f436a983e9633c5e5098d7ab01edf1a381c9495201b0000000800000000f55501d886fa8ef922a4be477ef30d932184be05323f2244008a944278346d415843673541496742437463616277537077566247476239436d3068686a2b5075756e485471597a52694d41433676563879771a00381e4f1a004fa10f40480016de9bafa1a688405842013f72930bc2558ba9617bd708bd4abf74e197ba4483a97ced4bd8029bc0c5be26696583bf0c963021d7009827f800494e13e8cb5c0e17a44955cfe2739d8ed5c301828bd82a5828000181e20392202058a6d1ab9902fade4bf717e7331aee4b46fc210ba1b972dcf3ea2048a541463e1b0000000800000000f55501d886fa8ef922a4be477ef30d932184be05323f2244008a944278346d41584367354149674a4b56744f37483938726c477248634d5738466a5764644d6d532f54356a34334b4c4944563575713037491a00381e501a004fa11040480016de9ad5b2b21640584201df7baeaa70111f1bf9cabbebb63a42ed93369fdbc655810c47539e2109e457984c9dfb31ebbb414bf6bde203b8b16895df94722e92d76f49d075deb1528e4e1d01828bd82a5828000181e2039220204792c6cd5e8d978b7093e5d9a26a72c45fe7c815fad2bced0745522d5b2004261b0000000800000000f55501d886fa8ef922a4be477ef30d932184be05323f2244008a944278346d41584367354149677567622f52586644466330763238562b304e4d684c5263723376596f70682f455246325435742f354970731a00381e501a004fa11040480016de9ad5b2b2164058420119c8ffdccfa265a45e1f93b09d158cb6f6d9313a9acb81dbd40ac6006d25003e29d4bcc093e3cc3de10fb0a3f4221c8e98a8785e3c63fc8843786af1f975cd3001828bd82a5828000181e203922020ffcfabfe3a003640cba142966a19f85d1028b10edb5b0d647273c55857af431f1b0000000800000000f55501d886fa8ef922a4be477ef30d932184be05323f2244008a944278346d4158436735414967764c754c47416773594e6a3972372f734b67656433356f6d6d31545141614830716d6b6d307a54796774411a00381e501a004fa11040480016de9ad5b2b21640584201b639096d1da0d389c6fd4d4104be639dbb22c591fe7bdebb2907aee734a9b73e3cb9f9262905b81f92997b92e23616cf3f635eef8405e4d794f80f4fa431c2e7010000000000000000000000000000" - }, - "result": { - "gasUsed": "0x3374f7cd", - "output": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000002d82881a045b57631a045b57641a045b57651a045b57661a045b57671a045b57681a045b57691a045b576a42140100000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0xc72dce371722de2e44a8b2f9b33551bd2dd5ae8595caafe3d34b9f15b01734ad", - "transactionPosition": 75 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [ - 0 - ], - "action": { - "callType": "call", - "from": "0xff00000000000000000000000000000000000005", - "to": "0xff00000000000000000000000000000000108a0a", - "gas": "0x52f45b15", - "value": "0x0", - "input": "0x868e10c40000000000000000000000000000000000000000000000000000000014c1cb970000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000064500c4ffb3010000000000000000000000000000000000000000000000000000" - }, - "result": { - "gasUsed": "0x133019", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000001f500000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0xc72dce371722de2e44a8b2f9b33551bd2dd5ae8595caafe3d34b9f15b01734ad", - "transactionPosition": 75 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [ - 1 - ], - "action": { - "callType": "call", - "from": "0xff00000000000000000000000000000000000005", - "to": "0xff00000000000000000000000000000000000002", - "gas": "0x52e08644", - "value": "0x0", - "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "result": { - "gasUsed": "0xfabb0", - "output": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000418282581a000286387ff107ef952f3612eb2e2a6606418e51c0f2cceeda5f57011731c2a04034dd33df402838a815a0b69ec1184b8c104a0001c0dfc71cd077c4b900000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0xc72dce371722de2e44a8b2f9b33551bd2dd5ae8595caafe3d34b9f15b01734ad", - "transactionPosition": 75 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [ - 2 - ], - "action": { - "callType": "call", - "from": "0xff00000000000000000000000000000000000005", - "to": "0xff00000000000000000000000000000000000004", - "gas": "0x52cfba64", - "value": "0x0", - "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000009000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "result": { - "gasUsed": "0x105fb2", - "output": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000578449007d2903b8000000004a000190f76c1adff180004c00907e2dd41a18e7c7a7f2bd82581a0001916cb98a2c3dfb67a389a588fb0e593f762dd6c9195851235601fba7e16707ee65746d4671e80aa2bb15bc7d6ebe3b000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0xc72dce371722de2e44a8b2f9b33551bd2dd5ae8595caafe3d34b9f15b01734ad", - "transactionPosition": 75 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [ - 3 - ], - "action": { - "callType": "staticcall", - "from": "0xff00000000000000000000000000000000000005", - "to": "0xff0000000000000000000000000000000022cdaa", - "gas": "0x52bcb4e2", - "value": "0x0", - "input": "0x868e10c4000000000000000000000000000000000000000000000000000000009d8b06780000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000e38258418c87b3a0c61f501ebe01004d2c3989e74e8d3409e66ea7cf0485433f61ea84b92cab8863c916181ce0de0547b883855699afb28d70d8ed96c4fa49608949373100589d8bd82a5828000181e203922020638379e47a3916bae849a7d2eb688c4c79f2a16dc32813f0fbc2799237183c251b0000000800000000f55501d886fa8ef922a4be477ef30d932184be05323f2244008a944278346d41584367354149677a69414f5733616d6d6f2f2f342b6f4e77425546714c787a57712b5365565143506f79524842494f4c46451a00381e4f1a004fa10f40480016de9bafa1a688400000000000000000000000000000000000000000000000000000000000" - }, - "result": { - "gasUsed": "0x2e9807", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000001f500000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0xc72dce371722de2e44a8b2f9b33551bd2dd5ae8595caafe3d34b9f15b01734ad", - "transactionPosition": 75 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [ - 4 - ], - "action": { - "callType": "staticcall", - "from": "0xff00000000000000000000000000000000000005", - "to": "0xff0000000000000000000000000000000022cdaa", - "gas": "0x528ae02a", - "value": "0x0", - "input": "0x868e10c4000000000000000000000000000000000000000000000000000000009d8b06780000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000e382584144ad58edfe5d5d30d7b9789535d6c44c6f0014a05b3cb8e1141c1bf9a506d34c68d42e1f2b90ad6b13e231d84358535d36eb329f4e126dc5667eeebe8f87d40701589d8bd82a5828000181e203922020a646954f2e700a51f149491da320e5065d7266a505b4013f3fe2b95a410487341b0000000800000000f55501d886fa8ef922a4be477ef30d932184be05323f2244008a944278346d415843673541496777613542697561305a34474d65625947757336787a484e5a6e47726c744b456d7535744b3536676e366e551a00381e4f1a004fa10f40480016de9bafa1a688400000000000000000000000000000000000000000000000000000000000" - }, - "result": { - "gasUsed": "0x26f6e7", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000001f500000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0xc72dce371722de2e44a8b2f9b33551bd2dd5ae8595caafe3d34b9f15b01734ad", - "transactionPosition": 75 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [ - 5 - ], - "action": { - "callType": "staticcall", - "from": "0xff00000000000000000000000000000000000005", - "to": "0xff0000000000000000000000000000000022cdaa", - "gas": "0x526100b3", - "value": "0x0", - "input": "0x868e10c4000000000000000000000000000000000000000000000000000000009d8b06780000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000e382584111c340ca79b4ec152f975556bdca273716cd512588b630a12ac48109c4719e00406e504a97193fd228a1fa0492381acf41c3929fdc8f5a2d0cec1eef7216c79d01589d8bd82a5828000181e20392202019553461c49b73c9ac6661d0bcbad1abd0b5747cb3b2c2cdbaaf2f0ee22e4f381b0000000800000000f55501d886fa8ef922a4be477ef30d932184be05323f2244008a944278346d41584367354149676d43454d37766c6163766b78657351395a6e785554564433696473695259644c613641636b4d6266514b381a00381e4f1a004fa10f40480016de9bafa1a688400000000000000000000000000000000000000000000000000000000000" - }, - "result": { - "gasUsed": "0x26f6e7", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000001f500000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0xc72dce371722de2e44a8b2f9b33551bd2dd5ae8595caafe3d34b9f15b01734ad", - "transactionPosition": 75 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [ - 6 - ], - "action": { - "callType": "staticcall", - "from": "0xff00000000000000000000000000000000000005", - "to": "0xff0000000000000000000000000000000022cdaa", - "gas": "0x5237213c", - "value": "0x0", - "input": "0x868e10c4000000000000000000000000000000000000000000000000000000009d8b06780000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000e3825841634845b6b07346e86e066a38f58506581f2c0edc279607b5d55f0245a2b0713046385efd92bd31fc2553d484d6067a699f1b9997dac16cd7f6467904f029f25501589d8bd82a5828000181e20392202078ef0796f6fb842665ab9c1896535d700bea5b1f70189272d8bea7a61b36f43b1b0000000800000000f55501d886fa8ef922a4be477ef30d932184be05323f2244008a944278346d415843673541496749617553335659766c5578394e7575384c42776d507a55326f6d6e6a4c724e2b534474686439535656776b1a00381e4f1a004fa10f40480016de9bafa1a688400000000000000000000000000000000000000000000000000000000000" - }, - "result": { - "gasUsed": "0x26f6e7", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000001f500000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0xc72dce371722de2e44a8b2f9b33551bd2dd5ae8595caafe3d34b9f15b01734ad", - "transactionPosition": 75 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [ - 7 - ], - "action": { - "callType": "staticcall", - "from": "0xff00000000000000000000000000000000000005", - "to": "0xff0000000000000000000000000000000022cdaa", - "gas": "0x520d41c5", - "value": "0x0", - "input": "0x868e10c4000000000000000000000000000000000000000000000000000000009d8b06780000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000e38258413f72930bc2558ba9617bd708bd4abf74e197ba4483a97ced4bd8029bc0c5be26696583bf0c963021d7009827f800494e13e8cb5c0e17a44955cfe2739d8ed5c301589d8bd82a5828000181e203922020997081e8865104540ec72c6f436a983e9633c5e5098d7ab01edf1a381c9495201b0000000800000000f55501d886fa8ef922a4be477ef30d932184be05323f2244008a944278346d415843673541496742437463616277537077566247476239436d3068686a2b5075756e485471597a52694d41433676563879771a00381e4f1a004fa10f40480016de9bafa1a688400000000000000000000000000000000000000000000000000000000000" - }, - "result": { - "gasUsed": "0x26f6e7", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000001f500000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0xc72dce371722de2e44a8b2f9b33551bd2dd5ae8595caafe3d34b9f15b01734ad", - "transactionPosition": 75 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [ - 8 - ], - "action": { - "callType": "staticcall", - "from": "0xff00000000000000000000000000000000000005", - "to": "0xff0000000000000000000000000000000022cdaa", - "gas": "0x51e3624e", - "value": "0x0", - "input": "0x868e10c4000000000000000000000000000000000000000000000000000000009d8b06780000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000e3825841df7baeaa70111f1bf9cabbebb63a42ed93369fdbc655810c47539e2109e457984c9dfb31ebbb414bf6bde203b8b16895df94722e92d76f49d075deb1528e4e1d01589d8bd82a5828000181e20392202058a6d1ab9902fade4bf717e7331aee4b46fc210ba1b972dcf3ea2048a541463e1b0000000800000000f55501d886fa8ef922a4be477ef30d932184be05323f2244008a944278346d41584367354149674a4b56744f37483938726c477248634d5738466a5764644d6d532f54356a34334b4c4944563575713037491a00381e501a004fa11040480016de9ad5b2b216400000000000000000000000000000000000000000000000000000000000" - }, - "result": { - "gasUsed": "0x26f6e7", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000001f500000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0xc72dce371722de2e44a8b2f9b33551bd2dd5ae8595caafe3d34b9f15b01734ad", - "transactionPosition": 75 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [ - 9 - ], - "action": { - "callType": "staticcall", - "from": "0xff00000000000000000000000000000000000005", - "to": "0xff0000000000000000000000000000000022cdaa", - "gas": "0x51b982d6", - "value": "0x0", - "input": "0x868e10c4000000000000000000000000000000000000000000000000000000009d8b06780000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000e382584119c8ffdccfa265a45e1f93b09d158cb6f6d9313a9acb81dbd40ac6006d25003e29d4bcc093e3cc3de10fb0a3f4221c8e98a8785e3c63fc8843786af1f975cd3001589d8bd82a5828000181e2039220204792c6cd5e8d978b7093e5d9a26a72c45fe7c815fad2bced0745522d5b2004261b0000000800000000f55501d886fa8ef922a4be477ef30d932184be05323f2244008a944278346d41584367354149677567622f52586644466330763238562b304e4d684c5263723376596f70682f455246325435742f354970731a00381e501a004fa11040480016de9ad5b2b216400000000000000000000000000000000000000000000000000000000000" - }, - "result": { - "gasUsed": "0x26f6e7", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000001f500000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0xc72dce371722de2e44a8b2f9b33551bd2dd5ae8595caafe3d34b9f15b01734ad", - "transactionPosition": 75 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [ - 10 - ], - "action": { - "callType": "staticcall", - "from": "0xff00000000000000000000000000000000000005", - "to": "0xff0000000000000000000000000000000022cdaa", - "gas": "0x518fa35f", - "value": "0x0", - "input": "0x868e10c4000000000000000000000000000000000000000000000000000000009d8b06780000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000e3825841b639096d1da0d389c6fd4d4104be639dbb22c591fe7bdebb2907aee734a9b73e3cb9f9262905b81f92997b92e23616cf3f635eef8405e4d794f80f4fa431c2e701589d8bd82a5828000181e203922020ffcfabfe3a003640cba142966a19f85d1028b10edb5b0d647273c55857af431f1b0000000800000000f55501d886fa8ef922a4be477ef30d932184be05323f2244008a944278346d4158436735414967764c754c47416773594e6a3972372f734b67656433356f6d6d31545141614830716d6b6d307a54796774411a00381e501a004fa11040480016de9ad5b2b216400000000000000000000000000000000000000000000000000000000000" - }, - "result": { - "gasUsed": "0x26f6e7", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000001f500000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0xc72dce371722de2e44a8b2f9b33551bd2dd5ae8595caafe3d34b9f15b01734ad", - "transactionPosition": 75 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [ - 11 - ], - "action": { - "callType": "call", - "from": "0xff00000000000000000000000000000000000005", - "to": "0xff00000000000000000000000000000000000007", - "gas": "0x508a6662", - "value": "0x0", - "input": "0x868e10c400000000000000000000000000000000000000000000000000000000c26ddbd50000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000064500aa9b8b010000000000000000000000000000000000000000000000000000" - }, - "result": { - "gasUsed": "0x2edc57", - "output": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000104f002b5ff708418d6c8000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0xc72dce371722de2e44a8b2f9b33551bd2dd5ae8595caafe3d34b9f15b01734ad", - "transactionPosition": 75 - }, - { - "type": "call", - "subtraces": 1, - "traceAddress": [ - 12 - ], - "action": { - "callType": "call", - "from": "0xff00000000000000000000000000000000000005", - "to": "0xff00000000000000000000000000000000000007", - "gas": "0x4a86bae6", - "value": "0x0", - "input": "0x868e10c400000000000000000000000000000000000000000000000000000000d7d4deed00000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000026f844500aa9b8b014200064e0003782dace9d9000000000000005902538288861a00108a0ad82a5828000181e203922020638379e47a3916bae849a7d2eb688c4c79f2a16dc32813f0fbc2799237183c251b00000008000000001a001782c01a001b77401a00381e4f861a00108a0ad82a5828000181e203922020a646954f2e700a51f149491da320e5065d7266a505b4013f3fe2b95a410487341b00000008000000001a001782c01a001b77401a00381e4f861a00108a0ad82a5828000181e20392202019553461c49b73c9ac6661d0bcbad1abd0b5747cb3b2c2cdbaaf2f0ee22e4f381b00000008000000001a001782c01a001b77401a00381e4f861a00108a0ad82a5828000181e20392202078ef0796f6fb842665ab9c1896535d700bea5b1f70189272d8bea7a61b36f43b1b00000008000000001a001782c01a001b77401a00381e4f861a00108a0ad82a5828000181e203922020997081e8865104540ec72c6f436a983e9633c5e5098d7ab01edf1a381c9495201b00000008000000001a001782c01a001b77401a00381e4f861a00108a0ad82a5828000181e20392202058a6d1ab9902fade4bf717e7331aee4b46fc210ba1b972dcf3ea2048a541463e1b00000008000000001a001782c01a001b77401a00381e50861a00108a0ad82a5828000181e2039220204792c6cd5e8d978b7093e5d9a26a72c45fe7c815fad2bced0745522d5b2004261b00000008000000001a001782c01a001b77401a00381e50861a00108a0ad82a5828000181e203922020ffcfabfe3a003640cba142966a19f85d1028b10edb5b0d647273c55857af431f1b00000008000000001a001782c01a001b77401a00381e50800000000000000000000000000000000000" - }, - "result": { - "gasUsed": "0x397cbb2", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000067844f002b5c7eda94a39380000000000000500006a8d4d4487a428de5110000000000520002f0503706f730bd424045568000000000583083820880820080881a034d18b51a034d18b61a034d18b71a034d18b81a034d18b91a034d18ba1a034d18bb1a034d18bc00000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0xc72dce371722de2e44a8b2f9b33551bd2dd5ae8595caafe3d34b9f15b01734ad", - "transactionPosition": 75 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [ - 12, - 0 - ], - "action": { - "callType": "call", - "from": "0xff00000000000000000000000000000000000007", - "to": "0xff00000000000000000000000000000000000006", - "gas": "0x475d9536", - "value": "0x0", - "input": "0x868e10c400000000000000000000000000000000000000000000000000000000de180de3000000000000000000000000000000000000000000000000000000000000005100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000277821a85223bdf59026e861a0022cdaa06054e0003782dace9d9000000000000005902538288861a00108a0ad82a5828000181e203922020638379e47a3916bae849a7d2eb688c4c79f2a16dc32813f0fbc2799237183c251b00000008000000001a001782c01a001b77401a00381e4f861a00108a0ad82a5828000181e203922020a646954f2e700a51f149491da320e5065d7266a505b4013f3fe2b95a410487341b00000008000000001a001782c01a001b77401a00381e4f861a00108a0ad82a5828000181e20392202019553461c49b73c9ac6661d0bcbad1abd0b5747cb3b2c2cdbaaf2f0ee22e4f381b00000008000000001a001782c01a001b77401a00381e4f861a00108a0ad82a5828000181e20392202078ef0796f6fb842665ab9c1896535d700bea5b1f70189272d8bea7a61b36f43b1b00000008000000001a001782c01a001b77401a00381e4f861a00108a0ad82a5828000181e203922020997081e8865104540ec72c6f436a983e9633c5e5098d7ab01edf1a381c9495201b00000008000000001a001782c01a001b77401a00381e4f861a00108a0ad82a5828000181e20392202058a6d1ab9902fade4bf717e7331aee4b46fc210ba1b972dcf3ea2048a541463e1b00000008000000001a001782c01a001b77401a00381e50861a00108a0ad82a5828000181e2039220204792c6cd5e8d978b7093e5d9a26a72c45fe7c815fad2bced0745522d5b2004261b00000008000000001a001782c01a001b77401a00381e50861a00108a0ad82a5828000181e203922020ffcfabfe3a003640cba142966a19f85d1028b10edb5b0d647273c55857af431f1b00000008000000001a001782c01a001b77401a00381e508040000000000000000000" - }, - "result": { - "gasUsed": "0xa1c4027", - "output": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000003083820880820080881a034d18b51a034d18b61a034d18b71a034d18b81a034d18b91a034d18ba1a034d18bb1a034d18bc00000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0xc72dce371722de2e44a8b2f9b33551bd2dd5ae8595caafe3d34b9f15b01734ad", - "transactionPosition": 75 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [ - 13 - ], - "action": { - "callType": "call", - "from": "0xff00000000000000000000000000000000000005", - "to": "0xff0000000000000000000000000000000022cdaa", - "gas": "0x10787f37", - "value": "0x0", - "input": "0x868e10c400000000000000000000000000000000000000000000000000000000f98c996600000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000009582588d8bd82a5828000181e203922020638379e47a3916bae849a7d2eb688c4c79f2a16dc32813f0fbc2799237183c251b0000000800000000f54500aa9b8b0144008a944278346d41584367354149677a69414f5733616d6d6f2f2f342b6f4e77425546714c787a57712b5365565143506f79524842494f4c46451a00381e4f1a004fa10f40480016de9bafa1a688401a045b57630000000000000000000000" - }, - "result": { - "gasUsed": "0x98587", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0xc72dce371722de2e44a8b2f9b33551bd2dd5ae8595caafe3d34b9f15b01734ad", - "transactionPosition": 75 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [ - 14 - ], - "action": { - "callType": "call", - "from": "0xff00000000000000000000000000000000000005", - "to": "0xff0000000000000000000000000000000022cdaa", - "gas": "0x106e1d88", - "value": "0x0", - "input": "0x868e10c400000000000000000000000000000000000000000000000000000000f98c996600000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000009582588d8bd82a5828000181e203922020a646954f2e700a51f149491da320e5065d7266a505b4013f3fe2b95a410487341b0000000800000000f54500aa9b8b0144008a944278346d415843673541496777613542697561305a34474d65625947757336787a484e5a6e47726c744b456d7535744b3536676e366e551a00381e4f1a004fa10f40480016de9bafa1a688401a045b57640000000000000000000000" - }, - "result": { - "gasUsed": "0x98587", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0xc72dce371722de2e44a8b2f9b33551bd2dd5ae8595caafe3d34b9f15b01734ad", - "transactionPosition": 75 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [ - 15 - ], - "action": { - "callType": "call", - "from": "0xff00000000000000000000000000000000000005", - "to": "0xff0000000000000000000000000000000022cdaa", - "gas": "0x1063bbd9", - "value": "0x0", - "input": "0x868e10c400000000000000000000000000000000000000000000000000000000f98c996600000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000009582588d8bd82a5828000181e20392202019553461c49b73c9ac6661d0bcbad1abd0b5747cb3b2c2cdbaaf2f0ee22e4f381b0000000800000000f54500aa9b8b0144008a944278346d41584367354149676d43454d37766c6163766b78657351395a6e785554564433696473695259644c613641636b4d6266514b381a00381e4f1a004fa10f40480016de9bafa1a688401a045b57650000000000000000000000" - }, - "result": { - "gasUsed": "0x98587", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0xc72dce371722de2e44a8b2f9b33551bd2dd5ae8595caafe3d34b9f15b01734ad", - "transactionPosition": 75 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [ - 16 - ], - "action": { - "callType": "call", - "from": "0xff00000000000000000000000000000000000005", - "to": "0xff0000000000000000000000000000000022cdaa", - "gas": "0x10595a2a", - "value": "0x0", - "input": "0x868e10c400000000000000000000000000000000000000000000000000000000f98c996600000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000009582588d8bd82a5828000181e20392202078ef0796f6fb842665ab9c1896535d700bea5b1f70189272d8bea7a61b36f43b1b0000000800000000f54500aa9b8b0144008a944278346d415843673541496749617553335659766c5578394e7575384c42776d507a55326f6d6e6a4c724e2b534474686439535656776b1a00381e4f1a004fa10f40480016de9bafa1a688401a045b57660000000000000000000000" - }, - "result": { - "gasUsed": "0x98587", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0xc72dce371722de2e44a8b2f9b33551bd2dd5ae8595caafe3d34b9f15b01734ad", - "transactionPosition": 75 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [ - 17 - ], - "action": { - "callType": "call", - "from": "0xff00000000000000000000000000000000000005", - "to": "0xff0000000000000000000000000000000022cdaa", - "gas": "0x104ef87c", - "value": "0x0", - "input": "0x868e10c400000000000000000000000000000000000000000000000000000000f98c996600000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000009582588d8bd82a5828000181e203922020997081e8865104540ec72c6f436a983e9633c5e5098d7ab01edf1a381c9495201b0000000800000000f54500aa9b8b0144008a944278346d415843673541496742437463616277537077566247476239436d3068686a2b5075756e485471597a52694d41433676563879771a00381e4f1a004fa10f40480016de9bafa1a688401a045b57670000000000000000000000" - }, - "result": { - "gasUsed": "0x98587", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0xc72dce371722de2e44a8b2f9b33551bd2dd5ae8595caafe3d34b9f15b01734ad", - "transactionPosition": 75 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [ - 18 - ], - "action": { - "callType": "call", - "from": "0xff00000000000000000000000000000000000005", - "to": "0xff0000000000000000000000000000000022cdaa", - "gas": "0x104496cd", - "value": "0x0", - "input": "0x868e10c400000000000000000000000000000000000000000000000000000000f98c996600000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000009582588d8bd82a5828000181e20392202058a6d1ab9902fade4bf717e7331aee4b46fc210ba1b972dcf3ea2048a541463e1b0000000800000000f54500aa9b8b0144008a944278346d41584367354149674a4b56744f37483938726c477248634d5738466a5764644d6d532f54356a34334b4c4944563575713037491a00381e501a004fa11040480016de9ad5b2b216401a045b57680000000000000000000000" - }, - "result": { - "gasUsed": "0x98587", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0xc72dce371722de2e44a8b2f9b33551bd2dd5ae8595caafe3d34b9f15b01734ad", - "transactionPosition": 75 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [ - 19 - ], - "action": { - "callType": "call", - "from": "0xff00000000000000000000000000000000000005", - "to": "0xff0000000000000000000000000000000022cdaa", - "gas": "0x103a351e", - "value": "0x0", - "input": "0x868e10c400000000000000000000000000000000000000000000000000000000f98c996600000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000009582588d8bd82a5828000181e2039220204792c6cd5e8d978b7093e5d9a26a72c45fe7c815fad2bced0745522d5b2004261b0000000800000000f54500aa9b8b0144008a944278346d41584367354149677567622f52586644466330763238562b304e4d684c5263723376596f70682f455246325435742f354970731a00381e501a004fa11040480016de9ad5b2b216401a045b57690000000000000000000000" - }, - "result": { - "gasUsed": "0x98587", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0xc72dce371722de2e44a8b2f9b33551bd2dd5ae8595caafe3d34b9f15b01734ad", - "transactionPosition": 75 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [ - 20 - ], - "action": { - "callType": "call", - "from": "0xff00000000000000000000000000000000000005", - "to": "0xff0000000000000000000000000000000022cdaa", - "gas": "0x102fd36f", - "value": "0x0", - "input": "0x868e10c400000000000000000000000000000000000000000000000000000000f98c996600000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000009582588d8bd82a5828000181e203922020ffcfabfe3a003640cba142966a19f85d1028b10edb5b0d647273c55857af431f1b0000000800000000f54500aa9b8b0144008a944278346d4158436735414967764c754c47416773594e6a3972372f734b67656433356f6d6d31545141614830716d6b6d307a54796774411a00381e501a004fa11040480016de9ad5b2b216401a045b576a0000000000000000000000" - }, - "result": { - "gasUsed": "0x98587", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0xc72dce371722de2e44a8b2f9b33551bd2dd5ae8595caafe3d34b9f15b01734ad", - "transactionPosition": 75 - }, - { - "type": "call", - "subtraces": 3, - "traceAddress": [], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000001c17d8", - "to": "0xff000000000000000000000000000000001c17eb", - "gas": "0x4af5d01", - "value": "0x0", - "input": "0x868e10c4000000000000000000000000000000000000000000000000000000000000001c000000000000000000000000000000000000000000000000000000000000005100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000072818187081a000122f0d82a5829000182e20381e802203050bd59cec207bc1408562d631cd13b7a00056b34e3dc612288cec5950573111a0037e0c0811a045b35581a00412a9ad82a5828000181e203922020e296585450fb35885860e0c4ac16cc5a7b1013b0f76d736b884763912a3e7e080000000000000000000000000000" - }, - "result": { - "gasUsed": "0x362e335", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0x78132bb8f711bfc5da6a26980693e7256b6ba22e5d829b3d2e8614ba26b0a953", - "transactionPosition": 76 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [ - 0 - ], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000001c17eb", - "to": "0xff00000000000000000000000000000000000002", - "gas": "0x4a3f6b1", - "value": "0x0", - "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "result": { - "gasUsed": "0xfabb0", - "output": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000418282581a000286387ff107ef952f3612eb2e2a6606418e51c0f2cceeda5f57011731c2a04034dd33df402838a815a0b69ec1184b8c104a0001c0dfc71cd077c4b900000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0x78132bb8f711bfc5da6a26980693e7256b6ba22e5d829b3d2e8614ba26b0a953", - "transactionPosition": 76 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [ - 1 - ], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000001c17eb", - "to": "0xff00000000000000000000000000000000000004", - "gas": "0x4933169", - "value": "0x0", - "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000009000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "result": { - "gasUsed": "0x105fb2", - "output": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000578449007d2903b8000000004a000190f76c1adff180004c00907e2dd41a18e7c7a7f2bd82581a0001916cb98a2c3dfb67a389a588fb0e593f762dd6c9195851235601fba7e16707ee65746d4671e80aa2bb15bc7d6ebe3b000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0x78132bb8f711bfc5da6a26980693e7256b6ba22e5d829b3d2e8614ba26b0a953", - "transactionPosition": 76 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [ - 2 - ], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000001c17eb", - "to": "0xff00000000000000000000000000000000000005", - "gas": "0x4811bf8", - "value": "0x0", - "input": "0x868e10c4000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000f818183081a00412a9a811a045b35580000000000000000000000000000000000" - }, - "result": { - "gasUsed": "0x478528", - "output": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000002e8181d82a5828000181e203922020e296585450fb35885860e0c4ac16cc5a7b1013b0f76d736b884763912a3e7e08000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0x78132bb8f711bfc5da6a26980693e7256b6ba22e5d829b3d2e8614ba26b0a953", - "transactionPosition": 76 - }, - { - "type": "call", - "subtraces": 1, - "traceAddress": [], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000002d0815", - "to": "0xff000000000000000000000000000000002d082d", - "gas": "0x512a1bc", - "value": "0x0", - "input": "0x868e10c4000000000000000000000000000000000000000000000000000000000000000700000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000078782194e995907808c80a1a7f1aef23e9c5a72f013f261279d9c83df068122083bfc73c928562b99d7e142e8323c3b1ba398efc3fddd17d686367ba915da2e8f05f5605d58f2957927a51de60a07ab0dbd24ac1e8a52b6af11b95df831d14dbac25d750fd4a45dca0837df29befcba63b8178ea8f02340b6d7d6bdb058bfc565ded5260ada6394f7c68c9a4d8494e84eeac2866efd78a5ab8995d7fa578bdc9df4b54ede4212535f7f890a681a729126bc2012fbb57b1cb3b59a93bec4617ee15ad138e9985b14b4955cd39414d99e873f5f4647c36d32362ee8d01c892a0117abed4d76e5f4f8fffbef81f01b535662b42ce3ccf5b415a2b8a671847288a295efc0f210cd27009e8084a633405e7f458cf404909035e07384d4f599bc4d7a2de45c9630b2b12c33072bff14f9e8dc7cbefc0f2573c2227e89711e63ae803944745a28949dc0ac962d4b8faccb5730b1a5e95e59c269dc1fb1a45fe8186d56382cfcc8c2cb70e93f01a16f059d08f5cde17a7fc62454ec77182142a08e69d2b2b4ff48333542077db708bd647e0ac8f4d1db47b183c91f6084957b28fb75778288f8b83975bc630c1ee44dcd9a9b95c3e440aac0bfe88683986dcca45b0e4c16286c3a2aeb9f7e1d43b914cd0b3f629ee426feff417c321ddb9b798070e040a269c9e22b1e1a81e20931d105241068cf3ae4792bda0f15593e4482dfeeae4e2f0386f08eb5cf7f1bbec416fa4378dfd75924a56d04610790aeb39aaaa8c2840f7c52fcb8f89a8b2d7cc4336ecac6d9e40223d45b31f2926dd0e45a1bfb6f0b6cfb5f4d0f0d8f9255aa0021ea31b75191ea0159be650d495e4dbe608db1ecfba2da7de60a321e5f7da691ad4db7db3b5c7885ad51cea72d38ad5c0eb37188c86cbe01c938896c0b6893c8698a5f7f1eb5cea5cdbcd39f718098086ea5be03d25c5d9ab5a30d96b78308b14a7ca1b098b8045c4d90a78206fc18fe6e1cf8c4cca2cda8dcd6b345211e4d8841a90978a7a21c58474c06aa5e568adaaa9fa880ac84956894d2e976d459bed01056a00e69bfccfe9d47f65fdef85720468801603ba01a63865c99acba06990df8d8ada941b6ebedc7484a9a574a18b16cea4683a8bfd6a90e1d845fa4ca568f3e4ea10818a8b551ee7defa34f7d95a71ed3bd40ceb688ec6c00cc207ce8305060b96a5acfb86c38a514bc3223a21e36a2212bf47bef2c2e6b3a93ad463501f46a336efed31b3d9e9998dbb10362e86135838c62a8fbb0489c18f72110dd2fc63428d2dd7e4351b2e075ffc89d9f99a1c2e049c1b5c7ddcebf1972cc4208122a1c981f44bd5c4810a2233922ceadebff26e745d4dba5cd698659399f6eb4b80e14b0f5b2d3604c971c4d6d5a2a3aad696a5660ceb110379fad11e21bcbcf82cc7c9de0ea2afbc0a90c4e22b1562cb1f436a758f2550d8ec0f06ea21e2a230e94e2b1eb5f3dba6341a01dcb08c9c9e19a68e1302b6ec8d9453214ec9340261329fc6389bbe2ee256e72c6ee1440ec2cd7d6e9a0ccaa1676826978e9763fdb9883efd5856573bffda76b38e20a84a48ef7aa8f17163962498dad8493ee48f8f0aca45098fc026df4b3dc20231bb647d894380c97bdedcd652bce638f65cb1b82c672e1aba05a9220b3540cbfed4bc74626309f44fd91d4d666349fd06ac0af5eaa45e37913a9ac44edd81d852c06f2a4fc567dc4c88164be2e004c7c4adc45223a4f6c0e34056c15393ef77c4a6f7f8ebd40774402335828324485c93f37cc01627a3234c1544fbda48a4d48be691d60055a3dbc38890cc0a5c9c89b4487f9865c0279f8898b77f66ee7c0b5f60b7f93cff14cffbd5465f61130bcee8107b17b6f761697f9008048dee7d0d3ae8519d43e3769b6c954fbbde729cd7c2fb86f97a78a947df5709f793c3d532e1dc4f81943b2f4e3502ffa30bd2d0da55c7661d6e779218c9b5708687671a0d2c7aa11962792e3ceb0cd8fd43824d6076c9566cc9fcf53af9c5ca560d6c2f73032ad1bc2a6b7667a4d925604e8b0ae8b55152a14ca4945e04f764f32c499d127884d6aa62fcea45b9c8d4a4f92cef803c55f66794f15cd5408dd57b802f4b95076a42eaf8bc88cae56becbdbb80130d2a52271d56c12876c2468726b439ea6e3558513790133e1dabb8574bb9cb99e70062f8cb206a536d8531deef4622096152e2e11d781b076e340edead07f4d34716bb6a5e6f0f26d3db1ba392c423857d9a2415eb660aae7b35aea3690a916e06ca2f927c5746b850f48239eec274963a123579ff3c33cdd40c83f04c6bfb307827953be12769e909af08c922f5872b8091629d637234884ed042edcca7c763e92163bc5e1ef1dfa28bd61ff429eb805a50e5cd798e723a051b57f08f9d4cebb4c5b5d78c8941e7a506d48fbf39b62c0a9255ed8a21170fecc1749cbb3b3ce64becf159388dc98ef1ce0163e12083ebafe678083245e05fb0fe66eefde1a1af1cb5529efe6897d7982a0a52fe1a6eb7c2d0df9568427e81a5c2dc38229752c83511345b03aaf28e40f40d6437410404158149cc36906067c6228cf679e1edc64b37abab0026c2fbf628c75c140466df6dd059ed0f4ab80b87bd7b107d8d5a1d40238c594468b699a34606c6a45aab2b7e834df48b7b2956fd06ccbffaded621041a9d8f78061daba66f2f943910c69ff71169a5a2464e05895a10ed0c38973d188afbd1500000000000000000000000000000000000000000000000000" - }, - "result": { - "gasUsed": "0x88fc3f", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0xf9b0bea08c4a70da033330b656816716dd0bb2907a20fbb247e02bd6f5a7506c", - "transactionPosition": 77 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [ - 0 - ], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000002d082d", - "to": "0xff00000000000000000000000000000000000004", - "gas": "0x4ba7b2f", - "value": "0x0", - "input": "0x868e10c400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000008318808821a002d082d194e99811a045b25435820e1d4ff2fffdc2d955643405d87b009d9b4e4416564a87718a503d569c42a9c13582079dec049eddef67a2d8acaafb048cce7ccea8cd69df4b68830bb33041f5914c25907808c80a1a7f1aef23e9c5a72f013f261279d9c83df068122083bfc73c928562b99d7e142e8323c3b1ba398efc3fddd17d686367ba915da2e8f05f5605d58f2957927a51de60a07ab0dbd24ac1e8a52b6af11b95df831d14dbac25d750fd4a45dca0837df29befcba63b8178ea8f02340b6d7d6bdb058bfc565ded5260ada6394f7c68c9a4d8494e84eeac2866efd78a5ab8995d7fa578bdc9df4b54ede4212535f7f890a681a729126bc2012fbb57b1cb3b59a93bec4617ee15ad138e9985b14b4955cd39414d99e873f5f4647c36d32362ee8d01c892a0117abed4d76e5f4f8fffbef81f01b535662b42ce3ccf5b415a2b8a671847288a295efc0f210cd27009e8084a633405e7f458cf404909035e07384d4f599bc4d7a2de45c9630b2b12c33072bff14f9e8dc7cbefc0f2573c2227e89711e63ae803944745a28949dc0ac962d4b8faccb5730b1a5e95e59c269dc1fb1a45fe8186d56382cfcc8c2cb70e93f01a16f059d08f5cde17a7fc62454ec77182142a08e69d2b2b4ff48333542077db708bd647e0ac8f4d1db47b183c91f6084957b28fb75778288f8b83975bc630c1ee44dcd9a9b95c3e440aac0bfe88683986dcca45b0e4c16286c3a2aeb9f7e1d43b914cd0b3f629ee426feff417c321ddb9b798070e040a269c9e22b1e1a81e20931d105241068cf3ae4792bda0f15593e4482dfeeae4e2f0386f08eb5cf7f1bbec416fa4378dfd75924a56d04610790aeb39aaaa8c2840f7c52fcb8f89a8b2d7cc4336ecac6d9e40223d45b31f2926dd0e45a1bfb6f0b6cfb5f4d0f0d8f9255aa0021ea31b75191ea0159be650d495e4dbe608db1ecfba2da7de60a321e5f7da691ad4db7db3b5c7885ad51cea72d38ad5c0eb37188c86cbe01c938896c0b6893c8698a5f7f1eb5cea5cdbcd39f718098086ea5be03d25c5d9ab5a30d96b78308b14a7ca1b098b8045c4d90a78206fc18fe6e1cf8c4cca2cda8dcd6b345211e4d8841a90978a7a21c58474c06aa5e568adaaa9fa880ac84956894d2e976d459bed01056a00e69bfccfe9d47f65fdef85720468801603ba01a63865c99acba06990df8d8ada941b6ebedc7484a9a574a18b16cea4683a8bfd6a90e1d845fa4ca568f3e4ea10818a8b551ee7defa34f7d95a71ed3bd40ceb688ec6c00cc207ce8305060b96a5acfb86c38a514bc3223a21e36a2212bf47bef2c2e6b3a93ad463501f46a336efed31b3d9e9998dbb10362e86135838c62a8fbb0489c18f72110dd2fc63428d2dd7e4351b2e075ffc89d9f99a1c2e049c1b5c7ddcebf1972cc4208122a1c981f44bd5c4810a2233922ceadebff26e745d4dba5cd698659399f6eb4b80e14b0f5b2d3604c971c4d6d5a2a3aad696a5660ceb110379fad11e21bcbcf82cc7c9de0ea2afbc0a90c4e22b1562cb1f436a758f2550d8ec0f06ea21e2a230e94e2b1eb5f3dba6341a01dcb08c9c9e19a68e1302b6ec8d9453214ec9340261329fc6389bbe2ee256e72c6ee1440ec2cd7d6e9a0ccaa1676826978e9763fdb9883efd5856573bffda76b38e20a84a48ef7aa8f17163962498dad8493ee48f8f0aca45098fc026df4b3dc20231bb647d894380c97bdedcd652bce638f65cb1b82c672e1aba05a9220b3540cbfed4bc74626309f44fd91d4d666349fd06ac0af5eaa45e37913a9ac44edd81d852c06f2a4fc567dc4c88164be2e004c7c4adc45223a4f6c0e34056c15393ef77c4a6f7f8ebd40774402335828324485c93f37cc01627a3234c1544fbda48a4d48be691d60055a3dbc38890cc0a5c9c89b4487f9865c0279f8898b77f66ee7c0b5f60b7f93cff14cffbd5465f61130bcee8107b17b6f761697f9008048dee7d0d3ae8519d43e3769b6c954fbbde729cd7c2fb86f97a78a947df5709f793c3d532e1dc4f81943b2f4e3502ffa30bd2d0da55c7661d6e779218c9b5708687671a0d2c7aa11962792e3ceb0cd8fd43824d6076c9566cc9fcf53af9c5ca560d6c2f73032ad1bc2a6b7667a4d925604e8b0ae8b55152a14ca4945e04f764f32c499d127884d6aa62fcea45b9c8d4a4f92cef803c55f66794f15cd5408dd57b802f4b95076a42eaf8bc88cae56becbdbb80130d2a52271d56c12876c2468726b439ea6e3558513790133e1dabb8574bb9cb99e70062f8cb206a536d8531deef4622096152e2e11d781b076e340edead07f4d34716bb6a5e6f0f26d3db1ba392c423857d9a2415eb660aae7b35aea3690a916e06ca2f927c5746b850f48239eec274963a123579ff3c33cdd40c83f04c6bfb307827953be12769e909af08c922f5872b8091629d637234884ed042edcca7c763e92163bc5e1ef1dfa28bd61ff429eb805a50e5cd798e723a051b57f08f9d4cebb4c5b5d78c8941e7a506d48fbf39b62c0a9255ed8a21170fecc1749cbb3b3ce64becf159388dc98ef1ce0163e12083ebafe678083245e05fb0fe66eefde1a1af1cb5529efe6897d7982a0a52fe1a6eb7c2d0df9568427e81a5c2dc38229752c83511345b03aaf28e40f40d6437410404158149cc36906067c6228cf679e1edc64b37abab0026c2fbf628c75c140466df6dd059ed0f4ab80b87bd7b107d8d5a1d40238c594468b699a34606c6a45aab2b7e834df48b7b2956fd06ccbffaded621041a9d8f78061daba66f2f943910c69ff71169a5a2464e05895a10ed0c38973d188afbd15d82a5829000182e20381e8022046a040e4fb486d77bd3aefb25a4eb3670e46e3f935fade483164764113c9092ad82a5828000181e2039220208e04de2905b7795a206f4ca5423c6a65dabfd8304b05b92aeb298af986ff1c01000000000000000000000000000000" - }, - "result": { - "gasUsed": "0x309b9c7", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0xf9b0bea08c4a70da033330b656816716dd0bb2907a20fbb247e02bd6f5a7506c", - "transactionPosition": 77 - }, - { - "type": "call", - "subtraces": 1, - "traceAddress": [], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000002d0815", - "to": "0xff000000000000000000000000000000002d082d", - "gas": "0x4d995a0", - "value": "0x0", - "input": "0x868e10c4000000000000000000000000000000000000000000000000000000000000000700000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000078782194e7659078099831f9576b59ca994613a166b25313bd47dfea452d5fca7152208025b41569c4b746255e1f9b84c29ae1a2fd7b51c41b8ef90add5e5507af6fc9477acf8a4e467d92a6f8b9192187d58a63cfebbbb58db31e89a5cb0cec05afa21126a9f1f4d12a51c2bf2630a8f35de9f5b5917dbbd53fb2a80b940dba66b544572dcc9442f0b504c48d4af9930d653ad9e5de6d407b58f2ea14e8c4d0daa6b1ba3a9ee6aa6336bfe6a6d62b9b76bd8ce9a3cb83b67c98695a0d83762582a3c0a0f9ee2e4f081b5f7573a0879bef50e2833c42862f1c18a880b9caa2a55f0eb415e8394fef0c5e681cb33cb06532f49658ffe675138b16d27817e745e0b0cc3928eaf99644f8eaebb6f1679a0a9fb3814a3342004b5885dc1a14a27b4fa64286dd0a076e3b60276a16e86e2736bd0a808734caede2fb2f09db852720f3e531d3226ef7c42dab93285600995614ce01dbdf40c0a2450ae04149d63878dee9f00c37980523cadd7570c2e9fe5ed4b2b33a16f510e5cdfe2add1e44df129097b71a25f918bc5ed80278fa2c13977ce824c8772f285c8ff2cae6502d59f46965a18169fae74f22801c0360ee99355f2d05e8cae2635e9148d397467af6b2288b27f2104fce3a1de18900fe71808c5d30b050f2a6842794dc547b49ca9c8d9eafd6dc5e40f9999ad0d06dfdf3a3148d6504816215035bf4ef67f767196837fd6332179e426838a1a6015d1ed9eecce775bb1cf805beeb22f96ccbed2588efcee9e0900ca3d1dcda9cd9fb1b74d1421f69e9f64116ca7f847824983039a2cf530ab96016bb625df50b880b01ea628f7bac496a4884a2be94a8bae8d344f0e4ebcb1910cf9d4547df86ba460645e4803f4a9f94b1ab3385552872be6f3a8dfb1993843ff36dd9f84f8bea6dafdf293a110d6617c14d2f9baed553c8a13fc1b711f1eeb45eb24a4ec0407f15431b667d49129837af9ed1dd815feb000650de073f80802572806947ae12feb9068dab35f2b3ba34a2fa9a4086187a81a66b249c8bee5b6ceb4bd8e453db444ef365eeea12e1e2ad3b656a2071a21512e972a99b7578898f82e458a6dcb91f2fe327f52b66d72167780f91bf3087924285f659f13900e441d56720ad2706460b970a36a67ec7d4ae41dbb0c63e490fc93ce81e6462b22842cc6fcdcda160f534531f6142ec8b6c4a46b506e1098e19994af6317be2aaa17f028adc4c43a18a2b77b16b1504c73782e5a9d0cdae8ad687921e1e5a1994c1cf0a46cd6cb78b318e72fdcd162adf463642e7bc48a6c92028af24770f3456e2dec29a3f3dc5b310183734a2c2f736a28807168b68e1985d39c39586eab9ded0df6d5643cd41eaee50789eda6cee7540552c9ec67094db48f7d7053605b266747545e72283d5640548aac9fce739dd4004d4ef869a72480a04609bd8f87054815b3cdfb18929784b1c12d8d6aa81bb417625924df826dcc025a1cce3c7203d906c6f25d7a863b1831bfb61737060a63707c5308f125fde71c50686e8813373aea7451e15e1579508a43b7bf9ac23546bfe1e5d80de563979d455615f690a07a44b2e7f4fa8460f1a1ce0a428495e3746cca143099ea00351ab4e001eeb3a8f2f8dc7ceaa06e9680424438aa2401867f354e33808fee11abc04240d19eeb91fd9551fb828a32cbd7e3e1b924247bd77f37335c957be1af84013c9ed4ad5beaee30bf3838c9bdaa4b4c9c6ef4b838360983426cde4454d1220f8677da23a117844bb82e168c51781222bc620ad9e8341141391b7f1e74ba73c751b47a8c2a44bbf90f2926a19fef298afdf0ed34c7f34e8c3f3339c653a7adb80caf0d61be88fb521e254a313d3118f0d59d9a77b5b29d321c08c739fd7256bc4cf42fa1b3c200bddfeea074843a99e8b14bc42ec667fc1a46356e3e18aea5bb5cbb981e867e2a9f26e4c00e4f922741e132900115257ece0265cdc485a9a6b1e2141fb5048e05e911a34b14eccb6c9cc5ad8af7be6cb6c5d9159832640d0773f5b3cb57cb4f85d9673db29632bc0c5bc7d26db2d396c8d74932fc3a7670440b76ea62b96e69dca52efccfebc9ae594b3a0e26143124d8a84ac427cd974889dd6b74405dd8d2367a7ab0e4aeec9b51b1e96f4819f48deb0afb32d9b0b9bbc4701c9d60da394dcea1d86c98edb8b6a065bc0c19840d2e6f454459f9aeedb422addd265807259f0dfceb1321cb3acd3b40584bf8b3340066580a27ce0c9b289959fce200924097e34d457096a4c5e3c3da0e644d665c071d75b3f7828361f7d801e48527af973586fbd0c07de1925a07388f606844c7c379a347b3439648a6752e95a4e89fd64823c8ac619459b93cf7716c1dcea6ddd460f4c42eb9994ff98a8b00cf9a74b13152e9fb1bfcf0b00d45869bdfd88355ec1d473a8752bf1a2a04640ab3918bc4f0bef6b9187b74c414b0d1ee19ff3a1aeee50b73300d3010bd2698ca444c629509acce503d59248b475602fe48abea5a4b8da9886e71d00f4ba62142c8790b630b357014be80b83aadd3784423b90d54cce1ac94297a694e0da2f284a0af093c1a73557ff126cfd29e035d59c283c826d17da2af6d967936735edd32c4e3dbaddc3b113a2a03c2b7ee953b194f05f2b17e54fa36fb3b30396fb858ef99398e82a3ea62113cac8f1a216017083de9d1c5524b777dfba1051c3bc2d510cfde0fb762cea5f226d8b6e64b00000000000000000000000000000000000000000000000000" - }, - "result": { - "gasUsed": "0x89592a", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0x11af98bb4b63e184700045f41d8370daa555dc189c7670a92b59c4fc7c47ff06", - "transactionPosition": 78 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [ - 0 - ], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000002d082d", - "to": "0xff00000000000000000000000000000000000004", - "gas": "0x48112a4", - "value": "0x0", - "input": "0x868e10c400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000008318808821a002d082d194e76811a045b225a58208e503e3d366299b9095156df2ccafbc49fbe238c3592a879c7cbc3fa095d26545820f7f9630e9e1ff4c01611a3dd41958c27ef95804e97da3fafe1e0c27da131a91459078099831f9576b59ca994613a166b25313bd47dfea452d5fca7152208025b41569c4b746255e1f9b84c29ae1a2fd7b51c41b8ef90add5e5507af6fc9477acf8a4e467d92a6f8b9192187d58a63cfebbbb58db31e89a5cb0cec05afa21126a9f1f4d12a51c2bf2630a8f35de9f5b5917dbbd53fb2a80b940dba66b544572dcc9442f0b504c48d4af9930d653ad9e5de6d407b58f2ea14e8c4d0daa6b1ba3a9ee6aa6336bfe6a6d62b9b76bd8ce9a3cb83b67c98695a0d83762582a3c0a0f9ee2e4f081b5f7573a0879bef50e2833c42862f1c18a880b9caa2a55f0eb415e8394fef0c5e681cb33cb06532f49658ffe675138b16d27817e745e0b0cc3928eaf99644f8eaebb6f1679a0a9fb3814a3342004b5885dc1a14a27b4fa64286dd0a076e3b60276a16e86e2736bd0a808734caede2fb2f09db852720f3e531d3226ef7c42dab93285600995614ce01dbdf40c0a2450ae04149d63878dee9f00c37980523cadd7570c2e9fe5ed4b2b33a16f510e5cdfe2add1e44df129097b71a25f918bc5ed80278fa2c13977ce824c8772f285c8ff2cae6502d59f46965a18169fae74f22801c0360ee99355f2d05e8cae2635e9148d397467af6b2288b27f2104fce3a1de18900fe71808c5d30b050f2a6842794dc547b49ca9c8d9eafd6dc5e40f9999ad0d06dfdf3a3148d6504816215035bf4ef67f767196837fd6332179e426838a1a6015d1ed9eecce775bb1cf805beeb22f96ccbed2588efcee9e0900ca3d1dcda9cd9fb1b74d1421f69e9f64116ca7f847824983039a2cf530ab96016bb625df50b880b01ea628f7bac496a4884a2be94a8bae8d344f0e4ebcb1910cf9d4547df86ba460645e4803f4a9f94b1ab3385552872be6f3a8dfb1993843ff36dd9f84f8bea6dafdf293a110d6617c14d2f9baed553c8a13fc1b711f1eeb45eb24a4ec0407f15431b667d49129837af9ed1dd815feb000650de073f80802572806947ae12feb9068dab35f2b3ba34a2fa9a4086187a81a66b249c8bee5b6ceb4bd8e453db444ef365eeea12e1e2ad3b656a2071a21512e972a99b7578898f82e458a6dcb91f2fe327f52b66d72167780f91bf3087924285f659f13900e441d56720ad2706460b970a36a67ec7d4ae41dbb0c63e490fc93ce81e6462b22842cc6fcdcda160f534531f6142ec8b6c4a46b506e1098e19994af6317be2aaa17f028adc4c43a18a2b77b16b1504c73782e5a9d0cdae8ad687921e1e5a1994c1cf0a46cd6cb78b318e72fdcd162adf463642e7bc48a6c92028af24770f3456e2dec29a3f3dc5b310183734a2c2f736a28807168b68e1985d39c39586eab9ded0df6d5643cd41eaee50789eda6cee7540552c9ec67094db48f7d7053605b266747545e72283d5640548aac9fce739dd4004d4ef869a72480a04609bd8f87054815b3cdfb18929784b1c12d8d6aa81bb417625924df826dcc025a1cce3c7203d906c6f25d7a863b1831bfb61737060a63707c5308f125fde71c50686e8813373aea7451e15e1579508a43b7bf9ac23546bfe1e5d80de563979d455615f690a07a44b2e7f4fa8460f1a1ce0a428495e3746cca143099ea00351ab4e001eeb3a8f2f8dc7ceaa06e9680424438aa2401867f354e33808fee11abc04240d19eeb91fd9551fb828a32cbd7e3e1b924247bd77f37335c957be1af84013c9ed4ad5beaee30bf3838c9bdaa4b4c9c6ef4b838360983426cde4454d1220f8677da23a117844bb82e168c51781222bc620ad9e8341141391b7f1e74ba73c751b47a8c2a44bbf90f2926a19fef298afdf0ed34c7f34e8c3f3339c653a7adb80caf0d61be88fb521e254a313d3118f0d59d9a77b5b29d321c08c739fd7256bc4cf42fa1b3c200bddfeea074843a99e8b14bc42ec667fc1a46356e3e18aea5bb5cbb981e867e2a9f26e4c00e4f922741e132900115257ece0265cdc485a9a6b1e2141fb5048e05e911a34b14eccb6c9cc5ad8af7be6cb6c5d9159832640d0773f5b3cb57cb4f85d9673db29632bc0c5bc7d26db2d396c8d74932fc3a7670440b76ea62b96e69dca52efccfebc9ae594b3a0e26143124d8a84ac427cd974889dd6b74405dd8d2367a7ab0e4aeec9b51b1e96f4819f48deb0afb32d9b0b9bbc4701c9d60da394dcea1d86c98edb8b6a065bc0c19840d2e6f454459f9aeedb422addd265807259f0dfceb1321cb3acd3b40584bf8b3340066580a27ce0c9b289959fce200924097e34d457096a4c5e3c3da0e644d665c071d75b3f7828361f7d801e48527af973586fbd0c07de1925a07388f606844c7c379a347b3439648a6752e95a4e89fd64823c8ac619459b93cf7716c1dcea6ddd460f4c42eb9994ff98a8b00cf9a74b13152e9fb1bfcf0b00d45869bdfd88355ec1d473a8752bf1a2a04640ab3918bc4f0bef6b9187b74c414b0d1ee19ff3a1aeee50b73300d3010bd2698ca444c629509acce503d59248b475602fe48abea5a4b8da9886e71d00f4ba62142c8790b630b357014be80b83aadd3784423b90d54cce1ac94297a694e0da2f284a0af093c1a73557ff126cfd29e035d59c283c826d17da2af6d967936735edd32c4e3dbaddc3b113a2a03c2b7ee953b194f05f2b17e54fa36fb3b30396fb858ef99398e82a3ea62113cac8f1a216017083de9d1c5524b777dfba1051c3bc2d510cfde0fb762cea5f226d8b6e64bd82a5829000182e20381e80220f35099649c579a0471c666d7319476b5bdc31aa0d651e214daae463001788769d82a5828000181e2039220200eaa7afc2292c2eb6a2db7208cd738b6b5b7fb69577b134cf1817c6f4d39dc0d000000000000000000000000000000" - }, - "result": { - "gasUsed": "0x37f99a8", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0x11af98bb4b63e184700045f41d8370daa555dc189c7670a92b59c4fc7c47ff06", - "transactionPosition": 78 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000001ca66f", - "to": "0xff000000000000000000000000000000001ca698", - "gas": "0x336d63a", - "value": "0x0", - "input": "0x868e10c4000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000027a8518228382004082014082024081820d590240a08ff3b7b6fac42168d1bb9fe5ec46d2a1f0f7393abe61635e7cd7abb92358fc5b06b05b5598363c685a19a2eb0a789a974237d01d4e9abab7fd26c5f5b961c3c2e7e7a02f811aef4e9017183f01fbf2fcec48c11c66b04ec88c6205326d942c0481a6cd1f2a9ce933999b9f1ca17e7e250dace30151e38d334fd375c80942e881b4b75905866110ff9a8767765908bea19acda6d804c59114151663ef6aed4a26048afbc1b3541673ee9b223dec1adbc7e0317d90c570b2aa0ef83ca19fdc649143af73cce6bc1b5a2710b6da1abc090b08f26d9f14d4db79460f04803bfd718988bc44a81936da65d2631296f56489b8cce5f35c108aaf789d8f51cbccb80974d86b100876b421af58cc973a25363ad0e465c91a650677fd76e3d82d028dd910925f07bb1fe8e81bb8bbea9661f6bab48cd6a72c0fd8a70edb644cb4f1cb5c51be9168b28a2eba6e429f0fd69657fd88e05ef08bb018310481be280d48d435cf22bc452d61a846bae765092fd51625d5cea1a4d2b1fc2064b7b682393d35348c988668c6d985754deaa21b4ec3e7411268453a8f118295888def479a01e0c3a8fa274ce6c75cfe7ddd1f32d033a43080a25e8579a26b7dc65c1de7e88f22234842ce224f4f946717a7ada60282758ee63a6a08fb59f73ee5419aaeceeaac6511e0b58e0e14906e2e9343e52d9e6d05d4149ee4777bbfadb41f1d822dc68c5d5db3e1cc7d1cf8af15a7f2f874bbd867b13c74e229c51a1fe2e34d3e141592e5d16406695de5c9d4044770ccc776e49db6fdd3ac048ecf32d813e0e3fb883dc61a0037e5f8582047ab5fe94c06cdbaec8b74525a8ef23928a1bbb62440ccbcd599b10154404d69000000000000" - }, - "result": { - "gasUsed": "0x2a5696d", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0xab7a2853fd0c3c51361087f07a575acf5ac0930968bef1ac73bf360275a52281", - "transactionPosition": 79 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [], - "action": { - "callType": "call", - "from": "0xff0000000000000000000000000000000014b4b4", - "to": "0xff0000000000000000000000000000000014b4ca", - "gas": "0x27c657d", - "value": "0x0", - "input": "0x868e10c400000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000001b785182f8282004082014081820d590180a6333476a9dc6aaee94d32d85f613057168b6aca7e43fcd54ee38b03aae16a0917c867e0339d5f492cd987024f4141a0966b843654b2e9e7af330fe6d83fa05630c3aff5417eeb00e5e3aafce6d12ffdcd18b68cea29003838faee385a3626330a8e3c20ec69cdb8e3b5ce8594c9c9eaf68996771fc6b8399edd629b97856d7611475591ba61b86aafba95ebb482ec5498d88485f2cb9d6920fd45dd8a234091a31014aa836496dced595b0f5f89aa7abfa5c5eae63a11e93c0a3b241cf42901b931570b6fe9f665a4e62be9d3b1cfc5c3d915edc6b899b28f9e49ed67157375d74210aa54d116b9af6d1da31095d2eaa8a5d81d48da35aa43436e12c6d1a539c691aaf6a3575dcf80219a0214b5babfb9b8811e55ec3afd1b9fd73f1b82dff707ea2962acc711288fa110af7e7671ec90b1a7f0f40534c7fc9b538a09fd3e89a5e38de44b12b279f952775aa0075d8c9840f0bbb2c40c0a5c4b999329da775933d297d4c97101cb13ed3192edac7596df7ac4533cf1a38b07fa1e18b58b11eb1a0037e5f8582047ab5fe94c06cdbaec8b74525a8ef23928a1bbb62440ccbcd599b10154404d69000000000000000000" - }, - "result": { - "gasUsed": "0x20d2a3e", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0x395ddc88f44eadde7eab48be036779baba3f53083167c7517a713f15c96bc804", - "transactionPosition": 80 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [], - "action": { - "callType": "call", - "from": "0xff00000000000000000000000000000000112c55", - "to": "0xff00000000000000000000000000000000112c87", - "gas": "0x184c153", - "value": "0x0", - "input": "0x868e10c400000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000f385181e8182004081820d58c0b6ea07c16c33360054815486dabadebe23a05ea409bd99d4fbf7ee738771daa5b76f68d46c3a59f42766ef147e7ed9a299dfe9d4990acbcd4aa5c21ba31e98b599d8f904c11c3a312877e1ed3e9d76581bf5b1b6c37872136a084bf5e7be564a150ed4a52079e13f4fa2a95946893e961ed7ff6753f8412494ba67faca84a2d912a51d9388bbdee6338a6b58bf7283ae85f28878222de7ceea33fa13d229b5cb1ba5a74186feabf2752d0329b868f9e92f5cc92bc1f120bd2d1f8eccabb8ef0d1a0037e5f8582047ab5fe94c06cdbaec8b74525a8ef23928a1bbb62440ccbcd599b10154404d6900000000000000000000000000" - }, - "result": { - "gasUsed": "0x143ecfa", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0x865c98e58b41449ae2484a8b15d7a5fa601779e0a55194cc8ccf5276556c532c", - "transactionPosition": 81 - }, - { - "type": "call", - "subtraces": 1, - "traceAddress": [], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000002ae495", - "to": "0xff000000000000000000000000000000002b1e7f", - "gas": "0x526070a", - "value": "0xf2ac3304b3a9ee7", - "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000007000000000000000000000000000000000000000000000000000000000000005100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000787821958b3590780b104649bcc2962b63068e99c9027750b8c0624579c9782df07b725bbf8fd863d32bfb7e038c5ee49a53a243b7c4b619f8f01f2528b63d3d30ee42353e63991460d3017d690d5d1d4061308bcc06d6df4b637605a12c71871455de0ec0ca9d4bc0618c739d4a1d2840b40e941da2f979eecb008f4d42936fd507dd2ae990e56a60d0eb73c942acc11a32316852505bc07ab49bfdfbdee30b32738060a00deeb7a72fcecaca2c2fb0c78d2572b23d95a996fbae088c4089a1c7d78a6879e40b1d8a08912c83a221c96cdcf3c27a041039e0c082b49aa120a8612ebbb8b212643e5f919598c6d783c944ae489e397c5dd3ea0afd730a6bd1c439380d86e05420a08145ffff0b1c76da1f920b39ff18cb04a3dc17c98b841c7bbff185d647e3b73f411f897ba7012863e6c3f58d734e3e105ff9e66cf6fa967763ff752823c056262bac4ca4ec4cdea379a6021f4674f16e4897b09752a737058c6539bb958782e5e2a9c87fa86b281e4fcc52acb75d568f6bcaff64d47e2cd235efdf5d1587ba860af4a62d1b04809f8fb934412ae24a9ac4dae0f8b9b6984b08e016c7053aa46fb312790aa0aaf31e7996fbae29c724027a0309818b8abc4baf5c5d96a582f55b79cccea28bcdf5ee03001dfff5cd945dfa476cd9008aea7eea2362719bb57342e15719a80d95056f0db56af34639b334309caddc833513c772ba05b7a365d6e91141112e6570296fce32640717602308ca37497d9efbdf284d2f4e7502ef3072a9e01f4a9a055457ce9db2b66c6e20aac758aaa1d085a1143bafe1ca3bb961379a02718f0fbbc7394f89b6ed096317866c50eb17fbb56ef9e0ef4ba22eed07a2cfa955669b7861ff662fe5698eb2d6e608a209c2286f0a6ab0cea4db0a567747498be3aac058521cb00e1ad7c6b6223f01f755c9ea146ec5c36562116172684cb16ce4479da96c83f6f509002ce583dfaa127db607a494aa3676db46be3e5faf22a990280626e7e39de54b6437302e194aa3ccbda92483130976b902cb621226639664deb1564336ae7f7d60d8c81ede66a154b38c147ce6c8ddfc93e8c20116085603c2d8cbeb6eed491e82d1838809bba6ff51bf56155c1e3e5eb4811bfeb2f68c17e4830a4fdaae17bdc1ff6869aee810941f3a7d3e4b01b47a118bf44f98d0acc3580390a05a4597f3a055498485d7850ef0a30cc412156e872a8ac1525f00c09113370c97ee408b6669998c6bd4513dc1ed023093e93354650c5c9be1c302a3e6f4ea612c6f24b2f1d31344d0f8ba7715ce0d0ca3ec816938fc2b7355c3424a2d2af92c6ed135bb8a0f2e692dfac2a25ca123dac083ad7f379d9316d320da4d4f6df987064e5918b5b55d0e39d97a1e972097f3cc790edf8479aa57d1dc911cb1c584aabef898354e0858b7c869ea0f3b4a827bc681be89ede0af01dcd2856595eaccb0d6ffd4aefaf3c1d406e39faba62ea6d46d24000097db353f506c4054e9309827a32246d6db154689bd3d164e9ad0ee9e662cb4f66e4f191d2adab221a004f49a035ab639d3a7a9044a148b12c4d0809dbe51231938026da42e017e475eca0d6f6f584dae896b73c5c1048ef04d42389728b8ad82479de84600161a731949cf60a2b18093ce01b865c31cb192dcfa58942f15ce2396893408b81b69b02d146412f8c5c57962aef640b774098da0b21442042325efa87ec757e45b40d0b01555296a39172f3f72098c6564a7a40b5526b63208f411e01fe949af04904c39b78775cffca74cd36af636971621ea74c83ad29f2cc55974c5ba7fdfc4e3b743f906e9d739a545f5974bf44e2f695326a2296fc4eba138971507b5c208e78fbaa94a1f0f6332ff6f242679dca0034472a9ac61f993a8f09dae64b860a44aa5e61c527122e4074a24fae53f58b713df614d5a308c91299f13f34ccefb9c4aab4f75baec8d0ac16d8e79115ebb45499a39d5ff53b40eb21496ebbb37c9a691748b80dd24b9d6f869300799059f284c39b97f1f75b773d78793de665ad8af00dddbad2208b6b3add604e1fa2d567f44be4661d4e74d72fb2018dc31a6542e856c9ca57804cbc9b5af7e14ffbc1fc59a65e845ecbdbb8cf1a8797224dfa0c76e8f995f899d541d50dd45cab0f05312ab8ea7a8522a1756d6d2e770d93500f7782670068603ab43ad972b4a55b42e5e0932790eba71343d1da3dfe2490959a3c11c2a1e859134047d18fbf75445d34cbaf139550fe0d9a72f80e13310255cc82b955156746e04272811b74a0b3ca3c2866b59b12dda698d562a619238965ed1200b8b2f41d3e06318067a0c248112a3aaf3aa30a396be7422986e35b41e8d375f76010785ff3eb09489b70e5a2c82984a10812bf9b1c7f4f877df41bab0d7b01e685670c56f3e545329a4a882fb0a7264343d54597346b15df13f25d7a0931a887a7be4465b717ee4552a0192534a10b2d19d086d577d1adb8154787e3af1050370e60599d3636e0d5081e357c5483e2a5eafb2f71c6db5b1213f4a07ffbe2a86eec3960d242fe2106ffbd9865882ae4dfc2d9542cda9a26a3320e72db1fe68c1235c468bffdb20a26791df145dac5a0d2c9527ea841961c45fda95d3d0267955dfef6bd960231c1a8dfd36583533836a1dd94d4b456a97ef41237fd6d8d5b898aa21eed123218ef4b3aedd3e645c9c0166c26e652699bf951f8f6f0af9ae11500000000000000000000000000000000000000000000000000" - }, - "result": { - "gasUsed": "0x7dd123", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0xd5c156a48ee21fc31fd16f0102ba7f09ae50f5f040c95fec880ebd46b96c2506", - "transactionPosition": 82 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [ - 0 - ], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000002b1e7f", - "to": "0xff00000000000000000000000000000000000004", - "gas": "0x4d9396e", - "value": "0x0", - "input": "0x868e10c400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000008318808821a002b1e7f1958b3811a045b26ab5820f8ccb64bff08817a3cb554d2b97d7ad9fb9428ac6bde0bd9144cfdffec760af75820749b5789aaaaa013a8a90db28d47429b37b0c322abf2f2af8eb0261f3fd9f133590780b104649bcc2962b63068e99c9027750b8c0624579c9782df07b725bbf8fd863d32bfb7e038c5ee49a53a243b7c4b619f8f01f2528b63d3d30ee42353e63991460d3017d690d5d1d4061308bcc06d6df4b637605a12c71871455de0ec0ca9d4bc0618c739d4a1d2840b40e941da2f979eecb008f4d42936fd507dd2ae990e56a60d0eb73c942acc11a32316852505bc07ab49bfdfbdee30b32738060a00deeb7a72fcecaca2c2fb0c78d2572b23d95a996fbae088c4089a1c7d78a6879e40b1d8a08912c83a221c96cdcf3c27a041039e0c082b49aa120a8612ebbb8b212643e5f919598c6d783c944ae489e397c5dd3ea0afd730a6bd1c439380d86e05420a08145ffff0b1c76da1f920b39ff18cb04a3dc17c98b841c7bbff185d647e3b73f411f897ba7012863e6c3f58d734e3e105ff9e66cf6fa967763ff752823c056262bac4ca4ec4cdea379a6021f4674f16e4897b09752a737058c6539bb958782e5e2a9c87fa86b281e4fcc52acb75d568f6bcaff64d47e2cd235efdf5d1587ba860af4a62d1b04809f8fb934412ae24a9ac4dae0f8b9b6984b08e016c7053aa46fb312790aa0aaf31e7996fbae29c724027a0309818b8abc4baf5c5d96a582f55b79cccea28bcdf5ee03001dfff5cd945dfa476cd9008aea7eea2362719bb57342e15719a80d95056f0db56af34639b334309caddc833513c772ba05b7a365d6e91141112e6570296fce32640717602308ca37497d9efbdf284d2f4e7502ef3072a9e01f4a9a055457ce9db2b66c6e20aac758aaa1d085a1143bafe1ca3bb961379a02718f0fbbc7394f89b6ed096317866c50eb17fbb56ef9e0ef4ba22eed07a2cfa955669b7861ff662fe5698eb2d6e608a209c2286f0a6ab0cea4db0a567747498be3aac058521cb00e1ad7c6b6223f01f755c9ea146ec5c36562116172684cb16ce4479da96c83f6f509002ce583dfaa127db607a494aa3676db46be3e5faf22a990280626e7e39de54b6437302e194aa3ccbda92483130976b902cb621226639664deb1564336ae7f7d60d8c81ede66a154b38c147ce6c8ddfc93e8c20116085603c2d8cbeb6eed491e82d1838809bba6ff51bf56155c1e3e5eb4811bfeb2f68c17e4830a4fdaae17bdc1ff6869aee810941f3a7d3e4b01b47a118bf44f98d0acc3580390a05a4597f3a055498485d7850ef0a30cc412156e872a8ac1525f00c09113370c97ee408b6669998c6bd4513dc1ed023093e93354650c5c9be1c302a3e6f4ea612c6f24b2f1d31344d0f8ba7715ce0d0ca3ec816938fc2b7355c3424a2d2af92c6ed135bb8a0f2e692dfac2a25ca123dac083ad7f379d9316d320da4d4f6df987064e5918b5b55d0e39d97a1e972097f3cc790edf8479aa57d1dc911cb1c584aabef898354e0858b7c869ea0f3b4a827bc681be89ede0af01dcd2856595eaccb0d6ffd4aefaf3c1d406e39faba62ea6d46d24000097db353f506c4054e9309827a32246d6db154689bd3d164e9ad0ee9e662cb4f66e4f191d2adab221a004f49a035ab639d3a7a9044a148b12c4d0809dbe51231938026da42e017e475eca0d6f6f584dae896b73c5c1048ef04d42389728b8ad82479de84600161a731949cf60a2b18093ce01b865c31cb192dcfa58942f15ce2396893408b81b69b02d146412f8c5c57962aef640b774098da0b21442042325efa87ec757e45b40d0b01555296a39172f3f72098c6564a7a40b5526b63208f411e01fe949af04904c39b78775cffca74cd36af636971621ea74c83ad29f2cc55974c5ba7fdfc4e3b743f906e9d739a545f5974bf44e2f695326a2296fc4eba138971507b5c208e78fbaa94a1f0f6332ff6f242679dca0034472a9ac61f993a8f09dae64b860a44aa5e61c527122e4074a24fae53f58b713df614d5a308c91299f13f34ccefb9c4aab4f75baec8d0ac16d8e79115ebb45499a39d5ff53b40eb21496ebbb37c9a691748b80dd24b9d6f869300799059f284c39b97f1f75b773d78793de665ad8af00dddbad2208b6b3add604e1fa2d567f44be4661d4e74d72fb2018dc31a6542e856c9ca57804cbc9b5af7e14ffbc1fc59a65e845ecbdbb8cf1a8797224dfa0c76e8f995f899d541d50dd45cab0f05312ab8ea7a8522a1756d6d2e770d93500f7782670068603ab43ad972b4a55b42e5e0932790eba71343d1da3dfe2490959a3c11c2a1e859134047d18fbf75445d34cbaf139550fe0d9a72f80e13310255cc82b955156746e04272811b74a0b3ca3c2866b59b12dda698d562a619238965ed1200b8b2f41d3e06318067a0c248112a3aaf3aa30a396be7422986e35b41e8d375f76010785ff3eb09489b70e5a2c82984a10812bf9b1c7f4f877df41bab0d7b01e685670c56f3e545329a4a882fb0a7264343d54597346b15df13f25d7a0931a887a7be4465b717ee4552a0192534a10b2d19d086d577d1adb8154787e3af1050370e60599d3636e0d5081e357c5483e2a5eafb2f71c6db5b1213f4a07ffbe2a86eec3960d242fe2106ffbd9865882ae4dfc2d9542cda9a26a3320e72db1fe68c1235c468bffdb20a26791df145dac5a0d2c9527ea841961c45fda95d3d0267955dfef6bd960231c1a8dfd36583533836a1dd94d4b456a97ef41237fd6d8d5b898aa21eed123218ef4b3aedd3e645c9c0166c26e652699bf951f8f6f0af9ae115d82a5829000182e20381e80220f8558d739d8803bc6cebfb9d848e53a6dad4c37fab064e8c705621641b35074ed82a5828000181e20392202061158a40f8ea8f3f04148354f737e70549998404cdc66a5583c2f4e3cd0a0a1d000000000000000000000000000000" - }, - "result": { - "gasUsed": "0x30cf1b6", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0xd5c156a48ee21fc31fd16f0102ba7f09ae50f5f040c95fec880ebd46b96c2506", - "transactionPosition": 82 - }, - { - "type": "call", - "subtraces": 1, - "traceAddress": [], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000002ae495", - "to": "0xff000000000000000000000000000000002b1e7f", - "gas": "0x402499b", - "value": "0x105e0cd9863c06ef", - "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000007000000000000000000000000000000000000000000000000000000000000005100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000787821958cd590780adcd547b9f1054f3eb179481e1437f981389c937872090f003ddb6e08d753861e6f05e90b9990d98c754dee7d6dddcf1941a48d0ee5bc3570fd52520a5ce3d9da7b40f68761b3cf3a7633dde498283d95055e3e32521d8a720d41321026044280910a2478ff76ce23192d695ab518438f967da0da1912608ad89ea0a50d2702b5032f026c62cce894480256c74c639bab56435da3aefad1ad28659d132ecbce88d3f80b0cc4a3048e94f13803b84404f17dc0a2d57b81ccb1255eadf2a55d5fa892eb861314ca95a1d516f63df0bc93c77529573929e10aa35c8e0ba9c225df897ff0d005be902a2d74c4a693893015f8f14311232d816d1fad5a4e005e685d426dd78c531e9563dd8785ac95e44970fac7f3b7d8d6603329604061b626cae7502fbac2ef0c460af26be45e113b2b7a43d935116511231a1c33ac5d769ef3b0c9c3fc626fbec2f8cfb87572e68c2d617897a9fe18d703b26f4253e931ea3e9c1a4b5077fc7656797b8df389b14bb5e2888a7dcdfdf89dcbc81c34d236d31f021a7a207dc806af6f3c89889efedd24f511562a2d01bea7b55afdea26e486bb729cab9b33a63c3360f8d9f267673eee7aab384b4d6e92d99955b0f1d58dce11f6e41dfe0ad020db66d34b9809c01d2f652a567b0ee46bc2ea4743a3f570d96acbc1525a2691d8bc7e5a1abd34f91189f9aacf36cb833f75be11cf12d47a985667f8b65c5444b1995495ea618484846e5038a8bff82cdb012ffb11bfb14934cc26e4e00331511d49ab2634d2d521a0989aca51ba718f0247dbdb88693f094bbb045a4d9f586340fc388fb9b4e0c65b52a38287cb8614bdad6271c3122e845ce8e74fef8bf6a5764d4384c1273fe5ce3037a951b46409d1518c256e26776e58f7f54f710ee3f190b4706de47e1c4dc0f427fc026a55c4b78deda2aebc381b98de5e200a765da1713999ca2a0d4de1ae76f12442e43e367055cb854ca45aed1115a0e9b20aa1fd188613f7c75beb1a2d26617890e786f2bdbab208b9dad19c1e6ec5dd69b6bc65657fbe561f9efae97eda5d4d855841f450d60fdceeddb45f371b5ed86ddf2cd367ad9a977eb4d9161ee549f5ab243513b70fe3f5708d4f13a47731db9898777079601f103ad01acb74075eda60036ae233fc572e41e546081f852496dad5e90ee06d722cd3835294cda3a31b61a3a167138d00aed08ebbe66a970e80334142cfdc870855c9b6d32e5942d526ed4762a13f9372572cb2555bb3aef5428906719050e1aa6a16ebecd5b35a38f940b3d4b12308a155e5bf035484bc1c2955ca84c23c25801065f7bd907b43e98116262a29c52d313f5355adf15b98f7e9038d780a2ef90049625360d7aa4ad4db577bfd9258d5627932993250fbc0b10b037a899612b74ac70c044044ca1e2c4a4cabe88260646811723ec43609158519e563e44a34ac6ca462c1cc2e6d79b48ed604e5fb890f41f0239ee947dcf6daa11da281778f48e78f9280f54d54c728c2ec8299e710c6c3acda600ec4b55e421c4d77376d8da43f1eb65c473c00f08c3ae15348d1049f75449826c0f9ee69361241491d8261d82daded638e80e042cea44cd702a529329aa603704231b24e933aa6d700662ec989717c7bd430cd3023a53221052ec6c3539c2e15518dc75f6118da892225026bc05c0802b106e6240d28505cd2a4ba725484ca570ed17c7a254e7a338aefd50ae4f45e3bf2d8ffd4695ac84bda612cd4fbc6dd857735594ac3e0457fd9c7e650c66fc77b562c31ef1ce72e50efc6c2ff3fabfd59c601e98a35fbc269d02a7c274f6d814b167217d3053a50fb5181bfe154ccef4a6f83cdffc84d6483072335302dc9210f0370fb49d01fa9675c9952c660f53b0def14bb37043a979df12da6ee45e3ee721b8364c64f9bf80d3b53c67f4888d1b22defc537c0d50cb0fa1c0074ed63333d8e1095b945f902f303a784d070ab17b68765f138598501387130ab052f0056f3881d376b6eef1b256af53d985ae9e8c2613d6859acb171bd1101c29a5b3319eb0b97459948ff358d596fb1a08388368bc9174756855b3b1a629706bf31d16e9dccb3e05b57581da42995a170f8b77103c24ab1e11f287c7dd1c089238da7c24411148130654996d194c5bcab051a092be26332a16b195248002370eb07a43411aad2128e64c0ca0bbda8bb6b25e87628d8bd55c28666bfb999afa7e0dda689c8fb0d7059f418b2af12a70d310c2bb564551159e52f34ccf38f40ec3d9661b57a91c6f8d84a11f86058b4ca466ec86ccd32ac4d69aa0057d65f04dc6f6da6daf63db98605f948b9b9f191eb9a2fb7b4c6c669f446f7fd47300257a50cdfe05030011def8e0d493254dedb9e343a6e0209b91cd3237e2c6d2ae7f36ecfd5aea18d3a6f65568875e0f05b678701f09de308cdc203112568204e2bfe4dbaac2b2b6fd636c96230c2748e8d111252ce1dd1f208c26528d7df70c37546bbb173a609e591782252adc8245049da8326c3d4c5bb9fd4956de0c1204a9c88987c1a3df6973fb1aa9efdb3d9d6ec7aa69e066487eb29edf789e510e3d454eee28598dee5a505644cbc372d039c5ee473eab761ae5ad64a40e389bd65433d15e42de02d7f1bfb64f5d392999f48c0eb5a1d16a0e31453fe75db5aa2992f395d24f0364653bf9c19dedf45ddc3fd0f13454973785990d18c1d6ae2000000000000000000000000000000000000000000000000000" - }, - "result": { - "gasUsed": "0x7bf31d", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0xfd6e6033273714f103cb791d301d0cc728ec726a9ed10ac961ea47833131ab2b", - "transactionPosition": 83 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [ - 0 - ], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000002b1e7f", - "to": "0xff00000000000000000000000000000000000004", - "gas": "0x3b7567a", - "value": "0x0", - "input": "0x868e10c400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000008318808821a002b1e7f1958cd811a045b259d582042b3e721f3f06ce0795d00e70534f16e2e39d65a22e99601ea49474aee1c6ff65820b9d0d8ad40b13f20a3f521674d65d23f718195e3009e84f1a2b06f61fe851092590780adcd547b9f1054f3eb179481e1437f981389c937872090f003ddb6e08d753861e6f05e90b9990d98c754dee7d6dddcf1941a48d0ee5bc3570fd52520a5ce3d9da7b40f68761b3cf3a7633dde498283d95055e3e32521d8a720d41321026044280910a2478ff76ce23192d695ab518438f967da0da1912608ad89ea0a50d2702b5032f026c62cce894480256c74c639bab56435da3aefad1ad28659d132ecbce88d3f80b0cc4a3048e94f13803b84404f17dc0a2d57b81ccb1255eadf2a55d5fa892eb861314ca95a1d516f63df0bc93c77529573929e10aa35c8e0ba9c225df897ff0d005be902a2d74c4a693893015f8f14311232d816d1fad5a4e005e685d426dd78c531e9563dd8785ac95e44970fac7f3b7d8d6603329604061b626cae7502fbac2ef0c460af26be45e113b2b7a43d935116511231a1c33ac5d769ef3b0c9c3fc626fbec2f8cfb87572e68c2d617897a9fe18d703b26f4253e931ea3e9c1a4b5077fc7656797b8df389b14bb5e2888a7dcdfdf89dcbc81c34d236d31f021a7a207dc806af6f3c89889efedd24f511562a2d01bea7b55afdea26e486bb729cab9b33a63c3360f8d9f267673eee7aab384b4d6e92d99955b0f1d58dce11f6e41dfe0ad020db66d34b9809c01d2f652a567b0ee46bc2ea4743a3f570d96acbc1525a2691d8bc7e5a1abd34f91189f9aacf36cb833f75be11cf12d47a985667f8b65c5444b1995495ea618484846e5038a8bff82cdb012ffb11bfb14934cc26e4e00331511d49ab2634d2d521a0989aca51ba718f0247dbdb88693f094bbb045a4d9f586340fc388fb9b4e0c65b52a38287cb8614bdad6271c3122e845ce8e74fef8bf6a5764d4384c1273fe5ce3037a951b46409d1518c256e26776e58f7f54f710ee3f190b4706de47e1c4dc0f427fc026a55c4b78deda2aebc381b98de5e200a765da1713999ca2a0d4de1ae76f12442e43e367055cb854ca45aed1115a0e9b20aa1fd188613f7c75beb1a2d26617890e786f2bdbab208b9dad19c1e6ec5dd69b6bc65657fbe561f9efae97eda5d4d855841f450d60fdceeddb45f371b5ed86ddf2cd367ad9a977eb4d9161ee549f5ab243513b70fe3f5708d4f13a47731db9898777079601f103ad01acb74075eda60036ae233fc572e41e546081f852496dad5e90ee06d722cd3835294cda3a31b61a3a167138d00aed08ebbe66a970e80334142cfdc870855c9b6d32e5942d526ed4762a13f9372572cb2555bb3aef5428906719050e1aa6a16ebecd5b35a38f940b3d4b12308a155e5bf035484bc1c2955ca84c23c25801065f7bd907b43e98116262a29c52d313f5355adf15b98f7e9038d780a2ef90049625360d7aa4ad4db577bfd9258d5627932993250fbc0b10b037a899612b74ac70c044044ca1e2c4a4cabe88260646811723ec43609158519e563e44a34ac6ca462c1cc2e6d79b48ed604e5fb890f41f0239ee947dcf6daa11da281778f48e78f9280f54d54c728c2ec8299e710c6c3acda600ec4b55e421c4d77376d8da43f1eb65c473c00f08c3ae15348d1049f75449826c0f9ee69361241491d8261d82daded638e80e042cea44cd702a529329aa603704231b24e933aa6d700662ec989717c7bd430cd3023a53221052ec6c3539c2e15518dc75f6118da892225026bc05c0802b106e6240d28505cd2a4ba725484ca570ed17c7a254e7a338aefd50ae4f45e3bf2d8ffd4695ac84bda612cd4fbc6dd857735594ac3e0457fd9c7e650c66fc77b562c31ef1ce72e50efc6c2ff3fabfd59c601e98a35fbc269d02a7c274f6d814b167217d3053a50fb5181bfe154ccef4a6f83cdffc84d6483072335302dc9210f0370fb49d01fa9675c9952c660f53b0def14bb37043a979df12da6ee45e3ee721b8364c64f9bf80d3b53c67f4888d1b22defc537c0d50cb0fa1c0074ed63333d8e1095b945f902f303a784d070ab17b68765f138598501387130ab052f0056f3881d376b6eef1b256af53d985ae9e8c2613d6859acb171bd1101c29a5b3319eb0b97459948ff358d596fb1a08388368bc9174756855b3b1a629706bf31d16e9dccb3e05b57581da42995a170f8b77103c24ab1e11f287c7dd1c089238da7c24411148130654996d194c5bcab051a092be26332a16b195248002370eb07a43411aad2128e64c0ca0bbda8bb6b25e87628d8bd55c28666bfb999afa7e0dda689c8fb0d7059f418b2af12a70d310c2bb564551159e52f34ccf38f40ec3d9661b57a91c6f8d84a11f86058b4ca466ec86ccd32ac4d69aa0057d65f04dc6f6da6daf63db98605f948b9b9f191eb9a2fb7b4c6c669f446f7fd47300257a50cdfe05030011def8e0d493254dedb9e343a6e0209b91cd3237e2c6d2ae7f36ecfd5aea18d3a6f65568875e0f05b678701f09de308cdc203112568204e2bfe4dbaac2b2b6fd636c96230c2748e8d111252ce1dd1f208c26528d7df70c37546bbb173a609e591782252adc8245049da8326c3d4c5bb9fd4956de0c1204a9c88987c1a3df6973fb1aa9efdb3d9d6ec7aa69e066487eb29edf789e510e3d454eee28598dee5a505644cbc372d039c5ee473eab761ae5ad64a40e389bd65433d15e42de02d7f1bfb64f5d392999f48c0eb5a1d16a0e31453fe75db5aa2992f395d24f0364653bf9c19dedf45ddc3fd0f13454973785990d18c1d6ae20d82a5829000182e20381e80220a73bc120d1ef9c13aff71a3ca6e094ceab44768e7463c06c5411d342fa01ea2fd82a5828000181e203922020fcf4f8a87325eae4550a7b7833830414cfa062bec0a61e0de1981315771db603000000000000000000000000000000" - }, - "result": { - "gasUsed": "0x382d430", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0xfd6e6033273714f103cb791d301d0cc728ec726a9ed10ac961ea47833131ab2b", - "transactionPosition": 83 - }, - { - "type": "call", - "subtraces": 3, - "traceAddress": [], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000002cc31b", - "to": "0xff000000000000000000000000000000002cc320", - "gas": "0x30439df", - "value": "0x8abb7a55e7bee0", - "input": "0x868e10c4000000000000000000000000000000000000000000000000000000000000001c00000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000007081818708199e96d82a5829000182e20381e8022012bebfcae441d607d18402467f62c2ab7f7817897031757fc1e3804dc04442311a0037df6b811a045b14c01a004f9a76d82a5828000181e20392202055b250e600ac801d6356d82b0df8a453360fb0ec7084b1910e2759234e15902600000000000000000000000000000000" - }, - "result": { - "gasUsed": "0x20d62cd", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0xf56db0f8b6c0adf7d4b97adb155081e3d4159fb405b85419eb6d94d7d2452c78", - "transactionPosition": 84 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [ - 0 - ], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000002cc320", - "to": "0xff00000000000000000000000000000000000002", - "gas": "0x2f17cd0", - "value": "0x0", - "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "result": { - "gasUsed": "0xfabb0", - "output": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000418282581a000286387ff107ef952f3612eb2e2a6606418e51c0f2cceeda5f57011731c2a04034dd33df402838a815a0b69ec1184b8c104a0001c0dfc71cd077c4b900000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0xf56db0f8b6c0adf7d4b97adb155081e3d4159fb405b85419eb6d94d7d2452c78", - "transactionPosition": 84 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [ - 1 - ], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000002cc320", - "to": "0xff00000000000000000000000000000000000004", - "gas": "0x2e0b788", - "value": "0x0", - "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000009000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "result": { - "gasUsed": "0x105fb2", - "output": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000578449007d2903b8000000004a000190f76c1adff180004c00907e2dd41a18e7c7a7f2bd82581a0001916cb98a2c3dfb67a389a588fb0e593f762dd6c9195851235601fba7e16707ee65746d4671e80aa2bb15bc7d6ebe3b000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0xf56db0f8b6c0adf7d4b97adb155081e3d4159fb405b85419eb6d94d7d2452c78", - "transactionPosition": 84 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [ - 2 - ], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000002cc320", - "to": "0xff00000000000000000000000000000000000005", - "gas": "0x2cea217", - "value": "0x0", - "input": "0x868e10c4000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000f818183081a004f9a76811a045b14c00000000000000000000000000000000000" - }, - "result": { - "gasUsed": "0x476739", - "output": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000002e8181d82a5828000181e20392202055b250e600ac801d6356d82b0df8a453360fb0ec7084b1910e2759234e159026000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0xf56db0f8b6c0adf7d4b97adb155081e3d4159fb405b85419eb6d94d7d2452c78", - "transactionPosition": 84 - }, - { - "type": "call", - "subtraces": 3, - "traceAddress": [], - "action": { - "callType": "call", - "from": "0xff00000000000000000000000000000000241c2e", - "to": "0xff00000000000000000000000000000000241c3f", - "gas": "0x48d6a79", - "value": "0x0", - "input": "0x868e10c4000000000000000000000000000000000000000000000000000000000000001c000000000000000000000000000000000000000000000000000000000000005100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000072818187081a00010a76d82a5829000182e20381e80220c68a4c0a1f9bbacc1b43b94dd70a03f0360cf525da916df0d7021b78895cf1501a0037e0a1811a045b34251a004f93c3d82a5828000181e203922020ee6562b75f62cccc594244cb2f9eb26aca725a060f7eba8ee3c4b32e418c2b230000000000000000000000000000" - }, - "result": { - "gasUsed": "0x347e2a4", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0xd8ee13b065e0b4ca8217629d5ad2cce9dd7000642534213e4531e1b3f8a1da29", - "transactionPosition": 85 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [ - 0 - ], - "action": { - "callType": "call", - "from": "0xff00000000000000000000000000000000241c3f", - "to": "0xff00000000000000000000000000000000000002", - "gas": "0x4820429", - "value": "0x0", - "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "result": { - "gasUsed": "0xfabb0", - "output": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000418282581a000286387ff107ef952f3612eb2e2a6606418e51c0f2cceeda5f57011731c2a04034dd33df402838a815a0b69ec1184b8c104a0001c0dfc71cd077c4b900000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0xd8ee13b065e0b4ca8217629d5ad2cce9dd7000642534213e4531e1b3f8a1da29", - "transactionPosition": 85 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [ - 1 - ], - "action": { - "callType": "call", - "from": "0xff00000000000000000000000000000000241c3f", - "to": "0xff00000000000000000000000000000000000004", - "gas": "0x4713ee1", - "value": "0x0", - "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000009000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "result": { - "gasUsed": "0x105fb2", - "output": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000578449007d2903b8000000004a000190f76c1adff180004c00907e2dd41a18e7c7a7f2bd82581a0001916cb98a2c3dfb67a389a588fb0e593f762dd6c9195851235601fba7e16707ee65746d4671e80aa2bb15bc7d6ebe3b000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0xd8ee13b065e0b4ca8217629d5ad2cce9dd7000642534213e4531e1b3f8a1da29", - "transactionPosition": 85 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [ - 2 - ], - "action": { - "callType": "call", - "from": "0xff00000000000000000000000000000000241c3f", - "to": "0xff00000000000000000000000000000000000005", - "gas": "0x45f2970", - "value": "0x0", - "input": "0x868e10c4000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000f818183081a004f93c3811a045b34250000000000000000000000000000000000" - }, - "result": { - "gasUsed": "0x477613", - "output": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000002e8181d82a5828000181e203922020ee6562b75f62cccc594244cb2f9eb26aca725a060f7eba8ee3c4b32e418c2b23000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0xd8ee13b065e0b4ca8217629d5ad2cce9dd7000642534213e4531e1b3f8a1da29", - "transactionPosition": 85 - }, - { - "type": "call", - "subtraces": 3, - "traceAddress": [], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000002ce389", - "to": "0xff000000000000000000000000000000002ce3be", - "gas": "0x31bd06d", - "value": "0x0", - "input": "0x868e10c4000000000000000000000000000000000000000000000000000000000000001c00000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000007081818708193f04d82a5829000182e20381e80220ee335a22ee6fe624ad6988dc7323bee4e562b9be560510700bf6d6a46914ca021a0037e0f1811a045361b11a00480bc5d82a5828000181e20392202088a1d5a6621a6fe38348e63ea64d091d8ab3060ee1769ad08c5e608db0ce4b3200000000000000000000000000000000" - }, - "result": { - "gasUsed": "0x21e08e5", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0x29a86bf568ea52bc778da4c2f1882f2b2459e8d5e638294f5b3c7341f39452a6", - "transactionPosition": 86 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [ - 0 - ], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000002ce3be", - "to": "0xff00000000000000000000000000000000000002", - "gas": "0x3106a46", - "value": "0x0", - "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "result": { - "gasUsed": "0xfabb0", - "output": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000418282581a000286387ff107ef952f3612eb2e2a6606418e51c0f2cceeda5f57011731c2a04034dd33df402838a815a0b69ec1184b8c104a0001c0dfc71cd077c4b900000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0x29a86bf568ea52bc778da4c2f1882f2b2459e8d5e638294f5b3c7341f39452a6", - "transactionPosition": 86 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [ - 1 - ], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000002ce3be", - "to": "0xff00000000000000000000000000000000000004", - "gas": "0x2ffa4fe", - "value": "0x0", - "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000009000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "result": { - "gasUsed": "0x105fb2", - "output": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000578449007d2903b8000000004a000190f76c1adff180004c00907e2dd41a18e7c7a7f2bd82581a0001916cb98a2c3dfb67a389a588fb0e593f762dd6c9195851235601fba7e16707ee65746d4671e80aa2bb15bc7d6ebe3b000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0x29a86bf568ea52bc778da4c2f1882f2b2459e8d5e638294f5b3c7341f39452a6", - "transactionPosition": 86 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [ - 2 - ], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000002ce3be", - "to": "0xff00000000000000000000000000000000000005", - "gas": "0x2ed8f8d", - "value": "0x0", - "input": "0x868e10c4000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000f818183081a00480bc5811a045361b10000000000000000000000000000000000" - }, - "result": { - "gasUsed": "0x4887a6", - "output": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000002e8181d82a5828000181e20392202088a1d5a6621a6fe38348e63ea64d091d8ab3060ee1769ad08c5e608db0ce4b32000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0x29a86bf568ea52bc778da4c2f1882f2b2459e8d5e638294f5b3c7341f39452a6", - "transactionPosition": 86 - }, - { - "type": "call", - "subtraces": 3, - "traceAddress": [], - "action": { - "callType": "call", - "from": "0xff0000000000000000000000000000000000040f", - "to": "0xff00000000000000000000000000000000000961", - "gas": "0x2223d42", - "value": "0x8abb87dff2adb5", - "input": "0x868e10c4000000000000000000000000000000000000000000000000000000000000001c0000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000708181870819b878d82a5829000182e20381e802204b991204e7215867548c947fe66e43215213e86fc030522fb3f35f9cd9a6e2311a0037df12811a045b0ded1a0047eca7d82a5828000181e203922020864c24ea0ee60743452cd1444db396bb8e7d84b77867be2b737c1cc7b3fc7e0c00000000000000000000000000000000" - }, - "result": { - "gasUsed": "0x1588de7", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0x21e6ee3748a0d4cbe0125a25f22d5f2611d4c3df2f3aa4296736a8470b3ba6da", - "transactionPosition": 87 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [ - 0 - ], - "action": { - "callType": "call", - "from": "0xff00000000000000000000000000000000000961", - "to": "0xff00000000000000000000000000000000000002", - "gas": "0x20f8033", - "value": "0x0", - "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "result": { - "gasUsed": "0xfabb0", - "output": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000418282581a000286387ff107ef952f3612eb2e2a6606418e51c0f2cceeda5f57011731c2a04034dd33df402838a815a0b69ec1184b8c104a0001c0dfc71cd077c4b900000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0x21e6ee3748a0d4cbe0125a25f22d5f2611d4c3df2f3aa4296736a8470b3ba6da", - "transactionPosition": 87 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [ - 1 - ], - "action": { - "callType": "call", - "from": "0xff00000000000000000000000000000000000961", - "to": "0xff00000000000000000000000000000000000004", - "gas": "0x1febaeb", - "value": "0x0", - "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000009000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "result": { - "gasUsed": "0x105fb2", - "output": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000578449007d2903b8000000004a000190f76c1adff180004c00907e2dd41a18e7c7a7f2bd82581a0001916cb98a2c3dfb67a389a588fb0e593f762dd6c9195851235601fba7e16707ee65746d4671e80aa2bb15bc7d6ebe3b000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0x21e6ee3748a0d4cbe0125a25f22d5f2611d4c3df2f3aa4296736a8470b3ba6da", - "transactionPosition": 87 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [ - 2 - ], - "action": { - "callType": "call", - "from": "0xff00000000000000000000000000000000000961", - "to": "0xff00000000000000000000000000000000000005", - "gas": "0x1eca57a", - "value": "0x0", - "input": "0x868e10c4000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000f818183081a0047eca7811a045b0ded0000000000000000000000000000000000" - }, - "result": { - "gasUsed": "0x477494", - "output": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000002e8181d82a5828000181e203922020864c24ea0ee60743452cd1444db396bb8e7d84b77867be2b737c1cc7b3fc7e0c000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0x21e6ee3748a0d4cbe0125a25f22d5f2611d4c3df2f3aa4296736a8470b3ba6da", - "transactionPosition": 87 - }, - { - "type": "call", - "subtraces": 1, - "traceAddress": [], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000002c43aa", - "to": "0xff000000000000000000000000000000002c43b6", - "gas": "0x413ba3c", - "value": "0x1a86cf1d84124d89", - "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000007000000000000000000000000000000000000000000000000000000000000005100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000787821925b959078097b3ea67b0001eb0ba6353d1eab30d9a7d5f9d64b1fc95a1ba6bbe5f11044aba6e3b40ec1fbaa7166ffc8c64971a27daab26e31ce688f7e1f24885ee59ecc8677d59583424689338d48829a9e4d58d2e906dd592bd26d202a5759808a1acc7a6063ece776d981d3d8444a369728d65d875a3666e804785f65460925fcae2cad1defebb202b02eda7b981f6430479db06ac90441ffaefd375988c308c894388c83db61e80aa53106012c61a3ba70d4eab30435d7186a9f6c536c556a1c994e11cb4fd170312132b5c59b29c3f6b4b1188dc51c92352419f4504d3122eabd074144ecfcaff2dfa6c27781368afb9f0a49f872f659845c00949f638c96e473010286922edbe98e8db6132594fe9f487f7fb66bdd7f9a27b7813c1f29be7cd833d9711d2d04bac97f5ef5598f73110020987ae2da8f2db9befd50a033cd74c2d878d12f9b69b5d7e3192a76362ddad3c8f00b2da963f9c44818ebde797a3343b196751522f4de82fded7fc43d2ceb05b143c103821d56694a5f7f01d3678affbd8c58b110c139d7c49442a2e591d8edd45e9b9d62975ff4fa4c2f75fd4073dc6647ead728e690971b348c1dae698a402a0d697ec1aad2b423077d9cc55b913d016b217d90d21f310e4342b5fe901f492aa7d093a2e4567bdf41a4d9e86cd7242467c1003908559f717a2ab1e4e2baf7311364544229550a8ba213596202efd70cac595c51408723a68bb0aa5984e32dcf3bba7328dbc7ad8dc0b8650cc9995bb99eef736fb4a5f8929e66b787ec028fec84d7487e3125220ec2db0f96b85314be66b88cdf061dc2b5a1442ff6341a038fb1118f81fb2718031634313e65516b8fe58643d215778c7c6afae8d5d5c569d3726b757cc558705e99d76eab551b2fe464c4a79e432ad4eefd478646f6f61fe1613ecf973d42466c1b3dec9d6e8cbc1681808ae1bb85bdacc9f8cb060ed9fba00fba8694b1dbd950ab5e4a597058faa19e77990be62cbba63f476a752bfe48aaac4a5ff8bc85b1b0c1f0fe9b1caa247196c3394c8f0f8f13708b77213155d5614f5bf7af0b9c9f574717b5a5d72f4f7367aa0593563e9be8c5f2d4c3f2096d1f3c9d82bfec6b8ce2edf5aeaab4fba6d9b72e95c9638ae856af76fe598b088f6ddabaf8c1aa1af5137cbf681819f4e4db91ae336431e7c1c5363673ea9b332393003e031ae9bf4b20dd5a4a7859365ccedb10c2e2e226e1684f249afca9268c23352fb36e9ebd18cf254b9e6c70268275c5b83ff8b30c7abea185f4969bb3673330d959675f76b6bd839620c8dec153d4a7d79d012d994211acefeebf8b15f0f07fd7a4420e5328f7ec096d3292574d492698fb159987beb422ec5c620a211bdff153e57f567ac37843ca7dc3844961836f78ef5f590d9d1d8c29c9797060b2386dcb0b3d02b7ffe3b2e25cb45f4ba0de07b71a9864e7e0a2dd895bf642f49954ca2a7f95007a2ffb7dfa570c6e124fe007e06d4b66aa8ce81f550ec3c1cc4ce9186425b348d191232c76cb4018703b42495f28e113c80411566c85d61523f71aa08976c181005d30b306168d2c5c88ebdb951603f190dfd3fe48ede2858e02a77b90fcdbdcc34fa5098c9f3cf6142be856da830f43d8ab50b990c29e4a957241946974fb63f8da961b82b11ad4034e90528548659e5aaf3156dbbfbfa8d83c3e07981758442aa3be462c5983908a882ed8fb298e3c6bce51db1fa61c0faed67f7014d9f7fab463b088ec32aad30c680f463009f785e2f74003a90bcc05c4dedc376a11daca09891c2cee6242d8ace7743525c6b8988b41612fdbeea48098605b597b4cf3b736205688e3c1cfb2c5d8e9a70f805014159b9bc02a6baf2cf5daeec39bd1057343572c776f66be721e139ac74a30f6630e165d498640b4a3074689e7ee7bb04a8d0611b9323b4141195d94b26d4147ae734f3002c644c671f8c971e539323ce50fc44b6e045faafd3dac798d67cbb4268707aa9ea783eedf2300989fce3e2a519775bdfb01a25ba57ec7d608700fed67cc95b4cad48e9301edbb790e79de8acdac2a6cf2d3201251e6931b2ac655dcab2a41a761082a3b3d565b27455a4a278f70b58ae17cba43cbb7865fa9915a5129d9e20f7ffbb8a46e7861c85da4d780a5a0af6b7396c7a64ba30023ad9871671c13a682b600f9c64894524ce34dc8ce1509e8cc5ad34e899833aa8f67933d2f6521e9c425f77e8a978bc052aba8c8d23c30dfb1dc40ada63d5743705b8a6dc053e918d8b5e7f6840b2d9810900ed076c6cdcf7e143d8a6a759a9dde1f10b714fa57d2e87350a9d3c57f973a9db4576f68703b901938504a87267da534c2b6f6bb517c229002df0a23cc7dbebf1b81e8412c13786ce60cff177b940a76e5b499de0d8cf8457ae43ae9908f3ae0cc22b9778ec04404efbf93afce4c8f015894d3e4ae82d11f61d41ad02e0be7cf7c8639b8d280dc152fd75e9d0efcbdb9b90502d757f7a2d386f8bd001a95753bf81308b1dc88da58b1f221dd4e8e5217345b7cb0b6fe940874920af33481091a1e3ed6024afab00eb8cd3e15e142d9b5e1446a4554923ce97cb385867786e95304fd8c62dc0de08ba942d7d7493120b4b4a54b379594dc0558381305d6fba675582f6d7761513470ffeb5de56e9448922b452ec437704f08699c4831684239f07162b6debfe74f2dd61ac707cb98aec2a00000000000000000000000000000000000000000000000000" - }, - "result": { - "gasUsed": "0x62aed2", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0xf9b127b6341a6e269f846e39956e38fe62f80c4ba8af19158913490d0d87aca3", - "transactionPosition": 88 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [ - 0 - ], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000002c43b6", - "to": "0xff00000000000000000000000000000000000004", - "gas": "0x3e21973", - "value": "0x0", - "input": "0x868e10c400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000008318808821a002c43b61925b9811a045b1f095820834b0ec1478d47321f74ea8410e823e1b5849bad4ad3f9e99398927317394e795820a6370784af85f07361123ee661399b72bef32bebedf8edc99da47959d5e2c81b59078097b3ea67b0001eb0ba6353d1eab30d9a7d5f9d64b1fc95a1ba6bbe5f11044aba6e3b40ec1fbaa7166ffc8c64971a27daab26e31ce688f7e1f24885ee59ecc8677d59583424689338d48829a9e4d58d2e906dd592bd26d202a5759808a1acc7a6063ece776d981d3d8444a369728d65d875a3666e804785f65460925fcae2cad1defebb202b02eda7b981f6430479db06ac90441ffaefd375988c308c894388c83db61e80aa53106012c61a3ba70d4eab30435d7186a9f6c536c556a1c994e11cb4fd170312132b5c59b29c3f6b4b1188dc51c92352419f4504d3122eabd074144ecfcaff2dfa6c27781368afb9f0a49f872f659845c00949f638c96e473010286922edbe98e8db6132594fe9f487f7fb66bdd7f9a27b7813c1f29be7cd833d9711d2d04bac97f5ef5598f73110020987ae2da8f2db9befd50a033cd74c2d878d12f9b69b5d7e3192a76362ddad3c8f00b2da963f9c44818ebde797a3343b196751522f4de82fded7fc43d2ceb05b143c103821d56694a5f7f01d3678affbd8c58b110c139d7c49442a2e591d8edd45e9b9d62975ff4fa4c2f75fd4073dc6647ead728e690971b348c1dae698a402a0d697ec1aad2b423077d9cc55b913d016b217d90d21f310e4342b5fe901f492aa7d093a2e4567bdf41a4d9e86cd7242467c1003908559f717a2ab1e4e2baf7311364544229550a8ba213596202efd70cac595c51408723a68bb0aa5984e32dcf3bba7328dbc7ad8dc0b8650cc9995bb99eef736fb4a5f8929e66b787ec028fec84d7487e3125220ec2db0f96b85314be66b88cdf061dc2b5a1442ff6341a038fb1118f81fb2718031634313e65516b8fe58643d215778c7c6afae8d5d5c569d3726b757cc558705e99d76eab551b2fe464c4a79e432ad4eefd478646f6f61fe1613ecf973d42466c1b3dec9d6e8cbc1681808ae1bb85bdacc9f8cb060ed9fba00fba8694b1dbd950ab5e4a597058faa19e77990be62cbba63f476a752bfe48aaac4a5ff8bc85b1b0c1f0fe9b1caa247196c3394c8f0f8f13708b77213155d5614f5bf7af0b9c9f574717b5a5d72f4f7367aa0593563e9be8c5f2d4c3f2096d1f3c9d82bfec6b8ce2edf5aeaab4fba6d9b72e95c9638ae856af76fe598b088f6ddabaf8c1aa1af5137cbf681819f4e4db91ae336431e7c1c5363673ea9b332393003e031ae9bf4b20dd5a4a7859365ccedb10c2e2e226e1684f249afca9268c23352fb36e9ebd18cf254b9e6c70268275c5b83ff8b30c7abea185f4969bb3673330d959675f76b6bd839620c8dec153d4a7d79d012d994211acefeebf8b15f0f07fd7a4420e5328f7ec096d3292574d492698fb159987beb422ec5c620a211bdff153e57f567ac37843ca7dc3844961836f78ef5f590d9d1d8c29c9797060b2386dcb0b3d02b7ffe3b2e25cb45f4ba0de07b71a9864e7e0a2dd895bf642f49954ca2a7f95007a2ffb7dfa570c6e124fe007e06d4b66aa8ce81f550ec3c1cc4ce9186425b348d191232c76cb4018703b42495f28e113c80411566c85d61523f71aa08976c181005d30b306168d2c5c88ebdb951603f190dfd3fe48ede2858e02a77b90fcdbdcc34fa5098c9f3cf6142be856da830f43d8ab50b990c29e4a957241946974fb63f8da961b82b11ad4034e90528548659e5aaf3156dbbfbfa8d83c3e07981758442aa3be462c5983908a882ed8fb298e3c6bce51db1fa61c0faed67f7014d9f7fab463b088ec32aad30c680f463009f785e2f74003a90bcc05c4dedc376a11daca09891c2cee6242d8ace7743525c6b8988b41612fdbeea48098605b597b4cf3b736205688e3c1cfb2c5d8e9a70f805014159b9bc02a6baf2cf5daeec39bd1057343572c776f66be721e139ac74a30f6630e165d498640b4a3074689e7ee7bb04a8d0611b9323b4141195d94b26d4147ae734f3002c644c671f8c971e539323ce50fc44b6e045faafd3dac798d67cbb4268707aa9ea783eedf2300989fce3e2a519775bdfb01a25ba57ec7d608700fed67cc95b4cad48e9301edbb790e79de8acdac2a6cf2d3201251e6931b2ac655dcab2a41a761082a3b3d565b27455a4a278f70b58ae17cba43cbb7865fa9915a5129d9e20f7ffbb8a46e7861c85da4d780a5a0af6b7396c7a64ba30023ad9871671c13a682b600f9c64894524ce34dc8ce1509e8cc5ad34e899833aa8f67933d2f6521e9c425f77e8a978bc052aba8c8d23c30dfb1dc40ada63d5743705b8a6dc053e918d8b5e7f6840b2d9810900ed076c6cdcf7e143d8a6a759a9dde1f10b714fa57d2e87350a9d3c57f973a9db4576f68703b901938504a87267da534c2b6f6bb517c229002df0a23cc7dbebf1b81e8412c13786ce60cff177b940a76e5b499de0d8cf8457ae43ae9908f3ae0cc22b9778ec04404efbf93afce4c8f015894d3e4ae82d11f61d41ad02e0be7cf7c8639b8d280dc152fd75e9d0efcbdb9b90502d757f7a2d386f8bd001a95753bf81308b1dc88da58b1f221dd4e8e5217345b7cb0b6fe940874920af33481091a1e3ed6024afab00eb8cd3e15e142d9b5e1446a4554923ce97cb385867786e95304fd8c62dc0de08ba942d7d7493120b4b4a54b379594dc0558381305d6fba675582f6d7761513470ffeb5de56e9448922b452ec437704f08699c4831684239f07162b6debfe74f2dd61ac707cb98aec2ad82a5829000182e20381e802207b7490375005b4ab7a45bfe5fbad53683f38a72527393440345e75a6a7ee7d63d82a5828000181e203922020ef12b88fae9a11c8ef6e7e43fdc9bb2059e9a0fc0e291d68a3c49c9c86b3421f000000000000000000000000000000" - }, - "result": { - "gasUsed": "0x30ed5a3", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0xf9b127b6341a6e269f846e39956e38fe62f80c4ba8af19158913490d0d87aca3", - "transactionPosition": 88 - }, - { - "type": "call", - "subtraces": 1, - "traceAddress": [], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000002b4022", - "to": "0xff000000000000000000000000000000002b404f", - "gas": "0x43a0269", - "value": "0x1a3ede932ea26d50", - "input": "0x868e10c400000000000000000000000000000000000000000000000000000000000000070000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000007878219e3e0590780ace586801c6af561f2983ca1ad7268d41a4eb125eb08064de6fd3f4d9b98b7ead52c60ac58919e54c354f2dbfc689aa3b9cf5dfad2c937f4ac2eb1bb4c2c370a795f86b6ebe359c6f8f830294dcde42d14ee689d057aa9db003483040ac387d810cf29f32e6a5d52cc11e158b8cf21e0094a4697627d342027f95edcb02865213b28f44787c89c2ac5571e0728de3eadb465cfc2072da9f60e124cfb93b7d246b609bb3ec8ef3388efd231bc257757745e9c2aa3faf541294f3043aedf93bb889127de8e1458fb10461df73403003017c924cf4300df4468afa4fddd67d0d0733b8c8c31db8897163041e77509498f218757d2773e3dae955a7aeeefd8a83443182de39ecbfa995fc03fe7fdc55c719c8057f9a4c8576a584d9fe4f6b1541d060d8350ef3203cc2367660197004902c7f3d2811adea9a7a498e73ce55e48ab54b41c29312c21576e904c45c2d9360331b2faddbed720ea9a1b9791669cc9d7f00bd1d5ba70deef07dfbdd438078a7bd91e137b4ef11f95f73ff0066abd34ccc6995aef7549db39eaaf2903c5b3f12e6f2a22c33e71515ecd3797f9f675660677ed657863235563b44c6b3ee9c27b584b90fc1752ee2418c4c0982fb820956825f874f4f23e958243064022f46523520570cc99803877aa7a987fd19bbb6d4e150a68864b7d49edd4c8ddefde9093e9b3f7b7e6a3ed19e7babddb4128f56de6cf96bd1e8904e2c915f3ac3beabcb7ec4995772c6eaccea909cffe8f7833dd6673427426ebd071743a95311d02867b2e1b82454dcd143978e42f968ccd112606dca3c83fa8365e5bcb170d4b7483fb0006ef832120a065c4fec044678551b78afe8a9a0c75af5d5c2b1c3aeaa02c47c63ca538d1e45ffacf6ea891295b264f2802e0b8031fc297d94f177edb50b207c20ef742fad5b9788cee3dabb6a6840f2bd91714e5cab90b173ff8c576544abdb8108f995c0dad05db88e13fc829b80341e2921d74b61fb063bf7e15df5e06f00d03a9f0c98979243ef53c5a1e78b1c592d901310b8efbe96b72ddb6c2e73e353cb3b769bcf83cc603bfd4a76643927b256c977ac06e3f679e38b455305df070ac3bb320390d3bc1fe6a6a0f5ad0a2c9a1fb8a1229d462b6289d5bfbbe613acf29d588b1888a9a3fa7ba9075d67dec51a97dc0e6a94ec455f15fe9173219a21a60ccae610697f44776dc63c07c8b0bc76fe20e17db74014ab5b3aed158c7f6db1cc0d339d6d72b5c2143574af4931306f800d754790d2703d5a7b15344de0f78a2e4aff46351654e4086b8807a6c93a74d5873a3c3d82a4572a0a9c8ba6f39f13effc3ae5451422e27b9ce09a0a5781f22d5b173352c76c969fa192c7ad6dcb8f8159e710fe8eacad3966df41f61375c5e74f3b5d8d8915faa5725a5301a4aca928cb9235aa184a7a853df2a384664e9dac20eb12d0b08c014c21e9b8611d516b5ec78093d8b9d10b8b27438958e2b89f4701726a3d9e3ec3d9a74418c0bb63dbde0e163fa2dc77560a4deaf90b691bb4fe9b11bfa00206245df864d52c75a73926395b7fbcf65f5ec4b87c8911d4d6ceb45b2a8313ed8e9136ab6ae614032e42cc3b097925cf6d8a6aef13a01a0ee363627ac36c8e0b82be6f304baf2598631bb0f24bd12a27147aa8b4476b9c29ba54f27339b9c3061d7729b78f26de21a701b8190c4d8adefaf926b8aa9212a849f34c50a8706764adb4ee8e3888f69e28b47ba3a320128254d7f7d994e0b9084e01fa3162ce7eed9109edb81dd36c8e96c226adb2c6660f2142c240213f721d134cb27afdcd1675fd19d517eaff5a3fc7693238f50fa55a2666bfdda36f1f8109e85471505f3c73b64bad8bdb801ee8ca53130d253c31573129359854ab14ae3eabe228ebea10bc1664ca4cf47b1888432b5766b9cb5467119ea7ad7ac2b20f0692b5c846dd9658babd9eb787b52d38d7feda3b3500f7850d8f1b6a94de3e79e5f6068ab487fc487790041a030747e086a375b473e33e477efe6d6b9a823f8ed7d541b038b41f6b43e9d6fd91a3ece1537843ea8a1a7266411f4b39c5573575b16a4293194f10a007f0335d984b4b06ff1b68181fcfa1b8f65d97358b248dd2c040de6fb99c3d1501620e7be07c32c30be700820f91312d6519824af63b2b171319b17813ab66487cccf75177cdb023122cf5c21040843a2c917cae89f34d870b4dfa2b867c49c96a06f073f20468226aa3e7b89454f79fca095ec962a28c5c40efd492d68ecdfc4da742368fd6956288b4d5144e7a826a805bbffdf1c46bb9b37069917e2fa997e375d5ccd9a6f646ba803eebf0bf1f1ffcf9d366e1862c5dc816d6db5aadbee419841a8cdde740f73afe869a91a6899c96f2993a1c882b55b37a1032639d1968a9e4046b7af3007d0293af5e4b74389847d6a7faa602f6f97610a14b2316c9045bfb9698c95a6da1bb682f44a269eea77e633cabb12b1d5993a97032b85a929584407182a0349ec834fbc6c87e5198e59ac0eb6e9bc83ad4ceca1bc7bca8cb34349e342e22ba2aea22125d46ebb473c47f6269b73d597fa9f82bbc70ea5198b7f48e016392d189125a15888054763581c87424a486b22e775c70ae9913d57df4428af047814233ef6998f4ea5179318af9872bde490053bd8f778257d599d0f481444f1c922010273acf2026fddc17dd6e5b24184515f6f7f10b5b300000000000000000000000000000000000000000000000000" - }, - "result": { - "gasUsed": "0x80a80b", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0xc30e8b748b22427010e409aece99f62953c57de4f68383af4fd363beb0801e8d", - "transactionPosition": 89 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [ - 0 - ], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000002b404f", - "to": "0xff00000000000000000000000000000000000004", - "gas": "0x3ea6944", - "value": "0x0", - "input": "0x868e10c400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000008318808821a002b404f19e3e0811a045b29de5820d92f7e1060a1f96fa21101609789a7b51e4dad941c5ed5345bccc93f19e12cc45820e886746cf4ddfc8999519b72e3a9d45b7d53ebdc4865e810d9d7b78a186c479e590780ace586801c6af561f2983ca1ad7268d41a4eb125eb08064de6fd3f4d9b98b7ead52c60ac58919e54c354f2dbfc689aa3b9cf5dfad2c937f4ac2eb1bb4c2c370a795f86b6ebe359c6f8f830294dcde42d14ee689d057aa9db003483040ac387d810cf29f32e6a5d52cc11e158b8cf21e0094a4697627d342027f95edcb02865213b28f44787c89c2ac5571e0728de3eadb465cfc2072da9f60e124cfb93b7d246b609bb3ec8ef3388efd231bc257757745e9c2aa3faf541294f3043aedf93bb889127de8e1458fb10461df73403003017c924cf4300df4468afa4fddd67d0d0733b8c8c31db8897163041e77509498f218757d2773e3dae955a7aeeefd8a83443182de39ecbfa995fc03fe7fdc55c719c8057f9a4c8576a584d9fe4f6b1541d060d8350ef3203cc2367660197004902c7f3d2811adea9a7a498e73ce55e48ab54b41c29312c21576e904c45c2d9360331b2faddbed720ea9a1b9791669cc9d7f00bd1d5ba70deef07dfbdd438078a7bd91e137b4ef11f95f73ff0066abd34ccc6995aef7549db39eaaf2903c5b3f12e6f2a22c33e71515ecd3797f9f675660677ed657863235563b44c6b3ee9c27b584b90fc1752ee2418c4c0982fb820956825f874f4f23e958243064022f46523520570cc99803877aa7a987fd19bbb6d4e150a68864b7d49edd4c8ddefde9093e9b3f7b7e6a3ed19e7babddb4128f56de6cf96bd1e8904e2c915f3ac3beabcb7ec4995772c6eaccea909cffe8f7833dd6673427426ebd071743a95311d02867b2e1b82454dcd143978e42f968ccd112606dca3c83fa8365e5bcb170d4b7483fb0006ef832120a065c4fec044678551b78afe8a9a0c75af5d5c2b1c3aeaa02c47c63ca538d1e45ffacf6ea891295b264f2802e0b8031fc297d94f177edb50b207c20ef742fad5b9788cee3dabb6a6840f2bd91714e5cab90b173ff8c576544abdb8108f995c0dad05db88e13fc829b80341e2921d74b61fb063bf7e15df5e06f00d03a9f0c98979243ef53c5a1e78b1c592d901310b8efbe96b72ddb6c2e73e353cb3b769bcf83cc603bfd4a76643927b256c977ac06e3f679e38b455305df070ac3bb320390d3bc1fe6a6a0f5ad0a2c9a1fb8a1229d462b6289d5bfbbe613acf29d588b1888a9a3fa7ba9075d67dec51a97dc0e6a94ec455f15fe9173219a21a60ccae610697f44776dc63c07c8b0bc76fe20e17db74014ab5b3aed158c7f6db1cc0d339d6d72b5c2143574af4931306f800d754790d2703d5a7b15344de0f78a2e4aff46351654e4086b8807a6c93a74d5873a3c3d82a4572a0a9c8ba6f39f13effc3ae5451422e27b9ce09a0a5781f22d5b173352c76c969fa192c7ad6dcb8f8159e710fe8eacad3966df41f61375c5e74f3b5d8d8915faa5725a5301a4aca928cb9235aa184a7a853df2a384664e9dac20eb12d0b08c014c21e9b8611d516b5ec78093d8b9d10b8b27438958e2b89f4701726a3d9e3ec3d9a74418c0bb63dbde0e163fa2dc77560a4deaf90b691bb4fe9b11bfa00206245df864d52c75a73926395b7fbcf65f5ec4b87c8911d4d6ceb45b2a8313ed8e9136ab6ae614032e42cc3b097925cf6d8a6aef13a01a0ee363627ac36c8e0b82be6f304baf2598631bb0f24bd12a27147aa8b4476b9c29ba54f27339b9c3061d7729b78f26de21a701b8190c4d8adefaf926b8aa9212a849f34c50a8706764adb4ee8e3888f69e28b47ba3a320128254d7f7d994e0b9084e01fa3162ce7eed9109edb81dd36c8e96c226adb2c6660f2142c240213f721d134cb27afdcd1675fd19d517eaff5a3fc7693238f50fa55a2666bfdda36f1f8109e85471505f3c73b64bad8bdb801ee8ca53130d253c31573129359854ab14ae3eabe228ebea10bc1664ca4cf47b1888432b5766b9cb5467119ea7ad7ac2b20f0692b5c846dd9658babd9eb787b52d38d7feda3b3500f7850d8f1b6a94de3e79e5f6068ab487fc487790041a030747e086a375b473e33e477efe6d6b9a823f8ed7d541b038b41f6b43e9d6fd91a3ece1537843ea8a1a7266411f4b39c5573575b16a4293194f10a007f0335d984b4b06ff1b68181fcfa1b8f65d97358b248dd2c040de6fb99c3d1501620e7be07c32c30be700820f91312d6519824af63b2b171319b17813ab66487cccf75177cdb023122cf5c21040843a2c917cae89f34d870b4dfa2b867c49c96a06f073f20468226aa3e7b89454f79fca095ec962a28c5c40efd492d68ecdfc4da742368fd6956288b4d5144e7a826a805bbffdf1c46bb9b37069917e2fa997e375d5ccd9a6f646ba803eebf0bf1f1ffcf9d366e1862c5dc816d6db5aadbee419841a8cdde740f73afe869a91a6899c96f2993a1c882b55b37a1032639d1968a9e4046b7af3007d0293af5e4b74389847d6a7faa602f6f97610a14b2316c9045bfb9698c95a6da1bb682f44a269eea77e633cabb12b1d5993a97032b85a929584407182a0349ec834fbc6c87e5198e59ac0eb6e9bc83ad4ceca1bc7bca8cb34349e342e22ba2aea22125d46ebb473c47f6269b73d597fa9f82bbc70ea5198b7f48e016392d189125a15888054763581c87424a486b22e775c70ae9913d57df4428af047814233ef6998f4ea5179318af9872bde490053bd8f778257d599d0f481444f1c922010273acf2026fddc17dd6e5b24184515f6f7f10b5b3d82a5829000182e20381e80220602e2506c0e5e031448c0607714e86e9c234bfdc00bb8639580eec0e8ea41373d82a5828000181e203922020d5796125fc67380346b8b2734a81ff08e1e9f457606ef2a4528ea411888fef23000000000000000000000000000000" - }, - "result": { - "gasUsed": "0x312ead6", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0xc30e8b748b22427010e409aece99f62953c57de4f68383af4fd363beb0801e8d", - "transactionPosition": 89 - }, - { - "type": "call", - "subtraces": 3, - "traceAddress": [], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000002cc336", - "to": "0xff000000000000000000000000000000002cc33b", - "gas": "0x3528aa9", - "value": "0x8abb7a55e7bee0", - "input": "0x868e10c4000000000000000000000000000000000000000000000000000000000000001c00000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000007081818708198b12d82a5829000182e20381e80220a4c966fc3621b677c749e58fce4a19d87f495a2bb82e5f5a294900321f38ba3c1a0037e0c2811a045b34061a004f9b81d82a5828000181e2039220200a7617f7ebeee2028f3677e7d760301d0c595d3d786d2daef70aae4cc7fb162100000000000000000000000000000000" - }, - "result": { - "gasUsed": "0x24c0262", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0xdd94aee23e9cfa008afff1f60c7eb2a27e2896878f2be8ab834cd71caf5d3ff8", - "transactionPosition": 90 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [ - 0 - ], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000002cc33b", - "to": "0xff00000000000000000000000000000000000002", - "gas": "0x33fcd9a", - "value": "0x0", - "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "result": { - "gasUsed": "0xfabb0", - "output": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000418282581a000286387ff107ef952f3612eb2e2a6606418e51c0f2cceeda5f57011731c2a04034dd33df402838a815a0b69ec1184b8c104a0001c0dfc71cd077c4b900000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0xdd94aee23e9cfa008afff1f60c7eb2a27e2896878f2be8ab834cd71caf5d3ff8", - "transactionPosition": 90 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [ - 1 - ], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000002cc33b", - "to": "0xff00000000000000000000000000000000000004", - "gas": "0x32f0852", - "value": "0x0", - "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000009000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "result": { - "gasUsed": "0x105fb2", - "output": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000578449007d2903b8000000004a000190f76c1adff180004c00907e2dd41a18e7c7a7f2bd82581a0001916cb98a2c3dfb67a389a588fb0e593f762dd6c9195851235601fba7e16707ee65746d4671e80aa2bb15bc7d6ebe3b000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0xdd94aee23e9cfa008afff1f60c7eb2a27e2896878f2be8ab834cd71caf5d3ff8", - "transactionPosition": 90 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [ - 2 - ], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000002cc33b", - "to": "0xff00000000000000000000000000000000000005", - "gas": "0x31cf2e1", - "value": "0x0", - "input": "0x868e10c4000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000f818183081a004f9b81811a045b34060000000000000000000000000000000000" - }, - "result": { - "gasUsed": "0x476eaa", - "output": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000002e8181d82a5828000181e2039220200a7617f7ebeee2028f3677e7d760301d0c595d3d786d2daef70aae4cc7fb1621000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0xdd94aee23e9cfa008afff1f60c7eb2a27e2896878f2be8ab834cd71caf5d3ff8", - "transactionPosition": 90 - }, - { - "type": "call", - "subtraces": 2, - "traceAddress": [], - "action": { - "callType": "call", - "from": "0xff00000000000000000000000000000000020bb1", - "to": "0xff000000000000000000000000000000000020d3", - "gas": "0x1d9e63c", - "value": "0x0", - "input": "0x868e10c4000000000000000000000000000000000000000000000000000000000000001c0000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000408181870819321dd82a5829000182e20381e80220613a2f762e0bbf0a1fdfeb573b16c9ef105d5ce19c5ec21194cfccdfe2c9c6231a0037e043801a004f8a90f6" - }, - "result": { - "gasUsed": "0x1653202", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0xc4a33ab23d55c88a9eb2745a97e37dd6b44d96b9f14a00a9a1c06df904b759c3", - "transactionPosition": 91 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [ - 0 - ], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000000020d3", - "to": "0xff00000000000000000000000000000000000002", - "gas": "0x1cea250", - "value": "0x0", - "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "result": { - "gasUsed": "0xfabb0", - "output": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000418282581a000286387ff107ef952f3612eb2e2a6606418e51c0f2cceeda5f57011731c2a04034dd33df402838a815a0b69ec1184b8c104a0001c0dfc71cd077c4b900000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0xc4a33ab23d55c88a9eb2745a97e37dd6b44d96b9f14a00a9a1c06df904b759c3", - "transactionPosition": 91 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [ - 1 - ], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000000020d3", - "to": "0xff00000000000000000000000000000000000004", - "gas": "0x1bddd08", - "value": "0x0", - "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000009000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "result": { - "gasUsed": "0x105fb2", - "output": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000578449007d2903b8000000004a000190f76c1adff180004c00907e2dd41a18e7c7a7f2bd82581a0001916cb98a2c3dfb67a389a588fb0e593f762dd6c9195851235601fba7e16707ee65746d4671e80aa2bb15bc7d6ebe3b000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0xc4a33ab23d55c88a9eb2745a97e37dd6b44d96b9f14a00a9a1c06df904b759c3", - "transactionPosition": 91 - }, - { - "type": "call", - "subtraces": 1, - "traceAddress": [], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000001bbcc3", - "to": "0xff000000000000000000000000000000001bba4e", - "gas": "0x608c127", - "value": "0x0", - "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000007000000000000000000000000000000000000000000000000000000000000005100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000789821a002138aa5907808c23dc69fd600fac65df2b08b4ee5595049dce3df8a1ca6c1a0b47067300709c7bba1e1b7edf0f1ae8a11dced9fd646c87042bbbfbf8e2e77651c10597cb746fa0ea2caaddb219a8b79a0fdd9bc84242471f8b097d6e6ee1dc547c7f068905f103fff2cb2220608de1f525e034d46bed5968ad6799e44af0ba7805e86c2124dc262c3c6b8d73c10ab458a651ffde6b77a4621bd6af909eb88bb583559717d37c0363d75329d4f444c47a71e44670c1d7c9f6e08fcdbcc8fe70542e445893dd5ea51f205a5d4695eb2ff2a484df3be74a6462cccced5c7c552ec2e3568d02d4eab26e05da573b652f1c5a6bd51092350f83f803a8e496c069e866a9bf26c956bbeb82488d4410d2cab92a151abcc41526e74a57b4131ddcfa7c2d3e0706b6e8310481e5d7e2bf283ab1ae5650716b4ead437aed5bc50aa57138e976e885c28a1fc6e6e9a09619974b09bca55f34348d718ce52829d085704dab7715d49c2541c4a9301c7b45d85526e4829b4c07505c07508879e27a1f61bd261bf5d64d599f7ba195237ddae768b0fd06c377c24beffb203055e3ffed44a4a8584f5433ab586182a403f41d2ed434452ba32a08f9c24d8fe621b8bfc6c7e3d914c706e589e68563c1ee9c18f561d569dfc486f6765e30bdf165806e41b0648e82f1c9ebcdf574145c3c3bc6b92ff866337d46005ef56b786233c9d87f8793081bb7f903cb1e783152b482e86577a5df8cdb63744a47ab8f6d27b6380b9be7a4c53aba1f8b985e06e9623280468ea84aae5c6b4835a20fc43a40f4a0bbbbf2160586f5072fdbf999a08763587c500af0629bcd25bdf32c542e7e7af6835a6949527b804379ea137a17c623aa5917c802756919ba64a76ea8dcd49cd3478dd5d2f6d8d2f00204c2028c1b49136d750360613e277542de1930ee1559c581f1a6b3842abf100034380ecadd6dd1bc13adef306133549d463f45dc29968ad5c15b0adf9c13e051ebae7cf2a5bcb1a858df6daa3e19c2f01120af1b38e1ce0412135c8a1426fc0f009888ecdc65e0d91ad80e1fd42856598cd06e4dfbc6a9c7600fa56a48f755599fac92e816b9dce6982768819a6e24ecd3e0d8ab12fb35283b974796b9e304cdd961b15770331f0186c6db6966ddbbf7bef2a74ea8881ddc1b5cf22c6f764b0e68782b6df21f4dcd69ead4744298eb49c1ad87f086c1236e1e3ca477a7048c480c781254bc5a68886f5d94ff26d00439ac1805266f20547f11753b0d50e724e8930ce5be8af4d3b325d40eaca084d7050143831ec1bc40b5ad1b06aad472a0ff5cefdaa94aa33e5ad0ed5299436799a9a6b42a1222f56995de79fd7979b712eb8212810f8651832c7f4341ef4be3e2801de16898be3fdcd20f45aecc718c7066e160e0867afb2ac813a572645b20857d24388edfb15259db541187c2c5cb2c438b2068c64b2bf275004dabd8259134108df796d4a9603f9e26626521c718a28fc9a10a30613abb792b2a9e9d464295867fdd94968134d3895e7efc6ce4c884d213a81692625c28db3e18d5e03e3e6dd39fe9894eb02c50689c09890cdd1a27ce56623acd2381e7b38250bfa880d59049cdf1adecfe224446513fca3df71db5b5500ca038fb9569adf1f526926b9bfa66e27df4470baa2f1429bb1c2676cf8877f173c8d8c2e0c152e17cb6794cb80083cce0a2e7a57c31a5ba41913ed9806f2d50e3a889fdc888e24841b62b5f1c9cdc74f1152e882fb789846533a5e4caf0d5f1ea050856b5f7f82f8728a859107978cd95d62652a1e7191b1a296096dd8cf250de8b22e948d4102f2955b61a3effa2b667a49cb58c688bf2510a018e360db50394e57c41d39b866f0144e9bf9856108c83572149b6621a5f9491d732ede5b7bcd7b34f810066df96dbcc341b32a0c7756f05345b66ffddffc5eaa5de22928efcc6c80a949374db5a546008c47ef4fbf296b316dd03a57a40a89c730b9f3602df445547ea5ba4262e51919e2cb4c71ed4f5c63740d347c1471d05a9a266fe318ec306fdb9ddc6211caef273867e3e409dc317e4e04d49e40b474e29456f2f3e6252d2a18ed90e5188d13c74cde5e904d8cdb95c55c636c83c085c58ebaccea0ce2b1ec5b0696932007b538bd977ca26f630e9df8a16c2b031692a160ffa45f5d6e9a625412163c2771a54c4432d61f49ce9de85b475dc33eae4b2886cf4343ce2ed591a9f3f48b1b6bfafe3e59515ff9ee1895596e80c9295004d71ec645a4b840a37057c8b07b7ec4b2a2fa07c3624a48b34a49173b6713e85418fd52bf222ab43029c6d1a629633d78153423134ab11ad45b15376c4bf132fb440b81053df7b1b4f25761ea895ea89b5c512f753859fc182cef73d8a33b958d2cc2d4383f0ada70d8d6d466654f62130959ac09a4c380f8f552d8b1afb6ce83abd2fff317cb0d7b2b347a2b0b46e29ebb6a23810ac8e0c4b90dbd81e268f2ce8659fdf1da7c97c41096d9a0a19a69ccf3f7880d49b669384562670c4255e194bd26f3cca9773feaba4c280ba2e357a6c2981f5b28f711f730bb01db7401c184bda05241d71cf8c10694ccd1f852f9c849133bf5cd34dc343735aeabfdea12a318ebf2a94fedd7161d34c30d724f968741c1738ad0fd94e9667b291342901883f0ed894402afa5d3a113a640e230d4ed85b8fc2af361d15402b618eff62b5acc012fe9dc52b245b0000000000000000000000000000000000000000000000" - }, - "result": { - "gasUsed": "0x7ab5df", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0x42d6ff73ca53378a960a6b23df67cf45e01045369c0dec7d21a7a9eabb2cc8f9", - "transactionPosition": 92 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [ - 0 - ], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000001bba4e", - "to": "0xff00000000000000000000000000000000000004", - "gas": "0x5beec87", - "value": "0x0", - "input": "0x868e10c4000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000082e8808821a001bba4e1a002138aa805820f39f2cdf9f09e7637320a65dc2e350ad731fba586b3fe5cc1838cfd58ff596fe582081773ad568e0121c5b3b01461b0b703611f4bf3317c88a87897e2e8e123489d85907808c23dc69fd600fac65df2b08b4ee5595049dce3df8a1ca6c1a0b47067300709c7bba1e1b7edf0f1ae8a11dced9fd646c87042bbbfbf8e2e77651c10597cb746fa0ea2caaddb219a8b79a0fdd9bc84242471f8b097d6e6ee1dc547c7f068905f103fff2cb2220608de1f525e034d46bed5968ad6799e44af0ba7805e86c2124dc262c3c6b8d73c10ab458a651ffde6b77a4621bd6af909eb88bb583559717d37c0363d75329d4f444c47a71e44670c1d7c9f6e08fcdbcc8fe70542e445893dd5ea51f205a5d4695eb2ff2a484df3be74a6462cccced5c7c552ec2e3568d02d4eab26e05da573b652f1c5a6bd51092350f83f803a8e496c069e866a9bf26c956bbeb82488d4410d2cab92a151abcc41526e74a57b4131ddcfa7c2d3e0706b6e8310481e5d7e2bf283ab1ae5650716b4ead437aed5bc50aa57138e976e885c28a1fc6e6e9a09619974b09bca55f34348d718ce52829d085704dab7715d49c2541c4a9301c7b45d85526e4829b4c07505c07508879e27a1f61bd261bf5d64d599f7ba195237ddae768b0fd06c377c24beffb203055e3ffed44a4a8584f5433ab586182a403f41d2ed434452ba32a08f9c24d8fe621b8bfc6c7e3d914c706e589e68563c1ee9c18f561d569dfc486f6765e30bdf165806e41b0648e82f1c9ebcdf574145c3c3bc6b92ff866337d46005ef56b786233c9d87f8793081bb7f903cb1e783152b482e86577a5df8cdb63744a47ab8f6d27b6380b9be7a4c53aba1f8b985e06e9623280468ea84aae5c6b4835a20fc43a40f4a0bbbbf2160586f5072fdbf999a08763587c500af0629bcd25bdf32c542e7e7af6835a6949527b804379ea137a17c623aa5917c802756919ba64a76ea8dcd49cd3478dd5d2f6d8d2f00204c2028c1b49136d750360613e277542de1930ee1559c581f1a6b3842abf100034380ecadd6dd1bc13adef306133549d463f45dc29968ad5c15b0adf9c13e051ebae7cf2a5bcb1a858df6daa3e19c2f01120af1b38e1ce0412135c8a1426fc0f009888ecdc65e0d91ad80e1fd42856598cd06e4dfbc6a9c7600fa56a48f755599fac92e816b9dce6982768819a6e24ecd3e0d8ab12fb35283b974796b9e304cdd961b15770331f0186c6db6966ddbbf7bef2a74ea8881ddc1b5cf22c6f764b0e68782b6df21f4dcd69ead4744298eb49c1ad87f086c1236e1e3ca477a7048c480c781254bc5a68886f5d94ff26d00439ac1805266f20547f11753b0d50e724e8930ce5be8af4d3b325d40eaca084d7050143831ec1bc40b5ad1b06aad472a0ff5cefdaa94aa33e5ad0ed5299436799a9a6b42a1222f56995de79fd7979b712eb8212810f8651832c7f4341ef4be3e2801de16898be3fdcd20f45aecc718c7066e160e0867afb2ac813a572645b20857d24388edfb15259db541187c2c5cb2c438b2068c64b2bf275004dabd8259134108df796d4a9603f9e26626521c718a28fc9a10a30613abb792b2a9e9d464295867fdd94968134d3895e7efc6ce4c884d213a81692625c28db3e18d5e03e3e6dd39fe9894eb02c50689c09890cdd1a27ce56623acd2381e7b38250bfa880d59049cdf1adecfe224446513fca3df71db5b5500ca038fb9569adf1f526926b9bfa66e27df4470baa2f1429bb1c2676cf8877f173c8d8c2e0c152e17cb6794cb80083cce0a2e7a57c31a5ba41913ed9806f2d50e3a889fdc888e24841b62b5f1c9cdc74f1152e882fb789846533a5e4caf0d5f1ea050856b5f7f82f8728a859107978cd95d62652a1e7191b1a296096dd8cf250de8b22e948d4102f2955b61a3effa2b667a49cb58c688bf2510a018e360db50394e57c41d39b866f0144e9bf9856108c83572149b6621a5f9491d732ede5b7bcd7b34f810066df96dbcc341b32a0c7756f05345b66ffddffc5eaa5de22928efcc6c80a949374db5a546008c47ef4fbf296b316dd03a57a40a89c730b9f3602df445547ea5ba4262e51919e2cb4c71ed4f5c63740d347c1471d05a9a266fe318ec306fdb9ddc6211caef273867e3e409dc317e4e04d49e40b474e29456f2f3e6252d2a18ed90e5188d13c74cde5e904d8cdb95c55c636c83c085c58ebaccea0ce2b1ec5b0696932007b538bd977ca26f630e9df8a16c2b031692a160ffa45f5d6e9a625412163c2771a54c4432d61f49ce9de85b475dc33eae4b2886cf4343ce2ed591a9f3f48b1b6bfafe3e59515ff9ee1895596e80c9295004d71ec645a4b840a37057c8b07b7ec4b2a2fa07c3624a48b34a49173b6713e85418fd52bf222ab43029c6d1a629633d78153423134ab11ad45b15376c4bf132fb440b81053df7b1b4f25761ea895ea89b5c512f753859fc182cef73d8a33b958d2cc2d4383f0ada70d8d6d466654f62130959ac09a4c380f8f552d8b1afb6ce83abd2fff317cb0d7b2b347a2b0b46e29ebb6a23810ac8e0c4b90dbd81e268f2ce8659fdf1da7c97c41096d9a0a19a69ccf3f7880d49b669384562670c4255e194bd26f3cca9773feaba4c280ba2e357a6c2981f5b28f711f730bb01db7401c184bda05241d71cf8c10694ccd1f852f9c849133bf5cd34dc343735aeabfdea12a318ebf2a94fedd7161d34c30d724f968741c1738ad0fd94e9667b291342901883f0ed894402afa5d3a113a640e230d4ed85b8fc2af361d15402b618eff62b5acc012fe9dc52b245bd82a5829000182e20381e80220c76afb4b41e42e410d2e7e6626ff6b8b33164323e57d3ce64128fbc5010c6860d82a5828000181e203922020077e5fde35c50a9303a55009e3498a4ebedff39c42b710b730d8ec7ac7afa63e000000000000000000000000000000000000" - }, - "result": { - "gasUsed": "0x316261a", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0x42d6ff73ca53378a960a6b23df67cf45e01045369c0dec7d21a7a9eabb2cc8f9", - "transactionPosition": 92 - }, - { - "type": "call", - "subtraces": 1, - "traceAddress": [], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000001bbcc3", - "to": "0xff000000000000000000000000000000001bba4e", - "gas": "0x6adeeba", - "value": "0x0", - "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000007000000000000000000000000000000000000000000000000000000000000005100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000789821a00213857590780b1007b4492daf38cf92b272c7955ed27cd37a1eb17c5e88286ec68b3e21e1461cc1e34069af515a13e1835681f32b368b80a88a4bcb813e6e266251c6f33426c0ee3e8522218409424fdfbb592efc4d5ba8707eb6179df411f1524ee1949b98610e80b9d4b7be48c8bab6b64122a65146aa69f06a7bfe7dcf37596e1d1ddd1b6bca0d1aee19bbbc64f93a79e325cf2e69313642489c202aa3f20834782f9dc0b39e47d670d7ee6a9582d42bd06d77442076d3af3fdefacf2883c2bf52a87e25395748a4c99ff87d6ed45d6343c695bd515adaabeb7a79f4a333ff05c0e73c138a2dc3e540261bc57d7517327c8f81d0a8ac554f0b4407913e9dfffd128c3d0f8a7231f6ecf1023174389f7857f93ab45187fe90ed964e1b63cecc666b202f3f8031eb67cdd680f5ac27bb20aafe403baef23887c9e5983f5222f29bebb67b81ed4e9e4bb32b5c93120b2a13cd83b658d8f5989ff15d3aa81f86fca8850e414c2ea642b71d30bdd6a394394abc13e0d29b32bcbf9cb1843c07124c1c3a960b16c9796b59d275b98430fba4ffcbeb084f96238b1e936727613135ae0dd145fb72a87494f04f54f4855b2e4e699b1eababeabdfb405110110e0e55a3f93c7e89fe6c41f12f79f1f49c45331d2cc3e4653902901092b2b8ef047e2c717aa633d2596184a4b41834bc8473c80348699db9f228964bd4c86f6a8dd41963b3f3cf6673dc47a7fad3b13cda21489782d85aec609a2b03fe1ee546c367eaeba5e010456c7bca14242391787781874d71b7c04e302c7eea0731e869cf55f43a50b4660ef90b59b498b3c00eac63b5f163f921ee30438a214dfb3f1a152578b59d12ffac6f41613edfcacf34d517ab03fee2559f97c97cf212bb8aaf891ca30703ac9d1c06e1ddda63152027199c6c9e6f84e57b801357d3b9db691330d7ad1832cd70a9e821847e10113500d47424c7b7964aee8003dfe2f00f96d31575ac5228e7ae8dd60f1987fa5e2df0b9f733db586b65ad998ad6d88d622756de188a9b03c2311163a6e4771566976fbe573104a637f4888947cf15c0c0655dbce862b386a3ca53081b3b76f81958a5bd20bb5dbcdf009c2a7088c3d8784d51f682669b8f1b45f4764cd073aaa77a8089cee546ecdae57bd94b36d24bfb7e9199407c3cfcdf47fb4b6f681a8dd46017b1da29f0ec3b1d18f14166c5089c7ef26620f7503ddf6d4c1640f4d12e820c8ccc72598d33702f7d2483712306c217b646f938510c81afcda60bfcfbad6a134e18654b85180e4b08285b6b2c79c561af610d6c8ada60e88bc28e7ef39844611db8cb095a2ecfa622b31397cb93711e9207b77cf53867bb9993a97822c38fb055750c9a8022a0f0d90face484c80044eb15c2cafd7cdefb48476ab92cd458e5cfddd975ae85439522466afe858a792d929451c9fc080817e1be3056c3380f912b660d3ec2bf0d42a6fbe609d410e25367b219926c4105110766c1286e389ee46e47d3b1d3434b5365d851bad4e1f77d3e45f8ee3f65365c50f88ad65fa31b0b5701f0d7e3336d462d271a518c1642dbd94f8282faf96f00d85ef614ed27d10bd8479de6ead9109e94091154c162276d5c66fe99b3ffe43e5cb2c8dbc545a0dd131c4a30dc7a0eddb3ae96cace625bf72b9140ed306d90ac5057fbfd4b4b9679e1392f8df971cc1cba61a82779c41a3ba13ca860541c5aded732e46aa71bb64385f83ffa339548afbff8bfa9d289f41125e47fade43eaf9ca210a0d850b1b313023290bf0766f252b83450325221f80262b144db67c40f49b410cad37925cab42e97b1b9f37a485df0ff0b37ef87632ae07c593730bed2538a47fa2717561e0cfaa1c7b3d81e82c0947aac9ea8ec8a40055c1bbebd8cd5e8352bb82c4bfc5c90bbcbb6e6b5388fc7f158b7d1c6d4a4eafb0e496e801ac010aa6989b4d6bb0490642aa76bb4349bee326a8b00c06e9ba01ce15165a4dbb5e7da927fa59cd9175f5ba95e800ff43cdb1e65e19d48e552b2608850ef47e9b40ca7b1c0a3464937f4480211d6f4baae37ca8c956dfcebc6fe4e9389ac61c359ed6a4c9fa2edcfc70ded57ec626f4b7fd758613b104a9caf590cd07c4dd82140b06cbda7aa6d051d7b7d3842b4bd19971ae089e0a6816b3fa483a3d43fe8a4c722ab531a380ba1f50214f63cd0478ba8a1692e724d0c8f66cf4ef24f105e19d6dd2f4a889d5870009daf12259400e055f47799a8af59e5af090f063000bf9249e0873416878ae2e5d96b99559ba442fc66135822748dca90a17b62aba124c9dce11f4bc10de045ef27c250b0bbd300340abb044c0650646aed8570e3001c881254040027e8e8292f539b3cc41cade3427ce1e2fac0925efae5202961ed0eda80f590a19af89bef00fddabc96ee60dd5b9a7354bfd368de599d3f39fe6d00ca22ca43411abdd9519c4ba55e35799b1769f23509637131f493bd9561bb6c284a0711f22b702d7e3d8bc1adcc135bfcf7612277bf88801919448799005e25fa86afabd45820ec3178b0d62de904693b93366a31c9963bafb678f81168abed95b5efdfdb88f181bed43e298bc88ebce505591e9a31f1de187e713f22c1b01299f73e1e69c396d246576157bbec8200ac414ec7ea11c880682b2c2727d4ff5c28b1a049e3b369d992b932605b571f87d8cc9bae645ee150d9624f911e704c64cf9a3c28223d60000000000000000000000000000000000000000000000" - }, - "result": { - "gasUsed": "0x7aae4a", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0xf099c22c6ed9edfc55c1546600f7467d56f5e43350d300b9e11e76de226edc2e", - "transactionPosition": 93 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [ - 0 - ], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000001bba4e", - "to": "0xff00000000000000000000000000000000000004", - "gas": "0x66421af", - "value": "0x0", - "input": "0x868e10c4000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000082e8808821a001bba4e1a002138578058206b302393a7aff44a8f259b4ca67a22a687cc8e7df35eda9dc30dd32aed8a1afc582091901695a71c68c04728449c97e6f2842d92b0bb83026300a51b3bc8583385ca590780b1007b4492daf38cf92b272c7955ed27cd37a1eb17c5e88286ec68b3e21e1461cc1e34069af515a13e1835681f32b368b80a88a4bcb813e6e266251c6f33426c0ee3e8522218409424fdfbb592efc4d5ba8707eb6179df411f1524ee1949b98610e80b9d4b7be48c8bab6b64122a65146aa69f06a7bfe7dcf37596e1d1ddd1b6bca0d1aee19bbbc64f93a79e325cf2e69313642489c202aa3f20834782f9dc0b39e47d670d7ee6a9582d42bd06d77442076d3af3fdefacf2883c2bf52a87e25395748a4c99ff87d6ed45d6343c695bd515adaabeb7a79f4a333ff05c0e73c138a2dc3e540261bc57d7517327c8f81d0a8ac554f0b4407913e9dfffd128c3d0f8a7231f6ecf1023174389f7857f93ab45187fe90ed964e1b63cecc666b202f3f8031eb67cdd680f5ac27bb20aafe403baef23887c9e5983f5222f29bebb67b81ed4e9e4bb32b5c93120b2a13cd83b658d8f5989ff15d3aa81f86fca8850e414c2ea642b71d30bdd6a394394abc13e0d29b32bcbf9cb1843c07124c1c3a960b16c9796b59d275b98430fba4ffcbeb084f96238b1e936727613135ae0dd145fb72a87494f04f54f4855b2e4e699b1eababeabdfb405110110e0e55a3f93c7e89fe6c41f12f79f1f49c45331d2cc3e4653902901092b2b8ef047e2c717aa633d2596184a4b41834bc8473c80348699db9f228964bd4c86f6a8dd41963b3f3cf6673dc47a7fad3b13cda21489782d85aec609a2b03fe1ee546c367eaeba5e010456c7bca14242391787781874d71b7c04e302c7eea0731e869cf55f43a50b4660ef90b59b498b3c00eac63b5f163f921ee30438a214dfb3f1a152578b59d12ffac6f41613edfcacf34d517ab03fee2559f97c97cf212bb8aaf891ca30703ac9d1c06e1ddda63152027199c6c9e6f84e57b801357d3b9db691330d7ad1832cd70a9e821847e10113500d47424c7b7964aee8003dfe2f00f96d31575ac5228e7ae8dd60f1987fa5e2df0b9f733db586b65ad998ad6d88d622756de188a9b03c2311163a6e4771566976fbe573104a637f4888947cf15c0c0655dbce862b386a3ca53081b3b76f81958a5bd20bb5dbcdf009c2a7088c3d8784d51f682669b8f1b45f4764cd073aaa77a8089cee546ecdae57bd94b36d24bfb7e9199407c3cfcdf47fb4b6f681a8dd46017b1da29f0ec3b1d18f14166c5089c7ef26620f7503ddf6d4c1640f4d12e820c8ccc72598d33702f7d2483712306c217b646f938510c81afcda60bfcfbad6a134e18654b85180e4b08285b6b2c79c561af610d6c8ada60e88bc28e7ef39844611db8cb095a2ecfa622b31397cb93711e9207b77cf53867bb9993a97822c38fb055750c9a8022a0f0d90face484c80044eb15c2cafd7cdefb48476ab92cd458e5cfddd975ae85439522466afe858a792d929451c9fc080817e1be3056c3380f912b660d3ec2bf0d42a6fbe609d410e25367b219926c4105110766c1286e389ee46e47d3b1d3434b5365d851bad4e1f77d3e45f8ee3f65365c50f88ad65fa31b0b5701f0d7e3336d462d271a518c1642dbd94f8282faf96f00d85ef614ed27d10bd8479de6ead9109e94091154c162276d5c66fe99b3ffe43e5cb2c8dbc545a0dd131c4a30dc7a0eddb3ae96cace625bf72b9140ed306d90ac5057fbfd4b4b9679e1392f8df971cc1cba61a82779c41a3ba13ca860541c5aded732e46aa71bb64385f83ffa339548afbff8bfa9d289f41125e47fade43eaf9ca210a0d850b1b313023290bf0766f252b83450325221f80262b144db67c40f49b410cad37925cab42e97b1b9f37a485df0ff0b37ef87632ae07c593730bed2538a47fa2717561e0cfaa1c7b3d81e82c0947aac9ea8ec8a40055c1bbebd8cd5e8352bb82c4bfc5c90bbcbb6e6b5388fc7f158b7d1c6d4a4eafb0e496e801ac010aa6989b4d6bb0490642aa76bb4349bee326a8b00c06e9ba01ce15165a4dbb5e7da927fa59cd9175f5ba95e800ff43cdb1e65e19d48e552b2608850ef47e9b40ca7b1c0a3464937f4480211d6f4baae37ca8c956dfcebc6fe4e9389ac61c359ed6a4c9fa2edcfc70ded57ec626f4b7fd758613b104a9caf590cd07c4dd82140b06cbda7aa6d051d7b7d3842b4bd19971ae089e0a6816b3fa483a3d43fe8a4c722ab531a380ba1f50214f63cd0478ba8a1692e724d0c8f66cf4ef24f105e19d6dd2f4a889d5870009daf12259400e055f47799a8af59e5af090f063000bf9249e0873416878ae2e5d96b99559ba442fc66135822748dca90a17b62aba124c9dce11f4bc10de045ef27c250b0bbd300340abb044c0650646aed8570e3001c881254040027e8e8292f539b3cc41cade3427ce1e2fac0925efae5202961ed0eda80f590a19af89bef00fddabc96ee60dd5b9a7354bfd368de599d3f39fe6d00ca22ca43411abdd9519c4ba55e35799b1769f23509637131f493bd9561bb6c284a0711f22b702d7e3d8bc1adcc135bfcf7612277bf88801919448799005e25fa86afabd45820ec3178b0d62de904693b93366a31c9963bafb678f81168abed95b5efdfdb88f181bed43e298bc88ebce505591e9a31f1de187e713f22c1b01299f73e1e69c396d246576157bbec8200ac414ec7ea11c880682b2c2727d4ff5c28b1a049e3b369d992b932605b571f87d8cc9bae645ee150d9624f911e704c64cf9a3c28223d6d82a5829000182e20381e80220b004dbb253f292969d2a45ab68bd917a5bb6b2f9031f74638403a29361b03e0fd82a5828000181e203922020077e5fde35c50a9303a55009e3498a4ebedff39c42b710b730d8ec7ac7afa63e000000000000000000000000000000000000" - }, - "result": { - "gasUsed": "0x38bc17e", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0xf099c22c6ed9edfc55c1546600f7467d56f5e43350d300b9e11e76de226edc2e", - "transactionPosition": 93 - }, - { - "type": "call", - "subtraces": 1, - "traceAddress": [], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000001bbcc3", - "to": "0xff000000000000000000000000000000001bba4e", - "gas": "0x65623bf", - "value": "0x0", - "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000007000000000000000000000000000000000000000000000000000000000000005100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000789821a002138a5590780938809e4804b3536e70cb0531840c36f70f8131906dc55738323fd254b0f8f18832e0f414eecb7ab09e0c9fa971b9524a2df8199e2302002c584d0cfcd96dc2551dd37f06539dbd8721dfda01c3cdb7e8dbb9d4a3fc353056055197b0841ea550a104147ed57d86249ee9838f05fc23ac3656bd18c527862d624b75f4fb29cf1c86cdb9b232b44e35695742b71800b68824d4d88eb47c7ba20babaca7beb5fb74007d26e5f285fce5ecaeac4bad411b8987d887dc99d0030b2861eb7dc4700a9b82b3371e8454fab416288f44520e1308947cc468c68b489340804774cf5c8a4577f7749a84202b8f855170c681dcd0994bbdd6753f174ea6efbadf483278d57cbc93eb3dc9dc841cd3e7f70c93280117a3c3080a498a56b9dc66fcad5732ec70cec935046665103a1d0ed7629409d8cb0f5b8280ec38b07752a7b52bad8142d8c1a1bdf9fa05da66508e62ed1076ea78f4af3062b5546c4fe03dfcf49079c442d4ec450dd90a193ef337323b15c6076feb095dafda9640abcbe8a7c4233226c991ad259bcfaf2db6b55117614b6dec5dcbaadff4fb2ecc0152f74b6f78d7eea32ba4262e26e52246a91c2862f1477a38ea6bc1fa5805a16996af8b23fdb702f4ee31616c827af71b730c19392162b08c449cb72407d720b27ffd9b2b59fc3370db1eb60969f3b4f7c52796d8047963c2872bda2dc3f53ceb0e5bd9861e9ba55fe2ec7efd872b47f4841f4629dc0632197dff41ffdbb9f76176d9255d08a9f757c534b4dd6ecfb6f2700b11208d9f247d2b13661e6c761283d89824688d66049a1fbcc4dc6f623fe513f878a5c5a9de8b89482f9fe61e7dc9ba1f94d53b2162050f03a9056dcbdf255447e2a342e64a281daff99c80fc4460d23571a7bde6f3ec7c74e80adbf0e53b17e88b505d4bd5e54d4daa881819cdd5143d174130ace2e0a4ae39d81d37b2f2130b9fac40b14540bbaae81a0f156e76bb6c40000fae97789f1bd64a43f7ac4869b7d82b1dc569faaf5f4e578001dc2dc4e4b693f78464257d056de197ca2ebf24d02db47ed078a005a3653d3fecc334b6db1c37594781fac82018e230badade7730f75d55c29a594a73539a675fac512fdced85a95b79a874a585ff8a560c0e95bff0cfbd0790a96981373361d59342de7aa41b2592a98e6188626495895fc92b48fef4a34b39b07186fa9c1385e1d4cb12c1046f1483d1736f3881f98162cb507827c265c2f7d20f0a56334e75e056cc245fd34eaba22535ac6f6b81a94d78499a2659908a4af8ef74e19c6c1750589b1168a14cd43f4ba90eeabd5db32ca22056357aa79c914c87430f8e033e9d1ca73a60dd593c8d9b3150771833b871514f590d261ee8172649cbcb1c0af79cddf194261e0b0953f14edf1ded13a435e02dde8a7afa0aa2e858165fd467dacbfd8ad5cb59e51c11dadbab16a24aa34e8bb20a5d52bb4126dd9ef86420ec17a96cdf05aad211c7cec103a46c2b4ce956c73b51cb8421deef8ee19b13d8aa58daf0ae56e202436c193919b844b06fb9b21e9e2a900387a76e68cc654e16def4047035f485fc8b1618e987319d7950ccb42d6a70c8725e49dafff9a476a374a14cd09adef5a5c4b04dfa4887c6b64c4355d3a444c435f22275f7b5d8fc491236e62a0edeb5d2ac2a6d9d68ba7c3e1e6906a03d995934fbd9849b791ae648cdc0f1494ce758bff37b0116f3526cdafab18120f491323065fa345c2066e99d9ba2337bfec891a27e4ab0918d78652c329926d4186bccba63aba373b410e48301bebb03aadc8c36efed11575dd2ef1cbc5b9495dc498733dd4c7eb97c3019ec2f7a13da5c61f4130f4e89fad38b65b3bfaec1b846f70456b78869e25db44e67fbb97c6a680a9812b105c60b77074dca69c0d16a904da5388c5f935835f48e40eaf1e2da59fa2f5598b9653adf96dcaf14c68d5aadfa744fbfcb8688f195d6b8260b9f25015a58779b0ce0c00b1ef06f241effb557985c0680d131f9ba8e8f5d1bf351df45766fe4dacb08602352ac292f27e875c1adce61cc9a71bef08f567bb77e5f7586354711eb1c968bd4aa18966cbfd945b706e1cac579278aa5392da1204b1857c72b2ae68cbedf01c632e4e0aac3f88f691711d80ac557ae31ef84e69007fe74ff3195a3137f363a783418ab3cf1dc2df032e9d0ff227426b003249254b786dfa00b3a950dfd7225b6a372252e0ab276734ca6d61b86b268ca42a5aaf8134f9597ad3f6e6892d3d6aae36305cfc23c52cf519f882d61f8d43303fef819be2a11166fe83170cd28701662da9f08853b2853c09999197098cd894b0ac636192dee9d773a1d4299d93a64fe7f3b4c3897ba23652bc6635e6bbb66120e108e4d1f65ad78aa5ab3f170d7ca0e42626c279e685b3dd432f15a521c0a0e98e5fbcd0ba0d9eefcaa7b01d68b04b31c71ce9799a86f88410e84a3b61201ed738f737fd408cc284dfc7c66fef73490d4b39a0e23430833764084f60c98923296607f8c4fe4ca962118eac687c891fa444c9ccc72147f948be34b412cffcf5a8d635640388bea532cbca5cf32704c3139459d07dc2a82f77d2046008d1bd797a318b07a671879392e67e0bace396957faf2ed64393ea7ae07df1d70381b0bc3e2c70cac7c5f2274c87fac7c56bd4cd3af4297c538ec1fc4f8e862edbf759b59f052bf3aa6e87acbc2ccb78944d0000000000000000000000000000000000000000000000" - }, - "result": { - "gasUsed": "0x6e6346", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0x293e1eb922ef1b490b4d2c4c14b8e51c383ab38933509fdee3de8a7b4b96d0dd", - "transactionPosition": 94 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [ - 0 - ], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000001bba4e", - "to": "0xff00000000000000000000000000000000000004", - "gas": "0x618a1b7", - "value": "0x0", - "input": "0x868e10c4000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000082e8808821a001bba4e1a002138a5805820fe51bab9960b3c80d4c6c8357c418a5593f2c35c2a7337a94a6e6ca9486bb990582081773ad568e0121c5b3b01461b0b703611f4bf3317c88a87897e2e8e123489d8590780938809e4804b3536e70cb0531840c36f70f8131906dc55738323fd254b0f8f18832e0f414eecb7ab09e0c9fa971b9524a2df8199e2302002c584d0cfcd96dc2551dd37f06539dbd8721dfda01c3cdb7e8dbb9d4a3fc353056055197b0841ea550a104147ed57d86249ee9838f05fc23ac3656bd18c527862d624b75f4fb29cf1c86cdb9b232b44e35695742b71800b68824d4d88eb47c7ba20babaca7beb5fb74007d26e5f285fce5ecaeac4bad411b8987d887dc99d0030b2861eb7dc4700a9b82b3371e8454fab416288f44520e1308947cc468c68b489340804774cf5c8a4577f7749a84202b8f855170c681dcd0994bbdd6753f174ea6efbadf483278d57cbc93eb3dc9dc841cd3e7f70c93280117a3c3080a498a56b9dc66fcad5732ec70cec935046665103a1d0ed7629409d8cb0f5b8280ec38b07752a7b52bad8142d8c1a1bdf9fa05da66508e62ed1076ea78f4af3062b5546c4fe03dfcf49079c442d4ec450dd90a193ef337323b15c6076feb095dafda9640abcbe8a7c4233226c991ad259bcfaf2db6b55117614b6dec5dcbaadff4fb2ecc0152f74b6f78d7eea32ba4262e26e52246a91c2862f1477a38ea6bc1fa5805a16996af8b23fdb702f4ee31616c827af71b730c19392162b08c449cb72407d720b27ffd9b2b59fc3370db1eb60969f3b4f7c52796d8047963c2872bda2dc3f53ceb0e5bd9861e9ba55fe2ec7efd872b47f4841f4629dc0632197dff41ffdbb9f76176d9255d08a9f757c534b4dd6ecfb6f2700b11208d9f247d2b13661e6c761283d89824688d66049a1fbcc4dc6f623fe513f878a5c5a9de8b89482f9fe61e7dc9ba1f94d53b2162050f03a9056dcbdf255447e2a342e64a281daff99c80fc4460d23571a7bde6f3ec7c74e80adbf0e53b17e88b505d4bd5e54d4daa881819cdd5143d174130ace2e0a4ae39d81d37b2f2130b9fac40b14540bbaae81a0f156e76bb6c40000fae97789f1bd64a43f7ac4869b7d82b1dc569faaf5f4e578001dc2dc4e4b693f78464257d056de197ca2ebf24d02db47ed078a005a3653d3fecc334b6db1c37594781fac82018e230badade7730f75d55c29a594a73539a675fac512fdced85a95b79a874a585ff8a560c0e95bff0cfbd0790a96981373361d59342de7aa41b2592a98e6188626495895fc92b48fef4a34b39b07186fa9c1385e1d4cb12c1046f1483d1736f3881f98162cb507827c265c2f7d20f0a56334e75e056cc245fd34eaba22535ac6f6b81a94d78499a2659908a4af8ef74e19c6c1750589b1168a14cd43f4ba90eeabd5db32ca22056357aa79c914c87430f8e033e9d1ca73a60dd593c8d9b3150771833b871514f590d261ee8172649cbcb1c0af79cddf194261e0b0953f14edf1ded13a435e02dde8a7afa0aa2e858165fd467dacbfd8ad5cb59e51c11dadbab16a24aa34e8bb20a5d52bb4126dd9ef86420ec17a96cdf05aad211c7cec103a46c2b4ce956c73b51cb8421deef8ee19b13d8aa58daf0ae56e202436c193919b844b06fb9b21e9e2a900387a76e68cc654e16def4047035f485fc8b1618e987319d7950ccb42d6a70c8725e49dafff9a476a374a14cd09adef5a5c4b04dfa4887c6b64c4355d3a444c435f22275f7b5d8fc491236e62a0edeb5d2ac2a6d9d68ba7c3e1e6906a03d995934fbd9849b791ae648cdc0f1494ce758bff37b0116f3526cdafab18120f491323065fa345c2066e99d9ba2337bfec891a27e4ab0918d78652c329926d4186bccba63aba373b410e48301bebb03aadc8c36efed11575dd2ef1cbc5b9495dc498733dd4c7eb97c3019ec2f7a13da5c61f4130f4e89fad38b65b3bfaec1b846f70456b78869e25db44e67fbb97c6a680a9812b105c60b77074dca69c0d16a904da5388c5f935835f48e40eaf1e2da59fa2f5598b9653adf96dcaf14c68d5aadfa744fbfcb8688f195d6b8260b9f25015a58779b0ce0c00b1ef06f241effb557985c0680d131f9ba8e8f5d1bf351df45766fe4dacb08602352ac292f27e875c1adce61cc9a71bef08f567bb77e5f7586354711eb1c968bd4aa18966cbfd945b706e1cac579278aa5392da1204b1857c72b2ae68cbedf01c632e4e0aac3f88f691711d80ac557ae31ef84e69007fe74ff3195a3137f363a783418ab3cf1dc2df032e9d0ff227426b003249254b786dfa00b3a950dfd7225b6a372252e0ab276734ca6d61b86b268ca42a5aaf8134f9597ad3f6e6892d3d6aae36305cfc23c52cf519f882d61f8d43303fef819be2a11166fe83170cd28701662da9f08853b2853c09999197098cd894b0ac636192dee9d773a1d4299d93a64fe7f3b4c3897ba23652bc6635e6bbb66120e108e4d1f65ad78aa5ab3f170d7ca0e42626c279e685b3dd432f15a521c0a0e98e5fbcd0ba0d9eefcaa7b01d68b04b31c71ce9799a86f88410e84a3b61201ed738f737fd408cc284dfc7c66fef73490d4b39a0e23430833764084f60c98923296607f8c4fe4ca962118eac687c891fa444c9ccc72147f948be34b412cffcf5a8d635640388bea532cbca5cf32704c3139459d07dc2a82f77d2046008d1bd797a318b07a671879392e67e0bace396957faf2ed64393ea7ae07df1d70381b0bc3e2c70cac7c5f2274c87fac7c56bd4cd3af4297c538ec1fc4f8e862edbf759b59f052bf3aa6e87acbc2ccb78944dd82a5829000182e20381e80220a85c7858b9d76a0adb990410e518b6022325a8bd1743404b270c1ce80c9af42dd82a5828000181e203922020077e5fde35c50a9303a55009e3498a4ebedff39c42b710b730d8ec7ac7afa63e000000000000000000000000000000000000" - }, - "result": { - "gasUsed": "0x3f9c875", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0x293e1eb922ef1b490b4d2c4c14b8e51c383ab38933509fdee3de8a7b4b96d0dd", - "transactionPosition": 94 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000001fb931", - "to": "0xff0000000000000000000000000000000012d687", - "gas": "0x18c2204", - "value": "0x0", - "input": "0x868e10c400000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000f285058182004081820e58c081685d1d10d34edf3142ea3abfb4d9265e7ea2389b907852444da5f0952c0046f45b50589f6653be572e2590f82f26a4ada3ba4aa1637b7831390365b606352026d216bfc959d08c69ca7965439b86c03356b396062bcb98a458de9f302598960a06fdcdb4ac9ad54cecd47fffda00ea3b8a6fc666669325d25980c3ed20bfaf296f814dbc68978a23a30e03c28fc03fac83f85f9a7c23f15894643a8bef5046b4800a556ef86ff61691c7adfd35382506baf6713194c9f8a3c28be624078ba01a0037e5f8582047ab5fe94c06cdbaec8b74525a8ef23928a1bbb62440ccbcd599b10154404d690000000000000000000000000000" - }, - "result": { - "gasUsed": "0x149e229", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0xa66054d53dc628a9f4132fad4beb3b4c8b77a9ecd878c31d58d9773310643125", - "transactionPosition": 95 - }, - { - "type": "call", - "subtraces": 1, - "traceAddress": [], - "action": { - "callType": "call", - "from": "0xff00000000000000000000000000000000235fdd", - "to": "0xff0000000000000000000000000000000001dd67", - "gas": "0x4e8c5d3", - "value": "0x1a604e782960a295", - "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000007000000000000000000000000000000000000000000000000000000000000005100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000789821a000a0023590780b1f46cf7da5aa8c412fdf20a3d29ffcb51e2090c60eca8ce7a6755cf86aac57d7dd57e66936da82659fbd0c263a7e9f38f3f539c4612078bb4cd042c493dbf3f240d6d2cc8716015b23e328f81bd082f1eacc23be4f00d1a36ae41db43600b4f0e95245fc967a52846d2510376596d14beea198356a3bebb4fc3cfff9b6239b25f5aa7c7ae64601deaed2be87b43a690b207bde83a79cb313f63bce1733fb94edae6bf878fa48803ef520cc8ae554ac1f34af9f55fb2717ce4d8dc9a59f1883d88c2b30a9303dbf7e63d1ba121ccb1d9105105d677a218602469cb651779b2fb8833e3a2fb949662511eb0e48ec57ad3b897e378aade2102729778bf7d9fac14e84a170aaa8a859ae05bd1617bbdee6d18e4f6742f19fcfc4d060f829cc2a19c1901d1c0a05e1ed11268b6218f9b393b29aa9c38fecabfdf11bf3781144e1e8b9aaf33b49b5ac2998cd0bcc19da4393eadf4334ab0053ee3076f4d6df1ed36fce20e00e4a735d3afc854a8752e50b7719a5feedaaa19e3067deea209e79a4bb0aee06ada2bdd419f02248ae079c7d9948c97b7d448e95326fd49a6abdf9506e3ed3a6dec1aecf431b0db2e42092f194fa9a71f648f9037c0a60ff5cab539b4e2ff47cca05d9c68b57acd0659005362fc753908599b75f2e403881bf3a17ea0550d9a1983496c891611b29dcec699c1332ff50847c22ec04a8e102f019aadbec4ef48c92879f449987188031bb4ed8cb68d0512ce2ec9204f85365793b9d9ea58941baab7823223a15805bd194ca9866d74dc3abf6ddec70633fe928518b951f39671b4d21b2c2c12500671848cbc84bb6cf98057fce0cb90f993cbc69c1e106d6195581eb8eb441a7c1502057a9f68f28ef4856c69a3643c3490129176378adfa1a297857a7f2f2ca80383c8ad7fa9969ef8bab649368a92c268b730ee01302e12604ea9644e5b0d1e640d7aca21e09f0cbedcf0eb6327a59d3e98503437ded0fc5894ef0440130f16727512ffbec3bfa4fd02753b12a0c65a040b71984bed42457725c9b2c9f494709f17fe698c157a690cf89339cc91f412109e3765bf76e6951ff2be4cf037b0463266d0bea907d01e76b1ce8e8240fdf9cff6c89baf70b232063f849885461b9d6206663e4d2833819de0187c675cb90d5bac958312fca8e926a113933f99251f8366a39fe273d1f2964c98e007acd7d15afa67ca70f8ff19f6a810ccb02a9e88991b7f54fc1b7248f5e740a6a90e362beb8a3c8bd20971ea11e3a5d5a541015b0690cca58f1e528fbe3b738e24a30e51dd775989f2dba69d89a2abe38865dde95af7a69bda1c610d2944f14de8edc0bdced10dfc0f3fd98e840f0627444e20da4b7947be112db42c1adb81230a603fa69fc0a8f3c5c87da309297a8c5d4a47eb3ee07000f81ad7b96dc27cd2639b359b08f63f79be56d41675c9af34d60f9142fd8c16d8489192ed4587cf2fc8029036d12a134b4c00c8002123b500acda1257dd1c501cf76f84c153cf5c018a3b18eeb1d63d39771938799043cd413e7b8e68b5a42d7054e609b0b5e857b878591dca7c5930fee7e313af3b8c1b48d49b3bafd6710d5ac22f13b4f63f12103bc779855772b229add4c5a21aa769fe3d0f6cad677e2d8a1c6c6da878f2cf49d4b42be7a617536bd924c93645bac6b43fef51efdccbc8679525fcaef83e89a8d41418644b2ab90813ea70a9d999f6b9a7eb233b48dc7928eda4917b07e3e0cba92674dbc8b0168d5d821d0bc093e7ba8c4c020e7a2374a96299b6f85c94a5ea467baab46b24fe798efc5e8debfd737aba2899b9bc759aec49102fa2e50915f3fbbad9ae2cb3f0ceaf287753a97330067c4e45df6da8bb01a34a6e76ccf228a28be3b6de92d158967a65898358bd47b4605ff64e320a62e4a72a394bc35df7e90cec68d8e4df0a771e3958474af79eb4d5376125e3f0b363714b9986d6985263f45f8580e05ec91e51123c868a9470fc490597432364ad04f20653b8335114b15f5c18de5e362ebc32ae2512dddfccc5c4db97e77bfeb4394ba114e2f9447c953ec89a94c39a25f8ec0bd2b0f89e3762043501aed99d6159ab4abeab32f66967fb6cab04fb56ee6ffd652095daa21642ae9083d5afec856642f9ecca82cac18df1f1fef5c868d946759af794f04319696b6fc63a83e75de58192c5f4bf21ef5e6c69a8b6afa88f4daa6c5cba2f03787b3c6cbf33421012bd9788a58dabd91d3b7769c901a46c31ed41d0459afeece50ac8de9d97c91c40c041b1173537a1f49b183ae68b6944756a63fd6000f6f881584d8e7e4863a9f3247956f108ed85a11c3b3d9e60634a62828b38322c845cd5ac80420146154d55fe1bac709803c803ece6de386d0df9d379f9ab10b82f0a29cf2d98e6ba564d2a8368508dc85c8b9876ce150fe7d88e4684723e0190c12de8200207fa88fe3af9590c8f0b19d0f0cc4ea1276779fa6815ce4294147f9189355a4dc71446560f06948889f4b7ef9b2c720260607cd9b01a69de0eec1432cafea0f87c5a1b6d0d11cbdc51c20a724238cdfd949e1221e13e8b29ea7c170eac8d9981eed96e72d59443212096d6299e286db80e661fd68bbd2d49434b90e11db7627860b17531086e59e049d5a510fb61c86c94fbc7fed1474c59280346f879698715675210bedbeab847af0e6c88d478dc20148d564fba21e557dcec0000000000000000000000000000000000000000000000" - }, - "result": { - "gasUsed": "0x9b3e40", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0xc209712792b9bb2ca548df72c743c2efa109181e953fac3f07e8e7026d7aa78f", - "transactionPosition": 96 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [ - 0 - ], - "action": { - "callType": "call", - "from": "0xff0000000000000000000000000000000001dd67", - "to": "0xff00000000000000000000000000000000000004", - "gas": "0x47e9165", - "value": "0x0", - "input": "0x868e10c400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000008338808821a0001dd671a000a0023811a045b18855820e846cbc689aebc3ec97bcdcb44da2e58c3e092c75ee715dfa353496d8d00286a582040123884cdb875d63ca4b9ceeb2f02840ac99070376e535012b6a5ed5323deee590780b1f46cf7da5aa8c412fdf20a3d29ffcb51e2090c60eca8ce7a6755cf86aac57d7dd57e66936da82659fbd0c263a7e9f38f3f539c4612078bb4cd042c493dbf3f240d6d2cc8716015b23e328f81bd082f1eacc23be4f00d1a36ae41db43600b4f0e95245fc967a52846d2510376596d14beea198356a3bebb4fc3cfff9b6239b25f5aa7c7ae64601deaed2be87b43a690b207bde83a79cb313f63bce1733fb94edae6bf878fa48803ef520cc8ae554ac1f34af9f55fb2717ce4d8dc9a59f1883d88c2b30a9303dbf7e63d1ba121ccb1d9105105d677a218602469cb651779b2fb8833e3a2fb949662511eb0e48ec57ad3b897e378aade2102729778bf7d9fac14e84a170aaa8a859ae05bd1617bbdee6d18e4f6742f19fcfc4d060f829cc2a19c1901d1c0a05e1ed11268b6218f9b393b29aa9c38fecabfdf11bf3781144e1e8b9aaf33b49b5ac2998cd0bcc19da4393eadf4334ab0053ee3076f4d6df1ed36fce20e00e4a735d3afc854a8752e50b7719a5feedaaa19e3067deea209e79a4bb0aee06ada2bdd419f02248ae079c7d9948c97b7d448e95326fd49a6abdf9506e3ed3a6dec1aecf431b0db2e42092f194fa9a71f648f9037c0a60ff5cab539b4e2ff47cca05d9c68b57acd0659005362fc753908599b75f2e403881bf3a17ea0550d9a1983496c891611b29dcec699c1332ff50847c22ec04a8e102f019aadbec4ef48c92879f449987188031bb4ed8cb68d0512ce2ec9204f85365793b9d9ea58941baab7823223a15805bd194ca9866d74dc3abf6ddec70633fe928518b951f39671b4d21b2c2c12500671848cbc84bb6cf98057fce0cb90f993cbc69c1e106d6195581eb8eb441a7c1502057a9f68f28ef4856c69a3643c3490129176378adfa1a297857a7f2f2ca80383c8ad7fa9969ef8bab649368a92c268b730ee01302e12604ea9644e5b0d1e640d7aca21e09f0cbedcf0eb6327a59d3e98503437ded0fc5894ef0440130f16727512ffbec3bfa4fd02753b12a0c65a040b71984bed42457725c9b2c9f494709f17fe698c157a690cf89339cc91f412109e3765bf76e6951ff2be4cf037b0463266d0bea907d01e76b1ce8e8240fdf9cff6c89baf70b232063f849885461b9d6206663e4d2833819de0187c675cb90d5bac958312fca8e926a113933f99251f8366a39fe273d1f2964c98e007acd7d15afa67ca70f8ff19f6a810ccb02a9e88991b7f54fc1b7248f5e740a6a90e362beb8a3c8bd20971ea11e3a5d5a541015b0690cca58f1e528fbe3b738e24a30e51dd775989f2dba69d89a2abe38865dde95af7a69bda1c610d2944f14de8edc0bdced10dfc0f3fd98e840f0627444e20da4b7947be112db42c1adb81230a603fa69fc0a8f3c5c87da309297a8c5d4a47eb3ee07000f81ad7b96dc27cd2639b359b08f63f79be56d41675c9af34d60f9142fd8c16d8489192ed4587cf2fc8029036d12a134b4c00c8002123b500acda1257dd1c501cf76f84c153cf5c018a3b18eeb1d63d39771938799043cd413e7b8e68b5a42d7054e609b0b5e857b878591dca7c5930fee7e313af3b8c1b48d49b3bafd6710d5ac22f13b4f63f12103bc779855772b229add4c5a21aa769fe3d0f6cad677e2d8a1c6c6da878f2cf49d4b42be7a617536bd924c93645bac6b43fef51efdccbc8679525fcaef83e89a8d41418644b2ab90813ea70a9d999f6b9a7eb233b48dc7928eda4917b07e3e0cba92674dbc8b0168d5d821d0bc093e7ba8c4c020e7a2374a96299b6f85c94a5ea467baab46b24fe798efc5e8debfd737aba2899b9bc759aec49102fa2e50915f3fbbad9ae2cb3f0ceaf287753a97330067c4e45df6da8bb01a34a6e76ccf228a28be3b6de92d158967a65898358bd47b4605ff64e320a62e4a72a394bc35df7e90cec68d8e4df0a771e3958474af79eb4d5376125e3f0b363714b9986d6985263f45f8580e05ec91e51123c868a9470fc490597432364ad04f20653b8335114b15f5c18de5e362ebc32ae2512dddfccc5c4db97e77bfeb4394ba114e2f9447c953ec89a94c39a25f8ec0bd2b0f89e3762043501aed99d6159ab4abeab32f66967fb6cab04fb56ee6ffd652095daa21642ae9083d5afec856642f9ecca82cac18df1f1fef5c868d946759af794f04319696b6fc63a83e75de58192c5f4bf21ef5e6c69a8b6afa88f4daa6c5cba2f03787b3c6cbf33421012bd9788a58dabd91d3b7769c901a46c31ed41d0459afeece50ac8de9d97c91c40c041b1173537a1f49b183ae68b6944756a63fd6000f6f881584d8e7e4863a9f3247956f108ed85a11c3b3d9e60634a62828b38322c845cd5ac80420146154d55fe1bac709803c803ece6de386d0df9d379f9ab10b82f0a29cf2d98e6ba564d2a8368508dc85c8b9876ce150fe7d88e4684723e0190c12de8200207fa88fe3af9590c8f0b19d0f0cc4ea1276779fa6815ce4294147f9189355a4dc71446560f06948889f4b7ef9b2c720260607cd9b01a69de0eec1432cafea0f87c5a1b6d0d11cbdc51c20a724238cdfd949e1221e13e8b29ea7c170eac8d9981eed96e72d59443212096d6299e286db80e661fd68bbd2d49434b90e11db7627860b17531086e59e049d5a510fb61c86c94fbc7fed1474c59280346f879698715675210bedbeab847af0e6c88d478dc20148d564fba21e557dcecd82a5829000182e20381e802209e50cb8175ce18db05fdce8f8651f33386cf196129b4be40a075c99187914609d82a5828000181e203922020af0d7d76927d5d2e47081e7d3d428177f43834c9386416c54fca266b3444f21700000000000000000000000000" - }, - "result": { - "gasUsed": "0x31bebc8", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0xc209712792b9bb2ca548df72c743c2efa109181e953fac3f07e8e7026d7aa78f", - "transactionPosition": 96 - }, - { - "type": "call", - "subtraces": 7, - "traceAddress": [], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000001bac42", - "to": "0xff00000000000000000000000000000000000005", - "gas": "0x1273e026", - "value": "0x0", - "input": "0x868e10c400000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000eb8181828bd82a5828000181e20392202056bc2504599e6513bd273965038a95a26005013ceb8a54569a0046a513b162371b0000000800000000f555010f29c20971f6e29cead00b57277fb5975fd83f914400a29f74783b6261666b726569686572366e326c6f657735777166783777797768616e63347a74346c6a7776703263356f62727968346a756f356a6371616637651a003814d61a004f81164048001b6a51c29fe8e04058420192040362a715acc12b1e0985cf7fa25f4935d562a4e79f1699945b56c51e1cd242a0363d28cb1f9d735816b771f086829db7b4c88fc94f03a4814cfb597a9d1601000000000000000000000000000000000000000000" - }, - "result": { - "gasUsed": "0x8e9f7bd", - "output": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000982811a045b576b410c0000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0x1fc5fcce6d413a9fd7bd827fd070aeeec4762d054b583f5633da53347117a8b4", - "transactionPosition": 97 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [ - 0 - ], - "action": { - "callType": "call", - "from": "0xff00000000000000000000000000000000000005", - "to": "0xff000000000000000000000000000000001d0fa2", - "gas": "0x12604c76", - "value": "0x0", - "input": "0x868e10c40000000000000000000000000000000000000000000000000000000014c1cb970000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000054400c2d86e000000000000000000000000000000000000000000000000000000" - }, - "result": { - "gasUsed": "0x13301e", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000001f500000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0x1fc5fcce6d413a9fd7bd827fd070aeeec4762d054b583f5633da53347117a8b4", - "transactionPosition": 97 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [ - 1 - ], - "action": { - "callType": "call", - "from": "0xff00000000000000000000000000000000000005", - "to": "0xff00000000000000000000000000000000000002", - "gas": "0x124c779f", - "value": "0x0", - "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "result": { - "gasUsed": "0xfabb0", - "output": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000418282581a000286387ff107ef952f3612eb2e2a6606418e51c0f2cceeda5f57011731c2a04034dd33df402838a815a0b69ec1184b8c104a0001c0dfc71cd077c4b900000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0x1fc5fcce6d413a9fd7bd827fd070aeeec4762d054b583f5633da53347117a8b4", - "transactionPosition": 97 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [ - 2 - ], - "action": { - "callType": "call", - "from": "0xff00000000000000000000000000000000000005", - "to": "0xff00000000000000000000000000000000000004", - "gas": "0x123babbf", - "value": "0x0", - "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000009000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "result": { - "gasUsed": "0x105fb2", - "output": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000578449007d2903b8000000004a000190f76c1adff180004c00907e2dd41a18e7c7a7f2bd82581a0001916cb98a2c3dfb67a389a588fb0e593f762dd6c9195851235601fba7e16707ee65746d4671e80aa2bb15bc7d6ebe3b000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0x1fc5fcce6d413a9fd7bd827fd070aeeec4762d054b583f5633da53347117a8b4", - "transactionPosition": 97 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [ - 3 - ], - "action": { - "callType": "staticcall", - "from": "0xff00000000000000000000000000000000000005", - "to": "0xff0000000000000000000000000000000021f2a6", - "gas": "0x1228a5ee", - "value": "0x0", - "input": "0x868e10c4000000000000000000000000000000000000000000000000000000009d8b06780000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000ea82584192040362a715acc12b1e0985cf7fa25f4935d562a4e79f1699945b56c51e1cd242a0363d28cb1f9d735816b771f086829db7b4c88fc94f03a4814cfb597a9d160158a48bd82a5828000181e20392202056bc2504599e6513bd273965038a95a26005013ceb8a54569a0046a513b162371b0000000800000000f555010f29c20971f6e29cead00b57277fb5975fd83f914400a29f74783b6261666b726569686572366e326c6f657735777166783777797768616e63347a74346c6a7776703263356f62727968346a756f356a6371616637651a003814d61a004f81164048001b6a51c29fe8e04000000000000000000000000000000000000000000000" - }, - "result": { - "gasUsed": "0x2e9853", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000001f500000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0x1fc5fcce6d413a9fd7bd827fd070aeeec4762d054b583f5633da53347117a8b4", - "transactionPosition": 97 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [ - 4 - ], - "action": { - "callType": "call", - "from": "0xff00000000000000000000000000000000000005", - "to": "0xff00000000000000000000000000000000000007", - "gas": "0x111c5e8e", - "value": "0x0", - "input": "0x868e10c400000000000000000000000000000000000000000000000000000000c26ddbd50000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000064500a6e587010000000000000000000000000000000000000000000000000000" - }, - "result": { - "gasUsed": "0x2ce23f", - "output": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000104f00120654f8b6172b36ea1e89d8000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0x1fc5fcce6d413a9fd7bd827fd070aeeec4762d054b583f5633da53347117a8b4", - "transactionPosition": 97 - }, - { - "type": "call", - "subtraces": 1, - "traceAddress": [ - 5 - ], - "action": { - "callType": "call", - "from": "0xff00000000000000000000000000000000000005", - "to": "0xff00000000000000000000000000000000000007", - "gas": "0x10ece94f", - "value": "0x0", - "input": "0x868e10c400000000000000000000000000000000000000000000000000000000d7d4deed000000000000000000000000000000000000000000000000000000000000005100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000067844500a6e587014200064d006f05b59d3b20000000000000584d8281861a001d0fa2d82a5828000181e20392202056bc2504599e6513bd273965038a95a26005013ceb8a54569a0046a513b162371b00000008000000001a00176c401a001b60c01a003814d68000000000000000000000000000000000000000000000000000" - }, - "result": { - "gasUsed": "0x378d1f2", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000043844f001205e5f30079f016ea1e89d80000500006a8d5434e2fdfc905110000000000520002f050b9c8c93d441ca1915680000000004d83820180820080811a034d18bd0000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0x1fc5fcce6d413a9fd7bd827fd070aeeec4762d054b583f5633da53347117a8b4", - "transactionPosition": 97 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [ - 5, - 0 - ], - "action": { - "callType": "call", - "from": "0xff00000000000000000000000000000000000007", - "to": "0xff00000000000000000000000000000000000006", - "gas": "0xddc0709", - "value": "0x0", - "input": "0x868e10c400000000000000000000000000000000000000000000000000000000de180de300000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000006e821a85223bdf5866861a0021f2a606054d006f05b59d3b20000000000000584d8281861a001d0fa2d82a5828000181e20392202056bc2504599e6513bd273965038a95a26005013ceb8a54569a0046a513b162371b00000008000000001a00176c401a001b60c01a003814d68040000000000000000000000000000000000000" - }, - "result": { - "gasUsed": "0x2807281", - "output": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000d83820180820080811a034d18bd00000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0x1fc5fcce6d413a9fd7bd827fd070aeeec4762d054b583f5633da53347117a8b4", - "transactionPosition": 97 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [ - 6 - ], - "action": { - "callType": "call", - "from": "0xff00000000000000000000000000000000000005", - "to": "0xff0000000000000000000000000000000021f2a6", - "gas": "0x311f73c", - "value": "0x0", - "input": "0x868e10c400000000000000000000000000000000000000000000000000000000f98c996600000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000009c8258948bd82a5828000181e20392202056bc2504599e6513bd273965038a95a26005013ceb8a54569a0046a513b162371b0000000800000000f54500a6e587014400a29f74783b6261666b726569686572366e326c6f657735777166783777797768616e63347a74346c6a7776703263356f62727968346a756f356a6371616637651a003814d61a004f81164048001b6a51c29fe8e0401a045b576b00000000" - }, - "result": { - "gasUsed": "0x9858a", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0x1fc5fcce6d413a9fd7bd827fd070aeeec4762d054b583f5633da53347117a8b4", - "transactionPosition": 97 - }, - { - "type": "call", - "subtraces": 1, - "traceAddress": [], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000002ce389", - "to": "0xff000000000000000000000000000000002ce3be", - "gas": "0x41499d9", - "value": "0x0", - "input": "0x868e10c4000000000000000000000000000000000000000000000000000000000000000700000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000078782193e18590780912ab6e26f928bffa3c68c9a467677eec142ecf7a2d7c57711f54aa9bebf313780e8f80cc5646a351ad95444b99ed0dca55ae5fad63d8694ef89818973ae10a1aeb4b104ae7f4803b0465d687d24c4311ff1a29125137f091a1f384e83c6dd6f0f31df5cea648ebaf3bf23e192e5570a56c6146a1a8b60124d047a11b0985d11ad2611bdf7042e2cd719dd55845779cc93293d56ec0c121918b2a2f69de6f516102859fea9b3e7aae7ccbc556e73015f404fbe8f2a6084a013f1b746d69c755b8533c06e199c3a3cd991793311106e59a79f319b007afbc7ec2b8cb93f9a42f14f2e5e7f39e26aa6b677313eca6a1f00ab0ce08d0eb91c0f1c9fad8069012697d54aa1ec9caaedad169001eaa0665504405c765b25f4d1209bb69b2f4b3f52390f1e0235828501c87f43b5bae1f51d54c5bb5e513d33441385086b40c08b2787d88350ef8b2e4e26ae68eb77d6e47c91b19bf6180433e342c57b7128d755a3bb1508a42134043ff697ca4915e666cb1e98ee0176480144a92c4d2a239e16343090965542b9073d87d453707027cd41b3cc4bedf13cd490a355e4da51322757aeb1c42aa193a7c848e55ab81a6e43696084f9c5965d467b8b797db16b4cdd8f15576a7f2efc5ec18c356c7a079edaca1be4b842a820c2776049f49c3e237a6f8d096f7473ca10b1b419adbce98aa92d0d34e7926d463166bf2b7afdaad927c6649d3b719a14db07e358a9b817ba1df393813cea360cf3754a12449716490218ec147c01f394fd5a78e1fd1efd1825f74b6c5ca1640b328ad5b7078f3d00b8988f85acb0bd284a1515d31b7daf53f780c22179fceafcbbef9e4cf3e99c4028671aba7c2adfcf2b455e914f962705f1c96e97042c1eedc665844f2bdf90159975255aaef853a9198cbfcbfdd982ea59ce4ccf92b9463035534c450743eafa9148eb11fafb866c07d79d4cdc3ae79ed0f2b95579adbd9ed25fa3d18a9a3ed4c936a47c9d670cc583a98d71490c569bc4637f93deea73cfa34ed7fd465c93e714b33431255c3300d0d03055df972b067d19bacbfe7fc79b25db29ac9718f188ec0421b2defd12c408a48c5d33bbc655fcce869995f33f9fd53f3dc36a73b7a362430bca272639b40d134e1d7405bdd8983c7bac2d8fb7208c6219b0899ae317015eaebcceee8fc9b93d573cbe70d378e50ba9a969f4a69407524a4023ef97aef3c01d128576f63b5699cb5c691a7039c4ef3ca9af3baf713495f149d7425cb417c52052f3e530e1e438dcf8b627f0263a27caa010917169b91952820d1a46a4b4f54357fda87463a5c866ce6afe7d9b23eefe2e173d737fe492470fa481ab883e0a82b86637364dcdc03c47318423f7f512839510ad64b82dd392b5d0c6acc2b824a9727a7717819dd8aba714e2eb11bf32348c632f06d66ab968e070ee328e357c90db353536eb2a2edca946abafd2f26b6f9ad6096fb44894d5a4d50e573d9c1c2d0fb3bfa5ec202c581b1cc08fd7594ebe6892ce53457fd7268bac26471341a438f811c9130d5397d61a98351e79d64ca387ede4e93eeaced4a56528a04f1299d9086887b2c726eb504e111af2445ed0aa76363a0ee535be1dd027a7adb43426caa0d90cac9116b5c36d120430ca5dae1c98644b72c6f847d74a0e71ae69604a4a4bab5628fdac4f92aaa86c70c4533334b366a751f23473cf65616aea0661fdbee4b9d3f2cb82578f44703e8ec514a72ce9b299e617d37106dd23f4c53dbe27780dcdb0312007fb60c2d43b3257ac99e75b71cbb611ebfc72c1fb724b15a8fe58b2ddcc2a5cd976a15ce41b4dd09cb9d09054e835d6d60b0172bf904074853ca9c6613c0fae8f792586238416b054c8710fd387e4d46c229a6d0d8f9b653bac5386284710668bb3fca857163f6db48e0420f0087dcb0dedde46fe29a90641b6c3c49db5d351a267305eeaea95c6e62e16a91917b7816972524fea118f26bf851baf069b8ecf2081cf91451d697be0b6887bccf0a3b67c96d44d1bff69cef9546d08e8dd66cb7417e7a2723440a6b6442c2a58bece8cba21ab9f197c3d0bc74c781e30efd25cf2542639ac3acfabdea3f7af221ad28c797930d368076cbcd112c0a1bf198d0c081d7f7f216889bcb5b446d720fa7f1333fba0441216568b55b9e3a869c47afaabdeb282b65c774c98db3d00c00406276b12d86c44f8dd8970bd7dc738bbe1ad712a10137ed87968c0f0c8afc53ba3f9a070e700dcd22b47340d17c2c7b2f1cd2039c89334e722e25b7d619c8319a6727f4b68279c64538ce4c03318853f4e1944b8d81d8b8007aca927ffbc02e69a77c45d2632a7c0afcaa90f5e6f2736f58bc304dd1170f685e8b47fccaded0661255d6ab8a290e4a8c44dedcf3c9660a7d35f040f0d63e14bde335b7968d5ed6e984a892b0a6a8139d96eb54a8d1022e8db082e062e0c252c3e01d6403afab8aaf6bc6f24d8157ed6cecad155e259ad5b018db69ccc08c0f15811ea78917fb72ad9e9573c7645fcf59cfa531b078ac8fe5b51efb61d8c1343ec3d890f34d6095d6000f0be39e9d0dfeb532f2708168b75f28ceddb56aa4581da4d5aca0b39a456ea7b8e43e883c9d3b12d51c0826ccb69d1e77956afd2adfc115a1e44ad78e68c2b93b61d71e0ddd4cdaaa7fd3589a1b9d2a42df325724d181b7c96af26643d47603af1289ede8f54c634ddb400000000000000000000000000000000000000000000000000" - }, - "result": { - "gasUsed": "0x604a1e", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0x2fcb2ae181befb6e530f111ad25c983e1d2e208d11caa73145c811266bdf34bf", - "transactionPosition": 98 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [ - 0 - ], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000002ce3be", - "to": "0xff00000000000000000000000000000000000004", - "gas": "0x3e52cf6", - "value": "0x0", - "input": "0x868e10c400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000008318808821a002ce3be193e18811a0453643c58200b7238a7ac085ec5b663a2ac9babde2c59068dd6e6e61e57784fee049b0eab2058201015e3ffb83aba7ad6eed57e89a09176c78e97da5c95b02f947c84ec8868be3e590780912ab6e26f928bffa3c68c9a467677eec142ecf7a2d7c57711f54aa9bebf313780e8f80cc5646a351ad95444b99ed0dca55ae5fad63d8694ef89818973ae10a1aeb4b104ae7f4803b0465d687d24c4311ff1a29125137f091a1f384e83c6dd6f0f31df5cea648ebaf3bf23e192e5570a56c6146a1a8b60124d047a11b0985d11ad2611bdf7042e2cd719dd55845779cc93293d56ec0c121918b2a2f69de6f516102859fea9b3e7aae7ccbc556e73015f404fbe8f2a6084a013f1b746d69c755b8533c06e199c3a3cd991793311106e59a79f319b007afbc7ec2b8cb93f9a42f14f2e5e7f39e26aa6b677313eca6a1f00ab0ce08d0eb91c0f1c9fad8069012697d54aa1ec9caaedad169001eaa0665504405c765b25f4d1209bb69b2f4b3f52390f1e0235828501c87f43b5bae1f51d54c5bb5e513d33441385086b40c08b2787d88350ef8b2e4e26ae68eb77d6e47c91b19bf6180433e342c57b7128d755a3bb1508a42134043ff697ca4915e666cb1e98ee0176480144a92c4d2a239e16343090965542b9073d87d453707027cd41b3cc4bedf13cd490a355e4da51322757aeb1c42aa193a7c848e55ab81a6e43696084f9c5965d467b8b797db16b4cdd8f15576a7f2efc5ec18c356c7a079edaca1be4b842a820c2776049f49c3e237a6f8d096f7473ca10b1b419adbce98aa92d0d34e7926d463166bf2b7afdaad927c6649d3b719a14db07e358a9b817ba1df393813cea360cf3754a12449716490218ec147c01f394fd5a78e1fd1efd1825f74b6c5ca1640b328ad5b7078f3d00b8988f85acb0bd284a1515d31b7daf53f780c22179fceafcbbef9e4cf3e99c4028671aba7c2adfcf2b455e914f962705f1c96e97042c1eedc665844f2bdf90159975255aaef853a9198cbfcbfdd982ea59ce4ccf92b9463035534c450743eafa9148eb11fafb866c07d79d4cdc3ae79ed0f2b95579adbd9ed25fa3d18a9a3ed4c936a47c9d670cc583a98d71490c569bc4637f93deea73cfa34ed7fd465c93e714b33431255c3300d0d03055df972b067d19bacbfe7fc79b25db29ac9718f188ec0421b2defd12c408a48c5d33bbc655fcce869995f33f9fd53f3dc36a73b7a362430bca272639b40d134e1d7405bdd8983c7bac2d8fb7208c6219b0899ae317015eaebcceee8fc9b93d573cbe70d378e50ba9a969f4a69407524a4023ef97aef3c01d128576f63b5699cb5c691a7039c4ef3ca9af3baf713495f149d7425cb417c52052f3e530e1e438dcf8b627f0263a27caa010917169b91952820d1a46a4b4f54357fda87463a5c866ce6afe7d9b23eefe2e173d737fe492470fa481ab883e0a82b86637364dcdc03c47318423f7f512839510ad64b82dd392b5d0c6acc2b824a9727a7717819dd8aba714e2eb11bf32348c632f06d66ab968e070ee328e357c90db353536eb2a2edca946abafd2f26b6f9ad6096fb44894d5a4d50e573d9c1c2d0fb3bfa5ec202c581b1cc08fd7594ebe6892ce53457fd7268bac26471341a438f811c9130d5397d61a98351e79d64ca387ede4e93eeaced4a56528a04f1299d9086887b2c726eb504e111af2445ed0aa76363a0ee535be1dd027a7adb43426caa0d90cac9116b5c36d120430ca5dae1c98644b72c6f847d74a0e71ae69604a4a4bab5628fdac4f92aaa86c70c4533334b366a751f23473cf65616aea0661fdbee4b9d3f2cb82578f44703e8ec514a72ce9b299e617d37106dd23f4c53dbe27780dcdb0312007fb60c2d43b3257ac99e75b71cbb611ebfc72c1fb724b15a8fe58b2ddcc2a5cd976a15ce41b4dd09cb9d09054e835d6d60b0172bf904074853ca9c6613c0fae8f792586238416b054c8710fd387e4d46c229a6d0d8f9b653bac5386284710668bb3fca857163f6db48e0420f0087dcb0dedde46fe29a90641b6c3c49db5d351a267305eeaea95c6e62e16a91917b7816972524fea118f26bf851baf069b8ecf2081cf91451d697be0b6887bccf0a3b67c96d44d1bff69cef9546d08e8dd66cb7417e7a2723440a6b6442c2a58bece8cba21ab9f197c3d0bc74c781e30efd25cf2542639ac3acfabdea3f7af221ad28c797930d368076cbcd112c0a1bf198d0c081d7f7f216889bcb5b446d720fa7f1333fba0441216568b55b9e3a869c47afaabdeb282b65c774c98db3d00c00406276b12d86c44f8dd8970bd7dc738bbe1ad712a10137ed87968c0f0c8afc53ba3f9a070e700dcd22b47340d17c2c7b2f1cd2039c89334e722e25b7d619c8319a6727f4b68279c64538ce4c03318853f4e1944b8d81d8b8007aca927ffbc02e69a77c45d2632a7c0afcaa90f5e6f2736f58bc304dd1170f685e8b47fccaded0661255d6ab8a290e4a8c44dedcf3c9660a7d35f040f0d63e14bde335b7968d5ed6e984a892b0a6a8139d96eb54a8d1022e8db082e062e0c252c3e01d6403afab8aaf6bc6f24d8157ed6cecad155e259ad5b018db69ccc08c0f15811ea78917fb72ad9e9573c7645fcf59cfa531b078ac8fe5b51efb61d8c1343ec3d890f34d6095d6000f0be39e9d0dfeb532f2708168b75f28ceddb56aa4581da4d5aca0b39a456ea7b8e43e883c9d3b12d51c0826ccb69d1e77956afd2adfc115a1e44ad78e68c2b93b61d71e0ddd4cdaaa7fd3589a1b9d2a42df325724d181b7c96af26643d47603af1289ede8f54c634ddb4d82a5829000182e20381e802202f151b4c0c83a349d0df400c799410b60872287923ed67236c6fded247435711d82a5828000181e2039220200b139cb28169e7bf96a3e893f06114798319b67738e3f1d3e25c15251fc05809000000000000000000000000000000" - }, - "result": { - "gasUsed": "0x31d48a6", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0x2fcb2ae181befb6e530f111ad25c983e1d2e208d11caa73145c811266bdf34bf", - "transactionPosition": 98 - }, - { - "type": "call", - "subtraces": 1, - "traceAddress": [], - "action": { - "callType": "call", - "from": "0xff00000000000000000000000000000000194d9c", - "to": "0xff0000000000000000000000000000000018b644", - "gas": "0x420cf8b", - "value": "0x0", - "input": "0x868e10c4000000000000000000000000000000000000000000000000000000000000000700000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000078782197cae590780af74af52a5e7cfc11112a58658f3464000126bb0bdbe9cac881aab7373a5b01444a96ce7cac4da6cab7931ad1302d103ad1a909beafb5e6da3a12dc834107ad467b58db1d330b54187c5e76133ce0dfae5286e16dc77f21200b431311385ccae08f31082cb5fe315e2f03763339b2a8e7f5d4aa22394ab9b1bd2e5a2ee9f457878d7922cc963fb7c9df63dac57bd3bd1b4e474c042691e7691229c7832cd385e5dcf57d63f89f56b417d6a0454f6f7ebe10ddafec855583091e9f0a182fc8efeb873b773a6a2a5ec2f78424c1b274d629653ab7f501ca8e153b5bd133bfbd4875b708ab19a70dba7ade20d6c711e228a96a87a7b889c690cab7afe89b65bf75a1320383fe2575e886a9f2e540c73c683ebcf237a7e9249e1e79897026f5410211837ebfbd01106ada77feb2fddd1594668877f93c92007f819e2958174cfee14c3a9f19e0cb82184ca9a0bcbad69dc7fb54baed0248d1ab6523a03a483b8894ff90a82f720b05cda51ba3473d78070ef62dfcb4db8381b8bf00fce3b1e47c599b29c4e4f3d3537cf32cb0b414a43b05ecac3008bee1e41f7b0e434330b144ca8943d0eb355a21800b489c8747a8df65799ce85c5278b8af057ecaa0094ecc3b7125326e168bb9e3a6840305c138f04e8ac6c9833050e67d2f5b5bae0b82e704f0e478ad5e249af7c71e5f813f4e2762789e22db59bd998a600a96f12ebb4cccbbc0167250e5068db6097b417820193ffb61b14ed1decd0a4c4ce4aa22e0a4b4345b112cc70fbe58e39b55d8658d0293f2e0553fb9296203aab2a8e3921bf12e3abc25b8a68a82b8a32f780d643706c2fdd95aa83e0f80a1148ecdeee6d82f68b7d0970d26535d0f8a45ecbe83ff1736890aee1ca3b2c3c16c04b7a79be725e27a83d2a650c953c7618359ff8248c341387fe194553c7aa2d7c36b90427b78bb9011137de00fa755df244a478ef86182e1154bcb9704693d2a4b36bbef19817ca6fba18738f3ab605231ca40458590ce8a25215e86e4fded86922730312c0e1a9fce06819f308e197e58cc9df7505ef779f011945960debf96779a0d3b4853865953aac108b3d10cbece20950a0612147e65edc8a59aaf7c8ca518f9fdb452b316f4ebf3fd18664b692cf0195b2f66467b9b8c398e118c1e90d45be22969e12a226d9c51d1f5e437de700096f7bca6b5b52b72ea04b89828b0d5d2f2349b11e35150b53c844616524804b13ff6e50bcc2cee62a91bac6c97ef5601a4c6d26b70e3ae78c18b41560995ed358bf8bce8a40aa30218576eab0068c6f4d7a9f16524830fe8418d98fe8475a1ca81174b85c84e70944a47cb840aab45b22036cf0a428b3f42ae6f5b44a9f9cf8a8e160659c887db1eb574690f261328c2d7d61d77e2571823a0e071b541b25779deda2b99aee9712bb5f78d30b6924e67a466cf6e5cee885fd7dd1cbe54e19a0acce5a4ec125b4191402fec6f7cd9e6b74f693b6e1a11552aa1d0c74db96db59cecd8165540ed97de16ae68161ffd0004e46e94db01f6e210ba0befbcc545c75f26f63f6a5d4b4c3390118c4f9d250a816bb83b162ae29dfe7f43161f5383e6a6514a1dfe089af68a6050d8628af0ae8ed721ac2eaf797d0a8291139c8c4f0803ad4d1e1476d641f65ce498b78f28e779aaa3b509105e295b178789c0a88a002a698c32d6500900bc46da2152ba8b48e1adb116603a56ac12af02a647547f6bab816ce8b90909c28b88be764c8534344fea5b4a98d6d13427c0a1a8b3479fd6ab5fcd298c5bbbd53073ac3162a1d71573b03bed5fac16fa8180dbd516bdeb05f8194fba24014950634092426f4a3894967e29fa3911776d59c2c96fc592c73e90542ae654a087a5773045e35dabf4607f78cd01df0c2b14687a7f041dfb54ea9b2edc2e13c91df1d99fab8354d8ac2d7397c332c05573deb2482054e35f750e6b36c337abf0292a2b7dacc7ca94a1b8aae9f2d2355a79fd807e28fa6b7a05ce0bf4246422361a8f92597f47ac6d0536fd34a0e43ce101097d99bb0ca17255046025305e2847ed242c64e86e131b71de29848e8a731dbbf72fc21df1ef705064a4fec6e8dcc64a2e80de3013a9969563667fa312044c74bb6fe0b548f01a00434d80d3f16ee2f0023995c758050f2023e44cdc2d409b98ca630e47e0889e322adff03315a959e517b3ea1bec48e754c7507ffcb6aab665673033a950ea575006f67fe715a380f8c157d40b5a8cb1c9feca96555c1993672a737f13a375700ff5402e77440163eb33d751e7395011d6ec56bad25a7471a154199067a029e6a456b64728fb5551c75d578e6fa7d4c208a5d5121c8068ccbad10d37097f4eaa65ab1fe0aaaf4a246a938d6532465c9ee33568fc97829bc3e8ebe41006890cea32107e374d877f5145541d6c42c9565835f304b072dd680f7b28e0636c37c77c83cf94c7995ed2dd6e3de65145100e31530df58cf3c033e273d8dd958d04c24b0b4d025dddd658c91ac7d9a533d5cfdbd053b222df4fbfbef900da6a930ae232efa070958c210dcc655d513504248ed405924bc76e35ccdba1787732ff9f074899c01a79af782c9ad343869a7b3cde3812d6c51d453469a99f81b1eaca9e1eb1cf672dff039f9790cb72cfc70e23b1810a2ff1fad6f18e0bd7e45b7669f206444383f572c31555dc9aade67df6a23e7f458b47041d2185d4300000000000000000000000000000000000000000000000000" - }, - "result": { - "gasUsed": "0x6c75d9", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0x0e60536ae59f9e7056602af5758917ceb7b122029a30d36b81f755578da9b6f9", - "transactionPosition": 99 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [ - 0 - ], - "action": { - "callType": "call", - "from": "0xff0000000000000000000000000000000018b644", - "to": "0xff00000000000000000000000000000000000004", - "gas": "0x3e5247f", - "value": "0x0", - "input": "0x868e10c4000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000082c8808821a0018b644197cae805820459182f6c450d64f4ce1b2cdd1f1b72010f8d595fd9303f5cea43b716af1082a58203982169b56bf07db8724ad2c00f8855d497d4eee8ca4ad3562991728ee37cfd7590780af74af52a5e7cfc11112a58658f3464000126bb0bdbe9cac881aab7373a5b01444a96ce7cac4da6cab7931ad1302d103ad1a909beafb5e6da3a12dc834107ad467b58db1d330b54187c5e76133ce0dfae5286e16dc77f21200b431311385ccae08f31082cb5fe315e2f03763339b2a8e7f5d4aa22394ab9b1bd2e5a2ee9f457878d7922cc963fb7c9df63dac57bd3bd1b4e474c042691e7691229c7832cd385e5dcf57d63f89f56b417d6a0454f6f7ebe10ddafec855583091e9f0a182fc8efeb873b773a6a2a5ec2f78424c1b274d629653ab7f501ca8e153b5bd133bfbd4875b708ab19a70dba7ade20d6c711e228a96a87a7b889c690cab7afe89b65bf75a1320383fe2575e886a9f2e540c73c683ebcf237a7e9249e1e79897026f5410211837ebfbd01106ada77feb2fddd1594668877f93c92007f819e2958174cfee14c3a9f19e0cb82184ca9a0bcbad69dc7fb54baed0248d1ab6523a03a483b8894ff90a82f720b05cda51ba3473d78070ef62dfcb4db8381b8bf00fce3b1e47c599b29c4e4f3d3537cf32cb0b414a43b05ecac3008bee1e41f7b0e434330b144ca8943d0eb355a21800b489c8747a8df65799ce85c5278b8af057ecaa0094ecc3b7125326e168bb9e3a6840305c138f04e8ac6c9833050e67d2f5b5bae0b82e704f0e478ad5e249af7c71e5f813f4e2762789e22db59bd998a600a96f12ebb4cccbbc0167250e5068db6097b417820193ffb61b14ed1decd0a4c4ce4aa22e0a4b4345b112cc70fbe58e39b55d8658d0293f2e0553fb9296203aab2a8e3921bf12e3abc25b8a68a82b8a32f780d643706c2fdd95aa83e0f80a1148ecdeee6d82f68b7d0970d26535d0f8a45ecbe83ff1736890aee1ca3b2c3c16c04b7a79be725e27a83d2a650c953c7618359ff8248c341387fe194553c7aa2d7c36b90427b78bb9011137de00fa755df244a478ef86182e1154bcb9704693d2a4b36bbef19817ca6fba18738f3ab605231ca40458590ce8a25215e86e4fded86922730312c0e1a9fce06819f308e197e58cc9df7505ef779f011945960debf96779a0d3b4853865953aac108b3d10cbece20950a0612147e65edc8a59aaf7c8ca518f9fdb452b316f4ebf3fd18664b692cf0195b2f66467b9b8c398e118c1e90d45be22969e12a226d9c51d1f5e437de700096f7bca6b5b52b72ea04b89828b0d5d2f2349b11e35150b53c844616524804b13ff6e50bcc2cee62a91bac6c97ef5601a4c6d26b70e3ae78c18b41560995ed358bf8bce8a40aa30218576eab0068c6f4d7a9f16524830fe8418d98fe8475a1ca81174b85c84e70944a47cb840aab45b22036cf0a428b3f42ae6f5b44a9f9cf8a8e160659c887db1eb574690f261328c2d7d61d77e2571823a0e071b541b25779deda2b99aee9712bb5f78d30b6924e67a466cf6e5cee885fd7dd1cbe54e19a0acce5a4ec125b4191402fec6f7cd9e6b74f693b6e1a11552aa1d0c74db96db59cecd8165540ed97de16ae68161ffd0004e46e94db01f6e210ba0befbcc545c75f26f63f6a5d4b4c3390118c4f9d250a816bb83b162ae29dfe7f43161f5383e6a6514a1dfe089af68a6050d8628af0ae8ed721ac2eaf797d0a8291139c8c4f0803ad4d1e1476d641f65ce498b78f28e779aaa3b509105e295b178789c0a88a002a698c32d6500900bc46da2152ba8b48e1adb116603a56ac12af02a647547f6bab816ce8b90909c28b88be764c8534344fea5b4a98d6d13427c0a1a8b3479fd6ab5fcd298c5bbbd53073ac3162a1d71573b03bed5fac16fa8180dbd516bdeb05f8194fba24014950634092426f4a3894967e29fa3911776d59c2c96fc592c73e90542ae654a087a5773045e35dabf4607f78cd01df0c2b14687a7f041dfb54ea9b2edc2e13c91df1d99fab8354d8ac2d7397c332c05573deb2482054e35f750e6b36c337abf0292a2b7dacc7ca94a1b8aae9f2d2355a79fd807e28fa6b7a05ce0bf4246422361a8f92597f47ac6d0536fd34a0e43ce101097d99bb0ca17255046025305e2847ed242c64e86e131b71de29848e8a731dbbf72fc21df1ef705064a4fec6e8dcc64a2e80de3013a9969563667fa312044c74bb6fe0b548f01a00434d80d3f16ee2f0023995c758050f2023e44cdc2d409b98ca630e47e0889e322adff03315a959e517b3ea1bec48e754c7507ffcb6aab665673033a950ea575006f67fe715a380f8c157d40b5a8cb1c9feca96555c1993672a737f13a375700ff5402e77440163eb33d751e7395011d6ec56bad25a7471a154199067a029e6a456b64728fb5551c75d578e6fa7d4c208a5d5121c8068ccbad10d37097f4eaa65ab1fe0aaaf4a246a938d6532465c9ee33568fc97829bc3e8ebe41006890cea32107e374d877f5145541d6c42c9565835f304b072dd680f7b28e0636c37c77c83cf94c7995ed2dd6e3de65145100e31530df58cf3c033e273d8dd958d04c24b0b4d025dddd658c91ac7d9a533d5cfdbd053b222df4fbfbef900da6a930ae232efa070958c210dcc655d513504248ed405924bc76e35ccdba1787732ff9f074899c01a79af782c9ad343869a7b3cde3812d6c51d453469a99f81b1eaca9e1eb1cf672dff039f9790cb72cfc70e23b1810a2ff1fad6f18e0bd7e45b7669f206444383f572c31555dc9aade67df6a23e7f458b47041d2185d43d82a5829000182e20381e802200b3169218eaabe0a254cadd20d2eac54c4e57c1839c602b8ed7a1fb2bcfa0e5fd82a5828000181e203922020077e5fde35c50a9303a55009e3498a4ebedff39c42b710b730d8ec7ac7afa63e0000000000000000000000000000000000000000" - }, - "result": { - "gasUsed": "0x31f607f", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0x0e60536ae59f9e7056602af5758917ceb7b122029a30d36b81f755578da9b6f9", - "transactionPosition": 99 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000002d44e1", - "to": "0xff00000000000000000000000000000000116ff5", - "gas": "0x501f15", - "value": "0xd015638ef2350000", - "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "result": { - "gasUsed": "0x12c553", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0xdf41cbcc8c905708453c4636c5b0ebf709ca0464982158a996000a244a3dcd13", - "transactionPosition": 100 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [], - "action": { - "callType": "call", - "from": "0xff00000000000000000000000000000000241537", - "to": "0xff000000000000000000000000000000002d44d5", - "gas": "0xbbacb", - "value": "0xce8869a46a946e435", - "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "result": { - "gasUsed": "0x12c03f", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0xd0e4426b5949939e81e1aa223c77a6d1fa37d7687e32f319ad9dbfcf608128e6", - "transactionPosition": 101 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [], - "action": { - "callType": "call", - "from": "0x53bdfdea92f7a60aef82228926d02878018acb4e", - "to": "0x8460766edc62b525fc1fa4d628fc79229dc73031", - "gas": "0x6b64a5", - "value": "0x0", - "input": "0x5535dbf60000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003b6261667962656965677273376934636a6e6a767072666a356d67653367747076767a6261796e3672657a6b6433666a36746a6a70797763707a68690000000000" - }, - "result": { - "gasUsed": "0x6143fc", - "output": "0x000000000000000000000000000000000000000000000000000000000000043e" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0xf73074b893cce66d2f798bc0862e12501da242db7004d3bd4aab36d7e0fcbc91", - "transactionPosition": 102 - }, - { - "type": "call", - "subtraces": 1, - "traceAddress": [], - "action": { - "callType": "call", - "from": "0xff00000000000000000000000000000000172b9c", - "to": "0xff00000000000000000000000000000000172b21", - "gas": "0x5205362", - "value": "0x0", - "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000007000000000000000000000000000000000000000000000000000000000000005100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000789821a000a651a5907808fd515f991b4f6a33de58875255e78be7d80628951a6f23f375280c3ec01e5aac19d252b2f8ddbe279ddb5758c96dea3b5be4a3f34581d5eba0e152cf8e649c5c272759b0b268aaaf09336e5ef9a9d0eea3b4d6e346f16370709411194f1274f03d6160a838e2d85e954e0070d434f05033a791cd1e0c149df1dab881b11423d43851c40be685faca80d7cc78959beb3975f8c49164ca96c11a2ede913c5da8cf168bd6407080e794bbac843e44f84c57b987103da83c6a9761c4dc00dbfa072b64ba1914e5588853cc3975f3463023dc27f04d6294ccf24b512c371066f3dc102db9c89172683d405c605e81488a03184c0b2996e963bc2297813598f82fa0ebdf4a13e95284e8807ffbb81573b8e73d495d18a53b9a25e39e8ccf36a50078101f9cbb2c68ad953c9e98e99dea30d8f65b821286709adcd4b99754a696e4f782e8715ac7458dda55863e46c87c10365a36bad7db606b42f49dad977252ba8fc2b615a9a68b6e4c5db36af1959788c9c01f6c42e30aec6b9618d475723feee7985d8d8a0119154f3a0cc0b44cfa94acb31e8d34a1eb9db44d389f5e6b840c80997fa17428581ce07cebe0e5a72a57d8b8df76fe7162c5fcd22771f6f39e7f09c420c298016be276d70715ad584b7e27545258c0c082f9df2a642e3f27b85db410aa8cd1e46b323b673d27f93c0aa1508e387d86a474565af8540c5912201af661ad35a7ccf0b7557d3047e6c467afce9b99ef937a026f17c63b6c56cfa2c075555e687959e1ac7d4ef164401b2dab427c4b8925eb4961db305123f932d15517b91d87218e46cad09696eb1b70f82d588c8d52d920ed13bf990f469bbe15e95c04402738842f4690e0d6963eaca9a214e9539f274424752c6855cd172f134fee67c397179ebabc8c67d87ac3862e9cb38b609aadfc476aa45acefeeaf1dde8e1d12cf3be18a24904f5261c36912844abbc6f986be2c730731556cb83ba4fb0a2e1c1f61836b805b0b404ef458aaee2ec5901f50c87c245772d68a062988be6c0178910957cd03a46d9d60cb3c6522a03e84fa2c4e752bffdf0faa82a1fe17374287456fedd11dfd4ea22163d1b3e0d4bd7c84ee73f63f8f6511ecd3297591ceb9bb59a6a52ca4d9316fa852ca949b496e8bc3ababaa8256ce13c413ff4c863fac0abb195ed523fc7ac26750864b33dcbfeb53febbd388ce3cf2cc541d9be71a8f0aa7ea56e806597ca6d08a4b763a68a1d8be498a1bec737872fe8ef18db169071932627a8f9192a498d2cb6a73ac23b3b88a0b1d3bfa271ce50abbe7d1b60cc1b9c0525b7d39daa328b854390c1538a69b6e73168143378d540cf1f11236bf4896da263ea751fe44d06031f0d65d52c735cfe0cbe82809adc1e09691e75a14cfb7c6e8f120544525c0ead12a3471bcfe8bf9aa3555c13c12a07ae2aae1ddf970b9cf714cfad51ff458228e051e00f2b46e1ef86870b6085b8e5820aaac1543cf1348d1fe21bb39dbe6352b52641688b2c2c666c8eba27394edcfe1500633a623ec4e65157ce59cbba97b8dc75694f238b9d980e93e579cc5b3c11a357b5158cbe89dd04a61397cd88524ba05daa6fbdf592ccb92821b05a5a09cd214a6c11162b1b0b250d1556573222dad8504cdf1dfa81b83b4456ec6ed7424597f602d6aab1641fa1e109d1e5ed934780e8f95a5ca829a4877cc3ec49e7bd5abb9ca0ff0af71ef2dd252824457b87f9d5e54dcc5443d8de0490d16517332458d89e25582890016f96e20bbbd6d81b9846d168eed1b6bffa6dabad8df76e144afddb8b5bde341ce94654ee214f7dca3d8ea5a5b925cb3749a3350f700d62380b187aa7e0279fe528d3d752bb741ba9e973d00720302f3ad688c7d538d1d968691578e29ea1bad131aaf34192c96e9753768f80b2ff98bbcb4123ff4bb8023aaf6d8e548c0d834a4d351da4ad560be24ab0a9bc72119a9bf2a8d971a7bf5ebd463410c6eaeb7398b8d563354b54ee99274701b0efcae547875679f7fc9acd9695f569f2d88c51849ad90aaa9c92a5884ccfd8b70c4e95c881d759fb98e0a003b1ad27fa629745a708507d6dddfa415c4e882e960e319960601751a6b4b3dbf9d5c735c72a32d711de64eadaa44becb4230fbb737f5363b3b2cee9b64036872356012cb66ad34b06663639d792681d5aca663e80a103529c6a23e2a229f5ea3bb33651a023c0d8503d3c24feddc4182830fccd14176088ef4c363b154e4d2bb4323469831489e33496294cabc1f764ee81beb08d1995ad6fea8e474bdd2ae988b2e5dfc2508340b9db7a7ad87549061e3cb90a167f894d1a11642920b20afdd87278191e680b07a5c84cab915974e042ec97af0c0ec8eb728f9dc062a0e5b9eaf8bd9aef74e29b0a8ebd6a179cbceadfde17e47a397cc96e46f25f6e2634a1a8d3b9a17b5e36b8560f6c8f508f2ef9eeafb5497300cc7f8a459675f4136f042f92a05a75ba8e9c885d51202888170173b04568d187a18914c246f6359938928a1e799e02f0580fac0c7c0271270c47db574362902cbab47f4ef90f0fe2f25c3aefcbe255abd1915edec356e67835397832bd854d8b191f664e1e721960ee3e4857974a6762f13ae7528366986ad0eaab18e53da543e6eb480782f5bb0932a1a6347dbbdb5d939cf4eb26bbbc1fe6ca38bb130f0aab1fd10dfac48fc5e165a16465840c33ed8e70000000000000000000000000000000000000000000000" - }, - "result": { - "gasUsed": "0x9b4f05", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0x0bd64342ca2f272d82c6a77786e961b5fb1983158a496af3aeffca02bd7fce6a", - "transactionPosition": 103 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [ - 0 - ], - "action": { - "callType": "call", - "from": "0xff00000000000000000000000000000000172b21", - "to": "0xff00000000000000000000000000000000000004", - "gas": "0x4b6bf10", - "value": "0x0", - "input": "0x868e10c400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000008338808821a00172b211a000a651a811a045b2dc45820c84705285a78a4839d7dd9614c60acc16c72e827794f991eccaf716f0cff6ed95820fe634357a254919411f0fa829564b42326cc3ba9d7f50492d56d3a19b48234e85907808fd515f991b4f6a33de58875255e78be7d80628951a6f23f375280c3ec01e5aac19d252b2f8ddbe279ddb5758c96dea3b5be4a3f34581d5eba0e152cf8e649c5c272759b0b268aaaf09336e5ef9a9d0eea3b4d6e346f16370709411194f1274f03d6160a838e2d85e954e0070d434f05033a791cd1e0c149df1dab881b11423d43851c40be685faca80d7cc78959beb3975f8c49164ca96c11a2ede913c5da8cf168bd6407080e794bbac843e44f84c57b987103da83c6a9761c4dc00dbfa072b64ba1914e5588853cc3975f3463023dc27f04d6294ccf24b512c371066f3dc102db9c89172683d405c605e81488a03184c0b2996e963bc2297813598f82fa0ebdf4a13e95284e8807ffbb81573b8e73d495d18a53b9a25e39e8ccf36a50078101f9cbb2c68ad953c9e98e99dea30d8f65b821286709adcd4b99754a696e4f782e8715ac7458dda55863e46c87c10365a36bad7db606b42f49dad977252ba8fc2b615a9a68b6e4c5db36af1959788c9c01f6c42e30aec6b9618d475723feee7985d8d8a0119154f3a0cc0b44cfa94acb31e8d34a1eb9db44d389f5e6b840c80997fa17428581ce07cebe0e5a72a57d8b8df76fe7162c5fcd22771f6f39e7f09c420c298016be276d70715ad584b7e27545258c0c082f9df2a642e3f27b85db410aa8cd1e46b323b673d27f93c0aa1508e387d86a474565af8540c5912201af661ad35a7ccf0b7557d3047e6c467afce9b99ef937a026f17c63b6c56cfa2c075555e687959e1ac7d4ef164401b2dab427c4b8925eb4961db305123f932d15517b91d87218e46cad09696eb1b70f82d588c8d52d920ed13bf990f469bbe15e95c04402738842f4690e0d6963eaca9a214e9539f274424752c6855cd172f134fee67c397179ebabc8c67d87ac3862e9cb38b609aadfc476aa45acefeeaf1dde8e1d12cf3be18a24904f5261c36912844abbc6f986be2c730731556cb83ba4fb0a2e1c1f61836b805b0b404ef458aaee2ec5901f50c87c245772d68a062988be6c0178910957cd03a46d9d60cb3c6522a03e84fa2c4e752bffdf0faa82a1fe17374287456fedd11dfd4ea22163d1b3e0d4bd7c84ee73f63f8f6511ecd3297591ceb9bb59a6a52ca4d9316fa852ca949b496e8bc3ababaa8256ce13c413ff4c863fac0abb195ed523fc7ac26750864b33dcbfeb53febbd388ce3cf2cc541d9be71a8f0aa7ea56e806597ca6d08a4b763a68a1d8be498a1bec737872fe8ef18db169071932627a8f9192a498d2cb6a73ac23b3b88a0b1d3bfa271ce50abbe7d1b60cc1b9c0525b7d39daa328b854390c1538a69b6e73168143378d540cf1f11236bf4896da263ea751fe44d06031f0d65d52c735cfe0cbe82809adc1e09691e75a14cfb7c6e8f120544525c0ead12a3471bcfe8bf9aa3555c13c12a07ae2aae1ddf970b9cf714cfad51ff458228e051e00f2b46e1ef86870b6085b8e5820aaac1543cf1348d1fe21bb39dbe6352b52641688b2c2c666c8eba27394edcfe1500633a623ec4e65157ce59cbba97b8dc75694f238b9d980e93e579cc5b3c11a357b5158cbe89dd04a61397cd88524ba05daa6fbdf592ccb92821b05a5a09cd214a6c11162b1b0b250d1556573222dad8504cdf1dfa81b83b4456ec6ed7424597f602d6aab1641fa1e109d1e5ed934780e8f95a5ca829a4877cc3ec49e7bd5abb9ca0ff0af71ef2dd252824457b87f9d5e54dcc5443d8de0490d16517332458d89e25582890016f96e20bbbd6d81b9846d168eed1b6bffa6dabad8df76e144afddb8b5bde341ce94654ee214f7dca3d8ea5a5b925cb3749a3350f700d62380b187aa7e0279fe528d3d752bb741ba9e973d00720302f3ad688c7d538d1d968691578e29ea1bad131aaf34192c96e9753768f80b2ff98bbcb4123ff4bb8023aaf6d8e548c0d834a4d351da4ad560be24ab0a9bc72119a9bf2a8d971a7bf5ebd463410c6eaeb7398b8d563354b54ee99274701b0efcae547875679f7fc9acd9695f569f2d88c51849ad90aaa9c92a5884ccfd8b70c4e95c881d759fb98e0a003b1ad27fa629745a708507d6dddfa415c4e882e960e319960601751a6b4b3dbf9d5c735c72a32d711de64eadaa44becb4230fbb737f5363b3b2cee9b64036872356012cb66ad34b06663639d792681d5aca663e80a103529c6a23e2a229f5ea3bb33651a023c0d8503d3c24feddc4182830fccd14176088ef4c363b154e4d2bb4323469831489e33496294cabc1f764ee81beb08d1995ad6fea8e474bdd2ae988b2e5dfc2508340b9db7a7ad87549061e3cb90a167f894d1a11642920b20afdd87278191e680b07a5c84cab915974e042ec97af0c0ec8eb728f9dc062a0e5b9eaf8bd9aef74e29b0a8ebd6a179cbceadfde17e47a397cc96e46f25f6e2634a1a8d3b9a17b5e36b8560f6c8f508f2ef9eeafb5497300cc7f8a459675f4136f042f92a05a75ba8e9c885d51202888170173b04568d187a18914c246f6359938928a1e799e02f0580fac0c7c0271270c47db574362902cbab47f4ef90f0fe2f25c3aefcbe255abd1915edec356e67835397832bd854d8b191f664e1e721960ee3e4857974a6762f13ae7528366986ad0eaab18e53da543e6eb480782f5bb0932a1a6347dbbdb5d939cf4eb26bbbc1fe6ca38bb130f0aab1fd10dfac48fc5e165a16465840c33ed8e7d82a5829000182e20381e802209cefc42ae516244f0407001eccfd20d923218826471acf64a8a94d665ae51b2dd82a5828000181e20392202078d7f3d6cd9ba40272efa201f6dfb13dc5fd85d8cb3812bd5af6a23f0204c02e00000000000000000000000000" - }, - "result": { - "gasUsed": "0x32089f0", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0x0bd64342ca2f272d82c6a77786e961b5fb1983158a496af3aeffca02bd7fce6a", - "transactionPosition": 103 - }, - { - "type": "call", - "subtraces": 3, - "traceAddress": [], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000002af885", - "to": "0xff000000000000000000000000000000002afa9a", - "gas": "0x6aa2041", - "value": "0x8abb7a55e7bee0", - "input": "0x868e10c4000000000000000000000000000000000000000000000000000000000000001c000000000000000000000000000000000000000000000000000000000000005100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000072818187081a00019146d82a5829000182e20381e802202690c7f394488d63c61b511be885dd34412491e418f3022c5f2a70594a45da401a0037dcf0811a045adba51a004f7977d82a5828000181e2039220205fd60a468ccf211022611f407c8898cc32d838d4da91777a3eba380889390b3e0000000000000000000000000000" - }, - "result": { - "gasUsed": "0x4f77363", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0xda7c7b60dc15d026b250310e1b2db33b40e00542386f18c560612ebae86ff301", - "transactionPosition": 104 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [ - 0 - ], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000002afa9a", - "to": "0xff00000000000000000000000000000000000002", - "gas": "0x6976309", - "value": "0x0", - "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "result": { - "gasUsed": "0xfabb0", - "output": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000418282581a000286387ff107ef952f3612eb2e2a6606418e51c0f2cceeda5f57011731c2a04034dd33df402838a815a0b69ec1184b8c104a0001c0dfc71cd077c4b900000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0xda7c7b60dc15d026b250310e1b2db33b40e00542386f18c560612ebae86ff301", - "transactionPosition": 104 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [ - 1 - ], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000002afa9a", - "to": "0xff00000000000000000000000000000000000004", - "gas": "0x6869dc1", - "value": "0x0", - "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000009000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "result": { - "gasUsed": "0x105fb2", - "output": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000578449007d2903b8000000004a000190f76c1adff180004c00907e2dd41a18e7c7a7f2bd82581a0001916cb98a2c3dfb67a389a588fb0e593f762dd6c9195851235601fba7e16707ee65746d4671e80aa2bb15bc7d6ebe3b000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0xda7c7b60dc15d026b250310e1b2db33b40e00542386f18c560612ebae86ff301", - "transactionPosition": 104 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [ - 2 - ], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000002afa9a", - "to": "0xff00000000000000000000000000000000000005", - "gas": "0x6748850", - "value": "0x0", - "input": "0x868e10c4000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000f818183081a004f7977811a045adba50000000000000000000000000000000000" - }, - "result": { - "gasUsed": "0x487d76", - "output": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000002e8181d82a5828000181e2039220205fd60a468ccf211022611f407c8898cc32d838d4da91777a3eba380889390b3e000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0xda7c7b60dc15d026b250310e1b2db33b40e00542386f18c560612ebae86ff301", - "transactionPosition": 104 - }, - { - "type": "call", - "subtraces": 3, - "traceAddress": [], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000002ade3c", - "to": "0xff000000000000000000000000000000002ade40", - "gas": "0x375dc86", - "value": "0x8abb7a55e7bee0", - "input": "0x868e10c4000000000000000000000000000000000000000000000000000000000000001c00000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000007081818708192040d82a5829000182e20381e80220359da1ae33a951238ad1c46d5a5a8af8728e9f3dfb8c420a3c6989e1d451dc351a0037e08c811a045b324e1a004f084ed82a5828000181e203922020d07f551de5c94f8a5e6772d7a22d34450b43bfda443f5a059e35ede55ec0593100000000000000000000000000000000" - }, - "result": { - "gasUsed": "0x268464a", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0xbf0919237db95ca6842af1209bd67f3c15587a92fb9f7f1343dd4e66c5828a06", - "transactionPosition": 105 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [ - 0 - ], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000002ade40", - "to": "0xff00000000000000000000000000000000000002", - "gas": "0x3631f77", - "value": "0x0", - "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "result": { - "gasUsed": "0xfabb0", - "output": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000418282581a000286387ff107ef952f3612eb2e2a6606418e51c0f2cceeda5f57011731c2a04034dd33df402838a815a0b69ec1184b8c104a0001c0dfc71cd077c4b900000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0xbf0919237db95ca6842af1209bd67f3c15587a92fb9f7f1343dd4e66c5828a06", - "transactionPosition": 105 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [ - 1 - ], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000002ade40", - "to": "0xff00000000000000000000000000000000000004", - "gas": "0x3525a2f", - "value": "0x0", - "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000009000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "result": { - "gasUsed": "0x105fb2", - "output": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000578449007d2903b8000000004a000190f76c1adff180004c00907e2dd41a18e7c7a7f2bd82581a0001916cb98a2c3dfb67a389a588fb0e593f762dd6c9195851235601fba7e16707ee65746d4671e80aa2bb15bc7d6ebe3b000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0xbf0919237db95ca6842af1209bd67f3c15587a92fb9f7f1343dd4e66c5828a06", - "transactionPosition": 105 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [ - 2 - ], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000002ade40", - "to": "0xff00000000000000000000000000000000000005", - "gas": "0x34044be", - "value": "0x0", - "input": "0x868e10c4000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000f818183081a004f084e811a045b324e0000000000000000000000000000000000" - }, - "result": { - "gasUsed": "0x476423", - "output": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000002e8181d82a5828000181e203922020d07f551de5c94f8a5e6772d7a22d34450b43bfda443f5a059e35ede55ec05931000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0xbf0919237db95ca6842af1209bd67f3c15587a92fb9f7f1343dd4e66c5828a06", - "transactionPosition": 105 - }, - { - "type": "call", - "subtraces": 2, - "traceAddress": [], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000002ce88b", - "to": "0xff000000000000000000000000000000002ce88f", - "gas": "0x1f6eaed", - "value": "0x8abb7a55e7bee0", - "input": "0x868e10c4000000000000000000000000000000000000000000000000000000000000001c00000000000000000000000000000000000000000000000000000000000000510000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000004081818708190491d82a5829000182e20381e80220e679eb60d986e754a677a0b5219976140fff0f798521f46d166a7e597b7c20211a0037e09f801a004f8a90f6" - }, - "result": { - "gasUsed": "0x17c8c83", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0xb9208ab347ea30b568c4f9ab4fe310eeacfa107e9e5d970b38517334e51b2b2e", - "transactionPosition": 106 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [ - 0 - ], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000002ce88f", - "to": "0xff00000000000000000000000000000000000002", - "gas": "0x1e45019", - "value": "0x0", - "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "result": { - "gasUsed": "0xfabb0", - "output": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000418282581a000286387ff107ef952f3612eb2e2a6606418e51c0f2cceeda5f57011731c2a04034dd33df402838a815a0b69ec1184b8c104a0001c0dfc71cd077c4b900000000000000000000000000000000000000000000000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0xb9208ab347ea30b568c4f9ab4fe310eeacfa107e9e5d970b38517334e51b2b2e", - "transactionPosition": 106 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [ - 1 - ], - "action": { - "callType": "call", - "from": "0xff000000000000000000000000000000002ce88f", - "to": "0xff00000000000000000000000000000000000004", - "gas": "0x1d38ad1", - "value": "0x0", - "input": "0x868e10c40000000000000000000000000000000000000000000000000000000000000009000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - }, - "result": { - "gasUsed": "0x105fb2", - "output": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000051000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000578449007d2903b8000000004a000190f76c1adff180004c00907e2dd41a18e7c7a7f2bd82581a0001916cb98a2c3dfb67a389a588fb0e593f762dd6c9195851235601fba7e16707ee65746d4671e80aa2bb15bc7d6ebe3b000000000000000000" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0xb9208ab347ea30b568c4f9ab4fe310eeacfa107e9e5d970b38517334e51b2b2e", - "transactionPosition": 106 - }, - { - "type": "call", - "subtraces": 0, - "traceAddress": [], - "action": { - "callType": "call", - "from": "0x4ecdc893beb09121e4f5cbba469d33f5ff618442", - "to": "0x8460766edc62b525fc1fa4d628fc79229dc73031", - "gas": "0x2aac9395", - "value": "0x0", - "input": "0x603119b1000000000000000000000000000000000000000000000000000000000000043d0000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000580000000000000000000000000000000000000000000000000000000000000002bc0000000000000000000000008908d75fa9b38aff2997fbb405ecea53ea376235000000000000000000000000066ce586cee61c33b6a7cb88b424f2597b03fdd200000000000000000000000054b19c8921b4b9b7bfc63a7c1267b8b361edcb37000000000000000000000000e5e0ff05a5cbc02fd0b386cfd626d3c537e49103000000000000000000000000ce53e6defbd1f0810bfcd691660ad311d0db24f7000000000000000000000000e6235c47b9aeb4309ad568945dfda530ba3ea4a6000000000000000000000000d93910f8dcbcbf07e86a0b3235d47f04b9b0a8a5000000000000000000000000280f1cee998b784d87f39047f5c757783a131bcc000000000000000000000000ea0fd4a321a9f1fc06e4d46a9e991ebe91302a35000000000000000000000000b0a258e65801e52ad9b709a06af61282194360790000000000000000000000008ad34724ea8c951cb0fefc1cb3b1184bc802f2ad000000000000000000000000d096dbb2a8eec6ae3f1baf9ce6e46ef4e1e0989800000000000000000000000018be4742d88664350e0333af9b493afa77b3b71c00000000000000000000000069eb6aab03510f6d90b206a5cc3e9f7b92349e390000000000000000000000008f3b852c5394ad9618c4fffc0db895e8570033740000000000000000000000004e89c37b3f7d9fdc01075f252c4c9c0e8e1db6e80000000000000000000000002379198a71e2a3cb94e78628a8ebaceee1c0c6480000000000000000000000007f2645e86dbf5fd6fcf613073bd795d0aa8546f5000000000000000000000000137536822037b7fb2e78eeb824af708e1aeeacb2000000000000000000000000aa60b7c819c4fe893e4f24a81ec91b4a22cb526c00000000000000000000000062ef4551567eada4d52c719f810bf27ba90136c800000000000000000000000043457a112c3065f05ac9fba437d184972aa12b03000000000000000000000000dad969443185072d2e1f657aed34c8d970b3bb370000000000000000000000004a64d6b9ef098ed25fcc47a537bb483e2963d0860000000000000000000000005700c1928f7e9cf7af2205de589bf12263db6530000000000000000000000000a31688aeca874dfcb51c26d4adef91bd18f09490000000000000000000000000a6de99edc8a11f761eaadd5b646d2cde410bfb290000000000000000000000000a4979051b9bfe1bc19be8f7793f7a7858292f3e0000000000000000000000001aba27436b6add4de90b81c813764c8a7ebf79210000000000000000000000009e36e3d36b8811be651a9bc7b6557dc76cf43d47000000000000000000000000d472d30a5f14cfe9e489e32b670c11f1b658d28a000000000000000000000000449121e6099b23e3064056106841ebcb5d9f82e30000000000000000000000009872ae4277420bb1cc0c543f63933c044b4aaccf00000000000000000000000098b622ceeac4f1b0118d1ad9dd8ffff809f2075e000000000000000000000000e5d713f13538289634ddfedbfa06fc9180d928960000000000000000000000008bf19b7bd21c6e1b1c562a5dc72b1222de467d9c000000000000000000000000e6badb95e881d2fbef09fbaf07fb5ba12dc6fa670000000000000000000000008dda03750aeaf62032b769ec0441268ebc716d9f000000000000000000000000a547820f60ce4e1133f69eafdd6ef8e2a0d2bb6200000000000000000000000041311a5cb9430a255e91306c0a2134a09c723f76000000000000000000000000d4ed562aefba750c3086b468f19e99848672c9160000000000000000000000005a45f28aeef32da96db5b6c435081a677bdfe938000000000000000000000000ccd5ac13bf889d8e87bc726403cd354fe5e0331b0000000000000000000000007bd4557c830d1b55877aefb30c7bd85dd833d938000000000000000000000000cb0a53f1e7f20a5b4d983ea043989f895daa5e07000000000000000000000000114238b63efd246c18dbabaf0992b5e6c60e5cb2000000000000000000000000be7217c9e2daa6b5401baa8e7e7a79cb4ea873fc0000000000000000000000000f9269499c33a7ba0e23082f058fc1d8c116ee640000000000000000000000006366606f86ae38cd817f3688d49efaf79909409c00000000000000000000000060697cc054ac2e09b82952783b943f98fe75054a0000000000000000000000005d48ebd30cb6f1b3c901764df6472266cebc44eb000000000000000000000000fc253c6c81a5f2b74b804914d53f823b1548f81300000000000000000000000025cc895a5a051cae6874ce6c8dde6637317605fc0000000000000000000000006cf7a2c61da635eca30c5935a400f078a59254a60000000000000000000000003bdb257836e15857a610aac284af3502fc2aca200000000000000000000000009e665e20d2a11a71c9c3a37891b9a0a92e0f01ad0000000000000000000000006e5afb67b5978b1f7e56358577c6bf93789fe572000000000000000000000000f64ec0c50cefa072ff9147dc5a7dc4d3d8a0e6a8000000000000000000000000671f941215ff9db2f38d49cf6836ab0e79afc2c0000000000000000000000000faac9ecb2638acadd9698ba42591a1ec743bddae0000000000000000000000001fe73dd41b686b586fb8f9df8b3660812f608bd3000000000000000000000000d7ce18c2d099c482af073156db2882e8db851ade000000000000000000000000a319ea952a8dba1442c82a4f4f600d3a45dc0e18000000000000000000000000320d66c180785e5c16eb2ad89edca7ca5eb0b0c1000000000000000000000000b160e2fb32b315f0680d0e24a802a4a4ce9598c9000000000000000000000000fc96efc7e05aaf90c3fd23a6bc9d8d4c37b915ef0000000000000000000000007648ab9829b0b50bb73737707c5326952cd7cd23000000000000000000000000aeec7e0100c6f7d0a21666dddfe135cb4fe82306000000000000000000000000162161f0688b0056fdee01153b49f72ed1bffa6c0000000000000000000000007c3847a5876f80296b8504466d28cb92d44957ce00000000000000000000000010f376cd97c8ac7971e256f423fd63be80ca02be0000000000000000000000006899f9339471fbcebf0b78d87404a4bf7f741d38000000000000000000000000d49654bc9eaea08bb21f95e6f1839e92b3fb3352000000000000000000000000dd228dd2a81ec9f32fa17eb82ad0426c644d74960000000000000000000000009b88d5a03487335a05071e1291f416fb9641b8a600000000000000000000000081f6e5e5a83af4a6a153554a76a6948a7ba89664000000000000000000000000d5280e81f1116febd726a8b77501db93c65b783b000000000000000000000000a786214c5d2a4d5ec024fa686134dd73251dcd8f00000000000000000000000078fd38673ea90fa7f68c3c89004b373f7473e9f6000000000000000000000000859bc6c9a987a086aad26c0a0f910ce5cf5daa710000000000000000000000009a1f203bb3280fece69bada8a1ff1c0ec8d27cf4000000000000000000000000f68d6242c590bcb5298ff28f3968db1ae0841a020000000000000000000000007935c0f5c4231ebff41ce526a50e039ea77ab120000000000000000000000000d12b882f7cd12919e9feba6bf62d43aae942197c000000000000000000000000239886f67d8ea66d918f36c56d36115cc497f3d0000000000000000000000000e7bd0aed16097e245afa588dc2bb03c0fbc6786e0000000000000000000000008d0c1b15756be79279c30d13a9590fc1bc8a88b7000000000000000000000000369eab5c688d93d4d5126d848dd9a6d979df51a2000000000000000000000000e919edecc18a570c521ca0964a06464bb4aad2430000000000000000000000002f6a1b9c81f67094d11242a0c33ce10637ee8f30000000000000000000000000b53f321c0fe1e5e60929071e250191425983341e000000000000000000000000813df108a698b94adddcd11783999f649192d7aa000000000000000000000000fcd36d472645cdef280d4b7c3993ec2a90d01cfa0000000000000000000000001885724fd53172e5d862455949f9fde38c80c9d2000000000000000000000000a15bbd0012dc46ef6d222346e6bc4d55010753cc00000000000000000000000012341357e848b29e926ab86f3e5acfd6af451bfd0000000000000000000000004873ca40c9dcbb5fcdb6e5aeef5375dffd8e539300000000000000000000000048ede89338c4ddbbefd4743b3cde9e5672e6b6d1000000000000000000000000d155e64d9bb676006b64998c05508324f4f9f9710000000000000000000000006b0931570e75e89bc9f5ead3b2863eacc0e3172e00000000000000000000000091c291f1b8a414a5049ab689f85195822a377e23000000000000000000000000cc072c2a5bdab10fed049446ce12377f9e033e0d000000000000000000000000198a584e24d3ada3fcbdec1d7b0038833314fd9500000000000000000000000008ffeef28946ef3dba23372d7898be39f16a2ba2000000000000000000000000caea702803dfde8941f87919ce233596800a043b0000000000000000000000006166dc613bc40506042daa7396932adae383bd3e000000000000000000000000a494748369b21af5c869fcf71fe2971c64d21256000000000000000000000000cf9e38d0d01e4eb0f26008c4f67847e702f172ce000000000000000000000000390a9d994552822c16b84aca0117c9ff3dd8a2fe0000000000000000000000008e3fab1bae9d560c14529e0843427f69c2c328b9000000000000000000000000ede338b3c6ba9103a9c903d94a62a9a95edfa93900000000000000000000000088c0d01274f0765ca12f9b594ac1bdb70db09a3a0000000000000000000000000b573e104878da66d49cf33f9bc0e522512438db00000000000000000000000052865c5a84d51760824820cafb5e266554efd5890000000000000000000000006e6b41de78051f7c9ab381bd2e0256ecbc8272a7000000000000000000000000599b04bb8266f01b0fdbf547e253c37210a149920000000000000000000000005c7a4e019866bc832c8eb38a1cb59690683cf2310000000000000000000000004d037ad3b8b55b0ec8b44b2432f9743bf3c3a88c0000000000000000000000003b1732a79f372bd5700ae1eaa3d6daba833f0f76000000000000000000000000118bda236a0532b4a965fc04eb0c8c1c7a0c109300000000000000000000000081011ce12f603d8777abfe4fb4019eb4961f6160000000000000000000000000ec25125cd1bb1d4a8869bc987d305e3f4838a56e0000000000000000000000006294b6595081279238e5d80fd067b874271babc30000000000000000000000002eea10652c5fad91bcc827dd9020cd76e164456d00000000000000000000000011530b782cab259b0a36d62cf6463549b733d4f50000000000000000000000004bdc0e598d89a97072706b31a893e6d73d8c4859000000000000000000000000405b791237c5038f302566f2ed0be69ea8cbeda800000000000000000000000029422f22e47b48594b5c51033035f7213e43f362000000000000000000000000ecdefbf081c278da64dd3fd1937e1a0268d760870000000000000000000000005b0cbe0273d74f22018cf1b37aeb9ec8e3a1d4d8000000000000000000000000ab1be877bf15d074854b89536ced1d9088b55d3c000000000000000000000000988f66a135e579b5cf873d86496215f37401c4620000000000000000000000008699232f37afe708c4388acd0399ddf915c2f110000000000000000000000000a9912b8affd2b4eaf15067218b948a29a54ed6df000000000000000000000000af0aecf4a4d7f87a367b8ddb69e306e5cb7077710000000000000000000000002698ab33b993eb72ab833cb6bbb648a349bda2d70000000000000000000000005cbe0f8ca58fa82692e902ff0eedc79aa25f6d3e00000000000000000000000038169754af0f75f1de60cb768d3af55c588f52960000000000000000000000009e596e717f205b1a6582d86a16b773516df62ad9000000000000000000000000c60927d5794df3b57926a283e5a9bb211d27ed16000000000000000000000000eec6bba315c715931cebaa87d80336b468cbfb0200000000000000000000000053485d58437895522058b3869a6202498d2513230000000000000000000000000e83a2d60778fac08aeb930f3c320801fe2ccca200000000000000000000000042728c70b8abf85a8108ccba50ead645b8105440000000000000000000000000f2abf91e6d6b44e2a213350aa95ae3feb9046f840000000000000000000000007ccb0c9d2fd0bb862265df11b4425f74b77a44120000000000000000000000006a5d831a5171344ad921a4402fad0b961e76e17d00000000000000000000000056e9194e18db791f5b25937c6e570f7ed7b15358000000000000000000000000c841203d9104b3d3f58d5dbabb93f9411c0eff54000000000000000000000000b462cdc9777759a70f1f3377744aac293f40ac58000000000000000000000000279816532ec96b16eda9bb36aed3ca911c7db29e0000000000000000000000001fc416d6ce0af7aa72a0e3d2d622cfca73581bdb000000000000000000000000e393e4d2302001a928572eed995e5c61b4ee6350000000000000000000000000ef2935317885ff5750b58caf90c2addafbda7efb000000000000000000000000254934859cb06d70e72ee28e1852a0f1e321430d0000000000000000000000004f73b09126c5bbf0ee674ae7ca3bbc21b531e0ef0000000000000000000000000a7e2bdf61df32a1907a3d9325c065a3d76ede190000000000000000000000006eefe64ddb2cf3d59e63dcb065154de1512db2c800000000000000000000000055b51c17fc82818b32c0ae1464a3fcb0534e4d5900000000000000000000000037b6c31b4f5f17ce9e67c2144e908185db4d7a040000000000000000000000006213a491ea8a3c34cdea62844b4b2e58270ef6c4000000000000000000000000db2b72ee0c7adf4f6fe85ccde475c67baa29f4d400000000000000000000000086eee8b8d175341ebe65bc7b579fd90be72d59100000000000000000000000000e9bdbd393697e495e3eba1bbdd0b8e3afba0f98000000000000000000000000dd4bed194cf26e3ff836b5c6735a21a1d4fa8aef000000000000000000000000d9434d0aef7f702c558108e8ded78ca4f18ed93e000000000000000000000000ad8826c9b289d4f60e03faa004aaea160353f09900000000000000000000000089b124570529d9f8c89d85b32362a430626dad90000000000000000000000000d53492c49f4fac94027597fa35bc0b7bf42858130000000000000000000000003109643e03cc8d0af5577819854e37f1c3aa944400000000000000000000000012f760c4c3ddab0f7594b76edd807be229dc488500000000000000000000000046a8b144591746de657b136ad79c5f274ef61c86000000000000000000000000c99c12c10832c7762eac23d89cedf48249537ccf0000000000000000000000000573371f08f8e2b70dca981b255f19389f02a0ef0000000000000000000000001254d674dfe65f3f46d8c5d208df0005762dd1f7000000000000000000000000dfb86b184f801327540b19ee64f0cfd17faf96350000000000000000000000000f4fe5a26349044865cb55fcbed6f42522d89be8000000000000000000000000654bd531b78d52ec2bb8b9ae3e4a6592786e1b6f000000000000000000000000a84f80e1d5f1bdf6a01523d4b6f4549b5b0511ae00000000000000000000000011466ded9018d521dd4014605c337a3d3bad09b4000000000000000000000000093d1543dcf93722e1b37d13ce2c2c7b32531b980000000000000000000000007b7be2e99b0bfc47c30519cd2a61bf7cea479f79000000000000000000000000fb559565d4a10c730c0678ef58b739ae7adb75b2000000000000000000000000c0ac795873db5a707e64c2142c663262e2cd8f460000000000000000000000006b99e9cb2c3424b370e42383938f06249222e2c200000000000000000000000039287b94404df817797eb887cb3a387e2a27928f00000000000000000000000060ed857098ed6047bb468a16aaf25dcf273c1c6c0000000000000000000000007dd0923c0cb4d5b331eba921f05af043b796e6b400000000000000000000000075b35cd84d485c768297f8f9afe4b16feadc9104000000000000000000000000015053de878fcefb7ad5db719d6bdde6412e0321000000000000000000000000a3e56c6df3bfadbfcd65d2c7c1046284ce6d6067000000000000000000000000dd0a1890b0f3213102a9f858ccc798b3aec6b94b00000000000000000000000076301ef56a524fe83b891dccefb31178790de3520000000000000000000000007fe88717c62f0ca72ee22e8641bf92c0894638d1000000000000000000000000bcb4ea5da37b9893ab58803306e7150cb83e9752000000000000000000000000fff2abee60d164a8cbfb244d10a2b18dce0713b2000000000000000000000000cea1f873d995ec94b373eb69db2af632311182250000000000000000000000006ad8fd1a1d1916e7aa6f3cabd9c3f478dfef649b00000000000000000000000039d9293694f2b3e9302019b51b4e491429aedcd900000000000000000000000041f4622644bc6cab32938a5bc27f4eafc873efd7000000000000000000000000364dfbbf3861d39aca8fcd6b57123611e98dffca00000000000000000000000092da7004d5cf237bdf4658c9ac19caae13676b22000000000000000000000000e847268ca3b533b00338bf53615fb4c38f70492400000000000000000000000007c58779155a925ed1f5ba2d610d6b02462283cc000000000000000000000000de7c9084a48266320bbd883b5e3e1fbc8ec41e28000000000000000000000000e28ae523d8bddf57e1ecc370cd87d8c16443cbbb000000000000000000000000edd0eb4f0e8bfb28c1ac997c8d1a05c3571eacec0000000000000000000000000779ab62a4587531a1508f1364c0176ecce0b8980000000000000000000000001eabf73ab1c362c2ee70a47b1e0cc8e45f301294000000000000000000000000477cf615451cda2857a049f693e7614f45e548180000000000000000000000009c93175043bf08bd37f8c83b9094602470ca8bfe00000000000000000000000061ce230157c791e12d021001621e11d9f6a4f552000000000000000000000000452283b7c46bba04305654502b0a5d0553680fbe0000000000000000000000006afab857230275fcad0c75114a2d2f7fc154e6c7000000000000000000000000c34e83eabe5e64e903a9aed1b76a8942a286730f00000000000000000000000007221f9fab99c7298e20f182771222dfe118109100000000000000000000000039e41a1c062cd4fbbfa1d7aefe828384daca508c000000000000000000000000622c611ede07222a30f070bf0865437515c21bf9000000000000000000000000db18785ea18f2d4936be51850f368cbf6a6b505700000000000000000000000057177e72844c7633cb14b2ad702b3f1cb7b8924e00000000000000000000000001447dec147917844cea959b914fdc48d16c652100000000000000000000000095eddce628a95cdcf037f84977786b4daa993cd80000000000000000000000002ab19c7edf2e73e58bf987253561e2d1fef34308000000000000000000000000cc313f29d7b55da624cf86728fc1558f353582500000000000000000000000002e061715d5db6b03003a6d5d3745854a8908b383000000000000000000000000705e48b7acc67dd54d90ceb3aba33d4953352e9f0000000000000000000000001bba8da0a4550bb23f0c94f660d8794fa03d27620000000000000000000000004afdc8c6c9a8b3b8661c242c0145c491bc2a283a000000000000000000000000d6850a4a2d9ab65ae27563c543eebf20749341e70000000000000000000000004c68e6e61f7ec16cf79268bfc678e4a97b487f3000000000000000000000000058125994f6f0eabcb9bf80632726ce9610fceac3000000000000000000000000a2e3e1bf252b9eb1437ea2b2f621ae88f5f028080000000000000000000000005fc2c4b5a8dad8794947d134bf0de97ecb220a170000000000000000000000002ce5644f2e470b664e0b87dea744e9ec5e448180000000000000000000000000902aeb78ffaf5e5002ac1646da4c976e3cd20edb0000000000000000000000009f4ed99a67143d9b6f33e7860897d5319b380867000000000000000000000000906c38909af9cd416973553105553b2d1c0b1b670000000000000000000000004a05a2a6083924d195e2d0d656fcd60cf8061d0b0000000000000000000000006035cdb4e3fb3c99b5409da5fe9faaf041bb3c68000000000000000000000000dd3f1c9ea6c073174941de8c10bb6c977e5ccf67000000000000000000000000bffd404ab45201de5e73b8a45c37d91fcf556d340000000000000000000000000d75b9ec54fd3ea0231d7a5f566ece388075306000000000000000000000000069bc8512208d70cde4773ea1796061d222ed4fc7000000000000000000000000fe58475f3d6bece4d8d1493574be8b2a5df72dc5000000000000000000000000b67cee1374515722284e03e432477dda8aa3de130000000000000000000000007e9957075c46708b11bd5cbfbd1b0fa4c9365e17000000000000000000000000864150f2da1e98a6cd6bea45d93630d26a3c9e95000000000000000000000000c60554f08a76a6fbbfb1a0649d461d442e9882df000000000000000000000000a00b4f0670d28ce92b9d317f0c69278646348d9e000000000000000000000000c6a33c5cf611816110e1acc59f6f6cc551119bc6000000000000000000000000cd489fdd0ef4d501c8bc4077b5dd04709077f0b2000000000000000000000000a0e855a4618c4c65985be4850dc8cd6b769c1855000000000000000000000000b7976f651df2ec32e7f061b6db02c3f329a0c8ce0000000000000000000000005f8a6eaed330fc1731762364a11e375e28a7aa820000000000000000000000006a7c6d615feec103835779ae1e61dc2080ab0028000000000000000000000000abb29ea8981e83c989fe7cb608abc4b1a19cb18100000000000000000000000073a2f14385c8d7b7c739a43bf91019893920f953000000000000000000000000b9f5726ae8ae704d1ae39e6635913d1db654446c000000000000000000000000079476d18a486f67cd177462ef9cd0a24fa3d783000000000000000000000000098fe625f917e1d8ae190c1b9cdb9e7d854a94040000000000000000000000006f214b804485cbeb9ac3458019b6312ad734ca0600000000000000000000000047e58e865eaa11214e3560b74a3565c51ee19c3b000000000000000000000000a27d25421f4a0caa73c0482b2888ceafea15896d0000000000000000000000009be0c14dd4d17092e66643b384f101ae41c366d6000000000000000000000000650bd8722b4f6d1e291411788421e21b9682dd43000000000000000000000000b119de3bdf476b726261fddb34f574a19111f1ce000000000000000000000000aff6acef00e48495324920c61dbe226e362aea5d0000000000000000000000000e880fa4f87bd1231349966576ea34999083ab0c00000000000000000000000082da0a99f731bd3cede407a5082dd5ad38a0fa660000000000000000000000008dd09ea0c1486bbce042ab63bd1f00575ec57c10000000000000000000000000ae9fd3be2ad3ab993e8b12f53acb3370d2cf85c30000000000000000000000003de5e8bfa47dc9f7067543196dbd51c8d3a03cda000000000000000000000000f77c1491ea35053886c32542779add9b1bd8ff4a000000000000000000000000d1cc923e28d5cb89e60752f6640f923bc4f8450a000000000000000000000000f606c34a4f67344c1aa9008c77d3d1097005d42c000000000000000000000000d68aea6028172cfd417bbffb1065e2baf832bf5a0000000000000000000000006e763631e535de674135c4f64e78110cd3fc891a000000000000000000000000848823f7dafe1bb36b7e64d188488b70aa4f6c6b0000000000000000000000007dcb17cfc40f9236b08c08adb262b4099a3477e3000000000000000000000000c987478064f7a6339f7a0c63e11053b85b10c88d0000000000000000000000002c69f0b0b32e997ca9fb1adb9b122383c01bc96600000000000000000000000079a87ba6fac6983213b6c1421593a7d64e2fa0be000000000000000000000000497a1f6d9b13905db63f2b9f16fb7cd22aea406a0000000000000000000000007f8139c8269c917085537ad68e3600ddd9eb89c90000000000000000000000007d094037d0f0d5a5ebf67258cfe783ecf57a74040000000000000000000000008ad198abd937666cb3376a20ab6ca257f96f4e2d000000000000000000000000c9549d267d39e494770297a589a5a0bf3171d1690000000000000000000000005d6edea6017e54154e5aed9841e4623bc8877c04000000000000000000000000008880fefb10a5999473943edae788a6d54e4bc900000000000000000000000037668b5e0fc9bf6a45e367d6030b6609bbc32b61000000000000000000000000ae6ca04ed7eebdd5dd256e595072c54c304149d900000000000000000000000008d347e43e361c42a31daeb18ef2cc0d190d6bf10000000000000000000000007f50d5d38ea40b4e331d32aa85dd74bf7ad7e5110000000000000000000000004846c8dde5747a69960ed0dd8538c1b7ba393e940000000000000000000000000df2b635075fa358eed7c126baff1c9b862d3bbb0000000000000000000000001deb1e3a9a33e99886c7b3a91e34f2ec0d9b52c9000000000000000000000000ae4f25c994b518b4a0fd37f666e6f8f77daf16d20000000000000000000000000c2c5314dcd0a7bc928ab028c8ab384f9ac26ab7000000000000000000000000e1d0cfb183ca26466b81bdfcfb1d6775e3f4bb370000000000000000000000005b3c938e6f0303047b57c7d8c13e37783f795ba20000000000000000000000007090e22b5f2d4215a635beb85441b6128d011c76000000000000000000000000a1a93a8431d224f5f99a741d26433d90a4cd069a000000000000000000000000739dee60308f5f7a05be524b0785b36c95153402000000000000000000000000745ca64c4b7519399505ad7ea9ffce90f9cf3d3300000000000000000000000098c9c491b129a73fbc67fcc6afe4b32803bb34ba00000000000000000000000072d721c88690147421092956b0e58bab0bdc3034000000000000000000000000584c6e21412c7a6567c357571204acc2bc57430400000000000000000000000082735888d1c4043bfa804ffff5eda0b0e52bb4b5000000000000000000000000f9b84befdfcd0882aeeaea24f850430ef8666aac000000000000000000000000eb75d14fbdf23f1e79033e99706de359e4316dab00000000000000000000000035c443afd895ffb2b769a3416191a9ea500f946a00000000000000000000000081cd0d8c36d9f43073a6beea7bc2a143ec66885d0000000000000000000000002fe1bb61cc67cf64cce87fbf835b836f6a3c04e0000000000000000000000000c228d0a9137cc5e35bb9770926d138a19d1f73470000000000000000000000007816aae5c3a50999deb2c4467aaa4a1fe34507e4000000000000000000000000c6aac9efe2f7d96bbc1bcfed1612318daf302c69000000000000000000000000226bf460f545730b0cda9cf96c0393a55cf714fc00000000000000000000000017d0827fd96765a7af1018cd93006ccf1fabc5c60000000000000000000000005fd9239392ed3759eb48691b58ca41cea79b831100000000000000000000000011c337ecb620bcbaa3b8b9aaeaf397bebf93eb8e000000000000000000000000f37c8510bf7e21c556e13b625b32e1ab3b790d6700000000000000000000000051761896987eefacf1be63e919d26cbaaff33a300000000000000000000000008d0245ffddb94e6b0012e7a7c44728140aeb1503000000000000000000000000ccf9a84d7eb7eb332377a4ade4bc18448ac47eae000000000000000000000000400acd777f435768d5714d59544dc212cc750b47000000000000000000000000ab67ab90b3e8d8d1d31cde0d69097006819122d20000000000000000000000006261c7f5628c552e245db265b45d878a416b059400000000000000000000000002b7439d09fb3cb7e4c9d4124c06feff55c09a1b0000000000000000000000005cf6052840ecea31369e7bf426145ac2aa59cab6000000000000000000000000e14e36961222c06258eb01e9cd870905d24453e9000000000000000000000000ae1a546aa603395dd8ad9cbddd1c10ad36db904a000000000000000000000000c1b369f561a27830559bbc770ba4fbf96c255eca00000000000000000000000024b7c11f7e06ec108f2b924fc90a49120431fd0b00000000000000000000000071f5a573e4e06e590757326e6a63b02706d319bc000000000000000000000000c4df171fa405df3fdabb136143055cc04ebe97140000000000000000000000002676198d802660bb4121db3afcf1e11540f5d6f9000000000000000000000000244b8ca6996c29bef8ff7d83c6faf4efe9387d6400000000000000000000000094c15b3e5516ec5bb616d17a5833cab29c7d63f20000000000000000000000004b81de6df8bad151f2593c3ffa97c93879e8d9ef000000000000000000000000d32ca710b42fbd87f835b9b595dd1538b605f2f8000000000000000000000000edf1f554a63311572f8683dcc8a09b872d926928000000000000000000000000179f95c078bfb5cc303f54d08da04696833077f2000000000000000000000000d819e937426a84be689a63dcd4adfb2941ff22100000000000000000000000008c54c93ee4b0e630513f195368b615b9d6e236c1000000000000000000000000a758969c692085ef88173c392f2954270b9827bd0000000000000000000000007f1973e5f493ade52b98575bb513c2ea8bc52d93000000000000000000000000522695d7e879c5650759412a73745c7dd76cbaa7000000000000000000000000dde758ca495cbddce69c6fda4acdc855af11d4a30000000000000000000000003d7ff7c5c8bdcc4b0d3e746e20e44024762546ba00000000000000000000000054c58a2db5b2a58dbba43dd1e939daec6445872f00000000000000000000000058331c199e67a9e991e2061cbff70bcb15d65a49000000000000000000000000fc7ce4b74e8b1b5835caee6d34a6072ccfb36d8d0000000000000000000000004bb9a03990ffacea5d23e722091bc5d2718d1cbd000000000000000000000000f46b13d14a9d74ace7557fa069c7d63733de5fc5000000000000000000000000d2bac8ebb2f6f5a92cd13c0b5477f916d0d29bef000000000000000000000000bd1a4ed3b262ac6432602b7a18ca65ca29a364700000000000000000000000009e9e83275d92d3dca640b2f8c14d08e389a75f9d00000000000000000000000014cb0351377e3b206e63dbc435d3f185b958e0dd000000000000000000000000ec820902c78106daf869d2bd1a5ddb7419ef40cf0000000000000000000000000fa9dfd93a1a51c8eb7dcf025730d1ac02f83fa60000000000000000000000001cf0088c14a5a4571a41507736815b00c5e532980000000000000000000000006c1d498debc51fb25b6d06492cfb057e74ee6be00000000000000000000000009d7ec8b52818eeebb6b893b2f09d781c024e0425000000000000000000000000dfb671a4f29e9a7747e5a9bfdede8f9fb81982ce000000000000000000000000d865cab8d312053cb5dc3a8e2b47450a1f9f25ce0000000000000000000000005c6ef10648213c486570c17c76cf0922f9edca4700000000000000000000000088d3a16433ae3833adbc0ac1d3fb30e69cd3bdf7000000000000000000000000615a1d028784a273ab0f465795d793398b3887c600000000000000000000000091f6add8c6992bc3efadd528c20ace3ea9e2218e0000000000000000000000007f3f80ca889de690aece0db872e4c39f52ee0f170000000000000000000000001481f4f9cadbadf25f873631afb6ae22d25503a4000000000000000000000000d5c0356c800e7191a5af4cac2b7ae9c44f07b049000000000000000000000000d778ea38e4ccf3511a0f59c9806f37c7bda92cff000000000000000000000000ced5a895b7970f020819eddde9b0616d2ae053d700000000000000000000000034470cfaf0fbccb5935a6bed3baa686989b04eed000000000000000000000000c936019d1847acc03d01362ff05b19c521be154d00000000000000000000000016157022e231bd391cbe55711d492faa7021b805000000000000000000000000e91f81b0057862d8c5ca642a05db00ddfd6cbd02000000000000000000000000942f6da946cf77fb53170e8e416e595f5e3eb953000000000000000000000000fbc8688736727304d06252134cbba29ba95d1a6d0000000000000000000000006580ddac5674726ac60dba4470024615037df0350000000000000000000000002f7e4cf14adf900672408a7d21070ac34363df0d00000000000000000000000074f6e058315fba3ebf55d33cc6505ab5ab28cc4f000000000000000000000000f21eaeedfb2d605ba091bd08e7c30b23450dea2d000000000000000000000000642df68305263e02627260098c6e023ae29bdc75000000000000000000000000c3037ef8ec35c709f90f2981f322416c8d0fb8830000000000000000000000000d5a5df0f56b7c0c2d77e15a4b066034c30ba6690000000000000000000000008ce354651788679cb1385171412443fd1b51c16f000000000000000000000000d89f429469eaf8e36014149f748ada5404c584e300000000000000000000000019dee827123e28196c6785ab13a8d15cb6721ada00000000000000000000000009cc1b107188bd4b2db85ff1a9731ffdf2735cad0000000000000000000000008d4535ef821c773a905c7bb5aabad9865a4fe9a20000000000000000000000004399555e2fe753f7ae3ca5bf1a6735219733303a00000000000000000000000027af5c6b3b5606ad2d495c90738ed36c2acbb26b0000000000000000000000008e21dc17387e56705289d8fde6aedd71570b0598000000000000000000000000b884787b220a857af3f7d87da295cef35a60ab900000000000000000000000005a33449e1068622d4b9189a10e6b06f282668ed4000000000000000000000000dc69eec0c0c5135d11375bea03b76ca939566a06000000000000000000000000cb3eb3331d3a3021dccc0695f7f5bb356b38ccc80000000000000000000000000a489b5fae86079803fd840dec2cd6a8c9fcbad20000000000000000000000000af8d0491d94389ea61e5ef79f3924581e2e82c1000000000000000000000000db2fa4f76fd1cbb08b52e57387f2c73c8cc067f9000000000000000000000000a152e9b26df4eddd79115ef846bc74a1f2f853c70000000000000000000000003050eb14394f11e8cb74a1af6db83db05fabb12b000000000000000000000000264aa32dad1d8f0388b787eef55e8d5dff7d9d1a000000000000000000000000e599f271f0094c3fd5532648b7436eb68d4025ac00000000000000000000000033b9c695c88df21067855fcbcad5f7ee20d0b9820000000000000000000000008e27a7d508f292d709d7099662f42492bc8e599c0000000000000000000000006b27994021433c75b5115feca4fc31c9e1c3df21000000000000000000000000d299fa49a47faae8f7d1b69f0fb2588687eb4e5200000000000000000000000065efa2d869cffbe5558e4bb789041e32e75f785a00000000000000000000000044bfb7765841a406674f42cadcbf2bbfe69117bc000000000000000000000000c11c272f7c5f127dd911dcc3bfbe62872df77d8d000000000000000000000000f74662dc19c3d6480627e7e7542d28b30a3a1531000000000000000000000000743b0c678f819573d421c8d8f017281d11bf5056000000000000000000000000b8d86887bb379db33a17c11a7ff9cffb16e35131000000000000000000000000c6e678f796591bb22b58571f5db16fcf5a8bff3c000000000000000000000000ac531284c469b20ad3996dc04ceacbff3b4ca2f600000000000000000000000049cc90f95fb3795ada93bf86eb7a55ed93f347270000000000000000000000002cb98f1b05213be609d4f8c9099e5265760c2ab3000000000000000000000000966eb41f9215f74a76f15e101138eff62777231200000000000000000000000077daa2a89281c0936420f7ede2f0e68233501a54000000000000000000000000e05546e3eff21f2fb0fbec055ef6cba23f77304500000000000000000000000052b9d6664da609082e51f0e7a45a7a945de629710000000000000000000000009a6bed0f7f31ea12075593e8c3f7b3d7c16c6f8a0000000000000000000000005fd468903fad6436b61265101e10022292b0f3c0000000000000000000000000b07568a6853d97020a69f48c122c84d2aec76d92000000000000000000000000d0585568d1bfb1f626cf335c6c3e6cdcd84754f700000000000000000000000065cb1900ed9f1f7de66776ec75bd12bf7ab0163b0000000000000000000000008f7c8aa90ebdc51484a16e036b0797667c861912000000000000000000000000f6a6c660c0858007af06dff44050106e49227f57000000000000000000000000cb4e589c58b636291b73f36428a2901f9a901ced00000000000000000000000012f6c398833cbea0b56434d814ac107fba0c9bd8000000000000000000000000eccfc303f108e382b192cc9aab70e30a7aa7810f0000000000000000000000007be2d5150d7d7efdd0ffa8a957dec8895f8d1abb0000000000000000000000007c172e273d6feb01c041211140f39ea8be27b72500000000000000000000000054514197caaec3db94451f432c5092c3783763870000000000000000000000002516ffe75fdc84bf027ee14352839acf63cb041f000000000000000000000000c6508db99f4db213dd71c34784ec0505383789ce000000000000000000000000f8dfa50903d65fcc03da6348bf8fa88483965b160000000000000000000000002499a068f468f8896f2b7021cb04092c2476f56300000000000000000000000061ec0aef14f58a89df9749520a171eee30b4feb6000000000000000000000000b57935e3997173d8d2d471fbf0c5c8f9ae02b0c90000000000000000000000005e64578c4cf7abccb32a264cdaf0c3261975d94e000000000000000000000000c4bb855893df113f1f3616c942e683a440df52cc000000000000000000000000daf5025ec0844c9dadad3b8b3c4cad41b97d6596000000000000000000000000160339dc12dce4971ee95c545931c12bbf6c5287000000000000000000000000b59a8042ceb28de94b49601979d7910172156f03000000000000000000000000e8889b3afa1d2a37996cf3ac56c1e633b4f50dac0000000000000000000000004f001b31c83f6891c9ddd41dcfccee9986d2460c000000000000000000000000449362efaf626063228251f44f61770ed316ad5500000000000000000000000005da93afc48b874ccf93d3646b6a49cd4d0b321100000000000000000000000088b3adba67e1e3b16b11483fc630bb3f81c73b6d0000000000000000000000004d502f02b8da038aed30ab1b8524b6320c84ba1b000000000000000000000000c8d69f95a6e93981bb772f63cf4d61d7a905f9a1000000000000000000000000d4ec18973cf3af5779886907124e55085e91d6420000000000000000000000000de5344d8be36b90e7ca73301102981d3b2fda250000000000000000000000001174c8d3fe1ce6fa955c228760300e91500e4da1000000000000000000000000eead4f379456506a2451852ef1ddf1d8c563cbd7000000000000000000000000707cea598b987ec33371490b4675f3af9eb2b1dd0000000000000000000000000300248c6045adae61585f2e454da9c77b25b46c000000000000000000000000d1319e17a1bf454afdf55335a0dcceb6d72607f9000000000000000000000000f12d4e9585d3f11c9fdecd660a00717bd7e296de000000000000000000000000ef38a797c135876314189704f07725a785d062b8000000000000000000000000a5ded0412981543480a26de10d9575e680dcd7ae0000000000000000000000000be8c925e6d7bbe0ab308e77933bb04e05571082000000000000000000000000e007ee43f5effe8107efc3c5a80bdedcb5959c380000000000000000000000003f108f6b29f941943bd3c187b7dcc97337f0e28300000000000000000000000088efbadfe921329dfc18ae698a56e1917c62764700000000000000000000000032c14c8b0df2ade0d4deaefa67340bbe4b312a910000000000000000000000007058084b8ed1d2934b260b546aecea79ff3ddec5000000000000000000000000aadeaeae853d88b675cd126a02407a55190147980000000000000000000000003b5a09804421c0cc73ade78219e8009f611633c00000000000000000000000002b3241338e81947959246847e199ef164bebfcbf0000000000000000000000007226122e864d80295771965d3df6760ed8dc894c0000000000000000000000004153b278035d95cde210ee624444aaffdba85a4e000000000000000000000000a2ee16c8d256ff2b3390eb20e6481ae43d7a8081000000000000000000000000b8a41cfb33ec38012c542d28a2d2adf50c0335eb0000000000000000000000001e86ae93a09895ffdf65dbc30044a97266fa12c400000000000000000000000075f0c86c268ba51aff3a78cb8696ee350ba8bfde00000000000000000000000015e1c04e9c5ca82e345e9fe549d0e959f15695c70000000000000000000000006201f32c4a95c3b9c64248e76b8fceeb856458640000000000000000000000005b65b896918ebd74f843d0dcfd1e7bf658a9ccd0000000000000000000000000fb95a069bca28020ee26ee9249b8c8c3e39de19e0000000000000000000000002826e0a4cfe5fcec5279aec12c4c928d545f8e3b00000000000000000000000047ff9f6d8b60d7bf7b8b162fd15ff7466c030639000000000000000000000000347691621e017447144fdec87cdcc11a18e0c103000000000000000000000000c941fc5ef8613f0a6fc1928a1310f9f189b358830000000000000000000000004ee081fd9a51c1049164a047b0392b6eacf653bc0000000000000000000000008528520487cc55be4a0c1107245e64cc8c71ca930000000000000000000000000ee876b94c9b78f47a52ba13faf1717a0366d5f500000000000000000000000029bebb64d4ef42be9b84fdd220a3249d3af8af9a000000000000000000000000a3ed2f67fffe1bfd47acf19b2e87b642b6d9372500000000000000000000000057966d342f21a43159652e32ddf6daf55a21fbe20000000000000000000000002fe2d27315d598298bf2450da927ea0f5a8d280300000000000000000000000061364d8034eca6a67382873d6df7a3e2272fa95b0000000000000000000000007739d16c64bcd859b17a43086a037fa56f4c0c1500000000000000000000000032213e3cb433aa8387c8bf5352c8b6fb9a4721ae000000000000000000000000b7f4ed0af44cf6b136cae29d6438832ed51f26b600000000000000000000000068c9a24fc41881daf1a82c1d59048270cc510dac000000000000000000000000c61c871aca1ee6d97221f38192ce7a1e04716eed00000000000000000000000029a7344119c4fd3e582b5e1454a1448a35ef243a0000000000000000000000000b56d716e765c454f3065d7686495eef408371220000000000000000000000002e0f50ea52213cebc28706776a07a97f4796d88d000000000000000000000000bf2c8d2f98a4dbc05f51f475d81c48b3efb7dcba0000000000000000000000004d6954860a5291a60d64ac9f2c224b742dee40730000000000000000000000001ff00121e9f6f4839b08a6efe6348f0bcc9ecd5c0000000000000000000000009029c8a11e310f1abbcfcfdac4dae7fa3cf0f56b000000000000000000000000aa3cfe869ddb8c505ea32e7856aba47c511eef660000000000000000000000002725f5d4ff99d5cc8f20211dabb832c5fbd4f2d8000000000000000000000000a6c7ec7b289fdece521dbaafa274038bb95c5fcf000000000000000000000000e522124f4c57f0976ca00a7ab9125ce8ab6d1d09000000000000000000000000fa39b9dfc6f613217126c34206586d4bc6af3852000000000000000000000000ea2c0ae279e26079926886770b5710a95dc9d86c000000000000000000000000a20d921cec2a6c56d1ecc32e548aa713e2550fe30000000000000000000000006b090ee37ead372281fbedb5d8636796c6c89d35000000000000000000000000a2f266a1cc103b8ac1dcf564c5fae262c9fc8364000000000000000000000000e82fe8fd7beaecbaf02f0879f9cc7fb592e3d0c30000000000000000000000002e13ab492cc3001436ad48be416023ecaf0072c00000000000000000000000006c2e89dd9c3539f3c87f2e31244b664b92c85883000000000000000000000000aab8b753b05c90ec704e9dbe79a9e366f1a4775c000000000000000000000000eaf958a6d7bddfc4b1b5fb0b880b2a998df146850000000000000000000000009ef206fa56f673b57260654183398d1c7e210e2a000000000000000000000000cc9973acb08ee36ce1dd57eb94263449f722763000000000000000000000000019758c4247a22882fcc03f1c764c69774d56a610000000000000000000000000f177b67775e4b8b8b723d94a1e4b300c29c676d0000000000000000000000000be1558074111404da810fd48654941d522ee845d000000000000000000000000ba2d13ae3212781520f11d147fea508d8cf44056000000000000000000000000a5e09eba16c94ec69c24c2785dd7065289146cca00000000000000000000000080e88bb37de082057d587600a7efc1561d86d191000000000000000000000000be378a8d91a14e0cdaa61796509a75ee8255bfce0000000000000000000000005170e50ff672a6431293ce9c693b0ea7d1d2990d000000000000000000000000c98f8e16d9a40477c4053f887b828f1231cdab7a000000000000000000000000f2e67535aa6b4fafbd6e39ab65742542bd3d6780000000000000000000000000a6ef8ad6c76bb33b63f9619953b3584d30654c49000000000000000000000000a66c6cfb6b745a30ce0a18f9cca662a1a78d78d0000000000000000000000000ec287f89381b8fef25858e2854d3c6dfc2ee7ea1000000000000000000000000f50fee3a8196e49bfb6501e86411936ecb03e952000000000000000000000000644f41f35e07a3699df4366808ac08ca1037863b000000000000000000000000c5f5c91c036d2379f2e3ace8a3310246addd7554000000000000000000000000f068395f1adbe0d6f3b3e6c20d550b7a568be369000000000000000000000000018ad443331c1bc8af419e1ab8813e37dbf07b3e000000000000000000000000ca88eb1690dede759e2cbc0261a2601eb49352c9000000000000000000000000dceae123189d87008c87447fdd9466de64674baf000000000000000000000000ae880cbbc6739a153c5fc61aa2dcab963da2656b000000000000000000000000ab717ecbf05da67b55fda9fa23c330fb4afc93a2000000000000000000000000476ee4afa9112842c5f6d7014b6eb1c08f2f339100000000000000000000000003347f84d1a562fd978e05a0f6a37760b271d340000000000000000000000000b802553dc8afc397294c74af9ee04683bc1ae72d00000000000000000000000042d2ae06e68d447eaa5d5978b3ea8e66bf27ab53000000000000000000000000041609c83a2c8e1062f71ce15b4a1f2fff42ffba0000000000000000000000005e9d7face1d1369ac9069cea31336061992b267e000000000000000000000000088d52971c9802823e538a898847e5040c9c76190000000000000000000000008b27f703c1188aeb17d9f7f7d1f99ea7a26f8fb100000000000000000000000014cca55297ebf43fba0ab23613850a0c4d15376b000000000000000000000000c0f9cb7e611f6b9822654909bf6abbef0ccc255b000000000000000000000000d7a2dc21b878757a0cfc915f60a465fa595993f7000000000000000000000000a942d95aa06eb8dfaaf2e7d06f85f026e307810f00000000000000000000000021a415f52a87c12cb3163db935ea929d0843b48e0000000000000000000000000cf5deb1e629b8a0f33685babca4157b62a2336500000000000000000000000076befa597d53035ee0d43eab11e129f198a3b09400000000000000000000000000385aa52a301c030b166d8981c6917aa57a70020000000000000000000000006b668cb92c124dc26a9b4ba9fda27c0a8a60f8f60000000000000000000000009ae1d82fbb009e7bb2afe2547e4b3b6fe45342b3000000000000000000000000435bf4a1c73a382b92816c344ca2ac5e7e9975d200000000000000000000000033a4aa3cc0435ac3d247b62e03797c0a31d3229f0000000000000000000000002638db06e731c4b55afce63e1d933189acb431c70000000000000000000000007a947ac4e196397d91427d245c43e8f99a17c2ea000000000000000000000000b4fa647a23af5dfcc424475de99958063cb0e148000000000000000000000000e766bf1dc39ec15ca80267900d17c1442b544efc000000000000000000000000a860eefe8a03662c7348bb4529781dd21406244100000000000000000000000063cd05475910473ffccd41e729e4068b45dc8d510000000000000000000000002995f0a68fc4f51048f307bf081b8c09a0e4e99700000000000000000000000011222a31be36a6d853fb852a9a44ddb241290b08000000000000000000000000f58db63ae989b13a7ea02743417a1b58defd66740000000000000000000000001ab5971b8d837f4eb082e2c7d262292b9d82e1ff000000000000000000000000479cf15609cc892ecbeab83499a86c14c99c3bab000000000000000000000000ca90af695f8c87dbb080e0a73a6e1b97413a7b59000000000000000000000000329f234c378cd3e236fabb733f193d4008a5cd6d000000000000000000000000f260d61ed3c180539f921048983b3832a69d811a000000000000000000000000149d2fcc8eda76a098966622fc24f36cd74d538700000000000000000000000076cfffa7139903ba7233409eb2cd22c23d4c19fb000000000000000000000000ec65d4e35ca9f9ecaef3a2ffd64df1a0ac9d5be80000000000000000000000000f2556cabed056026f30fd55ee4b4e7c0c2f68b40000000000000000000000002cc1e26bbf1a53695110d78faa7b021aa2c8f20a00000000000000000000000086b6ec7076171530692cb325d94a1425148da97d0000000000000000000000004ac6dc39545947364b416937f6431ba1818f4fbb000000000000000000000000ad3dc8d2d8cf0abc333bcadcd2ffb7cafb912b910000000000000000000000001d6ff73049d71356ee20c5b403c641915faa4de2000000000000000000000000b7267f458b0d4f38744466590a8023a4a7e7ea56000000000000000000000000e56bcc23776410ab3b32ab0497b34154b73ff8b50000000000000000000000001bde8c21b47be99d58c43d283f7501703a64ab0b000000000000000000000000ced24b6a49c5217dd4f9cae19ac5cc2d7df38f61000000000000000000000000785bf7b0235908ba76207081aec8ba4ce361524e000000000000000000000000f0429528ded30121f9c2ab0d4de653d51becedf400000000000000000000000051b21605571c987ca5cb03f03ec7ee7206704af500000000000000000000000070a50c0e174a5e9f4a56e2ae849fcac97199dee9000000000000000000000000d7da71e7ddc95e464607efeac6c6c6d46a26f8610000000000000000000000000a7ffd571a8a2430d10095173dd94c96ed076abd00000000000000000000000031d18eed98e7d819644531252b8caae84b9e1771000000000000000000000000684e2a76cb3281bb3e9dc3e7d5a29151a1f4e160000000000000000000000000d9561a4546e0da11dc8ebe4ab5e02dc61fcd3e3a000000000000000000000000075773cdac7408e3e72919aefb543314c0015943000000000000000000000000ba0267881b1de1aec47ea0418d6c8ad20ef192290000000000000000000000003cff55c18176205ab656481921270b04939426b1000000000000000000000000f5691a5f8afb2c7bb618aa346085832bdd3d6a4a000000000000000000000000a8e5a934c85c4a07a8bf4b0d2320cabd33b59d5d000000000000000000000000f08b880a782d8c4083baa1073b0de22bf67c7d4c0000000000000000000000002776ca69dd33bce8b1cec3e6a51c7268313cb6d9000000000000000000000000847f1db65ce5e9232142c0ca5493c8df09035ef80000000000000000000000000ee44f027548fdcce4174e4471733346222cf446000000000000000000000000af87f44937eda988b6b1cce54d71fd2a6ae25ece000000000000000000000000a9293285ad996d39e187e265bf19ff9f6d58c2b3000000000000000000000000f2537e61d09eba3c0a1e3d2c5ee118deab383343000000000000000000000000974f61efc0dc1bfcfc86312caf88b7881c69fe9d00000000000000000000000076a38674d9bccab9400f06e01b8493cacaed54940000000000000000000000005bfe4e0780057bfb9553818febf3ae87424804fd000000000000000000000000209fdd51c58a081891c615adf5f1b4d59266e4e2000000000000000000000000ba324d55462b0822d9aab6cab88ab12bf7376913000000000000000000000000ad38221bede1e10715c406b89dddd263e85c22c1000000000000000000000000c67ddd52e383df02e3365d7f8569c8a439181d7a0000000000000000000000001261ef5836770e577acee2c1f235339b0639a24a000000000000000000000000d38910fe35774693713c748b98e59ca99acd028d00000000000000000000000072824bb44b35eab26e88e9e1f5e33e2ca66d761100000000000000000000000099ed616b50061b18acfd3aebcc40f6d5053c0afd00000000000000000000000019c1314ddb8089cf54ccfc9743ed4f6b4de16bc3000000000000000000000000af6e61096b8f2ec43f8bb8f74465df5ed0584189000000000000000000000000f55c26bc0da0a50677abb0329344d63e9791cc64000000000000000000000000a7790d6f5a9b708d77d2989ad7cb9403ba215d9d0000000000000000000000002f8c738d0dca8a63d5384ac9271c3b6c2f7b82e8000000000000000000000000735540a4e419242624e333401ed0cff11d8e394c000000000000000000000000fc61dde6fcd54d2d602298097a92810752ea2216000000000000000000000000a474f7df19c449350de2d8ed9530a96bd489e6180000000000000000000000007e1e3abd8dd50a480f1e94ace42024809a9976b00000000000000000000000007a5b3261d1e318b2b1d7cd8395fec2d114cf1e0700000000000000000000000022b8526cb9bf35870fb3b64d2830a342662fae1a000000000000000000000000273c1e818ad4323b261e6989adcd6ec292f39eb6000000000000000000000000b8bc2b71537cdbe0cbbebe3743d456751e48b437000000000000000000000000e5eb897aee540b3b61f9657ae6c7b3a8817e5bbc00000000000000000000000076bc03f1e645601bb46e1b913ad19f5cc68ead9e0000000000000000000000004e907e40d9e14d137ca9a4dc4e837526ab24a75a0000000000000000000000003e872672b1aa1928e2c595c792d3660bdb9bf009000000000000000000000000002c24046f6238c8a23d5e66af11e82b413a747300000000000000000000000016ed4972d859c4863f5aac4827ed35082fefe2dd00000000000000000000000093d7cd3e4b5b02a57ac94536c49e09b3b678298c0000000000000000000000002fe6d5d36fae80f36b49c41cc669495a15d61f1c000000000000000000000000ee336bb5175862703e2d121b2d62fe3230defa8d000000000000000000000000ca40f22df970cfac0fb548f4681544c0fb76d2260000000000000000000000004a0e6f7c6a68b009549f7022fc772f04f0b3d267000000000000000000000000500739e1734d4361a642e3dc1947eab3312cbe020000000000000000000000007f9777b9460f6f2e2fec0737b3ecb764135569cb00000000000000000000000054e8086deb08c761eaa0aa77b6b0846c9d2ffbb2000000000000000000000000c54885669813cabbf2809e1abfc8f64ff963081a000000000000000000000000cebe744ffe69ba5c903a648a9ee1e7951d06d35c000000000000000000000000c4b4c4c30ee327faf976fea34d91ada6dc65aae3000000000000000000000000cd52cd7fe07189bd16d236eb1da587bb9d2fbc690000000000000000000000009ac44065540cb31b69c9f18c22392c6eb6cbb7b30000000000000000000000000a791b8d6c823a58f59782bf901141d2e21d3867000000000000000000000000dc75d344dfcf394da9ef25500b83551809fd7c23000000000000000000000000d76a3139322bf6edcdd8e71cbe225a80c94a43d000000000000000000000000031a3135956b33f9936791e5f11546e7f9a11d91f00000000000000000000000067a045c776f4422d56d6f59c70fb42bba05be7c9000000000000000000000000f3e27a226c9c1c0169119dad4c6f8ce8b07342b2000000000000000000000000eaebad11a448fc6b3fdb40f53bbbcaefc9e12720000000000000000000000000715b03b71847e76ddef8ac19dc0bdf92f2fee7630000000000000000000000005032f427d9b911ebe63ad99f707e1fafa7b9a23b0000000000000000000000001758fe2c3492bed3f6e2a95247b6df04fbdc4cfc00000000000000000000000016aad9880d1504fef8b64ed16ee33e7d42bd9321000000000000000000000000edc03dda760e5793ecda69d9837956f246e2cc7c0000000000000000000000006a89f27f7dee87a4b69167ca795dbe07478de68900000000000000000000000091d762a023ee879222728725d1b4cfeca45aef1e00000000000000000000000046a3d887a93389e59c1081a96c23578a8a97e87100000000000000000000000072da397dfb39e9bdaf2e6e5852886a8e0f422b07000000000000000000000000e2c8c2258ab538d28501e23e938310946df84ac1000000000000000000000000bb022fa11c82a529f67d850029ae9ba111d6ae580000000000000000000000005dc5002fe446e7526aca4c30068f162ff806953b0000000000000000000000002e254caaf6662a65f28d0e6826e23305897686eb0000000000000000000000002da4b85b57ed5445eb9d92190f1cc31f70791cc7000000000000000000000000127f2b9ba900b3698589d66e22b457dcc2e988f00000000000000000000000001c5f2610033871625f91ef349ff5ad29168a5f4f000000000000000000000000381c1fbbf9821e2d3af022d9bfcd38a223d8d733000000000000000000000000af9ca6bf95489773edb5003abf3f96e137b0dce00000000000000000000000002187b84a100effa2c5c5bf6d31a959b9338a799d000000000000000000000000922875f3ac04bc4ee3f249216d83f5a3b30a1f83000000000000000000000000430d3d400469a788bba266a718916c886400c68e000000000000000000000000f2d811c2ac3e8019c18ff3530777c072cc8a580a000000000000000000000000dc5c5b4ef174c2c57fe6c712851729fa86496622000000000000000000000000e59e04766142949d054abfa7a9ae29147a2dbde5000000000000000000000000616e72cb01e23dfb60b412580fa26739185373ca0000000000000000000000005a1e7ef74e0fd75a2ee88cd6cec34ef3362efd0600000000000000000000000056f857ba48c51822a9ee3f0e658c608bc26732b10000000000000000000000008143cbd82f9329f112076510706639c58e7fee2f000000000000000000000000cb00f2d13d7840c4b60b995edc76d250e2bce4b600000000000000000000000016bdc0f3a107af309153772089298277891026d4000000000000000000000000d21fd880f77730e3fe563a3b884e2231426d423e0000000000000000000000000b2eed6a84dd1ba7d8ece83a1665bdb5a4428b94000000000000000000000000ab1ca7e2322b23c5a51c9704684efd8ce32a799e00000000000000000000000091e5e85ee93ca51b26137cb0bafd67ba9de477cc0000000000000000000000006ff0825bc7d48865078fe3413e4be66bd41677e0000000000000000000000000794cdc7d28cda9720186e8f81c57a084eef049b200000000000000000000000000000000000000000000000000000000000002bc0000000000000000000000000000000000000000000000000000005e627181b400000000000000000000000000000000000000000000000000002bc6d43b0bd200000000000000000000000000000000000000000000000000000018ee883f3e000000000000000000000000000000000000000000000000000000e22aaf624700000000000000000000000000000000000000000000000000000843a372ad7e00000000000000000000000000000000000000000000000000000018ee883f3e0000000000000000000000000000000000000000000000000000001726a316270000000000000000000000000000000000000000000000000000017ee175821700000000000000000000000000000000000000000000000000000018ee883f3e000000000000000000000000000000000000000000000000000000ecda0e58cf00000000000000000000000000000000000000000000000000000018ee883f3e0000000000000000000000000000000000000000000000000000001726a3162700000000000000000000000000000000000000000000000000000018ee883f3e00000000000000000000000000000000000000000000000000002bb6cd2c9a060000000000000000000000000000000000000000000000000000000e3f2948b50000000000000000000000000000000000000000000000000000001396d8c3fa0000000000000000000000000000000000000000000000000000001726a3162700000000000000000000000000000000000000000000000000000018ee883f3e00000000000000000000000000000000000000000000000000000018ee883f3e00000000000000000000000000000000000000000000000000000031dd107e7c00000000000000000000000000000000000000000000000000000011cef39ae3000000000000000000000000000000000000000000000000000001f7fa546c210000000000000000000000000000000000000000000000000000006ad9b5a153000000000000000000000000000000000000000000000000000000d05bbbc76300000000000000000000000000000000000000000000000000000018ee883f3e0000000000000000000000000000000000000000000000000000001726a316270000000000000000000000000000000000000000000000000000001726a31627000000000000000000000000000000000000000000000000000000903f82003100000000000000000000000000000000000000000000000000000051eb2d62150000000000000000000000000000000000000000000000000000019997e2ea6c0000000000000000000000000000000000000000000000000000001396d8c3fa000000000000000000000000000000000000000000000000000000f5c188264100000000000000000000000000000000000000000000000000000050234838fe000000000000000000000000000000000000000000000000000001597ba9233a00000000000000000000000000000000000000000000000000000010070e71cc0000000000000000000000000000000000000000000000000000004903b394a40000000000000000000000000000000000000000000000000000004e5b630fe8000000000000000000000000000000000000000000000000000000b215840ce10000000000000000000000000000000000000000000000000000015b438e4c5000000000000000000000000000000000000000000000000000000051eb2d621500000000000000000000000000000000000000000000000000000018ee883f3e0000000000000000000000000000000000000000000000000000005742dcdd5900000000000000000000000000000000000000000000000000000018ee883f3e000000000000000000000000000000000000000000000000000000590ac206700000000000000000000000000000000000000000000000000000007ca8a93c370000000000000000000000000000000000000000000000000000005c9a8c589e0000000000000000000000000000000000000000000000000000001396d8c3fa000000000000000000000000000000000000000000000000000000602a56aacb000000000000000000000000000000000000000000000000000000155ebded1000000000000000000000000000000000000000000000000000000073c12f6ec5000000000000000000000000000000000000000000000000000000557af7b443000000000000000000000000000000000000000000000000000000c93c2723080000000000000000000000000000000000000000000000000000006911d0783d000000000000000000000000000000000000000000000000000000f95152786e00000000000000000000000000000000000000000000000000000051eb2d621500000000000000000000000000000000000000000000000000000167bad26bf00000000000000000000000000000000000000000000000000000001ab66d685400000000000000000000000000000000000000000000000000000051eb2d6215000000000000000000000000000000000000000000000000000000356cdad0a9000000000000000000000000000000000000000000000000000000cccbf17536000000000000000000000000000000000000000000000000000000d3eb861991000000000000000000000000000000000000000000000000000000eb12292fb80000000000000000000000000000000000000000000000000000015ed3589e7e0000000000000000000000000000000000000000000000000000005ad2a72f8700000000000000000000000000000000000000000000000000002bca64055e000000000000000000000000000000000000000000000000000000003c8c6f750400000000000000000000000000000000000000000000000000000031dd107e7c0000000000000000000000000000000000000000000000000000002c856103380000000000000000000000000000000000000000000000000000004acb98bdba000000000000000000000000000000000000000000000000000000c77441f9f2000000000000000000000000000000000000000000000000000000155ebded100000000000000000000000000000000000000000000000000000001ab66d685400000000000000000000000000000000000000000000000000000107907bc124000000000000000000000000000000000000000000000000000000eea1f381e6000000000000000000000000000000000000000000000000000000155ebded1000000000000000000000000000000000000000000000000000000010070e71cc00000000000000000000000000000000000000000000000000000041e41ef049000000000000000000000000000000000000000000000000000000401c39c73200000000000000000000000000000000000000000000000000000043ac04195f0000000000000000000000000000000000000000000000000000004acb98bdba000000000000000000000000000000000000000000000000000000239de735c60000000000000000000000000000000000000000000000000000008e779cd71a000000000000000000000000000000000000000000000000000000d223a0f07a0000000000000000000000000000000000000000000000000000018990d4789f000000000000000000000000000000000000000000000000000000e7825edd8b0000000000000000000000000000000000000000000000000000004c937de6d1000000000000000000000000000000000000000000000000000000f069d8aafc000000000000000000000000000000000000000000000000000000e5ba79b47400000000000000000000000000000000000000000000000000000187c8ef4f890000000000000000000000000000000000000000000000000000003c8c6f750400000000000000000000000000000000000000000000000000000033a4f5a793000000000000000000000000000000000000000000000000000000de9ae51019000000000000000000000000000000000000000000000000000001a60f270a0b000000000000000000000000000000000000000000000000000000590ac206700000000000000000000000000000000000000000000000000000001726a31627000000000000000000000000000000000000000000000000000000473bce6b8d00000000000000000000000000000000000000000000000000000155ebded10c00000000000000000000000000000000000000000000000000000087580832bf0000000000000000000000000000000000000000000000000000001ab66d6854000000000000000000000000000000000000000000000000000000473bce6b8d000000000000000000000000000000000000000000000000000001726a316278000000000000000000000000000000000000000000000000000000cb040c4c1f0000000000000000000000000000000000000000000000000000018b58b9a1b6000000000000000000000000000000000000000000000000000000f231bdd41300000000000000000000000000000000000000000000000000000165f2ed42d9000000000000000000000000000000000000000000000000000000473bce6b8d0000000000000000000000000000000000000000000000000000016b4a9cbe1d0000000000000000000000000000000000000000000000000000016982b7950600000000000000000000000000000000000000000000000000000053b3128b2c0000000000000000000000000000000000000000000000000000017432168b8f000000000000000000000000000000000000000000000000000000d3eb861991000000000000000000000000000000000000000000000000000000ecda0e58cf000000000000000000000000000000000000000000000000000000e94a4406a200000000000000000000000000000000000000000000000000000018ee883f3e00000000000000000000000000000000000000000000000000000038fca522d7000000000000000000000000000000000000000000000000000000e94a4406a2000000000000000000000000000000000000000000000000000001726a316278000000000000000000000000000000000000000000000000000000eea1f381e6000000000000000000000000000000000000000000000000000000e062ca3930000000000000000000000000000000000000000000000000000000d9433594d50000000000000000000000000000000000000000000000000000010400b16ef7000000000000000000000000000000000000000000000000000000d5b36b42a70000000000000000000000000000000000000000000000000000019b5fc81383000000000000000000000000000000000000000000000000000000658206260f0000000000000000000000000000000000000000000000000000002abd7bda2100000000000000000000000000000000000000000000000000000157b3c3fa230000000000000000000000000000000000000000000000000000001726a31627000000000000000000000000000000000000000000000000000000557af7b4430000000000000000000000000000000000000000000000000000018b58b9a1b60000000000000000000000000000000000000000000000000000018ee883f3e40000000000000000000000000000000000000000000000000000010070e71cc900000000000000000000000000000000000000000000000000000018ee883f3e0000000000000000000000000000000000000000000000000000001ab66d685400000000000000000000000000000000000000000000000000000187c8ef4f8900000000000000000000000000000000000000000000000000000030152b556500000000000000000000000000000000000000000000000000000170a24c39610000000000000000000000000000000000000000000000000000009207672948000000000000000000000000000000000000000000000000000000d05bbbc7630000000000000000000000000000000000000000000000000000006749eb4f260000000000000000000000000000000000000000000000000000008cafb7ae030000000000000000000000000000000000000000000000000000017432168b8f000000000000000000000000000000000000000000000000000000038fca522d0000000000000000000000000000000000000000000000000000005742dcdd59000000000000000000000000000000000000000000000000000000db0b1abdec0000000000000000000000000000000000000000000000000000001396d8c3fa00000000000000000000000000000000000000000000000000000186010a2672000000000000000000000000000000000000000000000000000000e062ca3930000000000000000000000000000000000000000000000000000001609b3dc795000000000000000000000000000000000000000000000000000000e3f2948b5d000000000000000000000000000000000000000000000000000000c77441f9f2000000000000000000000000000000000000000000000000000000db0b1abdec0000000000000000000000000000000000000000000000000000005742dcdd59000000000000000000000000000000000000000000000000000000f95152786e0000000000000000000000000000000000000000000000000000002565cc5edd00000000000000000000000000000000000000000000000000000177c1e0ddbc000000000000000000000000000000000000000000000000000000557af7b4430000000000000000000000000000000000000000000000000000018990d4789f000000000000000000000000000000000000000000000000000000fb1937a1850000000000000000000000000000000000000000000000000000007750f9c0f300000000000000000000000000000000000000000000000000000050234838fe00000000000000000000000000000000000000000000000000000087580832bf0000000000000000000000000000000000000000000000000000005ad2a72f870000000000000000000000000000000000000000000000000000010238cc45e0000000000000000000000000000000000000000000000000000000d3eb861991000000000000000000000000000000000000000000000000000000fb1937a18500000000000000000000000000000000000000000000000000000038fca522d7000000000000000000000000000000000000000000000000000000e5ba79b4740000000000000000000000000000000000000000000000000000004e5b630fe8000000000000000000000000000000000000000000000000000000590ac20670000000000000000000000000000000000000000000000000000000be8cc82c80000000000000000000000000000000000000000000000000000000f231bdd4130000000000000000000000000000000000000000000000000000001726a31627000000000000000000000000000000000000000000000000000000f069d8aafc000000000000000000000000000000000000000000000000000000557af7b443000000000000000000000000000000000000000000000000000000eea1f381e60000000000000000000000000000000000000000000000000000015b438e4c5000000000000000000000000000000000000000000000000000000182713fd4450000000000000000000000000000000000000000000000000000000c77441f9f0000000000000000000000000000000000000000000000000000017989c606d3000000000000000000000000000000000000000000000000000001324df79b4600000000000000000000000000000000000000000000000000000071f94a45ae00000000000000000000000000000000000000000000000000000075891497dc00000000000000000000000000000000000000000000000000000050234838fe000000000000000000000000000000000000000000000000000001726a3162780000000000000000000000000000000000000000000000000000001726a31627000000000000000000000000000000000000000000000000000000cccbf17536000000000000000000000000000000000000000000000000000000d77b506bbe0000000000000000000000000000000000000000000000000000016982b79506000000000000000000000000000000000000000000000000000000f5c1882641000000000000000000000000000000000000000000000000000000f95152786e0000000000000000000000000000000000000000000000000000005ad2a72f87000000000000000000000000000000000000000000000000000000ecda0e58cf00000000000000000000000000000000000000000000000000000075891497dc000000000000000000000000000000000000000000000000000000f7896d4f5700000000000000000000000000000000000000000000000000000053b3128b2c000000000000000000000000000000000000000000000000000000fb1937a1850000000000000000000000000000000000000000000000000000006e697ff3810000000000000000000000000000000000000000000000000000016b4a9cbe1d00000000000000000000000000000000000000000000000000000053b3128b2c00000000000000000000000000000000000000000000000000000063ba20fcf9000000000000000000000000000000000000000000000000000000c93c27230800000000000000000000000000000000000000000000000000000063ba20fcf9000000000000000000000000000000000000000000000000000001843924fd5b000000000000000000000000000000000000000000000000000000557af7b4430000000000000000000000000000000000000000000000000000005ad2a72f8700000000000000000000000000000000000000000000000000000170a24c396100000000000000000000000000000000000000000000000000000175f9fbb4a5000000000000000000000000000000000000000000000000000000f069d8aafc0000000000000000000000000000000000000000000000000000005c9a8c589e000000000000000000000000000000000000000000000000000000ce93d69e4d00000000000000000000000000000000000000000000000000000043ac04195f000000000000000000000000000000000000000000000000000000ce93d69e4d000000000000000000000000000000000000000000000000000000038fca522d0000000000000000000000000000000000000000000000000000004e5b630fe80000000000000000000000000000000000000000000000000000008cafb7ae0300000000000000000000000000000000000000000000000000000011cef39ae3000000000000000000000000000000000000000000000000000000bcc4e303690000000000000000000000000000000000000000000000000000003ac48a4bee000000000000000000000000000000000000000000000000000000356cdad0a9000000000000000000000000000000000000000000000000000000d223a0f07a0000000000000000000000000000000000000000000000000000005742dcdd59000000000000000000000000000000000000000000000000000000d223a0f07a0000000000000000000000000000000000000000000000000000007031651c980000000000000000000000000000000000000000000000000000005c9a8c589e00000000000000000000000000000000000000000000000000000051eb2d62150000000000000000000000000000000000000000000000000000005c9a8c589e00000000000000000000000000000000000000000000000000000001c7e52916000000000000000000000000000000000000000000000000000000ce93d69e4d000000000000000000000000000000000000000000000000000000dcd2ffe7020000000000000000000000000000000000000000000000000000002abd7bda21000000000000000000000000000000000000000000000000000000d05bbbc763000000000000000000000000000000000000000000000000000000557af7b4430000000000000000000000000000000000000000000000000000001396d8c3fa0000000000000000000000000000000000000000000000000000001726a316270000000000000000000000000000000000000000000000000000001396d8c3fa0000000000000000000000000000000000000000000000000000004e5b630fe80000000000000000000000000000000000000000000000000000010238cc45e0000000000000000000000000000000000000000000000000000000d3eb861991000000000000000000000000000000000000000000000000000000fb1937a1850000000000000000000000000000000000000000000000000000008cafb7ae03000000000000000000000000000000000000000000000000000000f95152786e0000000000000000000000000000000000000000000000000000001726a31627000000000000000000000000000000000000000000000000000000d9433594d50000000000000000000000000000000000000000000000000000005ad2a72f870000000000000000000000000000000000000000000000000000001726a3162700000000000000000000000000000000000000000000000000000187c8ef4f890000000000000000000000000000000000000000000000000000001726a316270000000000000000000000000000000000000000000000000000017432168b8f00000000000000000000000000000000000000000000000000000053b3128b2c000000000000000000000000000000000000000000000000000000de9ae510190000000000000000000000000000000000000000000000000000004e5b630fe8000000000000000000000000000000000000000000000000000000155ebded100000000000000000000000000000000000000000000000000000006e697ff381000000000000000000000000000000000000000000000000000000b04d9ee3ca000000000000000000000000000000000000000000000000000001642b0819c2000000000000000000000000000000000000000000000000000000ecda0e58cf00000000000000000000000000000000000000000000000000000092076729480000000000000000000000000000000000000000000000000000005742dcdd59000000000000000000000000000000000000000000000000000000c5ac5cd0db00000000000000000000000000000000000000000000000000000149749ab16d0000000000000000000000000000000000000000000000000000006ad9b5a153000000000000000000000000000000000000000000000000000000b5a54e5f0e00000000000000000000000000000000000000000000000000000051eb2d6215000000000000000000000000000000000000000000000000000000155ebded10000000000000000000000000000000000000000000000000000000f231bdd413000000000000000000000000000000000000000000000000000000eea1f381e60000000000000000000000000000000000000000000000000000002e4d462c4f0000000000000000000000000000000000000000000000000000006ca19aca6a00000000000000000000000000000000000000000000000000000051eb2d621500000000000000000000000000000000000000000000000000000197cffdc155000000000000000000000000000000000000000000000000000000e062ca39300000000000000000000000000000000000000000000000000000017ee175821700000000000000000000000000000000000000000000000000000063ba20fcf9000000000000000000000000000000000000000000000000000000cb040c4c1f000000000000000000000000000000000000000000000000000000155ebded100000000000000000000000000000000000000000000000000000001ab66d6854000000000000000000000000000000000000000000000000000000d77b506bbe00000000000000000000000000000000000000000000000000000105c896980d000000000000000000000000000000000000000000000000000000e3f2948b5d0000000000000000000000000000000000000000000000000000008ae7d284ed000000000000000000000000000000000000000000000000000000ecda0e58cf000000000000000000000000000000000000000000000000000000de9ae51019000000000000000000000000000000000000000000000000000000b5a54e5f0e0000000000000000000000000000000000000000000000000000016b4a9cbe1d0000000000000000000000000000000000000000000000000000016b4a9cbe1d00000000000000000000000000000000000000000000000000000033a4f5a7930000000000000000000000000000000000000000000000000000005c9a8c589e0000000000000000000000000000000000000000000000000000001726a31627000000000000000000000000000000000000000000000000000000155ebded10000000000000000000000000000000000000000000000000000000590ac20670000000000000000000000000000000000000000000000000000000155ebded100000000000000000000000000000000000000000000000000000017d19905900000000000000000000000000000000000000000000000000000000d77b506bbe0000000000000000000000000000000000000000000000000000004903b394a4000000000000000000000000000000000000000000000000000000b215840ce100000000000000000000000000000000000000000000000000000031dd107e7c000000000000000000000000000000000000000000000000000000e94a4406a2000000000000000000000000000000000000000000000000000000557af7b44300000000000000000000000000000000000000000000000000000053b3128b2c0000000000000000000000000000000000000000000000000000008038738e64000000000000000000000000000000000000000000000000000000e7825edd8b0000000000000000000000000000000000000000000000000000017989c606d3000000000000000000000000000000000000000000000000000000de9ae51019000000000000000000000000000000000000000000000000000000f3f9a2fd2a0000000000000000000000000000000000000000000000000000004e5b630fe8000000000000000000000000000000000000000000000000000000be8cc82c80000000000000000000000000000000000000000000000000000000d9433594d50000000000000000000000000000000000000000000000000000005e627181b40000000000000000000000000000000000000000000000000000008038738e6400000000000000000000000000000000000000000000000000000050234838fe00000000000000000000000000000000000000000000000000000041e41ef0490000000000000000000000000000000000000000000000000000016b4a9cbe1d0000000000000000000000000000000000000000000000000000014b3c7fda840000000000000000000000000000000000000000000000000000005742dcdd59000000000000000000000000000000000000000000000000000000155ebded10000000000000000000000000000000000000000000000000000001609b3dc7950000000000000000000000000000000000000000000000000000001ab66d68540000000000000000000000000000000000000000000000000000008cafb7ae0300000000000000000000000000000000000000000000000000000041e41ef049000000000000000000000000000000000000000000000000000000891fed5bd60000000000000000000000000000000000000000000000000000015423f9a7f6000000000000000000000000000000000000000000000000000000c054ad5597000000000000000000000000000000000000000000000000000000ecda0e58cf000000000000000000000000000000000000000000000000000000590ac2067000000000000000000000000000000000000000000000000000000197cffdc1550000000000000000000000000000000000000000000000000000001726a316270000000000000000000000000000000000000000000000000000007ae0c41320000000000000000000000000000000000000000000000000000000d77b506bbe000000000000000000000000000000000000000000000000000000fb1937a18500000000000000000000000000000000000000000000000000000051eb2d6215000000000000000000000000000000000000000000000000000000590ac20670000000000000000000000000000000000000000000000000000000acbdd4919d0000000000000000000000000000000000000000000000000000017989c606d300000000000000000000000000000000000000000000000000000043ac04195f000000000000000000000000000000000000000000000000000000155ebded100000000000000000000000000000000000000000000000000000004e5b630fe80000000000000000000000000000000000000000000000000000007e708e654e000000000000000000000000000000000000000000000000000000155ebded1000000000000000000000000000000000000000000000000000000170a24c3961000000000000000000000000000000000000000000000000000000602a56aacb0000000000000000000000000000000000000000000000000000019d27ad3c9a000000000000000000000000000000000000000000000000000000e062ca393000000000000000000000000000000000000000000000000000000187c8ef4f89000000000000000000000000000000000000000000000000000000e5ba79b474000000000000000000000000000000000000000000000000000001195f6f5c0700000000000000000000000000000000000000000000000000000018ee883f3e00000000000000000000000000000000000000000000000000000018ee883f3e0000000000000000000000000000000000000000000000000000008038738e640000000000000000000000000000000000000000000000000000001396d8c3fa000000000000000000000000000000000000000000000000000000d5b36b42a70000000000000000000000000000000000000000000000000000005ad2a72f8700000000000000000000000000000000000000000000000000000170a24c39610000000000000000000000000000000000000000000000000000015b438e4c500000000000000000000000000000000000000000000000000000007031651c980000000000000000000000000000000000000000000000000000004c937de6d1000000000000000000000000000000000000000000000000000000d5b36b42a700000000000000000000000000000000000000000000000000000031dd107e7c000000000000000000000000000000000000000000000000000000fea901f3b2000000000000000000000000000000000000000000000000000000155ebded100000000000000000000000000000000000000000000000000000005742dcdd590000000000000000000000000000000000000000000000000000006749eb4f2600000000000000000000000000000000000000000000000000000050234838fe000000000000000000000000000000000000000000000000000000e7825edd8b0000000000000000000000000000000000000000000000000000006ad9b5a153000000000000000000000000000000000000000000000000000000e5ba79b47400000000000000000000000000000000000000000000000000000187c8ef4f890000000000000000000000000000000000000000000000000000001726a316270000000000000000000000000000000000000000000000000000004573e94276000000000000000000000000000000000000000000000000000000a0469071fd000000000000000000000000000000000000000000000000000000b76d338825000000000000000000000000000000000000000000000000000000602a56aacb0000000000000000000000000000000000000000000000000000006911d0783d0000000000000000000000000000000000000000000000000000005c9a8c589e000000000000000000000000000000000000000000000000000000a92e0a3f6f0000000000000000000000000000000000000000000000000000002e4d462c4f000000000000000000000000000000000000000000000000000000975f16a48c0000000000000000000000000000000000000000000000000000001ab66d685400000000000000000000000000000000000000000000000000000018ee883f3e0000000000000000000000000000000000000000000000000000007750f9c0f300000000000000000000000000000000000000000000000000000051eb2d62150000000000000000000000000000000000000000000000000000016b4a9cbe1d000000000000000000000000000000000000000000000000000000dcd2ffe7020000000000000000000000000000000000000000000000000000005742dcdd59000000000000000000000000000000000000000000000000000000e94a4406a2000000000000000000000000000000000000000000000000000000e94a4406a2000000000000000000000000000000000000000000000000000000d3eb8619910000000000000000000000000000000000000000000000000000005c9a8c589e0000000000000000000000000000000000000000000000000000006ca19aca6a0000000000000000000000000000000000000000000000000000005e627181b400000000000000000000000000000000000000000000000000000018ee883f3e00000000000000000000000000000000000000000000000000000073c12f6ec50000000000000000000000000000000000000000000000000000010ce82b3c6800000000000000000000000000000000000000000000000000000073c12f6ec500000000000000000000000000000000000000000000000000000105c896980d00000000000000000000000000000000000000000000000000000018ee883f3e00000000000000000000000000000000000000000000000000000018ee883f3e0000000000000000000000000000000000000000000000000000001396d8c3fa000000000000000000000000000000000000000000000000000000d223a0f07a00000000000000000000000000000000000000000000000000000018ee883f3e00000000000000000000000000000000000000000000000000000018ee883f3e000000000000000000000000000000000000000000000000000000be8cc82c80000000000000000000000000000000000000000000000000000000975f16a48c000000000000000000000000000000000000000000000000000000e5ba79b474000000000000000000000000000000000000000000000000000000d5b36b42a7000000000000000000000000000000000000000000000000000000e3f2948b5d0000000000000000000000000000000000000000000000000000007918deea09000000000000000000000000000000000000000000000000000000f5c18826410000000000000000000000000000000000000000000000000000003ac48a4bee000000000000000000000000000000000000000000000000000000b3dd6935f80000000000000000000000000000000000000000000000000000002565cc5edd000000000000000000000000000000000000000000000000000000eea1f381e600000000000000000000000000000000000000000000000000000050234838fe000000000000000000000000000000000000000000000000000000cccbf17536000000000000000000000000000000000000000000000000000000a59e3fed42000000000000000000000000000000000000000000000000000000b04d9ee3ca000000000000000000000000000000000000000000000000000000de9ae51019000000000000000000000000000000000000000000000000000000e3f2948b5d0000000000000000000000000000000000000000000000000000017d1990590000000000000000000000000000000000000000000000000000000053b3128b2c000000000000000000000000000000000000000000000000000000eea1f381e6000000000000000000000000000000000000000000000000000000e3f2948b5d0000000000000000000000000000000000000000000000000000005742dcdd59000000000000000000000000000000000000000000000000000000d5b36b42a70000000000000000000000000000000000000000000000000000019d27ad3c9a000000000000000000000000000000000000000000000000000000cccbf17536000000000000000000000000000000000000000000000000000001408d20e3fb000000000000000000000000000000000000000000000000000000891fed5bd6000000000000000000000000000000000000000000000000000000155ebded100000000000000000000000000000000000000000000000000000000e3f2948b5000000000000000000000000000000000000000000000000000000239de735c6000000000000000000000000000000000000000000000000000000c93c2723080000000000000000000000000000000000000000000000000000001726a31627000000000000000000000000000000000000000000000000000000e22aaf62470000000000000000000000000000000000000000000000000000007918deea09000000000000000000000000000000000000000000000000000000d5b36b42a700000000000000000000000000000000000000000000000000000011cef39ae300000000000000000000000000000000000000000000000000000093cf4c525e0000000000000000000000000000000000000000000000000000006911d0783d000000000000000000000000000000000000000000000000000000356cdad0a900000000000000000000000000000000000000000000000000000018ee883f3e00000000000000000000000000000000000000000000000000000018ee883f3e0000000000000000000000000000000000000000000000000000019d27ad3c9a0000000000000000000000000000000000000000000000000000004573e942760000000000000000000000000000000000000000000000000000001726a31627000000000000000000000000000000000000000000000000000000de9ae5101900000000000000000000000000000000000000000000000000000018ee883f3e000000000000000000000000000000000000000000000000000000e7825edd8b0000000000000000000000000000000000000000000000000000001726a316270000000000000000000000000000000000000000000000000000001396d8c3fa00000000000000000000000000000000000000000000000000000018ee883f3e00000000000000000000000000000000000000000000000000000087580832bf00000000000000000000000000000000000000000000000000000165f2ed42d9000000000000000000000000000000000000000000000000000000d5b36b42a70000000000000000000000000000000000000000000000000000005ad2a72f87000000000000000000000000000000000000000000000000000000d77b506bbe0000000000000000000000000000000000000000000000000000001726a316270000000000000000000000000000000000000000000000000000001ab66d685400000000000000000000000000000000000000000000000000000018ee883f3e000000000000000000000000000000000000000000000000000000d9433594d5000000000000000000000000000000000000000000000000000000f95152786e000000000000000000000000000000000000000000000000000000de9ae5101900000000000000000000000000000000000000000000000000000050234838fe0000000000000000000000000000000000000000000000000000004e5b630fe8000000000000000000000000000000000000000000000000000000a3d65ac42b000000000000000000000000000000000000000000000000000000c5ac5cd0db00000000000000000000000000000000000000000000000000000063ba20fcf900000000000000000000000000000000000000000000000000000018ee883f3e0000000000000000000000000000000000000000000000000000004c937de6d1000000000000000000000000000000000000000000000000000000602a56aacb00000000000000000000000000000000000000000000000000000018ee883f3e00000000000000000000000000000000000000000000000000000061f23bd3e2000000000000000000000000000000000000000000000000000000356cdad0a900000000000000000000000000000000000000000000000000000018ee883f3e0000000000000000000000000000000000000000000000000000001726a316270000000000000000000000000000000000000000000000000000001726a31627000000000000000000000000000000000000000000000000000000557af7b443000000000000000000000000000000000000000000000000000000eb12292fb8000000000000000000000000000000000000000000000000000000f3f9a2fd2a00000000000000000000000000000000000000000000000000000018ee883f3e000000000000000000000000000000000000000000000000000000cb040c4c1f000000000000000000000000000000000000000000000000000000c77441f9f200000000000000000000000000000000000000000000000000000186010a267200000000000000000000000000000000000000000000000000000018ee883f3e0000000000000000000000000000000000000000000000000000006749eb4f26000000000000000000000000000000000000000000000000000000dcd2ffe702000000000000000000000000000000000000000000000000000000cb040c4c1f000000000000000000000000000000000000000000000000000000bcc4e30369000000000000000000000000000000000000000000000000000000658206260f00000000000000000000000000000000000000000000000000000150942f55c800000000000000000000000000000000000000000000000000000018ee883f3e00000000000000000000000000000000000000000000000000000018ee883f3e0000000000000000000000000000000000000000000000000000003e54549e1b00000000000000000000000000000000000000000000000000000018ee883f3e000000000000000000000000000000000000000000000000000000ce93d69e4d00000000000000000000000000000000000000000000000000000186010a267200000000000000000000000000000000000000000000000000000149749ab16d000000000000000000000000000000000000000000000000000000e5ba79b474000000000000000000000000000000000000000000000000000000658206260f000000000000000000000000000000000000000000000000000000f3f9a2fd2a00000000000000000000000000000000000000000000000000000011cef39ae30000000000000000000000000000000000000000000000000000017b51ab2fea0000000000000000000000000000000000000000000000000000017989c606d3000000000000000000000000000000000000000000000000000001843924fd5b00000000000000000000000000000000000000000000000000000167bad26bf0000000000000000000000000000000000000000000000000000000c93c272308000000000000000000000000000000000000000000000000000000200e1ce3990000000000000000000000000000000000000000000000000000005ad2a72f870000000000000000000000000000000000000000000000000000004573e942760000000000000000000000000000000000000000000000000000008ae7d284ed0000000000000000000000000000000000000000000000000000009e7eab48e700000000000000000000000000000000000000000000000000000018ee883f3e000000000000000000000000000000000000000000000000000000bcc4e3036900000000000000000000000000000000000000000000000000000031dd107e7c0000000000000000000000000000000000000000000000000000009926fbcda3000000000000000000000000000000000000000000000000000000602a56aacb0000000000000000000000000000000000000000000000000000001726a3162700000000000000000000000000000000000000000000000000000050234838fe0000000000000000000000000000000000000000000000000000005ad2a72f870000000000000000000000000000000000000000000000000000008cafb7ae03000000000000000000000000000000000000000000000000000000c93c27230800000000000000000000000000000000000000000000000000000053b3128b2c000000000000000000000000000000000000000000000000000000557af7b44300000000000000000000000000000000000000000000000000000018ee883f3e00000000000000000000000000000000000000000000000000000018ee883f3e000000000000000000000000000000000000000000000000000000be8cc82c80000000000000000000000000000000000000000000000000000000fce11cca9c000000000000000000000000000000000000000000000000000000d05bbbc7630000000000000000000000000000000000000000000000000000007031651c980000000000000000000000000000000000000000000000000000001726a31627000000000000000000000000000000000000000000000000000000cccbf17536000000000000000000000000000000000000000000000000000000e94a4406a20000000000000000000000000000000000000000000000000000009926fbcda300000000000000000000000000000000000000000000000000000038fca522d70000000000000000000000000000000000000000000000000000004c937de6d100000000000000000000000000000000000000000000000000000043ac04195f0000000000000000000000000000000000000000000000000000008ae7d284ed0000000000000000000000000000000000000000000000000000004c937de6d10000000000000000000000000000000000000000000000000000001ab66d6854000000000000000000000000000000000000000000000000000000602a56aacb000000000000000000000000000000000000000000000000000000e062ca3930000000000000000000000000000000000000000000000000000000de9ae5101900000000000000000000000000000000000000000000000000000033a4f5a7930000000000000000000000000000000000000000000000000000015b438e4c50000000000000000000000000000000000000000000000000000000e062ca393000000000000000000000000000000000000000000000000000000018ee883f3e000000000000000000000000000000000000000000000000000000fce11cca9c000000000000000000000000000000000000000000000000000000c3e477a7c400000000000000000000000000000000000000000000000000000028f596b10a000000000000000000000000000000000000000000000000000000eea1f381e600000000000000000000000000000000000000000000000000000085902309a800000000000000000000000000000000000000000000000000000050234838fe00000000000000000000000000000000000000000000000000000050234838fe000000000000000000000000000000000000000000000000000000272db187f40000000000000000000000000000000000000000000000000000003e54549e1b0000000000000000000000000000000000000000000000000000001726a3162700000000000000000000000000000000000000000000000000000030152b55650000000000000000000000000000000000000000000000000000004acb98bdba00000000000000000000000000000000000000000000000000000043ac04195f0000000000000000000000000000000000000000000000000000001726a31627000000000000000000000000000000000000000000000000000000c054ad55970000000000000000000000000000000000000000000000000000003734bff9c00000000000000000000000000000000000000000000000000000017d1990590000000000000000000000000000000000000000000000000000000093cf4c525e000000000000000000000000000000000000000000000000000000356cdad0a900000000000000000000000000000000000000000000000000000050234838fe00000000000000000000000000000000000000000000000000000073c12f6ec500000000000000000000000000000000000000000000000000000051eb2d6215000000000000000000000000000000000000000000000000000000dcd2ffe70200000000000000000000000000000000000000000000000000000018ee883f3e00000000000000000000000000000000000000000000000000000018ee883f3e000000000000000000000000000000000000000000000000000000aaf5ef68860000000000000000000000000000000000000000000000000000008e779cd71a0000000000000000000000000000000000000000000000000000008ae7d284ed0000000000000000000000000000000000000000000000000000010238cc45e000000000000000000000000000000000000000000000000000000018ee883f3e00000000000000000000000000000000000000000000000000000018ee883f3e0000000000000000000000000000000000000000000000000000009aeee0f6b90000000000000000000000000000000000000000000000000000007750f9c0f30000000000000000000000000000000000000000000000000000009207672948000000000000000000000000000000000000000000000000000000ecda0e58cf000000000000000000000000000000000000000000000000000000d9433594d5000000000000000000000000000000000000000000000000000000f231bdd4130000000000000000000000000000000000000000000000000000001726a316270000000000000000000000000000000000000000000000000000007918deea09000000000000000000000000000000000000000000000000000000e7825edd8b000000000000000000000000000000000000000000000000000000602a56aacb0000000000000000000000000000000000000000000000000000004903b394a40000000000000000000000000000000000000000000000000000006749eb4f26000000000000000000000000000000000000000000000000000000cb040c4c1f00000000000000000000000000000000000000000000000000000018ee883f3e000000000000000000000000000000000000000000000000000000590ac2067000000000000000000000000000000000000000000000000000000018ee883f3e000000000000000000000000000000000000000000000000000000d223a0f07a00000000000000000000000000000000000000000000000000000021d6020caf0000000000000000000000000000000000000000000000000000001726a316270000000000000000000000000000000000000000000000000000001726a316270000000000000000000000000000000000000000000000000000001396d8c3fa00000000000000000000000000000000000000000000000000000087580832bf0000000000000000000000000000000000000000000000000000010ce82b3c680000000000000000000000000000000000000000000000000000005c9a8c589e000000000000000000000000000000000000000000000000000000590ac206700000000000000000000000000000000000000000000000000000001726a31627000000000000000000000000000000000000000000000000000000fea901f3b200000000000000000000000000000000000000000000000000000050234838fe000000000000000000000000000000000000000000000000000000d223a0f07a0000000000000000000000000000000000000000000000000000018ee883f3e4000000000000000000000000000000000000000000000000000000356cdad0a9000000000000000000000000000000000000000000000000000000eea1f381e60000000000000000000000000000000000000000000000000000008ae7d284ed000000000000000000000000000000000000000000000000000000155ebded10000000000000000000000000000000000000000000000000000000b215840ce1000000000000000000000000000000000000000000000000000000b93518b13c0000000000000000000000000000000000000000000000000000017432168b8f00000000000000000000000000000000000000000000000000000085902309a8000000000000000000000000000000000000000000000000000000473bce6b8d0000000000000000000000000000000000000000000000000000002abd7bda210000000000000000000000000000000000000000000000000000007031651c9800000000000000000000000000000000000000000000000000000053b3128b2c000000000000000000000000000000000000000000000000000000eb12292fb800000000000000000000000000000000000000000000000000000018ee883f3e0000000000000000000000000000000000000000000000000000007031651c9800000000000000000000000000000000000000000000000000000041e41ef049000000000000000000000000000000000000000000000000000000e062ca39300000000000000000000000000000000000000000000000000000005e627181b4000000000000000000000000000000000000000000000000000000d77b506bbe000000000000000000000000000000000000000000000000000000658206260f000000000000000000000000000000000000000000000000000000272db187f4000000000000000000000000000000000000000000000000000000e5ba79b47400000000000000000000000000000000000000000000000000000050234838fe000000000000000000000000000000000000000000000000000000e7825edd8b00000000000000000000000000000000000000000000000000000155ebded10c0000000000000000000000000000000000000000000000000000005742dcdd590000000000000000000000000000000000000000000000000000001ab66d6854000000000000000000000000000000000000000000000000000000155ebded10000000000000000000000000000000000000000000000000000000d9433594d5000000000000000000000000000000000000000000000000000000272db187f4000000000000000000000000000000000000000000000000000000c93c2723080000000000000000000000000000000000000000000000000000006ad9b5a15300000000000000000000000000000000000000000000000000000010070e71cc0000000000000000000000000000000000000000000000000000009597317b75000000000000000000000000000000000000000000000000000000f3f9a2fd2a00000000000000000000000000000000000000000000000000000018ee883f3e000000000000000000000000000000000000000000000000000000c77441f9f200000000000000000000000000000000000000000000000000000051eb2d621500000000000000000000000000000000000000000000000000000145e4d05f4000000000000000000000000000000000000000000000000000000010070e71cc00000000000000000000000000000000000000000000000000000083c83de0920000000000000000000000000000000000000000000000000000002565cc5edd00000000000000000000000000000000000000000000000000000083c83de092000000000000000000000000000000000000000000000000000000557af7b44300000000000000000000000000000000000000000000000000000030152b55650000000000000000000000000000000000000000000000000000005c9a8c589e0000000000000000000000000000000000000000000000000000010b20461351000000000000000000000000000000000000000000000000000000cccbf1753600000000000000000000000000000000000000000000000000000165f2ed42d90000000000000000000000000000000000000000000000000000005742dcdd5900000000000000000000000000000000000000000000000000000053b3128b2c000000000000000000000000000000000000000000000000000000155ebded1000000000000000000000000000000000000000000000000000000043ac04195f000000000000000000000000000000000000000000000000000000f95152786e000000000000000000000000000000000000000000000000000000a92e0a3f6f00000000000000000000000000000000000000000000000000000187c8ef4f89000000000000000000000000000000000000000000000000000000155ebded100000000000000000000000000000000000000000000000000000008cafb7ae03000000000000000000000000000000000000000000000000000000bcc4e303690000000000000000000000000000000000000000000000000000002e4d462c4f0000000000000000000000000000000000000000000000000000001726a31627000000000000000000000000000000000000000000000000000000dcd2ffe70200000000000000000000000000000000000000000000000000000018ee883f3e00000000000000000000000000000000000000000000000000000018ee883f3e00000000000000000000000000000000000000000000000000000018ee883f3e000000000000000000000000000000000000000000000000000000602a56aacb0000000000000000000000000000000000000000000000000000003734bff9c000000000000000000000000000000000000000000000000000000053b3128b2c0000000000000000000000000000000000000000000000000000001726a316270000000000000000000000000000000000000000000000000000001726a316270000000000000000000000000000000000000000000000000000005742dcdd5900000000000000000000000000000000000000000000000000000075891497dc000000000000000000000000000000000000000000000000000000c5ac5cd0db0000000000000000000000000000000000000000000000000000001726a31627000000000000000000000000000000000000000000000000000000155ebded10000000000000000000000000000000000000000000000000000000de9ae5101900000000000000000000000000000000000000000000000000000165f2ed42d90000000000000000000000000000000000000000000000000000001ab66d68540000000000000000000000000000000000000000000000000000001ab66d68540000000000000000000000000000000000000000000000000000001726a31627000000000000000000000000000000000000000000000000000000401c39c73200000000000000000000000000000000000000000000000000000018ee883f3e000000000000000000000000000000000000000000000000000000155ebded1000000000000000000000000000000000000000000000000000000018ee883f3e0000000000000000000000000000000000000000000000000000007ca8a93c3700000000000000000000000000000000000000000000000000000011cef39ae3000000000000000000000000000000000000000000000000000000be8cc82c800000000000000000000000000000000000000000000000000000006749eb4f260000000000000000000000000000000000000000000000000000001726a316270000000000000000000000000000000000000000000000000000002e4d462c4f000000000000000000000000000000000000000000000000000000d05bbbc7630000000000000000000000000000000000000000000000000000004acb98bdba0000000000000000000000000000000000000000000000000000004acb98bdba0000000000000000000000000000000000000000000000000000001726a31627" - }, - "result": { - "gasUsed": "0x24f678b1", - "output": "0x" - }, - "blockHash": "0xbeef70ac3db42f10dd1eb03f5f0640557acd72db61357cf3c4f47945d8beab79", - "blockNumber": 3663377, - "transactionHash": "0xdbbc58127cc21ef53ae41e63d815853a748ca6ecee21264466fb8960e0feefe3", - "transactionPosition": 107 - } + ... ] """ @@ -5570,23 +617,23 @@ defmodule EthereumJSONRPC.Filecoin do 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 + Enum.reduce(blocks_responses, [], fn %{id: id, result: transaction_result}, blocks_acc -> + extract_transactions_params(Map.fetch!(id_to_params, id), transaction_result) ++ blocks_acc end) end - defp extract_transactions_params(block_number, tx_result) do - tx_result + defp extract_transactions_params(block_number, transaction_result) do + transaction_result |> Enum.reduce( {[], 0}, # counter is the index of the internal transaction in transaction - fn %{"transactionHash" => tx_hash, "transactionPosition" => transaction_index} = calls_result, - {tx_acc, counter} -> - last_tx_response_from_accumulator = List.first(tx_acc) + fn %{"transactionHash" => transaction_hash, "transactionPosition" => transaction_index} = calls_result, + {transaction_acc, counter} -> + last_transaction_response_from_accumulator = List.first(transaction_acc) next_counter = - with {:empty_accumulator, false} <- {:empty_accumulator, is_nil(last_tx_response_from_accumulator)}, - true <- tx_hash !== last_tx_response_from_accumulator["transactionHash"] do + with {:empty_accumulator, false} <- {:empty_accumulator, is_nil(last_transaction_response_from_accumulator)}, + true <- transaction_hash !== last_transaction_response_from_accumulator["transactionHash"] do 0 else {:empty_accumulator, true} -> @@ -5601,13 +648,13 @@ defmodule EthereumJSONRPC.Filecoin do Map.merge( %{ "blockNumber" => block_number, - "transactionHash" => tx_hash, + "transactionHash" => transaction_hash, "transactionIndex" => transaction_index, "index" => next_counter }, calls_result ) - | tx_acc + | transaction_acc ], next_counter } diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/geth.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/geth.ex index d30131f..7c019de 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/geth.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/geth.ex @@ -152,23 +152,23 @@ defmodule EthereumJSONRPC.Geth do def to_transactions_params(blocks_responses, id_to_params) do blocks_responses - |> Enum.reduce({[], 0}, fn %{id: id, result: tx_result}, {blocks_acc, counter} -> + |> Enum.reduce({[], 0}, fn %{id: id, result: transaction_result}, {blocks_acc, counter} -> {transactions_params, _, new_counter} = - extract_transactions_params(Map.fetch!(id_to_params, id), tx_result, counter) + extract_transactions_params(Map.fetch!(id_to_params, id), transaction_result, counter) {transactions_params ++ blocks_acc, new_counter} end) |> elem(0) end - defp extract_transactions_params(block_number, tx_result, counter) do - Enum.reduce(tx_result, {[], 0, counter}, fn %{"txHash" => tx_hash, "result" => calls_result}, - {tx_acc, inner_counter, counter} -> + defp extract_transactions_params(block_number, transaction_result, counter) do + Enum.reduce(transaction_result, {[], 0, counter}, fn %{"txHash" => transaction_hash, "result" => calls_result}, + {transaction_acc, inner_counter, counter} -> { [ - {%{block_number: block_number, hash_data: tx_hash, transaction_index: inner_counter, id: counter}, + {%{block_number: block_number, hash_data: transaction_hash, transaction_index: inner_counter, id: counter}, %{id: counter, result: calls_result}} - | tx_acc + | transaction_acc ], inner_counter + 1, counter + 1 @@ -261,14 +261,14 @@ defmodule EthereumJSONRPC.Geth do request(%{id: id, method: "eth_getTransactionReceipt", params: [hash_data]}) end) |> json_rpc(json_rpc_named_arguments), - {:ok, txs} <- + {:ok, transactions} <- id_to_params |> Enum.map(fn {id, %{hash_data: hash_data}} -> request(%{id: id, method: "eth_getTransactionByHash", params: [hash_data]}) end) |> json_rpc(json_rpc_named_arguments) 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) + transactions_map = Enum.into(transactions, %{}, fn %{id: id, result: transaction} -> {id, transaction} end) tracer = if Application.get_env(:ethereum_jsonrpc, __MODULE__)[:tracer] == "polygon_edge", @@ -282,7 +282,7 @@ defmodule EthereumJSONRPC.Geth do %{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: id, result: tracer.replay(result, Map.fetch!(receipts_map, id), Map.fetch!(transactions_map, id))}, id_to_params ) end) diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/geth/tracer.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/geth/tracer.ex index 92aa18f..cc17cc1 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/geth/tracer.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/geth/tracer.ex @@ -6,10 +6,14 @@ defmodule EthereumJSONRPC.Geth.Tracer do import EthereumJSONRPC, only: [integer_to_quantity: 1, quantity_to_integer: 1] - def replay(%{"structLogs" => logs, "gas" => top_call_gas, "returnValue" => return_value} = result, receipt, tx) + def replay( + %{"structLogs" => logs, "gas" => top_call_gas, "returnValue" => return_value} = result, + receipt, + transaction + ) when is_list(logs) do %{"contractAddress" => contract_address} = receipt - %{"from" => from, "to" => to, "value" => value, "input" => input} = tx + %{"from" => from, "to" => to, "value" => value, "input" => input} = transaction top = to diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/http.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/http.ex index 4335a29..a411ab9 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/http.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/http.ex @@ -7,7 +7,7 @@ defmodule EthereumJSONRPC.HTTP do require Logger - import EthereumJSONRPC, only: [quantity_to_integer: 1] + import EthereumJSONRPC, only: [sanitize_id: 1] @behaviour Transport @@ -190,7 +190,7 @@ defmodule EthereumJSONRPC.HTTP do # argument matching. # Nethermind return string ids - id = quantity_to_integer(unstandardized["id"]) + id = sanitize_id(unstandardized["id"]) standardized = %{jsonrpc: jsonrpc, id: id} diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/ipc.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/ipc.ex index 7f1ff40..b030a6a 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/ipc.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/ipc.ex @@ -84,7 +84,6 @@ defmodule EthereumJSONRPC.IPC do else {:error, %Jason.DecodeError{data: ""}} -> {:error, :empty_response} {:error, error} -> {:error, {:invalid_json, error}} - {:error, error} -> {:error, error} end end diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/log.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/log.ex index e1b55f3..7e7ace5 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/log.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/log.ex @@ -46,7 +46,8 @@ defmodule EthereumJSONRPC.Log do index: 0, second_topic: nil, third_topic: nil, - transaction_hash: "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5" + transaction_hash: "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5", + transaction_index: 0 } iex> EthereumJSONRPC.Log.elixir_to_params( @@ -74,26 +75,30 @@ defmodule EthereumJSONRPC.Log do index: 0, second_topic: "0x000000000000000000000000c15bf627accd3b054075c7880425f903106be72a", third_topic: "0x000000000000000000000000a59eb37750f9c8f2e11aac6700e62ef89187e4ed", - transaction_hash: "0xf9b663b4e9b1fdc94eb27b5cfba04eb03d2f7b3fa0b24eb2e1af34f823f2b89e" + transaction_hash: "0xf9b663b4e9b1fdc94eb27b5cfba04eb03d2f7b3fa0b24eb2e1af34f823f2b89e", + transaction_index: 0 } """ - def elixir_to_params(%{ - "address" => address_hash, - "blockNumber" => block_number, - "blockHash" => block_hash, - "data" => data, - "logIndex" => index, - "topics" => topics, - "transactionHash" => transaction_hash - }) do + def elixir_to_params( + %{ + "address" => address_hash, + "blockNumber" => block_number, + "blockHash" => block_hash, + "data" => data, + "logIndex" => index, + "topics" => topics, + "transactionHash" => transaction_hash + } = log + ) do %{ address_hash: address_hash, block_number: block_number, block_hash: block_hash, data: data, index: index, - transaction_hash: transaction_hash + transaction_hash: transaction_hash, + transaction_index: log["transactionIndex"] } |> put_topics(topics) end diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/receipt.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/receipt.ex index 5e1e5f0..3faa882 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/receipt.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/receipt.ex @@ -27,6 +27,13 @@ defmodule EthereumJSONRPC.Receipt do ] ) + :scroll -> + @chain_type_fields quote( + do: [ + l1_fee: non_neg_integer() + ] + ) + :arbitrum -> @chain_type_fields quote( do: [ @@ -121,6 +128,9 @@ defmodule EthereumJSONRPC.Receipt do l1_gas_price: 0,\ l1_gas_used: 0\ """ + :scroll -> """ + l1_fee: 0\ + """ :arbitrum -> """ gas_used_for_l1: nil\ """ @@ -170,6 +180,9 @@ defmodule EthereumJSONRPC.Receipt do l1_gas_price: 0,\ l1_gas_used: 0\ """ + :scroll -> """ + l1_fee: 0\ + """ :arbitrum -> """ gas_used_for_l1: nil\ """ @@ -238,6 +251,14 @@ defmodule EthereumJSONRPC.Receipt do }) end + :scroll -> + defp chain_type_fields(params, elixir) do + params + |> Map.merge(%{ + l1_fee: Map.get(elixir, "l1Fee", 0) + }) + end + :arbitrum -> defp chain_type_fields(params, elixir) do params diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/receipts.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/receipts.ex index bcd912a..b3c2c04 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/receipts.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/receipts.ex @@ -111,6 +111,9 @@ defmodule EthereumJSONRPC.Receipts do l1_gas_price: 0,\ l1_gas_used: 0\ """ + :scroll -> """ + l1_fee: 0\ + """ :arbitrum -> """ gas_used_for_l1: nil\ """ diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/signed_authorization.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/signed_authorization.ex new file mode 100644 index 0000000..943183a --- /dev/null +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/signed_authorization.ex @@ -0,0 +1,58 @@ +defmodule EthereumJSONRPC.SignedAuthorization do + @moduledoc """ + The format of authorization tuples returned for + set code transactions [EIP-7702](https://eips.ethereum.org/EIPS/eip-7702). + """ + + import EthereumJSONRPC, only: [quantity_to_integer: 1] + + @typedoc """ + * `"chainId"` - specifies the chain for which the authorization was created `t:EthereumJSONRPC.quantity/0`. + * `"address"` - `t:EthereumJSONRPC.address/0` of the delegate contract. + * `"nonce"` - signature nonce `t:EthereumJSONRPC.quantity/0`. + * `"v"` - v component of the signature `t:EthereumJSONRPC.quantity/0`. + * `"r"` - r component of the signature `t:EthereumJSONRPC.quantity/0`. + * `"s"` - s component of the signature `t:EthereumJSONRPC.quantity/0`. + """ + @type t :: %{ + String.t() => EthereumJSONRPC.address() | EthereumJSONRPC.quantity() + } + + @typedoc """ + * `"chain_id"` - specifies the chain for which the authorization was created. + * `"address"` - address of the delegate contract. + * `"nonce"` - signature nonce. + * `"v"` - v component of the signature. + * `"r"` - r component of the signature. + * `"s"` - s component of the signature. + """ + @type params :: %{ + chain_id: non_neg_integer(), + address: EthereumJSONRPC.address(), + nonce: non_neg_integer(), + r: non_neg_integer(), + s: non_neg_integer(), + v: non_neg_integer() + } + + @doc """ + Converts a signed authorization map into its corresponding parameters map format. + + ## Parameters + - `raw`: Map with signed authorization data. + + ## Returns + - Parameters map in the `params()` format. + """ + @spec to_params(t()) :: params() + def to_params(raw) do + %{ + chain_id: quantity_to_integer(raw["chainId"]), + address: raw["address"], + nonce: quantity_to_integer(raw["nonce"]), + r: quantity_to_integer(raw["r"]), + s: quantity_to_integer(raw["s"]), + v: quantity_to_integer(raw["v"]) + } + end +end diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/transaction.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/transaction.ex index f835c4f..8611bdb 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/transaction.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/transaction.ex @@ -16,6 +16,7 @@ defmodule EthereumJSONRPC.Transaction do ] alias EthereumJSONRPC + alias EthereumJSONRPC.SignedAuthorization case Application.compile_env(:explorer, :chain_type) do :ethereum -> @@ -29,11 +30,18 @@ defmodule EthereumJSONRPC.Transaction do :optimism -> @chain_type_fields quote( do: [ - l1_tx_origin: EthereumJSONRPC.hash(), + l1_transaction_origin: EthereumJSONRPC.hash(), l1_block_number: non_neg_integer() ] ) + :scroll -> + @chain_type_fields quote( + do: [ + queue_index: non_neg_integer() + ] + ) + :suave -> @chain_type_fields quote( do: [ @@ -66,7 +74,7 @@ defmodule EthereumJSONRPC.Transaction do :arbitrum -> @chain_type_fields quote( do: [ - request_id: non_neg_integer() + request_id: EthereumJSONRPC.hash() ] ) @@ -74,8 +82,16 @@ defmodule EthereumJSONRPC.Transaction do @chain_type_fields quote(do: []) end + # todo: Check if it's possible to simplify by avoiding t -> elixir -> params conversions + # and directly convert t -> params. @type elixir :: %{ - String.t() => EthereumJSONRPC.address() | EthereumJSONRPC.hash() | String.t() | non_neg_integer() | nil + String.t() => + EthereumJSONRPC.address() + | EthereumJSONRPC.hash() + | String.t() + | non_neg_integer() + | [SignedAuthorization.params()] + | nil } @typedoc """ @@ -105,6 +121,7 @@ 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) + * `"authorizationList"` - `t:list/0` of `t:EthereumJSONRPC.SignedAuthorization.t/0` authorization tuples. Introduced in [EIP-7702](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-7702.md) #{case Application.compile_env(:explorer, :chain_type) do :ethereum -> """ * `"maxFeePerBlobGas"` - `t:EthereumJSONRPC.quantity/0` of wei to denote max fee per unit of blob gas used. Introduced in [EIP-4844](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-4844.md) @@ -114,6 +131,9 @@ defmodule EthereumJSONRPC.Transaction do * `"l1TxOrigin"` - . * `"l1BlockNumber"` - . """ + :scroll -> """ + * `"queueIndex"` - An index of L1MessageTx (replaces Nonce) in Scroll rollup. + """ :suave -> """ * `"executionNode"` - `t:EthereumJSONRPC.address/0` of execution node (used by Suave). * `"requestRecord"` - map of wrapped transaction data (used by Suave). @@ -128,7 +148,12 @@ defmodule EthereumJSONRPC.Transaction do """ @type t :: %{ String.t() => - EthereumJSONRPC.address() | EthereumJSONRPC.hash() | EthereumJSONRPC.quantity() | String.t() | nil + EthereumJSONRPC.address() + | EthereumJSONRPC.hash() + | EthereumJSONRPC.quantity() + | String.t() + | [SignedAuthorization.t()] + | nil } @type params :: %{ @@ -150,7 +175,8 @@ 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(), + authorization_list: [SignedAuthorization.params()] } @doc """ @@ -322,11 +348,12 @@ defmodule EthereumJSONRPC.Transaction do {"block_timestamp", :block_timestamp}, {"r", :r}, {"s", :s}, - {"v", :v} + {"v", :v}, + {"authorizationList", :authorization_list} ]) end - # txpool_content method on Erigon node returns tx data + # txpool_content method on Erigon node returns transaction data # without gas price def do_elixir_to_params( %{ @@ -368,11 +395,12 @@ defmodule EthereumJSONRPC.Transaction do {"block_timestamp", :block_timestamp}, {"r", :r}, {"s", :s}, - {"v", :v} + {"v", :v}, + {"authorizationList", :authorization_list} ]) end - # for legacy txs without maxPriorityFeePerGas and maxFeePerGas + # for legacy transactions without maxPriorityFeePerGas and maxFeePerGas def do_elixir_to_params( %{ "blockHash" => block_hash, @@ -414,7 +442,7 @@ defmodule EthereumJSONRPC.Transaction do ]) end - # for legacy txs without type, maxPriorityFeePerGas and maxFeePerGas + # for legacy transactions without type, maxPriorityFeePerGas and maxFeePerGas def do_elixir_to_params( %{ "blockHash" => block_hash, @@ -454,7 +482,7 @@ defmodule EthereumJSONRPC.Transaction do ]) end - # for txs without gasPrice, maxPriorityFeePerGas and maxFeePerGas + # for transactions without gasPrice, maxPriorityFeePerGas and maxFeePerGas def do_elixir_to_params( %{ "blockHash" => block_hash, @@ -504,13 +532,18 @@ defmodule EthereumJSONRPC.Transaction do ]) :optimism -> - # we need to put blobVersionedHashes for Indexer.Fetcher.Optimism.TxnBatch module + # we need to put blobVersionedHashes for Indexer.Fetcher.Optimism.TransactionBatch module put_if_present(params, elixir, [ - {"l1TxOrigin", :l1_tx_origin}, + {"l1TxOrigin", :l1_transaction_origin}, {"l1BlockNumber", :l1_block_number}, {"blobVersionedHashes", :blob_versioned_hashes} ]) + :scroll -> + put_if_present(params, elixir, [ + {"queueIndex", :queue_index} + ]) + :suave -> wrapped = Map.get(elixir, "requestRecord") @@ -662,7 +695,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 executionNode requestRecord blobVersionedHashes), + when key in ~w(blockHash condition creates from hash input jsonrpc publicKey raw to txType executionNode requestRecord blobVersionedHashes requestId), do: {key, value} # specific to Nethermind client @@ -670,7 +703,7 @@ defmodule EthereumJSONRPC.Transaction do do: {"input", value} defp entry_to_elixir({key, quantity}) - when key in ~w(gas gasPrice nonce r s standardV v value type maxPriorityFeePerGas maxFeePerGas maxFeePerBlobGas requestId) and + when key in ~w(gas gasPrice nonce r s standardV v value type maxPriorityFeePerGas maxFeePerGas maxFeePerBlobGas queueIndex) and quantity != nil do {key, quantity_to_integer(quantity)} end @@ -700,6 +733,9 @@ defmodule EthereumJSONRPC.Transaction do end end + defp entry_to_elixir({"authorizationList" = key, value}), + do: {key, value |> Enum.map(&SignedAuthorization.to_params/1)} + # Celo-specific fields if Application.compile_env(:explorer, :chain_type) == :celo do defp entry_to_elixir({key, value}) diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/utility/common_helper.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/utility/common_helper.ex index 154edb7..a6d0d55 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/utility/common_helper.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/utility/common_helper.ex @@ -17,6 +17,19 @@ defmodule EthereumJSONRPC.Utility.CommonHelper do end end + @doc """ + Puts value under nested key in keyword. + Similar to `Kernel.put_in/3` but inserts values in the middle if they're missing + """ + @spec put_in_keyword_nested(Keyword.t(), [atom()], any()) :: Keyword.t() + def put_in_keyword_nested(keyword, [last_path], value) do + Keyword.put(keyword || [], last_path, value) + end + + def put_in_keyword_nested(keyword, [nearest_path | rest_path], value) do + Keyword.put(keyword || [], nearest_path, put_in_keyword_nested(keyword[nearest_path], rest_path, value)) + end + defp convert_to_ms(number, "s"), do: :timer.seconds(number) defp convert_to_ms(number, "m"), do: :timer.minutes(number) defp convert_to_ms(number, "h"), do: :timer.hours(number) diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/utility/ranges_helper.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/utility/ranges_helper.ex index f53a8b1..3b27e0c 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/utility/ranges_helper.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/utility/ranges_helper.ex @@ -56,9 +56,10 @@ defmodule EthereumJSONRPC.Utility.RangesHelper do |> sanitize_ranges() end - defp number_in_ranges?(number, ranges) do + @spec number_in_ranges?(integer(), [Range.t()]) :: boolean() + def 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} + _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 @@ -78,7 +79,7 @@ defmodule EthereumJSONRPC.Utility.RangesHelper do |> Enum.reject(&is_nil/1) |> Enum.sort_by( fn - from.._to -> from + from.._to//_ -> from el -> el end, :asc @@ -86,10 +87,10 @@ defmodule EthereumJSONRPC.Utility.RangesHelper do |> Enum.chunk_while( nil, fn - _from.._to = chunk, nil -> + _from.._to//_ = chunk, nil -> {:cont, chunk} - _ch_from..ch_to = chunk, acc_from..acc_to = acc -> + _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)} @@ -97,7 +98,7 @@ defmodule EthereumJSONRPC.Utility.RangesHelper do num, nil -> {:halt, num} - num, acc_from.._ = acc -> + num, acc_from.._//_ = acc -> if Range.disjoint?(num..num, acc), do: {:cont, acc, num}, else: {:halt, acc_from} _, num -> @@ -113,7 +114,7 @@ defmodule EthereumJSONRPC.Utility.RangesHelper do @spec split([Range.t()], integer) :: [Range.t()] def split(ranges, size) do ranges - |> Enum.reduce([], fn from..to = range, acc -> + |> Enum.reduce([], fn from..to//_ = range, acc -> range_size = Range.size(range) if range_size > size do diff --git a/apps/ethereum_jsonrpc/mix.exs b/apps/ethereum_jsonrpc/mix.exs index 767b4b8..5cfbfb6 100644 --- a/apps/ethereum_jsonrpc/mix.exs +++ b/apps/ethereum_jsonrpc/mix.exs @@ -23,7 +23,7 @@ defmodule EthereumJsonrpc.MixProject do dialyzer: :test ], start_permanent: Mix.env() == :prod, - version: "6.8.0" + version: "6.9.2" ] end diff --git a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/geth/tracer_test.exs b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/geth/tracer_test.exs index bc57c17..f2a8da9 100644 --- a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/geth/tracer_test.exs +++ b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/geth/tracer_test.exs @@ -8,7 +8,7 @@ defmodule EthereumJSONRPC.Geth.TracerTest do test "same as callTracer" do struct_logs = File.read!(File.cwd!() <> "/test/support/fixture/geth/trace/struct_logger.json") |> Jason.decode!() - tx = "0xa0a5c30c5c5ec22b3346e0ae5ce09f8f41faf54f68a2a113eb15e363af90e9ab" + transaction = "0xa0a5c30c5c5ec22b3346e0ae5ce09f8f41faf54f68a2a113eb15e363af90e9ab" sl_calls = Tracer.replay(struct_logs["result"], struct_logs["receipt"], struct_logs["tx"]) @@ -18,7 +18,7 @@ defmodule EthereumJSONRPC.Geth.TracerTest do "blockNumber" => 0, "index" => index, "transactionIndex" => 0, - "transactionHash" => tx + "transactionHash" => transaction }) end) |> Calls.to_internal_transactions_params() @@ -40,7 +40,7 @@ defmodule EthereumJSONRPC.Geth.TracerTest do "blockNumber" => 0, "index" => index, "transactionIndex" => 0, - "transactionHash" => tx + "transactionHash" => transaction }) end) |> Calls.to_internal_transactions_params() diff --git a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/geth_test.exs b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/geth_test.exs index 61195d8..6617bbc 100644 --- a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/geth_test.exs +++ b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/geth_test.exs @@ -359,11 +359,12 @@ defmodule EthereumJSONRPC.GethTest do Application.put_env(:ethereum_jsonrpc, Geth, tracer: "call_tracer", debug_trace_timeout: "5s") - call_tracer_internal_txs = Geth.fetch_internal_transactions([transaction_params], json_rpc_named_arguments) + call_tracer_internal_transactions = + Geth.fetch_internal_transactions([transaction_params], json_rpc_named_arguments) Application.put_env(:ethereum_jsonrpc, Geth, tracer: "js", debug_trace_timeout: "5s") - assert call_tracer_internal_txs == + assert call_tracer_internal_transactions == Geth.fetch_internal_transactions([transaction_params], json_rpc_named_arguments) end @@ -431,11 +432,12 @@ defmodule EthereumJSONRPC.GethTest do Application.put_env(:ethereum_jsonrpc, Geth, tracer: "call_tracer", debug_trace_timeout: "5s") - call_tracer_internal_txs = Geth.fetch_internal_transactions([transaction_params], json_rpc_named_arguments) + call_tracer_internal_transactions = + Geth.fetch_internal_transactions([transaction_params], json_rpc_named_arguments) Application.put_env(:ethereum_jsonrpc, Geth, tracer: "js", debug_trace_timeout: "5s") - assert call_tracer_internal_txs == + assert call_tracer_internal_transactions == Geth.fetch_internal_transactions([transaction_params], json_rpc_named_arguments) end diff --git a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/receipts_test.exs b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/receipts_test.exs index 945f3f7..112ba41 100644 --- a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/receipts_test.exs +++ b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/receipts_test.exs @@ -86,7 +86,8 @@ defmodule EthereumJSONRPC.ReceiptsTest do "data" => data, "logIndex" => integer_to_quantity(index), "topics" => [first_topic], - "transactionHash" => transaction_hash + "transactionHash" => transaction_hash, + "transactionIndex" => integer_to_quantity(transaction_index) } ], "status" => native_status, @@ -110,7 +111,8 @@ defmodule EthereumJSONRPC.ReceiptsTest do index: ^index, second_topic: nil, third_topic: nil, - transaction_hash: ^transaction_hash + transaction_hash: ^transaction_hash, + transaction_index: ^transaction_index } | _ ], diff --git a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc_test.exs b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc_test.exs index 0409dc1..ecc2dbd 100644 --- a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc_test.exs +++ b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc_test.exs @@ -35,7 +35,8 @@ defmodule EthereumJSONRPCTest do [ %{block_quantity: "0x1", hash_data: hash} ], - json_rpc_named_arguments + json_rpc_named_arguments, + 1 ) == {:ok, %FetchedBalances{ @@ -49,6 +50,25 @@ defmodule EthereumJSONRPCTest do }} end + test "fetch latest block number from node if it wasn't provided", %{ + json_rpc_named_arguments: json_rpc_named_arguments + } do + if json_rpc_named_arguments[:transport] == EthereumJSONRPC.Mox do + expect(EthereumJSONRPC.Mox, :json_rpc, 2, fn + [%{id: id, method: "eth_getBlockByNumber"}], _options -> block_response(id, false, "0x1") + _json, _options -> {:ok, [%{id: 0, result: "0x1"}]} + end) + end + + assert {:ok, %FetchedBalances{}} = + EthereumJSONRPC.fetch_balances( + [ + %{block_quantity: "0x1", hash_data: "0x8bf38d4764929064f2d4d3a56520a76ab3df415b"} + ], + json_rpc_named_arguments + ) + end + test "with all invalid hash_data returns errors", %{json_rpc_named_arguments: json_rpc_named_arguments} do variant = Keyword.fetch!(json_rpc_named_arguments, :variant) @@ -90,7 +110,7 @@ defmodule EthereumJSONRPCTest do ], params_list: [] }} = - EthereumJSONRPC.fetch_balances([%{block_quantity: "0x1", hash_data: "0x0"}], json_rpc_named_arguments) + EthereumJSONRPC.fetch_balances([%{block_quantity: "0x1", hash_data: "0x0"}], json_rpc_named_arguments, 1) end test "with a mix of valid and invalid hash_data returns both", %{ @@ -163,7 +183,8 @@ defmodule EthereumJSONRPCTest do hash_data: "0x5" } ], - json_rpc_named_arguments + json_rpc_named_arguments, + 53 ) assert is_list(params_list) @@ -1058,7 +1079,7 @@ defmodule EthereumJSONRPCSyncTest do ]} end) - Application.put_env(:ethereum_jsonrpc, :disable_archive_balances?, "true") + Application.put_env(:ethereum_jsonrpc, :disable_archive_balances?, true) Application.put_env(:ethereum_jsonrpc, :archive_balances_window, 1) assert EthereumJSONRPC.fetch_balances( diff --git a/apps/explorer/config/config.exs b/apps/explorer/config/config.exs index 1ffec10..a1977ac 100644 --- a/apps/explorer/config/config.exs +++ b/apps/explorer/config/config.exs @@ -81,6 +81,11 @@ config :explorer, Explorer.Chain.Cache.StabilityValidatorsCounters, enable_consolidation: true, update_interval_in_milliseconds: update_interval_in_milliseconds_default +config :explorer, Explorer.Chain.Cache.BlackfortValidatorsCounters, + enabled: true, + enable_consolidation: true, + update_interval_in_milliseconds: update_interval_in_milliseconds_default + config :explorer, Explorer.Chain.Cache.TransactionActionTokensData, enabled: true config :explorer, Explorer.Chain.Cache.TransactionActionUniswapPools, enabled: true @@ -123,6 +128,11 @@ config :explorer, Explorer.Migrator.SanitizeIncorrectWETHTokenTransfers, enabled config :explorer, Explorer.Migrator.TransactionBlockConsensus, enabled: true config :explorer, Explorer.Migrator.TokenTransferBlockConsensus, enabled: true config :explorer, Explorer.Migrator.RestoreOmittedWETHTransfers, enabled: true +config :explorer, Explorer.Migrator.SanitizeMissingTokenBalances, enabled: true +config :explorer, Explorer.Migrator.SanitizeReplacedTransactions, enabled: true +config :explorer, Explorer.Migrator.ReindexInternalTransactionsWithIncompatibleStatus, enabled: true +config :explorer, Explorer.Migrator.SanitizeDuplicatedLogIndexLogs, enabled: true +config :explorer, Explorer.Migrator.RefetchContractCodes, 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 71bfcbe..7202a7f 100644 --- a/apps/explorer/config/dev.exs +++ b/apps/explorer/config/dev.exs @@ -20,6 +20,9 @@ config :explorer, Explorer.Repo.PolygonEdge, timeout: :timer.seconds(80) # Configure Polygon zkEVM database config :explorer, Explorer.Repo.PolygonZkevm, timeout: :timer.seconds(80) +# Configure Scroll database +config :explorer, Explorer.Repo.Scroll, timeout: :timer.seconds(80) + # Configure ZkSync database config :explorer, Explorer.Repo.ZkSync, timeout: :timer.seconds(80) @@ -46,6 +49,8 @@ config :explorer, Explorer.Repo.Mud, timeout: :timer.seconds(80) config :explorer, Explorer.Repo.ShrunkInternalTransactions, timeout: :timer.seconds(80) +config :explorer, Explorer.Repo.Blackfort, timeout: :timer.seconds(80) + config :explorer, Explorer.Tracer, env: "dev", disabled?: true config :logger, :explorer, diff --git a/apps/explorer/config/prod.exs b/apps/explorer/config/prod.exs index e273743..0f1f30c 100644 --- a/apps/explorer/config/prod.exs +++ b/apps/explorer/config/prod.exs @@ -34,6 +34,11 @@ config :explorer, Explorer.Repo.PolygonZkevm, timeout: :timer.seconds(60), ssl_opts: [verify: :verify_none] +config :explorer, Explorer.Repo.Scroll, + prepare: :unnamed, + timeout: :timer.seconds(60), + ssl_opts: [verify: :verify_none] + config :explorer, Explorer.Repo.ZkSync, prepare: :unnamed, timeout: :timer.seconds(60), @@ -94,6 +99,11 @@ config :explorer, Explorer.Repo.ShrunkInternalTransactions, timeout: :timer.seconds(60), ssl_opts: [verify: :verify_none] +config :explorer, Explorer.Repo.Blackfort, + prepare: :unnamed, + timeout: :timer.seconds(60), + ssl_opts: [verify: :verify_none] + config :explorer, Explorer.Tracer, env: "production", disabled?: true config :logger, :explorer, diff --git a/apps/explorer/config/runtime/test.exs b/apps/explorer/config/runtime/test.exs index 1dae56d..f4be94e 100644 --- a/apps/explorer/config/runtime/test.exs +++ b/apps/explorer/config/runtime/test.exs @@ -47,7 +47,13 @@ config :explorer, Explorer.Migrator.TokenTransferTokenType, enabled: false config :explorer, Explorer.Migrator.SanitizeIncorrectWETHTokenTransfers, enabled: false config :explorer, Explorer.Migrator.TransactionBlockConsensus, enabled: false config :explorer, Explorer.Migrator.TokenTransferBlockConsensus, enabled: false +config :explorer, Explorer.Migrator.ShrinkInternalTransactions, enabled: false config :explorer, Explorer.Migrator.RestoreOmittedWETHTransfers, enabled: false +config :explorer, Explorer.Migrator.SanitizeMissingTokenBalances, enabled: false +config :explorer, Explorer.Migrator.SanitizeReplacedTransactions, enabled: false +config :explorer, Explorer.Migrator.ReindexInternalTransactionsWithIncompatibleStatus, enabled: false +config :explorer, Explorer.Migrator.SanitizeDuplicatedLogIndexLogs, enabled: false +config :explorer, Explorer.Migrator.RefetchContractCodes, 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 f475825..f0f047b 100644 --- a/apps/explorer/config/test.exs +++ b/apps/explorer/config/test.exs @@ -14,7 +14,7 @@ config :explorer, Explorer.Repo, url: database_url, pool: Ecto.Adapters.SQL.Sandbox, # Default of `5_000` was too low for `BlockFetcher` test - ownership_timeout: :timer.minutes(7), + ownership_timeout: :timer.minutes(1), timeout: :timer.seconds(60), queue_target: 1000, migration_lock: nil, @@ -58,6 +58,7 @@ for repo <- [ Explorer.Repo.Optimism, Explorer.Repo.PolygonEdge, Explorer.Repo.PolygonZkevm, + Explorer.Repo.Scroll, Explorer.Repo.ZkSync, Explorer.Repo.Celo, Explorer.Repo.RSK, @@ -68,7 +69,8 @@ for repo <- [ Explorer.Repo.Filecoin, Explorer.Repo.Stability, Explorer.Repo.Mud, - Explorer.Repo.ShrunkInternalTransactions + Explorer.Repo.ShrunkInternalTransactions, + Explorer.Repo.Blackfort ] do config :explorer, repo, database: database, diff --git a/apps/explorer/lib/encrypt.ex b/apps/explorer/lib/encrypt.ex deleted file mode 100644 index e105feb..0000000 --- a/apps/explorer/lib/encrypt.ex +++ /dev/null @@ -1,116 +0,0 @@ -defmodule Mix.Tasks.Encrypt do - @moduledoc "The encrypt mix task: `mix help encrypt`" - use Mix.Task - - alias Ecto.Changeset - - alias Explorer.Account.{ - CustomABI, - Identity, - PublicTagsRequest, - TagAddress, - TagTransaction, - WatchlistAddress, - WatchlistNotification - } - - alias Explorer.Repo.Account - alias Mix.Task - - @shortdoc "Encrypt" - def run(_) do - Task.run("app.start") - - Identity - |> Account.all() - |> Enum.each(fn element -> - element - |> Changeset.change(%{ - encrypted_uid: element.uid, - encrypted_email: element.email, - encrypted_name: element.name, - encrypted_nickname: element.nickname, - encrypted_avatar: element.avatar, - uid_hash: element.uid - }) - |> Account.update!() - end) - - TagAddress - |> Account.all() - |> Enum.each(fn element -> - element - |> Changeset.change(%{ - encrypted_name: element.name, - encrypted_address_hash: element.address_hash, - address_hash_hash: element.address_hash |> to_string() |> String.downcase() - }) - |> Account.update!() - end) - - TagTransaction - |> Account.all() - |> Enum.each(fn element -> - element - |> Changeset.change(%{ - encrypted_name: element.name, - encrypted_tx_hash: element.tx_hash, - tx_hash_hash: element.tx_hash |> to_string() |> String.downcase() - }) - |> Account.update!() - end) - - CustomABI - |> Account.all() - |> Enum.each(fn element -> - element - |> Changeset.change(%{ - encrypted_name: element.name, - encrypted_address_hash: element.address_hash, - address_hash_hash: element.address_hash |> to_string() |> String.downcase() - }) - |> Account.update!() - end) - - WatchlistAddress - |> Account.all() - |> Enum.each(fn element -> - element - |> Changeset.change(%{ - encrypted_name: element.name, - encrypted_address_hash: element.address_hash, - address_hash_hash: element.address_hash |> to_string() |> String.downcase() - }) - |> Account.update!() - end) - - WatchlistNotification - |> Account.all() - |> Enum.each(fn element -> - element - |> Changeset.change(%{ - encrypted_name: element.name, - encrypted_from_address_hash: element.from_address_hash, - encrypted_to_address_hash: element.to_address_hash, - encrypted_transaction_hash: element.transaction_hash, - encrypted_subject: element.subject, - from_address_hash_hash: element.from_address_hash |> to_string() |> String.downcase(), - to_address_hash_hash: element.to_address_hash |> to_string() |> String.downcase(), - transaction_hash_hash: element.transaction_hash |> to_string() |> String.downcase(), - subject_hash: element.subject - }) - |> Account.update!() - end) - - PublicTagsRequest - |> Account.all() - |> Enum.each(fn element -> - element - |> Changeset.change(%{ - encrypted_full_name: element.full_name, - encrypted_email: element.email - }) - |> Account.update!() - end) - end -end diff --git a/apps/explorer/lib/explorer/account.ex b/apps/explorer/lib/explorer/account.ex index 5f2155b..ecbfdda 100644 --- a/apps/explorer/lib/explorer/account.ex +++ b/apps/explorer/lib/explorer/account.ex @@ -3,7 +3,86 @@ defmodule Explorer.Account do Context for Account module. """ + alias Ecto.Multi + alias Explorer.Account.Api.Key + + alias Explorer.Account.{ + CustomABI, + Identity, + PublicTagsRequest, + TagAddress, + TagTransaction, + Watchlist, + WatchlistAddress, + WatchlistNotification + } + + alias Explorer.Repo + def enabled? do Application.get_env(:explorer, __MODULE__)[:enabled] end + + @doc """ + Merges multiple Identity records into a primary Identity. + + This function consolidates data from multiple Identity records into a single + primary Identity. It performs a series of merge operations for various + associated entities (API keys, custom ABIs, public tags requests, address tags, + transaction tags, watchlists, watchlist addresses, and watchlist notifications) + and then deletes the merged Identity records. + + ## Parameters + - `identities`: A list of Identity structs. The first element is considered + the primary Identity, and the rest are merged into it. + + ## Returns + - A tuple containing two elements: + 1. The result of the transaction: + - `{:ok, result}` if the merge was successful + - `{:error, failed_operation, failed_value, changes_so_far}` if an error occurred + 2. The primary Identity struct or nil if the input list was empty + + ## Process + 1. Extracts IDs from the input Identity structs + 2. Performs the following merge operations in a single database transaction: + - Merges API keys + - Merges custom ABIs + - Merges public tags requests + - Merges address tags + - Merges transaction tags + - Acquires and merges watchlists + - Merges watchlist addresses + - Merges watchlist notifications + 3. Deletes the merged Identity records + 4. Commits the transaction + + ## Notes + - If an empty list is provided, the function returns `{{:ok, 0}, nil}`. + - This function uses Ecto.Multi for transactional integrity. + - All merge operations update the associated records to point to the primary Identity. + - Some merge operations (like custom ABIs and tags) set `user_created: false` to satisfy database constraints. + - The function relies on the account repository specified in the application configuration. + """ + @spec merge([Identity.t()]) :: {{:ok, any()} | {:error, any()} | Multi.failure(), Identity.t() | nil} + def merge([primary_identity | identities_to_merge]) do + primary_identity_id = primary_identity.id + identities_to_merge_ids = Enum.map(identities_to_merge, & &1.id) + + {Multi.new() + |> Key.merge(primary_identity_id, identities_to_merge_ids) + |> CustomABI.merge(primary_identity_id, identities_to_merge_ids) + |> PublicTagsRequest.merge(primary_identity_id, identities_to_merge_ids) + |> TagAddress.merge(primary_identity_id, identities_to_merge_ids) + |> TagTransaction.merge(primary_identity_id, identities_to_merge_ids) + |> Watchlist.acquire_for_merge(primary_identity_id, identities_to_merge_ids) + |> WatchlistAddress.merge() + |> WatchlistNotification.merge() + |> Identity.delete(identities_to_merge_ids) + |> Repo.account_repo().transaction(), primary_identity} + end + + def merge([]) do + {{:ok, 0}, nil} + end end diff --git a/apps/explorer/lib/explorer/account/api/key.ex b/apps/explorer/lib/explorer/account/api/key.ex index 5330ab9..7f286ee 100644 --- a/apps/explorer/lib/explorer/account/api/key.ex +++ b/apps/explorer/lib/explorer/account/api/key.ex @@ -4,6 +4,7 @@ defmodule Explorer.Account.Api.Key do """ use Explorer.Schema + alias Ecto.Multi alias Explorer.Account.Identity alias Ecto.{Changeset, UUID} alias Explorer.Repo @@ -126,4 +127,30 @@ defmodule Explorer.Account.Api.Key do def api_key_with_plan_by_value(_), do: nil def get_max_api_keys_count, do: @max_key_per_account + + @doc """ + Merges API keys from multiple identities into a primary identity. + + This function updates the `identity_id` of all API keys belonging to the + identities specified in `ids_to_merge` to the `primary_id`. It's designed to + be used as part of an Ecto.Multi transaction. + + ## Parameters + - `multi`: An Ecto.Multi struct to which this operation will be added. + - `primary_id`: The ID of the primary identity that will own the merged keys. + - `ids_to_merge`: A list of identity IDs whose API keys will be merged. + + ## Returns + - An updated Ecto.Multi struct with the merge operation added. + """ + @spec merge(Multi.t(), integer(), [integer()]) :: Multi.t() + def merge(multi, primary_id, ids_to_merge) do + Multi.run(multi, :merge_keys, fn repo, _ -> + {:ok, + repo.update_all( + from(key in __MODULE__, where: key.identity_id in ^ids_to_merge), + set: [identity_id: primary_id] + )} + end) + end end diff --git a/apps/explorer/lib/explorer/account/custom_abi.ex b/apps/explorer/lib/explorer/account/custom_abi.ex index 6c618a1..56f8b4b 100644 --- a/apps/explorer/lib/explorer/account/custom_abi.ex +++ b/apps/explorer/lib/explorer/account/custom_abi.ex @@ -5,9 +5,10 @@ defmodule Explorer.Account.CustomABI do use Explorer.Schema alias ABI.FunctionSelector - alias Ecto.Changeset + alias Ecto.{Changeset, Multi} alias Explorer.Account.Identity alias Explorer.{Chain, Repo} + alias Explorer.Chain.Hash import Explorer.Chain, only: [hash_to_lower_case_string: 1] import Ecto.Changeset @@ -21,6 +22,7 @@ defmodule Explorer.Account.CustomABI do field(:address_hash_hash, Cloak.Ecto.SHA256) :: binary() | nil field(:address_hash, Explorer.Encrypted.AddressHash, null: false) field(:name, Explorer.Encrypted.Binary, null: false) + field(:user_created, :boolean, null: false, default: true) belongs_to(:identity, Identity, null: false) @@ -177,12 +179,37 @@ defmodule Explorer.Account.CustomABI do def custom_abi_by_identity_id_and_address_hash_query(_, _), do: nil + @doc """ + Retrieves a custom ABI for a given address hash and identity ID. + + This function searches for a custom ABI associated with the provided address + hash and identity ID. It returns the first matching ABI if found, or nil if no + matching ABI exists. + + ## Parameters + - `address_hash`: The address hash to search for. Can be a `Hash.Address.t()`, + `String.t()`, or `nil`. + - `identity_id`: The identity ID associated with the custom ABI. Can be an + `integer()` or `nil`. + + ## Returns + - A `Explorer.Account.CustomABI` struct if a matching ABI is found. + - `nil` if no matching ABI is found or if either input is nil. + """ + @spec get_custom_abi_by_identity_id_and_address_hash(Hash.Address.t() | String.t() | nil, integer() | nil) :: + __MODULE__.t() | nil def get_custom_abi_by_identity_id_and_address_hash(address_hash, identity_id) when not is_nil(identity_id) and not is_nil(address_hash) do - address_hash - |> hash_to_lower_case_string() - |> custom_abi_by_identity_id_and_address_hash_query(identity_id) - |> Repo.account_repo().one() + abis = + address_hash + |> hash_to_lower_case_string() + |> custom_abi_by_identity_id_and_address_hash_query(identity_id) + |> Repo.account_repo().all() + + case abis do + [abi | _] -> abi + _ -> nil + end end def get_custom_abi_by_identity_id_and_address_hash(_, _), do: nil @@ -224,4 +251,32 @@ defmodule Explorer.Account.CustomABI do end def get_max_custom_abis_count, do: @max_abis_per_account + + @doc """ + Merges custom ABIs from multiple identities into a primary identity. + + This function updates all custom ABIs associated with the identities specified + in `ids_to_merge` to be associated with the `primary_id`. It also marks these + ABIs as not user-created in order to satisfy database constraint. + + ## Parameters + - `multi`: An `Ecto.Multi` struct to which the merge operation will be added. + - `primary_id`: An integer representing the ID of the primary identity to + which the custom ABIs will be merged. + - `ids_to_merge`: A list of integer IDs representing the identities whose + custom ABIs will be merged into the primary identity. + + ## Returns + - An updated `Ecto.Multi` struct with the merge operation added. + """ + @spec merge(Multi.t(), integer(), [integer()]) :: Multi.t() + def merge(multi, primary_id, ids_to_merge) do + Multi.run(multi, :merge_custom_abis, fn repo, _ -> + {:ok, + repo.update_all( + from(key in __MODULE__, where: key.identity_id in ^ids_to_merge), + set: [identity_id: primary_id, user_created: false] + )} + end) + end end diff --git a/apps/explorer/lib/explorer/account/identity.ex b/apps/explorer/lib/explorer/account/identity.ex index c9c55e6..7f3739d 100644 --- a/apps/explorer/lib/explorer/account/identity.ex +++ b/apps/explorer/lib/explorer/account/identity.ex @@ -2,22 +2,42 @@ defmodule Explorer.Account.Identity do @moduledoc """ Identity of user fetched via Oauth """ - use Explorer.Schema - import Ecto.Changeset + require Logger + require Poison + alias BlockScoutWeb.Chain + alias Ecto.Multi alias Explorer.Account.Api.Plan alias Explorer.Account.{TagAddress, Watchlist} + alias Explorer.{Chain, Repo} + alias Explorer.Chain.{Address, Hash} + alias Ueberauth.Auth + alias Ueberauth.Auth.{Extra, Info} + + @type session :: %{ + optional(:name) => String.t(), + optional(:watchlist_id) => integer(), + id: integer(), + uid: String.t(), + email: String.t(), + nickname: String.t(), + avatar: String.t(), + address_hash: String.t(), + email_verified: boolean() + } typed_schema "account_identities" do field(:uid_hash, Cloak.Ecto.SHA256) :: binary() | nil field(:uid, Explorer.Encrypted.Binary, null: false) field(:email, Explorer.Encrypted.Binary, null: false) - field(:name, Explorer.Encrypted.Binary, null: false) - field(:nickname, Explorer.Encrypted.Binary) + field(:name, :string, virtual: true) + field(:nickname, :string, virtual: true) + field(:address_hash, Hash.Address, virtual: true) field(:avatar, Explorer.Encrypted.Binary) field(:verification_email_sent_at, :utc_datetime_usec) + field(:otp_sent_at, :utc_datetime_usec) has_many(:tag_addresses, TagAddress) has_many(:watchlists, Watchlist) @@ -40,4 +60,271 @@ defmodule Explorer.Account.Identity do changeset |> force_change(:uid_hash, get_field(changeset, :uid)) end + + @doc """ + Populates Identity virtual fields with data from user-stored session. + + This function updates the virtual fields of an Identity struct with + information from the user's session. + + ## Parameters + - `identity`: The Identity struct to be updated. + - `session`: A map containing session information. + + ## Returns + - An updated Identity struct with populated virtual fields. + """ + @spec put_session_info(t(), session()) :: t() + def put_session_info(identity, %{name: name, nickname: nickname, address_hash: address_hash}) do + %__MODULE__{ + identity + | name: name, + nickname: nickname, + address_hash: address_hash + } + end + + def put_session_info(identity, %{name: name, nickname: nickname}) do + %__MODULE__{ + identity + | name: name, + nickname: nickname + } + end + + @doc """ + Finds an existing Identity or creates a new one based on authentication data. + + This function attempts to find an Identity matching the given authentication + data. If not found, it creates a new Identity. + + ## Parameters + - `auth`: An Auth struct containing authentication information. + + ## Returns + - `{:ok, session()}`: A tuple containing the atom `:ok` and a session map if + the Identity is found or successfully created. + - `{:error, Ecto.Changeset.t()}`: A tuple containing the atom `:error` and a + changeset if there was an error creating the Identity. + """ + @spec find_or_create(Auth.t()) :: {:ok, session()} | {:error, Ecto.Changeset.t()} + def find_or_create(%Auth{} = auth) do + case find_identity(auth) do + nil -> + case create_identity(auth) do + %__MODULE__{} = identity -> + {:ok, session_info(auth, identity)} + + {:error, changeset} -> + {:error, changeset} + end + + %{} = identity -> + update_identity(identity, update_identity_map(auth)) + {:ok, session_info(auth, identity)} + end + end + + defp create_identity(auth) do + with {:ok, %__MODULE__{} = identity} <- Repo.account_repo().insert(new_identity(auth)), + {:ok, _watchlist} <- add_watchlist(identity) do + identity + end + end + + defp update_identity(identity, attrs) do + identity + |> changeset(attrs) + |> Repo.account_repo().update() + end + + defp new_identity(auth) do + %__MODULE__{ + uid: auth.uid, + uid_hash: auth.uid, + email: email_from_auth(auth), + name: name_from_auth(auth), + nickname: nickname_from_auth(auth), + avatar: avatar_from_auth(auth), + address_hash: address_hash_from_auth(auth) + } + end + + defp add_watchlist(identity) do + watchlist = Ecto.build_assoc(identity, :watchlists, %{}) + + with {:ok, _} <- Repo.account_repo().insert(watchlist), + do: {:ok, identity} + end + + @doc """ + Finds an Identity based on authentication data or ID. + + This function searches for an Identity using either authentication data or + an ID. + + ## Parameters + - `auth_or_uid`: Either an Auth struct or an integer ID. + + ## Returns + - The found Identity struct or nil if not found. + """ + @spec find_identity(Auth.t() | integer()) :: t() | nil + def find_identity(auth_or_uid) do + Repo.account_repo().one(query_identity(auth_or_uid)) + end + + defp query_identity(%Auth{} = auth) do + from(i in __MODULE__, where: i.uid_hash == ^auth.uid) + end + + defp query_identity(id) do + from(i in __MODULE__, where: i.id == ^id) + end + + @doc """ + Finds multiple Identities based on a list of user IDs. + + This function retrieves multiple Identity structs that match the given list + of user IDs. + + ## Parameters + - `user_ids`: A list of user ID strings. + + ## Returns + - A list of found Identity structs. + """ + @spec find_identities([String.t()]) :: [t()] + def find_identities(user_ids) do + Repo.account_repo().all(query_identities(user_ids)) + end + + defp query_identities(user_ids) do + from(i in __MODULE__, where: i.uid_hash in ^user_ids) + end + + @doc """ + Deletes multiple Identities as part of a Multi transaction. + + This function adds a step to a Multi transaction to delete Identities with + the specified IDs. + + ## Parameters + - `multi`: The Multi struct to which the delete operation will be added. + - `ids_to_merge`: A list of Identity IDs to be deleted. + + ## Returns + - An updated Multi struct with the delete operation added. + """ + @spec delete(Multi.t(), [integer()]) :: Multi.t() + def delete(multi, ids_to_merge) do + Multi.run(multi, :delete_identities, fn repo, _ -> + query = from(identity in __MODULE__, where: identity.id in ^ids_to_merge) + {:ok, repo.delete_all(query)} + end) + end + + defp session_info(auth, identity) do + if email_verified_from_auth(auth) do + %{watchlists: [watchlist | _]} = Repo.account_repo().preload(identity, :watchlists) + + %{ + id: identity.id, + uid: auth.uid, + email: email_from_auth(auth), + name: name_from_auth(auth), + nickname: nickname_from_auth(auth), + avatar: avatar_from_auth(auth), + address_hash: address_hash_from_auth(auth), + watchlist_id: watchlist.id, + email_verified: true + } + else + %{ + id: identity.id, + uid: auth.uid, + email: email_from_auth(auth), + nickname: nickname_from_auth(auth), + avatar: avatar_from_auth(auth), + address_hash: address_hash_from_auth(auth), + email_verified: false + } + end + end + + defp update_identity_map(auth) do + %{ + email: email_from_auth(auth), + name: name_from_auth(auth), + nickname: nickname_from_auth(auth), + avatar: avatar_from_auth(auth), + address_hash: address_hash_from_auth(auth) + } + end + + # github does it this way + defp avatar_from_auth(%{info: %{urls: %{avatar_url: image}}}), do: image + + # facebook does it this way + defp avatar_from_auth(%{info: %{image: image}}), do: image + + defp email_from_auth(%Auth{extra: %Extra{raw_info: %{user: %{"user_metadata" => %{"email" => email}}}}}), + do: email + + defp email_from_auth(%{info: %{email: email}}), do: email + + defp nickname_from_auth(%{info: %{nickname: nickname}}), do: nickname + + defp name_from_auth(%{info: %{name: name}}) + when name != "" and not is_nil(name), + do: name + + defp name_from_auth(%{info: info}) do + [info.first_name, info.last_name, info.nickname] + |> Enum.map(&(&1 |> to_string() |> String.trim())) + |> case do + ["", "", nick] -> nick + ["", lastname, _] -> lastname + [name, "", _] -> name + [name, lastname, _] -> name <> " " <> lastname + end + end + + @doc """ + Extracts the address hash from authentication data. + + This function attempts to extract an Ethereum address hash from the + authentication data, either from user metadata or by parsing the UID. + + ## Parameters + - `auth`: An Auth struct containing authentication information. + + ## Returns + - A string representation of the Ethereum address hash, or nil if not found. + """ + @spec address_hash_from_auth(Auth.t()) :: String.t() | nil + def address_hash_from_auth(%Auth{ + extra: %Extra{raw_info: %{user: %{"user_metadata" => %{"web3_address_hash" => address_hash}}}} + }) do + address_hash + end + + def address_hash_from_auth(%Auth{uid: uid, info: %Info{nickname: nickname}}) do + case uid |> String.slice(-42..-1) |> Chain.string_to_address_hash() do + {:ok, address_hash} -> + address_hash |> Address.checksum() + + _ -> + case String.contains?(uid, "Passkey") && Chain.string_to_address_hash(nickname) do + {:ok, address_hash} -> address_hash |> Address.checksum() + _ -> nil + end + end + end + + defp email_verified_from_auth(%Auth{extra: %Extra{raw_info: %{user: %{"user_metadata" => %{"email" => _email}}}}}), + do: true + + defp email_verified_from_auth(%Auth{extra: %Extra{raw_info: %{user: %{"email_verified" => false}}}}), do: false + defp email_verified_from_auth(_), do: true end diff --git a/apps/explorer/lib/explorer/account/notifier/email.ex b/apps/explorer/lib/explorer/account/notifier/email.ex index 98e33be..8f48c05 100644 --- a/apps/explorer/lib/explorer/account/notifier/email.ex +++ b/apps/explorer/lib/explorer/account/notifier/email.ex @@ -35,7 +35,9 @@ defmodule Explorer.Account.Notifier.Email do |> add_dynamic_field("block_number", notification.block_number) |> add_dynamic_field("amount", amount(notification)) |> add_dynamic_field("name", notification.name) - |> add_dynamic_field("tx_fee", notification.tx_fee) + # todo: keep next line for compatibility with old version of SendGrid template. Remove it when the changes released and Sendgrid template updated. + |> add_dynamic_field("tx_fee", notification.transaction_fee) + |> add_dynamic_field("transaction_fee", notification.transaction_fee) |> add_dynamic_field("direction", direction(notification)) |> add_dynamic_field("method", notification.method) |> add_dynamic_field("transaction_url", transaction_url(notification)) diff --git a/apps/explorer/lib/explorer/account/notifier/notify.ex b/apps/explorer/lib/explorer/account/notifier/notify.ex index 3ad9e04..1214b86 100644 --- a/apps/explorer/lib/explorer/account/notifier/notify.ex +++ b/apps/explorer/lib/explorer/account/notifier/notify.ex @@ -129,7 +129,7 @@ defmodule Explorer.Account.Notifier.Notify do block_number: summary.block_number, amount: summary.amount, subject: summary.subject, - tx_fee: summary.tx_fee, + transaction_fee: summary.transaction_fee, name: summary.name, type: summary.type, from_address_hash_hash: hash_to_lower_case_string(summary.from_address_hash), diff --git a/apps/explorer/lib/explorer/account/notifier/summary.ex b/apps/explorer/lib/explorer/account/notifier/summary.ex index 568c977..d6231cf 100644 --- a/apps/explorer/lib/explorer/account/notifier/summary.ex +++ b/apps/explorer/lib/explorer/account/notifier/summary.ex @@ -19,7 +19,7 @@ defmodule Explorer.Account.Notifier.Summary do :method, :block_number, :amount, - :tx_fee, + :transaction_fee, :name, :subject, :type @@ -81,7 +81,7 @@ defmodule Explorer.Account.Notifier.Summary do to_address_hash: transaction.to_address_hash, block_number: transaction.block_number, amount: amount(transaction), - tx_fee: fee(transaction), + transaction_fee: fee(transaction), name: Explorer.coin_name(), subject: "Coin transaction", type: "COIN" @@ -96,7 +96,7 @@ defmodule Explorer.Account.Notifier.Summary do to_address_hash: transaction.created_contract_address_hash, block_number: transaction.block_number, amount: amount(transaction), - tx_fee: fee(transaction), + transaction_fee: fee(transaction), name: Explorer.coin_name(), subject: "Contract creation", type: "COIN" @@ -121,7 +121,7 @@ defmodule Explorer.Account.Notifier.Summary do block_number: transfer.block_number, amount: amount(transfer), subject: transfer.token.type, - tx_fee: fee(transaction), + transaction_fee: fee(transaction), name: token_name(transfer), type: transfer.token.type } @@ -135,7 +135,7 @@ defmodule Explorer.Account.Notifier.Summary do to_address_hash: transfer.to_address_hash, block_number: transfer.block_number, subject: to_string(transfer.token_ids && List.first(transfer.token_ids)), - tx_fee: fee(transaction), + transaction_fee: fee(transaction), name: token_name(transfer), type: transfer.token.type } @@ -149,7 +149,7 @@ defmodule Explorer.Account.Notifier.Summary do to_address_hash: transfer.to_address_hash, block_number: transfer.block_number, subject: token_ids(transfer), - tx_fee: fee(transaction), + transaction_fee: fee(transaction), name: token_name(transfer), type: transfer.token.type } @@ -165,7 +165,7 @@ defmodule Explorer.Account.Notifier.Summary do to_address_hash: transfer.to_address_hash, block_number: transfer.block_number, subject: if(token_ids_string == "", do: transfer.token.type, else: token_ids_string), - tx_fee: fee(transaction), + transaction_fee: fee(transaction), name: token_name(transfer), type: transfer.token.type } diff --git a/apps/explorer/lib/explorer/account/public_tags_request.ex b/apps/explorer/lib/explorer/account/public_tags_request.ex index 8cdf5e5..9d38c07 100644 --- a/apps/explorer/lib/explorer/account/public_tags_request.ex +++ b/apps/explorer/lib/explorer/account/public_tags_request.ex @@ -4,10 +4,10 @@ defmodule Explorer.Account.PublicTagsRequest do """ use Explorer.Schema - alias Ecto.Changeset + alias Ecto.{Changeset, Multi} alias Explorer.Account.Identity alias Explorer.Chain.Hash - alias Explorer.Repo + alias Explorer.{Helper, Repo} alias Explorer.ThirdPartyIntegrations.AirTable import Ecto.Changeset @@ -44,9 +44,7 @@ defmodule Explorer.Account.PublicTagsRequest do association_fields = request.__struct__.__schema__(:associations) waste_fields = association_fields ++ @local_fields - network = - Application.get_env(:block_scout_web, BlockScoutWeb.Endpoint)[:url][:host] <> - Application.get_env(:block_scout_web, BlockScoutWeb.Endpoint)[:url][:path] + network = Helper.get_app_host() <> Application.get_env(:block_scout_web, BlockScoutWeb.Endpoint)[:url][:path] request |> Map.from_struct() |> Map.drop(waste_fields) |> Map.put(:network, network) end @@ -252,4 +250,30 @@ defmodule Explorer.Account.PublicTagsRequest do end def get_max_public_tags_request_count, do: @max_public_tags_request_per_account + + @doc """ + Merges public tags requests from multiple identities into a primary identity. + + This function updates the `identity_id` of all public tags requests belonging to the + identities specified in `ids_to_merge` to the `primary_id`. It's designed to + be used as part of an Ecto.Multi transaction. + + ## Parameters + - `multi`: An Ecto.Multi struct to which this operation will be added. + - `primary_id`: The ID of the primary identity that will own the merged keys. + - `ids_to_merge`: A list of identity IDs whose public tags requests will be merged. + + ## Returns + - An updated Ecto.Multi struct with the merge operation added. + """ + @spec merge(Multi.t(), integer(), [integer()]) :: Multi.t() + def merge(multi, primary_id, ids_to_merge) do + Multi.run(multi, :merge_public_tags_requests, fn repo, _ -> + {:ok, + repo.update_all( + from(key in __MODULE__, where: key.identity_id in ^ids_to_merge), + set: [identity_id: primary_id] + )} + end) + end end diff --git a/apps/explorer/lib/explorer/account/tag_address.ex b/apps/explorer/lib/explorer/account/tag_address.ex index 87d8c40..c89bfb9 100644 --- a/apps/explorer/lib/explorer/account/tag_address.ex +++ b/apps/explorer/lib/explorer/account/tag_address.ex @@ -1,13 +1,13 @@ defmodule Explorer.Account.TagAddress do @moduledoc """ - Watchlist is root entity for WatchlistAddresses + User created custom address tags. """ use Explorer.Schema import Ecto.Changeset - alias Ecto.Changeset + alias Ecto.{Changeset, Multi} alias Explorer.Account.Identity alias Explorer.{Chain, PagingOptions, Repo} alias Explorer.Chain.{Address, Hash} @@ -18,6 +18,7 @@ defmodule Explorer.Account.TagAddress do field(:address_hash_hash, Cloak.Ecto.SHA256) :: binary() | nil field(:name, Explorer.Encrypted.Binary, null: false) field(:address_hash, Explorer.Encrypted.AddressHash, null: false) + field(:user_created, :boolean, null: false, default: true) belongs_to(:identity, Identity, null: false) @@ -177,4 +178,30 @@ defmodule Explorer.Account.TagAddress do end def get_max_tags_count, do: Application.get_env(:explorer, Explorer.Account)[:private_tags_limit] + + @doc """ + Merges address tags from multiple identities into a primary identity. + + This function updates the `identity_id` of all address tags belonging to the + identities specified in `ids_to_merge` to the `primary_id`. It's designed to + be used as part of an Ecto.Multi transaction. + + ## Parameters + - `multi`: An Ecto.Multi struct to which this operation will be added. + - `primary_id`: The ID of the primary identity that will own the merged keys. + - `ids_to_merge`: A list of identity IDs whose address tags will be merged. + + ## Returns + - An updated Ecto.Multi struct with the merge operation added. + """ + @spec merge(Multi.t(), integer(), [integer()]) :: Multi.t() + def merge(multi, primary_id, ids_to_merge) do + Multi.run(multi, :merge_tag_addresses, fn repo, _ -> + {:ok, + repo.update_all( + from(key in __MODULE__, where: key.identity_id in ^ids_to_merge), + set: [identity_id: primary_id, user_created: false] + )} + end) + end end diff --git a/apps/explorer/lib/explorer/account/tag_transaction.ex b/apps/explorer/lib/explorer/account/tag_transaction.ex index a11b770..24b14fd 100644 --- a/apps/explorer/lib/explorer/account/tag_transaction.ex +++ b/apps/explorer/lib/explorer/account/tag_transaction.ex @@ -7,22 +7,24 @@ defmodule Explorer.Account.TagTransaction do import Ecto.Changeset - alias Ecto.Changeset + alias Ecto.{Changeset, Multi} alias Explorer.Account.Identity alias Explorer.{Chain, PagingOptions, Repo} + alias Explorer.Chain.Hash import Explorer.Chain, only: [hash_to_lower_case_string: 1] typed_schema "account_tag_transactions" do - field(:tx_hash_hash, Cloak.Ecto.SHA256) :: binary() | nil + field(:transaction_hash_hash, Cloak.Ecto.SHA256) :: binary() | nil field(:name, Explorer.Encrypted.Binary, null: false) - field(:tx_hash, Explorer.Encrypted.TransactionHash, null: false) + field(:transaction_hash, Explorer.Encrypted.TransactionHash, null: false) + field(:user_created, :boolean, null: false, default: true) belongs_to(:identity, Identity, null: false) timestamps() end - @attrs ~w(name identity_id tx_hash)a + @attrs ~w(name identity_id transaction_hash)a def changeset do %__MODULE__{} @@ -36,7 +38,7 @@ defmodule Explorer.Account.TagTransaction do |> validate_required(@attrs, message: "Required") |> validate_length(:name, min: 1, max: 35) |> put_hashed_fields() - |> unique_constraint([:identity_id, :tx_hash_hash], message: "Transaction tag already exists") + |> unique_constraint([:identity_id, :transaction_hash_hash], message: "Transaction tag already exists") |> tag_transaction_count_constraint() |> check_transaction_existence() end @@ -50,20 +52,20 @@ defmodule Explorer.Account.TagTransaction do defp put_hashed_fields(changeset) do # Using force_change instead of put_change due to https://github.com/danielberkompas/cloak_ecto/issues/53 changeset - |> force_change(:tx_hash_hash, hash_to_lower_case_string(get_field(changeset, :tx_hash))) + |> force_change(:transaction_hash_hash, hash_to_lower_case_string(get_field(changeset, :transaction_hash))) end - defp check_transaction_existence(%Changeset{changes: %{tx_hash: tx_hash}} = changeset) do - check_transaction_existence_inner(changeset, tx_hash) + defp check_transaction_existence(%Changeset{changes: %{transaction_hash: transaction_hash}} = changeset) do + check_transaction_existence_inner(changeset, transaction_hash) end defp check_transaction_existence(changeset), do: changeset - defp check_transaction_existence_inner(changeset, tx_hash) do - if match?({:ok, _}, Chain.hash_to_transaction(tx_hash)) do + defp check_transaction_existence_inner(changeset, transaction_hash) do + if match?({:ok, _}, Chain.hash_to_transaction(transaction_hash)) do changeset else - add_error(changeset, :tx_hash, "Transaction does not exist") + add_error(changeset, :transaction_hash, "Transaction does not exist") end end @@ -122,20 +124,29 @@ defmodule Explorer.Account.TagTransaction do 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 - __MODULE__ - |> where([tag], tag.identity_id == ^identity_id and tag.tx_hash == ^tx_hash) - end + @doc """ + Retrieves tag transactions for a given transaction hash and identity ID. - def tag_transaction_by_transaction_hash_and_identity_id_query(_, _), do: nil + This function queries the database for all tag transactions that match both + the provided transaction hash and identity ID. - def get_tag_transaction_by_transaction_hash_and_identity_id(tx_hash, identity_id) - when not is_nil(tx_hash) and not is_nil(identity_id) do - tx_hash - |> hash_to_lower_case_string() - |> tag_transaction_by_transaction_hash_and_identity_id_query(identity_id) - |> Repo.account_repo().one() + ## Parameters + - `transaction_hash`: The transaction hash to search for. Can be a `String.t()`, + `Explorer.Chain.Hash.Full.t()`, or `nil`. + - `identity_id`: The identity ID to search for. Can be an `integer()` or `nil`. + + ## Returns + - A list of `Explorer.Account.TagTransaction` structs if matching records are found. + - `nil` if either `transaction_hash` or `identity_id` is `nil`. + """ + @spec get_tag_transaction_by_transaction_hash_and_identity_id(String.t() | Hash.Full.t() | nil, integer() | nil) :: + [__MODULE__.t()] | nil + def get_tag_transaction_by_transaction_hash_and_identity_id(transaction_hash, identity_id) + when not is_nil(transaction_hash) and not is_nil(identity_id) do + query = + from(tag in __MODULE__, where: tag.transaction_hash_hash == ^transaction_hash and tag.identity_id == ^identity_id) + + Repo.account_repo().all(query) end def get_tag_transaction_by_transaction_hash_and_identity_id(_, _), do: nil @@ -176,10 +187,36 @@ defmodule Explorer.Account.TagTransaction do end def get_max_tags_count, do: Application.get_env(:explorer, Explorer.Account)[:private_tags_limit] + + @doc """ + Merges transaction tags from multiple identities into a primary identity. + + This function updates the `identity_id` of all transaction tags belonging to the + identities specified in `ids_to_merge` to the `primary_id`. It's designed to + be used as part of an Ecto.Multi transaction. + + ## Parameters + - `multi`: An Ecto.Multi struct to which this operation will be added. + - `primary_id`: The ID of the primary identity that will own the merged keys. + - `ids_to_merge`: A list of identity IDs whose transaction tags will be merged. + + ## Returns + - An updated Ecto.Multi struct with the merge operation added. + """ + @spec merge(Multi.t(), integer(), [integer()]) :: Multi.t() + def merge(multi, primary_id, ids_to_merge) do + Multi.run(multi, :merge_tag_transactions, fn repo, _ -> + {:ok, + repo.update_all( + from(key in __MODULE__, where: key.identity_id in ^ids_to_merge), + set: [identity_id: primary_id, user_created: false] + )} + end) + end end defimpl Jason.Encoder, for: Explorer.Account.TagTransaction do - def encode(tx_tag, opts) do - Jason.Encode.string(tx_tag.name, opts) + def encode(transaction_tag, opts) do + Jason.Encode.string(transaction_tag.name, opts) end end diff --git a/apps/explorer/lib/explorer/account/watchlist.ex b/apps/explorer/lib/explorer/account/watchlist.ex index 0130ac6..86bc814 100644 --- a/apps/explorer/lib/explorer/account/watchlist.ex +++ b/apps/explorer/lib/explorer/account/watchlist.ex @@ -7,6 +7,7 @@ defmodule Explorer.Account.Watchlist do import Ecto.Changeset + alias Ecto.Multi alias Explorer.Account.{Identity, WatchlistAddress} @derive {Jason.Encoder, only: [:name, :watchlist_addresses]} @@ -24,4 +25,39 @@ defmodule Explorer.Account.Watchlist do |> cast(attrs, [:name]) |> validate_required([:name]) end + + @doc """ + Acquires data for merging from the database. + + This function is used to fetch data from the database in preparation for a merge operation. + It retrieves both the primary watchlist and the watchlists to be merged. + + ## Parameters + + * `multi` - An `Ecto.Multi` struct representing the current multi-operation transaction. + * `primary_id` - An integer representing the ID of the primary identity. + * `ids_to_merge` - A list of integers representing the IDs of the identities to be merged. + + ## Returns + + Returns an updated `Ecto.Multi` struct with two additional operations: + + * `:acquire_primary_watchlist` - Fetches the watchlists associated with the primary identity. + * `:acquire_watchlists_to_merge` - Fetches the watchlists associated with the identities to be merged. + + ## Notes + + This function is typically used as part of a larger transaction process for merging watchlists. + It prepares the data needed for the merge without actually performing the merge operation. + """ + @spec acquire_for_merge(Multi.t(), integer(), [integer()]) :: Multi.t() + def acquire_for_merge(multi, primary_id, ids_to_merge) do + multi + |> Multi.run(:acquire_primary_watchlist, fn repo, _ -> + {:ok, repo.all(from(watchlist in __MODULE__, where: watchlist.identity_id == ^primary_id))} + end) + |> Multi.run(:acquire_watchlists_to_merge, fn repo, _ -> + {:ok, repo.all(from(watchlist in __MODULE__, where: watchlist.identity_id in ^ids_to_merge))} + end) + end end diff --git a/apps/explorer/lib/explorer/account/watchlist_address.ex b/apps/explorer/lib/explorer/account/watchlist_address.ex index 14302eb..1dddc02 100644 --- a/apps/explorer/lib/explorer/account/watchlist_address.ex +++ b/apps/explorer/lib/explorer/account/watchlist_address.ex @@ -7,7 +7,7 @@ defmodule Explorer.Account.WatchlistAddress do import Ecto.Changeset - alias Ecto.Changeset + alias Ecto.{Changeset, Multi} alias Explorer.Account.Notifier.ForbiddenAddress alias Explorer.Account.Watchlist alias Explorer.{Chain, PagingOptions, Repo} @@ -19,6 +19,7 @@ defmodule Explorer.Account.WatchlistAddress do field(:address_hash_hash, Cloak.Ecto.SHA256) :: binary() | nil field(:name, Explorer.Encrypted.Binary, null: false) field(:address_hash, Explorer.Encrypted.AddressHash, null: false) + field(:user_created, :boolean, null: false, default: true) belongs_to(:watchlist, Watchlist, null: false) @@ -112,9 +113,6 @@ defmodule Explorer.Account.WatchlistAddress do else {:error, reason} -> add_error(changeset, :address_hash, reason) - - _ -> - add_error(changeset, :address_hash, "Address error") end end @@ -212,4 +210,54 @@ defmodule Explorer.Account.WatchlistAddress do end def preload_address_fetched_coin_balance(watchlist), do: watchlist + + @doc """ + Merges watchlist addresses into a primary watchlist. + + This function is used to merge multiple watchlists into a single primary watchlist. It updates + the `watchlist_id` of all addresses belonging to the watchlists being merged to point to the + primary watchlist. + + ## Parameters + + * `multi` - An `Ecto.Multi` struct representing the current multi-operation transaction. + + ## Returns + + Returns an updated `Ecto.Multi` struct with an additional `:merge_watchlist_addresses` operation. + + ## Operation Details + + The function adds a `:merge_watchlist_addresses` operation to the `Ecto.Multi` struct. This operation: + + 1. Identifies the primary watchlist and the watchlists to be merged from the results of previous operations. + 2. Updates all watchlist addresses associated with the watchlists being merged: + - Sets their `watchlist_id` to the ID of the primary watchlist. + - Sets their `user_created` flag to `false`. + + ## Notes + + - This function assumes that the `Explorer.Account.Watchlist.acquire_for_merge/3` function has been called previously in the + `Ecto.Multi` chain to provide the necessary data for the merge operation. + - After this operation, all addresses from the merged watchlists will be associated with the + primary watchlist, and their `user_created` status will be set to `false`. + """ + @spec merge(Multi.t()) :: Multi.t() + def merge(multi) do + multi + |> Multi.run(:merge_watchlist_addresses, fn repo, + %{ + acquire_primary_watchlist: [primary_watchlist | _], + acquire_watchlists_to_merge: watchlists_to_merge + } -> + primary_watchlist_id = primary_watchlist.id + watchlists_to_merge_ids = Enum.map(watchlists_to_merge, & &1.id) + + {:ok, + repo.update_all( + from(key in __MODULE__, where: key.watchlist_id in ^watchlists_to_merge_ids), + set: [watchlist_id: primary_watchlist_id, user_created: false] + )} + end) + end end diff --git a/apps/explorer/lib/explorer/account/watchlist_notification.ex b/apps/explorer/lib/explorer/account/watchlist_notification.ex index bf747c3..9d5c9d4 100644 --- a/apps/explorer/lib/explorer/account/watchlist_notification.ex +++ b/apps/explorer/lib/explorer/account/watchlist_notification.ex @@ -9,6 +9,7 @@ defmodule Explorer.Account.WatchlistNotification do import Ecto.Changeset import Explorer.Chain, only: [hash_to_lower_case_string: 1] + alias Ecto.Multi alias Explorer.Repo alias Explorer.Account.{Watchlist, WatchlistAddress} @@ -17,7 +18,7 @@ defmodule Explorer.Account.WatchlistNotification do field(:block_number, :integer, null: false) field(:direction, :string, null: false) field(:method, :string, null: false) - field(:tx_fee, :decimal, null: false) + field(:transaction_fee, :decimal, null: false) field(:type, :string, null: false) field(:viewed_at, :integer, null: false) field(:name, Explorer.Encrypted.Binary, null: false) @@ -41,7 +42,18 @@ defmodule Explorer.Account.WatchlistNotification do @doc false def changeset(watchlist_notifications, attrs) do watchlist_notifications - |> cast(attrs, [:amount, :direction, :name, :type, :method, :block_number, :tx_fee, :value, :decimals, :viewed_at]) + |> cast(attrs, [ + :amount, + :direction, + :name, + :type, + :method, + :block_number, + :transaction_fee, + :value, + :decimals, + :viewed_at + ]) |> validate_required([ :amount, :direction, @@ -49,7 +61,7 @@ defmodule Explorer.Account.WatchlistNotification do :type, :method, :block_number, - :tx_fee, + :transaction_fee, :value, :decimals, :viewed_at @@ -84,4 +96,56 @@ defmodule Explorer.Account.WatchlistNotification do defp watchlist_notification_30_days_limit do Application.get_env(:explorer, Explorer.Account)[:notifications_limit_for_30_days] end + + @doc """ + Merges watchlist notifications into a primary watchlist. + + This function is used to merge notifications from multiple watchlists into a single primary watchlist. + It updates the `watchlist_id` of all notifications belonging to the watchlists being merged to point + to the primary watchlist. + + ## Parameters + + * `multi` - An `Ecto.Multi` struct representing the current multi-operation transaction. + + ## Returns + + Returns an updated `Ecto.Multi` struct with an additional `:merge_watchlist_notifications` operation. + + ## Operation Details + + The function adds a `:merge_watchlist_notifications` operation to the `Ecto.Multi` struct. This operation: + + 1. Identifies the primary watchlist and the watchlists to be merged from the results of previous operations. + 2. Updates all notifications associated with the watchlists being merged: + - Sets their `watchlist_id` to the ID of the primary watchlist. + + + ## Notes + + - This function assumes that the `Explorer.Account.Watchlist.acquire_for_merge/3` function has been called previously in the + `Ecto.Multi` chain to provide the necessary data for the merge operation. + - After this operation, all notifications from the merged watchlists will be associated with the + primary watchlist. + - This function is typically used as part of a larger watchlist merging process, which may include + merging other related data such as watchlist addresses. + """ + @spec merge(Multi.t()) :: Multi.t() + def merge(multi) do + multi + |> Multi.run(:merge_watchlist_notifications, fn repo, + %{ + acquire_primary_watchlist: [primary_watchlist | _], + acquire_watchlists_to_merge: watchlists_to_merge + } -> + primary_watchlist_id = primary_watchlist.id + watchlists_to_merge_ids = Enum.map(watchlists_to_merge, & &1.id) + + {:ok, + repo.update_all( + from(notification in __MODULE__, where: notification.watchlist_id in ^watchlists_to_merge_ids), + set: [watchlist_id: primary_watchlist_id] + )} + end) + end end diff --git a/apps/explorer/lib/explorer/application.ex b/apps/explorer/lib/explorer/application.ex index 2e7638c..33aaed8 100644 --- a/apps/explorer/lib/explorer/application.ex +++ b/apps/explorer/lib/explorer/application.ex @@ -142,8 +142,21 @@ defmodule Explorer.Application do configure(Explorer.Migrator.TransactionBlockConsensus), configure(Explorer.Migrator.TokenTransferBlockConsensus), configure(Explorer.Migrator.RestoreOmittedWETHTransfers), + configure(Explorer.Migrator.FilecoinPendingAddressOperations), + configure_mode_dependent_process(Explorer.Migrator.ShrinkInternalTransactions, :indexer), + configure_chain_type_dependent_process(Explorer.Chain.Cache.BlackfortValidatorsCounters, :blackfort), configure_chain_type_dependent_process(Explorer.Chain.Cache.StabilityValidatorsCounters, :stability), - configure(Explorer.Migrator.ShrinkInternalTransactions) + Explorer.Migrator.SanitizeDuplicatedLogIndexLogs + |> configure() + |> configure_chain_type_dependent_process([ + :polygon_zkevm, + :rsk, + :filecoin + ]), + configure_mode_dependent_process(Explorer.Migrator.SanitizeMissingTokenBalances, :indexer), + configure_mode_dependent_process(Explorer.Migrator.SanitizeReplacedTransactions, :indexer), + configure_mode_dependent_process(Explorer.Migrator.ReindexInternalTransactionsWithIncompatibleStatus, :indexer), + Explorer.Migrator.RefetchContractCodes |> configure() |> configure_chain_type_dependent_process(:zksync) ] |> List.flatten() @@ -157,6 +170,7 @@ defmodule Explorer.Application do Explorer.Repo.Optimism, Explorer.Repo.PolygonEdge, Explorer.Repo.PolygonZkevm, + Explorer.Repo.Scroll, Explorer.Repo.ZkSync, Explorer.Repo.Celo, Explorer.Repo.RSK, @@ -166,7 +180,8 @@ defmodule Explorer.Application do Explorer.Repo.BridgedTokens, Explorer.Repo.Filecoin, Explorer.Repo.Stability, - Explorer.Repo.ShrunkInternalTransactions + Explorer.Repo.ShrunkInternalTransactions, + Explorer.Repo.Blackfort ] else [] @@ -174,7 +189,7 @@ defmodule Explorer.Application do end defp account_repo do - if System.get_env("ACCOUNT_DATABASE_URL") || Mix.env() == :test do + if Application.get_env(:explorer, Explorer.Account)[:enabled] || Mix.env() == :test do [Explorer.Repo.Account] else [] @@ -201,6 +216,14 @@ defmodule Explorer.Application do end end + defp configure_chain_type_dependent_process(process, chain_types) when is_list(chain_types) do + if Application.get_env(:explorer, :chain_type) in chain_types do + process + else + [] + end + end + defp configure_chain_type_dependent_process(process, chain_type) do if Application.get_env(:explorer, :chain_type) == chain_type do process @@ -209,6 +232,14 @@ defmodule Explorer.Application do end end + defp configure_mode_dependent_process(process, mode) do + if should_start?(process) and Application.get_env(:explorer, :mode) in [mode, :all] do + process + else + [] + end + end + defp sc_microservice_configure(process) do if Application.get_env(:explorer, Explorer.SmartContract.RustVerifierInterfaceBehaviour)[:eth_bytecode_db?] do process diff --git a/apps/indexer/lib/indexer/bound_queue.ex b/apps/explorer/lib/explorer/bound_queue.ex similarity index 99% rename from apps/indexer/lib/indexer/bound_queue.ex rename to apps/explorer/lib/explorer/bound_queue.ex index 44c1f13..daa44de 100644 --- a/apps/indexer/lib/indexer/bound_queue.ex +++ b/apps/explorer/lib/explorer/bound_queue.ex @@ -1,4 +1,4 @@ -defmodule Indexer.BoundQueue do +defmodule Explorer.BoundQueue do @moduledoc """ A queue that tracks its size and can have its size bound to a maximum. """ diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index 86f893d..34bbda7 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -260,6 +260,7 @@ defmodule Explorer.Chain do defp common_where_limit_order(query, paging_options) do query |> InternalTransaction.where_is_different_from_parent_transaction() + # todo: replace `index_int_tx_desc_order` with `index_internal_transaction_desc_order` in the next line when new frontend is bound to `index_internal_transaction_desc_order` property |> page_internal_transaction(paging_options, %{index_int_tx_desc_order: true}) |> limit(^paging_options.page_size) |> order_by( @@ -289,7 +290,7 @@ defmodule Explorer.Chain do |> 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.put_has_token_transfers_to_transaction(false) |> Transaction.matching_address_queries_list(direction, address_hashes) |> Enum.map(fn query -> Task.async(fn -> select_repo(options).all(query) end) end) end @@ -320,8 +321,8 @@ defmodule Explorer.Chain do filters = Keyword.get(options, :token_type) necessity_by_association = Keyword.get(options, :necessity_by_association) - direction - |> TokenTransfer.token_transfers_by_address_hash(address_hash, filters, paging_options) + address_hash + |> TokenTransfer.token_transfers_by_address_hash(direction, filters, paging_options) |> join_associations(necessity_by_association) |> select_repo(options).all() end @@ -572,15 +573,17 @@ defmodule Explorer.Chain do |> fetch_transactions_in_ascending_order_by_index() |> join(:inner, [transaction], block in assoc(transaction, :block)) |> where([_, block], block.hash == ^block_hash) - |> apply_filter_by_tx_type_to_transactions(type_filter) + |> apply_filter_by_type_to_transactions(type_filter) |> join_associations(necessity_by_association) - |> Transaction.put_has_token_transfers_to_tx(old_ui?) + |> Transaction.put_has_token_transfers_to_transaction(old_ui?) |> (&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) + Enum.map(&1, fn transaction -> + preload_token_transfers(transaction, @token_transfers_necessity_by_association, options) + end) )).() end @@ -594,10 +597,12 @@ defmodule Explorer.Chain do |> 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) + |> Transaction.put_has_token_transfers_to_transaction(false) |> (& &1).() |> select_repo(options).all() - |> (&Enum.map(&1, fn tx -> preload_token_transfers(tx, @token_transfers_necessity_by_association, options) end)).() + |> (&Enum.map(&1, fn transaction -> + preload_token_transfers(transaction, @token_transfers_necessity_by_association, options) + end)).() end @spec block_to_withdrawals( @@ -616,15 +621,15 @@ defmodule Explorer.Chain do end @doc """ - Finds sum of gas_used for new (EIP-1559) txs belongs to block + Finds sum of gas_used for new (EIP-1559) transactions belongs to block """ - @spec block_to_gas_used_by_1559_txs(Hash.Full.t()) :: non_neg_integer() - def block_to_gas_used_by_1559_txs(block_hash) do + @spec block_to_gas_used_by_1559_transactions(Hash.Full.t()) :: non_neg_integer() + def block_to_gas_used_by_1559_transactions(block_hash) do query = from( - tx in Transaction, - where: tx.block_hash == ^block_hash, - select: sum(tx.gas_used) + transaction in Transaction, + where: transaction.block_hash == ^block_hash, + select: sum(transaction.gas_used) ) result = Repo.one(query) @@ -632,18 +637,18 @@ defmodule Explorer.Chain do end @doc """ - Finds sum of priority fee for new (EIP-1559) txs belongs to block + Finds sum of priority fee for new (EIP-1559) transactions belongs to block """ - @spec block_to_priority_fee_of_1559_txs(Hash.Full.t()) :: Decimal.t() - def block_to_priority_fee_of_1559_txs(block_hash) do + @spec block_to_priority_fee_of_1559_transactions(Hash.Full.t()) :: Decimal.t() + def block_to_priority_fee_of_1559_transactions(block_hash) do block = Repo.get_by(Block, hash: block_hash) case block.base_fee_per_gas do %Wei{value: base_fee_per_gas} -> query = from( - tx in Transaction, - where: tx.block_hash == ^block_hash, + transaction in Transaction, + where: transaction.block_hash == ^block_hash, select: sum( fragment( @@ -651,20 +656,20 @@ defmodule Explorer.Chain do WHEN COALESCE(?,?) = 0 THEN 0 WHEN COALESCE(?,?) - ? < COALESCE(?,?) THEN (COALESCE(?,?) - ?) * ? ELSE COALESCE(?,?) * ? END", - tx.max_fee_per_gas, - tx.gas_price, - tx.max_fee_per_gas, - tx.gas_price, + transaction.max_fee_per_gas, + transaction.gas_price, + transaction.max_fee_per_gas, + transaction.gas_price, ^base_fee_per_gas, - tx.max_priority_fee_per_gas, - tx.gas_price, - tx.max_fee_per_gas, - tx.gas_price, + transaction.max_priority_fee_per_gas, + transaction.gas_price, + transaction.max_fee_per_gas, + transaction.gas_price, ^base_fee_per_gas, - tx.gas_used, - tx.max_priority_fee_per_gas, - tx.gas_price, - tx.gas_used + transaction.gas_used, + transaction.max_priority_fee_per_gas, + transaction.gas_price, + transaction.gas_used ) ) ) @@ -744,37 +749,6 @@ defmodule Explorer.Chain do def confirmations(nil, _), do: {:error, :pending} - @doc """ - Creates an address. - - iex> {:ok, %Explorer.Chain.Address{hash: hash}} = Explorer.Chain.create_address( - ...> %{hash: "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b"} - ...> ) - ...> to_string(hash) - "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b" - - A `String.t/0` value for `Explorer.Chain.Address.t/0` `hash` must have 40 hexadecimal characters after the `0x` prefix - to prevent short- and long-hash transcription errors. - - iex> {:error, %Ecto.Changeset{errors: errors}} = Explorer.Chain.create_address( - ...> %{hash: "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0"} - ...> ) - ...> errors - [hash: {"is invalid", [type: Explorer.Chain.Hash.Address, validation: :cast]}] - iex> {:error, %Ecto.Changeset{errors: errors}} = Explorer.Chain.create_address( - ...> %{hash: "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0ba"} - ...> ) - ...> errors - [hash: {"is invalid", [type: Explorer.Chain.Hash.Address, validation: :cast]}] - - """ - @spec create_address(map()) :: {:ok, Address.t()} | {:error, Ecto.Changeset.t()} - def create_address(attrs \\ %{}) do - %Address{} - |> Address.changeset(attrs) - |> Repo.insert() - end - @doc """ Creates a decompiled smart contract. """ @@ -941,7 +915,7 @@ defmodule Explorer.Chain do Returns `{:ok, %Explorer.Chain.Address{}}` if found - iex> {:ok, %Explorer.Chain.Address{hash: hash}} = Explorer.Chain.create_address( + iex> {:ok, %Explorer.Chain.Address{hash: hash}} = Explorer.Chain.Address.create( ...> %{hash: "0x5aaeb6053f3e94c9b9a09f33669435e7ef1beaed"} ...> ) iex> {:ok, %Explorer.Chain.Address{hash: found_hash}} = Explorer.Chain.hash_to_address(hash) @@ -1040,7 +1014,7 @@ defmodule Explorer.Chain do Returns `{:ok, %Explorer.Chain.Address{}}` if found - iex> {:ok, %Explorer.Chain.Address{hash: hash}} = Explorer.Chain.create_address( + iex> {:ok, %Explorer.Chain.Address{hash: hash}} = Explorer.Chain.Address.create( ...> %{hash: "0x5aaeb6053f3e94c9b9a09f33669435e7ef1beaed"} ...> ) iex> {:ok, %Explorer.Chain.Address{hash: found_hash}} = Explorer.Chain.hash_to_address(hash) @@ -1049,7 +1023,7 @@ defmodule Explorer.Chain do Returns `{:error, address}` if not found but created an address - iex> {:ok, %Explorer.Chain.Address{hash: hash}} = Explorer.Chain.create_address( + iex> {:ok, %Explorer.Chain.Address{hash: hash}} = Explorer.Chain.Address.create( ...> %{hash: "0x5aaeb6053f3e94c9b9a09f33669435e7ef1beaed"} ...> ) iex> {:ok, %Explorer.Chain.Address{hash: found_hash}} = Explorer.Chain.hash_to_address(hash) @@ -1086,7 +1060,7 @@ defmodule Explorer.Chain do {:ok, address} {:error, :not_found} -> - create_address(%{hash: to_string(hash)}) + Address.create(%{hash: to_string(hash)}) hash_to_address(hash, options, query_decompiled_code_flag) end end @@ -1098,7 +1072,11 @@ defmodule Explorer.Chain do """ @spec hashes_to_addresses([Hash.Address.t()], [necessity_by_association_option | api?]) :: [Address.t()] - def hashes_to_addresses(hashes, options \\ []) when is_list(hashes) do + def hashes_to_addresses(hashes, options \\ []) + + def hashes_to_addresses([], _), do: [] + + def hashes_to_addresses(hashes, options) when is_list(hashes) do necessity_by_association = Keyword.get(options, :necessity_by_association, %{}) query = @@ -1347,9 +1325,9 @@ defmodule Explorer.Chain do end end - # preload_to_detect_tt?: we don't need to preload more than one token transfer in case the tx inside the list (we don't show any token transfers on tx tile in new UI) + # preload_to_detect_tt?: we don't need to preload more than one token transfer in case the transaction inside the list (we don't show any token transfers on transaction tile in new UI) def preload_token_transfers( - %Transaction{hash: tx_hash, block_hash: block_hash} = transaction, + %Transaction{hash: transaction_hash, block_hash: block_hash} = transaction, necessity_by_association, options, preload_to_detect_tt? \\ true @@ -1362,12 +1340,12 @@ defmodule Explorer.Chain do token_transfers = TokenTransfer |> (&if(is_nil(block_hash), - do: where(&1, [token_transfer], token_transfer.transaction_hash == ^tx_hash), + do: where(&1, [token_transfer], token_transfer.transaction_hash == ^transaction_hash), else: where( &1, [token_transfer], - token_transfer.transaction_hash == ^tx_hash and token_transfer.block_hash == ^block_hash + token_transfer.transaction_hash == ^transaction_hash and token_transfer.block_hash == ^block_hash ) )).() |> limit(^limit) @@ -1504,9 +1482,9 @@ defmodule Explorer.Chain do full_blocks_range = max_saved_block_number - min_blockchain_trace_block_number - BlockNumberHelper.null_rounds_count() + 1 - processed_int_txs_for_blocks_count = max(0, full_blocks_range - pbo_count) + processed_int_transactions_for_blocks_count = max(0, full_blocks_range - pbo_count) - ratio = get_ratio(processed_int_txs_for_blocks_count, full_blocks_range) + ratio = get_ratio(processed_int_transactions_for_blocks_count, full_blocks_range) ratio |> (&if( @@ -1642,9 +1620,7 @@ defmodule Explorer.Chain do @doc """ Finds all `t:Explorer.Chain.Transaction.t/0` in the `t:Explorer.Chain.Block.t/0`. - ## Options - * `:necessity_by_association` - use to load `t:association/0` as `:required` or `:optional`. If an association is `:required`, and the `t:Explorer.Chain.Block.t/0` has no associated record for that association, then the `t:Explorer.Chain.Block.t/0` will not be included in the page `entries`. @@ -1652,7 +1628,6 @@ defmodule Explorer.Chain do `:key` (a tuple of the lowest/oldest `{block_number}`). Results will be the internal transactions older than the `block_number` that are passed. * ':block_type' - use to filter by type of block; Uncle`, `Reorg`, or `Block` (default). - """ @spec list_blocks_for_home([paging_options | necessity_by_association_option | api?]) :: [Block.t()] def list_blocks_for_home(options \\ []) when is_list(options) do @@ -1677,9 +1652,12 @@ defmodule Explorer.Chain do end end + def get_op_node_to_address, do: System.get_env("OP_NODE_TO_ADDRESS") || @op_node_to_address_hash + def get_op_node_from_address, do: System.get_env("OP_NODE_FROM_ADDRESS") || @op_node_from_address_hash + def transactions_available_for_home(page_size) do - {:ok, to_address_filter} = Chain.string_to_address_hash(@op_node_to_address_hash) - {:ok, from_address_filter} = Chain.string_to_address_hash(@op_node_from_address_hash) + {:ok, to_address_filter} = Chain.string_to_address_hash(get_op_node_to_address()) + {:ok, from_address_filter} = Chain.string_to_address_hash(get_op_node_from_address()) Transaction |> where([transaction], not is_nil(transaction.block_number) and not is_nil(transaction.index) @@ -1690,7 +1668,7 @@ defmodule Explorer.Chain do end defp block_from_cache(block_type, paging_options, necessity_by_association, options) do - case Blocks.take_enough(paging_options.page_size) do + case Blocks.atomic_take_enough(paging_options.page_size) do nil -> elements = fetch_blocks(block_type, paging_options, necessity_by_association, options) @@ -1704,7 +1682,7 @@ defmodule Explorer.Chain do end def uncles_from_cache(block_type, paging_options, necessity_by_association, options) do - case Uncles.take_enough(paging_options.page_size) do + case Uncles.atomic_take_enough(paging_options.page_size) do nil -> elements = fetch_blocks(block_type, paging_options, necessity_by_association, options) @@ -1951,12 +1929,14 @@ defmodule Explorer.Chain do when accumulator: term() def stream_blocks_with_unfetched_internal_transactions(initial, reducer, limited? \\ false) when is_function(reducer, 2) do + direction = Application.get_env(:indexer, :internal_transactions_fetch_order) + query = from( po in PendingBlockOperation, where: not is_nil(po.block_number), select: po.block_number, - order_by: [desc: po.block_number] + order_by: [{^direction, po.block_number}] ) query @@ -2157,11 +2137,11 @@ defmodule Explorer.Chain do {:error, :not_found} """ - @spec max_consensus_block_number() :: {:ok, Block.block_number()} | {:error, :not_found} - def max_consensus_block_number do + @spec max_consensus_block_number(Keyword.t()) :: {:ok, Block.block_number()} | {:error, :not_found} + def max_consensus_block_number(options \\ []) do Block |> where(consensus: true) - |> Repo.aggregate(:max, :number) + |> select_repo(options).aggregate(:max, :number) |> case do nil -> {:error, :not_found} number -> {:ok, number} @@ -2333,15 +2313,15 @@ defmodule Explorer.Chain do iex> insert(:block, number: 0) iex> insert(:block, number: 2) iex> insert(:block, number: 5) - iex> Explorer.Chain.missing_block_number_ranges(5..0) - [4..3, 1..1] + iex> Explorer.Chain.missing_block_number_ranges(5..0//-1) + [4..3//-1, 1..1] If only non-consensus blocks exist for a number, the number still counts as missing. iex> insert(:block, number: 0) iex> insert(:block, number: 1, consensus: false) iex> insert(:block, number: 2) - iex> Explorer.Chain.missing_block_number_ranges(2..0) + iex> Explorer.Chain.missing_block_number_ranges(2..0//-1) [1..1] if range starts with non-consensus block in the middle of the chain, it returns missing numbers. @@ -2363,7 +2343,7 @@ defmodule Explorer.Chain do @spec missing_block_number_ranges(Range.t()) :: [Range.t()] def missing_block_number_ranges(range) - def missing_block_number_ranges(range_start..range_end) do + def missing_block_number_ranges(range_start..range_end//_) do range_min = min(range_start, range_end) range_max = max(range_start, range_end) @@ -2524,8 +2504,6 @@ defmodule Explorer.Chain do @spec timestamp_to_block_number(DateTime.t(), :before | :after, boolean()) :: {:ok, Block.block_number()} | {:error, :not_found} def timestamp_to_block_number(given_timestamp, closest, from_api) do - {:ok, t} = Timex.format(given_timestamp, "%Y-%m-%d %H:%M:%S", :strftime) - consensus_blocks_query = from( block in Block, @@ -2535,7 +2513,7 @@ defmodule Explorer.Chain do gt_timestamp_query = from( block in consensus_blocks_query, - where: fragment("? >= TO_TIMESTAMP(?, 'YYYY-MM-DD HH24:MI:SS')", block.timestamp, ^t), + where: block.timestamp >= ^given_timestamp, order_by: [asc: block.timestamp], limit: 1, select: block @@ -2544,7 +2522,7 @@ defmodule Explorer.Chain do lt_timestamp_query = from( block in consensus_blocks_query, - where: fragment("? <= TO_TIMESTAMP(?, 'YYYY-MM-DD HH24:MI:SS')", block.timestamp, ^t), + where: block.timestamp <= ^given_timestamp, order_by: [desc: block.timestamp], limit: 1, select: block @@ -2556,8 +2534,7 @@ defmodule Explorer.Chain do from( block in subquery(union_query), select: block, - order_by: - fragment("abs(extract(epoch from (? - TO_TIMESTAMP(?, 'YYYY-MM-DD HH24:MI:SS'))))", block.timestamp, ^t), + order_by: fragment("abs(extract(epoch from (? - ?)))", block.timestamp, ^given_timestamp), limit: 1 ) @@ -2657,61 +2634,6 @@ defmodule Explorer.Chain do ) end - @doc """ - Returns the paged list of collated transactions that occurred recently from newest to oldest using `block_number` - and `index`. - - iex> newest_first_transactions = 50 |> insert_list(:transaction) |> with_block() |> Enum.reverse() - iex> oldest_seen = Enum.at(newest_first_transactions, 9) - iex> paging_options = %Explorer.PagingOptions{page_size: 10, key: {oldest_seen.block_number, oldest_seen.index}} - iex> recent_collated_transactions = Explorer.Chain.recent_collated_transactions_for_home(true, paging_options: paging_options) - iex> length(recent_collated_transactions) - 10 - iex> hd(recent_collated_transactions).hash == Enum.at(newest_first_transactions, 10).hash - true - - ## 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 list. - * `: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 recent_collated_transactions_for_home(true | false, [paging_options | necessity_by_association_option | api?]) :: [ - Transaction.t() - ] - def recent_collated_transactions_for_home(old_ui?, options \\ []) - when is_list(options) do - necessity_by_association = Keyword.get(options, :necessity_by_association, %{}) - paging_options = Keyword.get(options, :paging_options, @default_paging_options) - {:ok, to_address_filter} = Chain.string_to_address_hash(@op_node_to_address_hash) - {:ok, from_address_filter} = Chain.string_to_address_hash(@op_node_from_address_hash) - - case paging_options do - %PagingOptions{key: {0, 0}, is_index_in_asc_order: false} -> - [] - _ -> - paging_options - |> Transaction.fetch_transactions() - |> where([transaction], not is_nil(transaction.block_number) and not is_nil(transaction.index) - and transaction.to_address_hash != ^to_address_filter and transaction.from_address_hash != ^from_address_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?, - do: &1, - else: - Enum.map(&1, fn tx -> - preload_token_transfers(tx, @token_transfers_necessity_by_association, options) - end) - )).() - end - end - # RAP - random access pagination @spec recent_collated_transactions_for_rap([paging_options | necessity_by_association_option]) :: %{ :total_transactions_count => non_neg_integer(), @@ -2727,7 +2649,7 @@ defmodule Explorer.Chain do if is_nil(paging_options.key) or paging_options.page_number == 1 do paging_options.page_size |> Kernel.+(1) - |> Transactions.take_enough() + |> Transactions.atomic_take_enough() |> case do nil -> transactions = fetch_recent_collated_transactions_for_rap(paging_options, necessity_by_association) @@ -2746,6 +2668,45 @@ defmodule Explorer.Chain do def default_page_size, do: @default_page_size + @doc """ + Returns the paged list of collated transactions that occurred recently from newest to oldest using `block_number` + and `index`. + iex> newest_first_transactions = 50 |> insert_list(:transaction) |> with_block() |> Enum.reverse() + iex> oldest_seen = Enum.at(newest_first_transactions, 9) + iex> paging_options = %Explorer.PagingOptions{page_size: 10, key: {oldest_seen.block_number, oldest_seen.index}} + iex> recent_collated_transactions = Explorer.Chain.recent_collated_transactions_for_home(true, paging_options: paging_options) + iex> length(recent_collated_transactions) + 10 + iex> hd(recent_collated_transactions).hash == Enum.at(newest_first_transactions, 10).hash + true + ## 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 list. + * `: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 recent_collated_transactions_for_home(true | false, [paging_options | necessity_by_association_option | api?]) :: [ + Transaction.t() + ] + def recent_collated_transactions_for_home(old_ui?, options \\ []) + when is_list(options) do + necessity_by_association = Keyword.get(options, :necessity_by_association, %{}) + paging_options = Keyword.get(options, :paging_options, @default_paging_options) + method_id_filter = Keyword.get(options, :method) + type_filter = Keyword.get(options, :type) + + fetch_recent_collated_transactions_for_home( + old_ui?, + paging_options, + necessity_by_association, + method_id_filter, + type_filter, + options + ) + end + def fetch_recent_collated_transactions_for_rap(paging_options, necessity_by_association) do fetch_transactions_for_rap() |> where([transaction], not is_nil(transaction.block_number) and not is_nil(transaction.index)) @@ -2784,16 +2745,53 @@ defmodule Explorer.Chain do |> 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) + |> apply_filter_by_type_to_transactions(type_filter) + |> join_associations(necessity_by_association) + |> Transaction.put_has_token_transfers_to_transaction(old_ui?) + |> (&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 transaction -> + preload_token_transfers(transaction, @token_transfers_necessity_by_association, options) + end) + )).() + end + end + + def fetch_recent_collated_transactions_for_home( + old_ui?, + paging_options, + necessity_by_association, + method_id_filter, + type_filter, + options + ) do + + {:ok, to_address_filter} = Chain.string_to_address_hash(get_op_node_to_address()) + {:ok, from_address_filter} = Chain.string_to_address_hash(get_op_node_from_address()) + + case paging_options do + %PagingOptions{key: {0, 0}, is_index_in_asc_order: false} -> + [] + + _ -> + paging_options + |> Transaction.fetch_transactions() + |> where([transaction], not is_nil(transaction.block_number) and not is_nil(transaction.index) + and transaction.to_address_hash != ^to_address_filter and transaction.from_address_hash != ^from_address_filter) + |> apply_filter_by_method_id_to_transactions(method_id_filter) + |> apply_filter_by_type_to_transactions(type_filter) |> join_associations(necessity_by_association) - |> Transaction.put_has_token_transfers_to_tx(old_ui?) + |> Transaction.put_has_token_transfers_to_transaction(old_ui?) |> (&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) + Enum.map(&1, fn transaction -> + preload_token_transfers(transaction, @token_transfers_necessity_by_association, options) end) )).() end @@ -2838,26 +2836,30 @@ defmodule Explorer.Chain do |> limit(^paging_options.page_size) |> pending_transactions_query() |> apply_filter_by_method_id_to_transactions(method_id_filter) - |> apply_filter_by_tx_type_to_transactions(type_filter) + |> apply_filter_by_type_to_transactions(type_filter) |> order_by([transaction], desc: transaction.inserted_at, asc: transaction.hash) |> join_associations(necessity_by_association) |> (&if(old_ui?, do: preload(&1, [{:token_transfers, [:token, :from_address, :to_address]}]), else: &1)).() |> select_repo(options).all() end + @doc """ + Query to return all pending transactions + """ + @spec pending_transactions_query(Ecto.Queryable.t()) :: Ecto.Queryable.t() def pending_transactions_query(query) do from(transaction in query, where: is_nil(transaction.block_hash) and (is_nil(transaction.error) or transaction.error != "dropped/replaced") ) end + @doc """ + Returns pending transactions list from the DB + """ + @spec pending_transactions_list() :: Ecto.Schema.t() | term() def pending_transactions_list do - query = - from(transaction in Transaction, - where: is_nil(transaction.block_hash) and (is_nil(transaction.error) or transaction.error != "dropped/replaced") - ) - - query + Transaction + |> pending_transactions_query() |> Repo.all(timeout: :infinity) end @@ -3073,13 +3075,13 @@ defmodule Explorer.Chain do %Transaction{revert_reason: revert_reason} = transaction if revert_reason == nil do - fetch_tx_revert_reason(transaction) + fetch_transaction_revert_reason(transaction) else revert_reason end end - def fetch_tx_revert_reason(transaction) do + def fetch_transaction_revert_reason(transaction) do json_rpc_named_arguments = Application.get_env(:explorer, :json_rpc_named_arguments) hash_string = to_string(transaction.hash) @@ -3104,13 +3106,13 @@ defmodule Explorer.Chain do {:error, reason} -> Logger.error(fn -> - ["Error while fetching first trace for tx: #{hash_string} error reason: ", inspect(reason)] + ["Error while fetching first trace for transaction: #{hash_string} error reason: ", inspect(reason)] end) - fetch_tx_revert_reason_using_call(transaction) + fetch_transaction_revert_reason_using_call(transaction) :ignore -> - fetch_tx_revert_reason_using_call(transaction) + fetch_transaction_revert_reason_using_call(transaction) end if !is_nil(revert_reason) do @@ -3122,7 +3124,7 @@ defmodule Explorer.Chain do revert_reason end - defp fetch_tx_revert_reason_using_call(%Transaction{ + defp fetch_transaction_revert_reason_using_call(%Transaction{ block_number: block_number, to_address_hash: to_address_hash, from_address_hash: from_address_hash, @@ -3239,29 +3241,29 @@ defmodule Explorer.Chain do |> Data.to_string() end - def smart_contract_creation_tx_bytecode(address_hash) do - creation_tx_query = + def smart_contract_creation_transaction_bytecode(address_hash) do + creation_transaction_query = from( - tx in Transaction, + transaction in Transaction, left_join: a in Address, - on: tx.created_contract_address_hash == a.hash, - where: tx.created_contract_address_hash == ^address_hash, - where: tx.status == ^1, - select: %{init: tx.input, created_contract_code: a.contract_code}, - order_by: [desc: tx.block_number], + on: transaction.created_contract_address_hash == a.hash, + where: transaction.created_contract_address_hash == ^address_hash, + where: transaction.status == ^1, + select: %{init: transaction.input, created_contract_code: a.contract_code}, + order_by: [desc: transaction.block_number], limit: ^1 ) - tx_input = - creation_tx_query + transaction_input = + creation_transaction_query |> Repo.one() - if tx_input do - with %{init: input, created_contract_code: created_contract_code} <- tx_input do + if transaction_input do + with %{init: input, created_contract_code: created_contract_code} <- transaction_input do %{init: Data.to_string(input), created_contract_code: Data.to_string(created_contract_code)} end else - creation_int_tx_query = + creation_int_transaction_query = from( itx in InternalTransaction, join: t in assoc(itx, :transaction), @@ -3272,7 +3274,7 @@ defmodule Explorer.Chain do limit: ^1 ) - res = creation_int_tx_query |> Repo.one() + res = creation_int_transaction_query |> Repo.one() case res do %{init: init, created_contract_code: created_contract_code} -> @@ -3326,6 +3328,27 @@ defmodule Explorer.Chain do end end + @doc """ + Fetches contract creation input data from the transaction (not internal transaction). + """ + @spec contract_creation_input_data_from_transaction(String.t()) :: nil | binary() + def contract_creation_input_data_from_transaction(address_hash, options \\ []) do + transaction = + Transaction + |> where([transaction], transaction.created_contract_address_hash == ^address_hash) + |> select_repo(options).one() + + if transaction && transaction.input do + case Data.dump(transaction.input) do + {:ok, bytes} -> + bytes + + _ -> + nil + end + end + end + @doc """ Fetches contract creation input data. """ @@ -3527,33 +3550,61 @@ defmodule Explorer.Chain do where(query, [coin_balance], coin_balance.block_number < ^block_number) end + # todo: replace `index_int_tx_desc_order` with `index_internal_transaction_desc_order` in the next clause when new frontend is bound to `index_internal_transaction_desc_order` property def page_internal_transaction(_, _, _ \\ %{index_int_tx_desc_order: false}) def page_internal_transaction(query, %PagingOptions{key: nil}, _), do: query + # todo: keep next clause for compatibility with frontend and remove when new frontend is bound to `index_internal_transaction_desc_order` property + def page_internal_transaction(query, %PagingOptions{key: {block_number, transaction_index, index}}, %{ + index_int_tx_desc_order: desc_order + }) do + hardcoded_where_for_page_internal_transaction(query, block_number, transaction_index, index, desc_order) + end + def page_internal_transaction(query, %PagingOptions{key: {block_number, transaction_index, index}}, %{ - index_int_tx_desc_order: desc + index_internal_transaction_desc_order: desc_order }) do - hardcoded_where_for_page_int_tx(query, block_number, transaction_index, index, desc) + hardcoded_where_for_page_internal_transaction(query, block_number, transaction_index, index, desc_order) end - def page_internal_transaction(query, %PagingOptions{key: {0}}, %{index_int_tx_desc_order: desc}) do - if desc do + # todo: keep next clause for compatibility with frontend and remove when new frontend is bound to `index_internal_transaction_desc_order` property + def page_internal_transaction(query, %PagingOptions{key: {0}}, %{index_int_tx_desc_order: desc_order}) do + if desc_order do query else where(query, [internal_transaction], internal_transaction.index > 0) end end - def page_internal_transaction(query, %PagingOptions{key: {index}}, %{index_int_tx_desc_order: desc}) do - if desc do + def page_internal_transaction(query, %PagingOptions{key: {0}}, %{index_internal_transaction_desc_order: desc_order}) do + if desc_order do + query + else + where(query, [internal_transaction], internal_transaction.index > 0) + end + end + + # todo: keep next clause for compatibility with frontend and remove when new frontend is bound to `index_internal_transaction_desc_order` property + def page_internal_transaction(query, %PagingOptions{key: {index}}, %{index_int_tx_desc_order: desc_order}) do + if desc_order do + where(query, [internal_transaction], internal_transaction.index < ^index) + else + where(query, [internal_transaction], internal_transaction.index > ^index) + end + end + + def page_internal_transaction(query, %PagingOptions{key: {index}}, %{ + index_internal_transaction_desc_order: desc_order + }) do + if desc_order do where(query, [internal_transaction], internal_transaction.index < ^index) else where(query, [internal_transaction], internal_transaction.index > ^index) end end - defp hardcoded_where_for_page_int_tx(query, 0, 0, index, false), + defp hardcoded_where_for_page_internal_transaction(query, 0, 0, index, false), do: where( query, @@ -3562,7 +3613,7 @@ defmodule Explorer.Chain do internal_transaction.transaction_index == 0 and internal_transaction.index > ^index ) - defp hardcoded_where_for_page_int_tx(query, block_number, 0, index, false), + defp hardcoded_where_for_page_internal_transaction(query, block_number, 0, index, false), do: where( query, @@ -3572,7 +3623,7 @@ defmodule Explorer.Chain do internal_transaction.transaction_index == 0 and internal_transaction.index > ^index) ) - defp hardcoded_where_for_page_int_tx(query, block_number, transaction_index, index, false), + defp hardcoded_where_for_page_internal_transaction(query, block_number, transaction_index, index, false), do: where( query, @@ -3584,7 +3635,7 @@ defmodule Explorer.Chain do internal_transaction.transaction_index == ^transaction_index and internal_transaction.index > ^index) ) - defp hardcoded_where_for_page_int_tx(query, 0, 0, index, true), + defp hardcoded_where_for_page_internal_transaction(query, 0, 0, index, true), do: where( query, @@ -3593,7 +3644,7 @@ defmodule Explorer.Chain do internal_transaction.transaction_index == 0 and internal_transaction.index < ^index ) - defp hardcoded_where_for_page_int_tx(query, block_number, 0, 0, true), + defp hardcoded_where_for_page_internal_transaction(query, block_number, 0, 0, true), do: where( query, @@ -3601,7 +3652,7 @@ defmodule Explorer.Chain do internal_transaction.block_number < ^block_number ) - defp hardcoded_where_for_page_int_tx(query, block_number, 0, index, true), + defp hardcoded_where_for_page_internal_transaction(query, block_number, 0, index, true), do: where( query, @@ -3611,7 +3662,7 @@ defmodule Explorer.Chain do internal_transaction.transaction_index == 0 and internal_transaction.index < ^index) ) - defp hardcoded_where_for_page_int_tx(query, block_number, transaction_index, 0, true), + defp hardcoded_where_for_page_internal_transaction(query, block_number, transaction_index, 0, true), do: where( query, @@ -3621,7 +3672,7 @@ defmodule Explorer.Chain do internal_transaction.transaction_index < ^transaction_index) ) - defp hardcoded_where_for_page_int_tx(query, block_number, transaction_index, index, true), + defp hardcoded_where_for_page_internal_transaction(query, block_number, transaction_index, index, true), do: where( query, @@ -3775,6 +3826,7 @@ defmodule Explorer.Chain do from( token in Token, where: token.cataloged == false or is_nil(token.cataloged), + where: is_nil(token.skip_metadata) or token.skip_metadata == false, select: token.contract_address_hash ) @@ -3984,13 +4036,17 @@ defmodule Explorer.Chain do |> Enum.uniq() if Enum.empty?(filters) do - {:ok, []} + {0, []} else query = filters |> Enum.reduce(Transaction, fn {nonce, from_address}, query -> from(t in query, - or_where: t.nonce == ^nonce and t.from_address_hash == ^from_address and is_nil(t.block_hash) + or_where: + t.nonce == ^nonce and + t.from_address_hash == ^from_address and + is_nil(t.block_hash) and + (is_nil(t.error) or t.error != "dropped/replaced") ) end) # Enforce Transaction ShareLocks order (see docs: sharelocks.md) @@ -4286,18 +4342,20 @@ 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. + # Here we fetch from DB one transaction 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) + [transaction], + transaction.block_number == ^balance.block_number and + (transaction.value > ^0 or (transaction.gas_price > ^0 and transaction.gas_used > ^0)) and + (transaction.to_address_hash == ^balance.address_hash or + transaction.from_address_hash == ^balance.address_hash) ) - |> select([tx], tx.hash) + |> select([transaction], transaction.hash) |> limit(1) |> select_repo(options).one() end) @@ -4309,7 +4367,7 @@ defmodule Explorer.Chain do |> Enum.map(fn {{task, res}, balance} -> case res do {:ok, hash} -> - put_tx_hash(hash, balance) + put_transaction_hash(hash, balance) {:exit, _reason} -> balance @@ -4321,7 +4379,7 @@ defmodule Explorer.Chain do end) end - defp put_tx_hash(hash, coin_balance), + defp put_transaction_hash(hash, coin_balance), do: if(hash, do: %CoinBalance{coin_balance | transaction_hash: hash}, else: coin_balance) defp add_block_timestamp_to_balances( @@ -4932,7 +4990,7 @@ defmodule Explorer.Chain do def fetch_first_trace(transactions_params, json_rpc_named_arguments) do case EthereumJSONRPC.fetch_first_trace(transactions_params, json_rpc_named_arguments) do {:ok, [%{first_trace: first_trace, block_hash: block_hash, json_rpc_named_arguments: json_rpc_named_arguments}]} -> - format_tx_first_trace(first_trace, block_hash, json_rpc_named_arguments) + format_transaction_first_trace(first_trace, block_hash, json_rpc_named_arguments) {:error, error} -> {:error, error} @@ -4942,7 +5000,7 @@ defmodule Explorer.Chain do end end - defp format_tx_first_trace(first_trace, block_hash, json_rpc_named_arguments) do + defp format_transaction_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 Chain.string_to_address_hash(first_trace.to_address_hash) @@ -5181,7 +5239,7 @@ defmodule Explorer.Chain do if method_ids != [] do query - |> where([tx], fragment("SUBSTRING(? FOR 4)", tx.input) in ^method_ids) + |> where([transaction], fragment("SUBSTRING(? FOR 4)", transaction.input) in ^method_ids) else query end @@ -5212,49 +5270,49 @@ defmodule Explorer.Chain do end end - def apply_filter_by_tx_type_to_transactions(query, [_ | _] = filter) do - {dynamic, modified_query} = apply_filter_by_tx_type_to_transactions_inner(filter, query) + def apply_filter_by_type_to_transactions(query, [_ | _] = filter) do + {dynamic, modified_query} = apply_filter_by_type_to_transactions_inner(filter, query) modified_query |> where(^dynamic) end - def apply_filter_by_tx_type_to_transactions(query, _filter), do: query + def apply_filter_by_type_to_transactions(query, _filter), do: query - def apply_filter_by_tx_type_to_transactions_inner(dynamic \\ dynamic(false), filter, query) + def apply_filter_by_type_to_transactions_inner(dynamic \\ dynamic(false), filter, query) - def apply_filter_by_tx_type_to_transactions_inner(dynamic, [type | remain], query) do + def apply_filter_by_type_to_transactions_inner(dynamic, [type | remain], query) do case type do :contract_call -> dynamic |> filter_contract_call_dynamic() - |> apply_filter_by_tx_type_to_transactions_inner( + |> apply_filter_by_type_to_transactions_inner( remain, - join(query, :inner, [tx], address in assoc(tx, :to_address), as: :to_address) + join(query, :inner, [transaction], address in assoc(transaction, :to_address), as: :to_address) ) :contract_creation -> dynamic |> filter_contract_creation_dynamic() - |> apply_filter_by_tx_type_to_transactions_inner(remain, query) + |> apply_filter_by_type_to_transactions_inner(remain, query) :coin_transfer -> dynamic |> filter_transaction_dynamic() - |> apply_filter_by_tx_type_to_transactions_inner(remain, query) + |> apply_filter_by_type_to_transactions_inner(remain, query) :token_transfer -> dynamic |> filter_token_transfer_dynamic() - |> apply_filter_by_tx_type_to_transactions_inner(remain, query) + |> apply_filter_by_type_to_transactions_inner(remain, query) :token_creation -> dynamic |> filter_token_creation_dynamic() - |> apply_filter_by_tx_type_to_transactions_inner( + |> apply_filter_by_type_to_transactions_inner( remain, - join(query, :inner, [tx], token in Token, - on: token.contract_address_hash == tx.created_contract_address_hash, + join(query, :inner, [transaction], token in Token, + on: token.contract_address_hash == transaction.created_contract_address_hash, as: :created_token ) ) @@ -5262,43 +5320,43 @@ defmodule Explorer.Chain do :blob_transaction -> dynamic |> filter_blob_transaction_dynamic() - |> apply_filter_by_tx_type_to_transactions_inner(remain, query) + |> apply_filter_by_type_to_transactions_inner(remain, query) end end - def apply_filter_by_tx_type_to_transactions_inner(dynamic_query, _, query), do: {dynamic_query, query} + def apply_filter_by_type_to_transactions_inner(dynamic_query, _, query), do: {dynamic_query, query} def filter_contract_creation_dynamic(dynamic) do - dynamic([tx], ^dynamic or is_nil(tx.to_address_hash)) + dynamic([transaction], ^dynamic or is_nil(transaction.to_address_hash)) end def filter_transaction_dynamic(dynamic) do - dynamic([tx], ^dynamic or tx.value > ^0) + dynamic([transaction], ^dynamic or transaction.value > ^0) end def filter_contract_call_dynamic(dynamic) do - dynamic([tx, to_address: to_address], ^dynamic or not is_nil(to_address.contract_code)) + dynamic([transaction, to_address: to_address], ^dynamic or not is_nil(to_address.contract_code)) end def filter_token_transfer_dynamic(dynamic) do # TokenTransfer.__struct__.__meta__.source dynamic( - [tx], + [transaction], ^dynamic or fragment( "NOT (SELECT transaction_hash FROM token_transfers WHERE transaction_hash = ? LIMIT 1) IS NULL", - tx.hash + transaction.hash ) ) end def filter_token_creation_dynamic(dynamic) do - dynamic([tx, created_token: created_token], ^dynamic or not is_nil(created_token)) + dynamic([transaction, created_token: created_token], ^dynamic or not is_nil(created_token)) end def filter_blob_transaction_dynamic(dynamic) do # EIP-2718 blob transaction type - dynamic([tx], ^dynamic or tx.type == 3) + dynamic([transaction], ^dynamic or transaction.type == 3) end def count_verified_contracts do @@ -5329,11 +5387,14 @@ defmodule Explorer.Chain do def count_new_contracts do query = - from(tx in Transaction, - select: tx, + from(transaction in Transaction, + select: transaction, where: - tx.status == ^:ok and - fragment("NOW() - ? at time zone 'UTC' <= interval '24 hours'", tx.created_contract_code_indexed_at) + transaction.status == ^:ok and + fragment( + "NOW() - ? at time zone 'UTC' <= interval '24 hours'", + transaction.created_contract_code_indexed_at + ) ) query @@ -5468,12 +5529,34 @@ defmodule Explorer.Chain do end end + @doc """ + Retrieves the ID of a WatchlistAddress entry for a given watchlist and address. + + This function queries the WatchlistAddress table to find an entry that matches + both the provided watchlist ID and address hash. It returns the ID of the first + matching entry, if found. + + ## Parameters + - `watchlist_id`: The ID of the watchlist to search within. + - `address_hash`: The address hash to look for, as a `Hash.Address.t()` struct. + + ## Returns + - An integer representing the ID of the matching WatchlistAddress entry, if found. + - `nil` if no matching entry is found or if either input is `nil`. + """ + @spec select_watchlist_address_id(integer() | nil, Hash.Address.t() | nil) :: integer() | nil def select_watchlist_address_id(watchlist_id, address_hash) when not is_nil(watchlist_id) and not is_nil(address_hash) do - WatchlistAddress - |> where([wa], wa.watchlist_id == ^watchlist_id and wa.address_hash_hash == ^address_hash) - |> select([wa], wa.id) - |> Repo.account_repo().one() + wa_ids = + WatchlistAddress + |> where([wa], wa.watchlist_id == ^watchlist_id and wa.address_hash_hash == ^address_hash) + |> select([wa], wa.id) + |> Repo.account_repo().all() + + case wa_ids do + [wa_id | _] -> wa_id + _ -> nil + end end def select_watchlist_address_id(_watchlist_id, _address_hash), do: nil diff --git a/apps/explorer/lib/explorer/chain/address.ex b/apps/explorer/lib/explorer/chain/address.ex index ad8f669..1c86455 100644 --- a/apps/explorer/lib/explorer/chain/address.ex +++ b/apps/explorer/lib/explorer/chain/address.ex @@ -1,15 +1,10 @@ -defmodule Explorer.Chain.Address do +defmodule Explorer.Chain.Address.Schema do @moduledoc """ - A stored representation of a web3 address. - """ - - require Bitwise + A stored representation of a web3 address. - use Explorer.Schema - - alias Ecto.Association.NotLoaded - alias Ecto.Changeset - alias Explorer.{Chain, PagingOptions, Repo} + Changes in the schema should be reflected in the bulk import module: + - Explorer.Chain.Import.Runner.Addresses + """ alias Explorer.Chain.{ Address, @@ -18,6 +13,7 @@ defmodule Explorer.Chain.Address do DecompiledSmartContract, Hash, InternalTransaction, + SignedAuthorization, SmartContract, Token, Transaction, @@ -25,13 +21,142 @@ defmodule Explorer.Chain.Address do Withdrawal } + alias Explorer.Chain.Cache.{Accounts, NetVersion} + alias Explorer.Chain.SmartContract.Proxy.Models.Implementation + + @chain_type_fields (case Application.compile_env(:explorer, :chain_type) do + :filecoin -> + alias Explorer.Chain.Filecoin.{IDAddress, NativeAddress} + + quote do + [ + field(:filecoin_id, IDAddress), + field(:filecoin_robust, NativeAddress), + field( + :filecoin_actor_type, + Ecto.Enum, + values: + Enum.with_index([ + :account, + :cron, + :datacap, + :eam, + :ethaccount, + :evm, + :init, + :market, + :miner, + :multisig, + :paych, + :placeholder, + :power, + :reward, + :system, + :verifreg + ]) + ) + ] + end + + :zksync -> + quote do + [ + field(:contract_code_refetched, :boolean) + ] + end + + _ -> + [] + end) + + defmacro generate do + quote do + @primary_key false + @primary_key false + typed_schema "addresses" do + field(:hash, Hash.Address, primary_key: true) + field(:fetched_coin_balance, Wei) + field(:fetched_coin_balance_block_number, :integer) :: Block.block_number() | nil + field(:contract_code, Data) + field(:nonce, :integer) + field(:decompiled, :boolean, default: false) + field(:verified, :boolean, default: false) + field(:has_decompiled_code?, :boolean, virtual: true) + field(:stale?, :boolean, virtual: true) + field(:transactions_count, :integer) + field(:token_transfers_count, :integer) + field(:gas_used, :integer) + field(:ens_domain_name, :string, virtual: true) + field(:metadata, :any, virtual: true) + + has_one(:smart_contract, SmartContract, references: :hash) + has_one(:token, Token, foreign_key: :contract_address_hash, references: :hash) + has_one(:proxy_implementations, Implementation, foreign_key: :proxy_address_hash, references: :hash) + + has_one( + :contracts_creation_internal_transaction, + InternalTransaction, + foreign_key: :created_contract_address_hash, + references: :hash + ) + + has_one( + :contracts_creation_transaction, + Transaction, + foreign_key: :created_contract_address_hash, + references: :hash + ) + + has_many(:names, Address.Name, foreign_key: :address_hash, references: :hash) + has_one(:scam_badge, Address.ScamBadgeToAddress, foreign_key: :address_hash, references: :hash) + has_many(:decompiled_smart_contracts, DecompiledSmartContract, foreign_key: :address_hash, references: :hash) + has_many(:withdrawals, Withdrawal, foreign_key: :address_hash, references: :hash) + + # In practice, this is a one-to-many relationship, but we only need to check if any signed authorization + # exists for a given address. This done this way to avoid loading all signed authorizations for an address. + has_one(:signed_authorization, SignedAuthorization, foreign_key: :authority, references: :hash) + + timestamps() + + unquote_splicing(@chain_type_fields) + end + end + end +end + +defmodule Explorer.Chain.Address do + @moduledoc """ + A stored representation of a web3 address. + """ + + require Bitwise + require Explorer.Chain.Address.Schema + + use Explorer.Schema + + alias Ecto.Association.NotLoaded + alias Ecto.Changeset + alias Explorer.Helper, as: ExplorerHelper alias Explorer.Chain.Cache.{Accounts, NetVersion} alias Explorer.Chain.SmartContract.Proxy + alias Explorer.Chain.SmartContract.Proxy.EIP7702 alias Explorer.Chain.SmartContract.Proxy.Models.Implementation + alias Explorer.Chain.{Address, Data, Hash, InternalTransaction, Transaction} + alias Explorer.{Chain, PagingOptions, Repo} @optional_attrs ~w(contract_code fetched_coin_balance fetched_coin_balance_block_number nonce decompiled verified gas_used transactions_count token_transfers_count)a + @chain_type_optional_attrs (case Application.compile_env(:explorer, :chain_type) do + :filecoin -> + ~w(filecoin_id filecoin_robust filecoin_actor_type)a + + :zksync -> + ~w(contract_code_refetched)a + + _ -> + [] + end) @required_attrs ~w(hash)a - @allowed_attrs @optional_attrs ++ @required_attrs + @allowed_attrs @optional_attrs ++ @required_attrs ++ @chain_type_optional_attrs @typedoc """ Hash of the public key for this address. @@ -71,59 +196,75 @@ defmodule Explorer.Chain.Address do Solidity source code is in `smart_contract` `t:Explorer.Chain.SmartContract.t/0` `contract_source_code` *if* the contract has been verified * `names` - names known for the address + * `badges` - badges applied for the address * `inserted_at` - when this address was inserted * `updated_at` - when this address was last updated * `ens_domain_name` - virtual field for ENS domain name passing - + #{case Application.compile_env(:explorer, :chain_type) do + :filecoin -> """ + * `filecoin_native_address` - robust f0/f1/f2/f3/f4 Filecoin address + * `filecoin_id_address` - short f0 Filecoin address that may change during chain reorgs + * `filecoin_actor_type` - type of actor associated with the Filecoin address + """ + :zksync -> """ + * `contract_code_refetched` - true when Explorer.Migrator.RefetchContractCodes handled this address, or it's unnecessary (for addresses inserted after this) + """ + _ -> "" + end} `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. """ - @primary_key false - typed_schema "addresses" do - field(:hash, Hash.Address, primary_key: true) - field(:fetched_coin_balance, Wei) - field(:fetched_coin_balance_block_number, :integer) :: Block.block_number() | nil - field(:contract_code, Data) - field(:nonce, :integer) - field(:decompiled, :boolean, default: false) - field(:verified, :boolean, default: false) - field(:has_decompiled_code?, :boolean, virtual: true) - field(:stale?, :boolean, virtual: true) - field(:transactions_count, :integer) - field(:token_transfers_count, :integer) - field(:gas_used, :integer) - field(:ens_domain_name, :string, virtual: true) - field(:metadata, :any, virtual: true) - - # todo: remove virtual field for a single implementation when frontend is bound to "implementations" object value in API - field(:implementation, :any, virtual: true) - - has_one(:smart_contract, SmartContract, references: :hash) - has_one(:token, Token, foreign_key: :contract_address_hash, references: :hash) - has_one(:proxy_implementations, Implementation, foreign_key: :proxy_address_hash, references: :hash) - - has_one( - :contracts_creation_internal_transaction, - InternalTransaction, - foreign_key: :created_contract_address_hash, - references: :hash - ) + Explorer.Chain.Address.Schema.generate() - has_one( - :contracts_creation_transaction, - Transaction, - foreign_key: :created_contract_address_hash, - references: :hash - ) + @balance_changeset_required_attrs @required_attrs ++ ~w(fetched_coin_balance fetched_coin_balance_block_number)a - has_many(:names, Address.Name, foreign_key: :address_hash, references: :hash) - has_many(:decompiled_smart_contracts, DecompiledSmartContract, foreign_key: :address_hash, references: :hash) - has_many(:withdrawals, Withdrawal, foreign_key: :address_hash, references: :hash) + @doc """ + Creates an address. - timestamps() + iex> {:ok, %Explorer.Chain.Address{hash: hash}} = Explorer.Chain.Address.create( + ...> %{hash: "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b"} + ...> ) + ...> to_string(hash) + "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b" + + A `String.t/0` value for `Explorer.Chain.Address.t/0` `hash` must have 40 hexadecimal characters after the `0x` prefix + to prevent short- and long-hash transcription errors. + + iex> {:error, %Ecto.Changeset{errors: errors}} = Explorer.Chain.Address.create( + ...> %{hash: "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0"} + ...> ) + ...> errors + [hash: {"is invalid", [type: Explorer.Chain.Hash.Address, validation: :cast]}] + iex> {:error, %Ecto.Changeset{errors: errors}} = Explorer.Chain.Address.create( + ...> %{hash: "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0ba"} + ...> ) + ...> errors + [hash: {"is invalid", [type: Explorer.Chain.Hash.Address, validation: :cast]}] + + """ + @spec create(map()) :: {:ok, __MODULE__.t()} | {:error, Ecto.Changeset.t()} + def create(attrs \\ %{}) do + %__MODULE__{} + |> changeset(attrs) + |> Repo.insert() end - @balance_changeset_required_attrs @required_attrs ++ ~w(fetched_coin_balance fetched_coin_balance_block_number)a + @doc """ + Creates multiple instances of a `Explorer.Chain.Address`. + + ## Parameters + + - address_insert_params: List of address changesets to create. + + ## Returns + + - A list of created resource instances. + + """ + @spec create_multiple(list()) :: {non_neg_integer(), nil | [term()]} + def create_multiple(address_insert_params) do + Repo.insert_all(Address, address_insert_params, on_conflict: :nothing, returning: [:hash]) + end def balance_changeset(%__MODULE__{} = address, attrs) do address @@ -145,8 +286,8 @@ defmodule Explorer.Chain.Address do |> unique_constraint(:hash) end - @spec get(Hash.Address.t(), [Chain.necessity_by_association_option() | Chain.api?()]) :: t() | nil - def get(hash, options) do + @spec get(Hash.Address.t() | binary(), [Chain.necessity_by_association_option() | Chain.api?()]) :: t() | nil + def get(hash, options \\ []) do necessity_by_association = Keyword.get(options, :necessity_by_association, %{}) query = from(address in Address, where: address.hash == ^hash) @@ -328,7 +469,7 @@ defmodule Explorer.Chain.Address do if is_nil(paging_options.key) do paging_options.page_size - |> Accounts.take_enough() + |> Accounts.atomic_take_enough() |> case do nil -> get_addresses(options) @@ -345,7 +486,18 @@ defmodule Explorer.Chain.Address do end @doc """ - Checks if given address is smart-contract + Determines if the given address is a smart contract. + + This function checks the contract code of an address to determine if it's a + smart contract. + + ## Parameters + - `address`: The address to check. Can be an `Address` struct or any other value. + + ## Returns + - `true` if the address is a smart contract + - `false` if the address is not a smart contract + - `nil` if the contract code hasn't been loaded """ @spec smart_contract?(any()) :: boolean() | nil def smart_contract?(%__MODULE__{contract_code: nil}), do: false @@ -353,6 +505,31 @@ defmodule Explorer.Chain.Address do def smart_contract?(%NotLoaded{}), do: nil def smart_contract?(_), do: false + @doc """ + Checks if the given address is an Externally Owned Account (EOA) with code, + as defined in EIP-7702. + + This function determines whether an address represents an EOA that has + associated code, which is a special case introduced by EIP-7702. It checks + the contract code of the address for the presence of a delegate address + according to the EIP-7702 specification. + + ## Parameters + - `address`: The address to check. Can be an `Address` struct or any other value. + + ## Returns + - `true` if the address is an EOA with code (EIP-7702 compliant) + - `false` if the address is not an EOA with code + - `nil` if the contract code hasn't been loaded + """ + @spec eoa_with_code?(any()) :: boolean() | nil + def eoa_with_code?(%__MODULE__{contract_code: %Data{bytes: code}}) do + EIP7702.get_delegate_address(code) != nil + end + + def eoa_with_code?(%NotLoaded{}), do: nil + def eoa_with_code?(_), do: false + defp get_addresses(options) do accounts_with_n = fetch_top_addresses(options) @@ -380,6 +557,7 @@ defmodule Explorer.Chain.Address do ) base_query + |> ExplorerHelper.maybe_hide_scam_addresses(:hash) |> page_addresses(paging_options) |> limit(^paging_options.page_size) |> Chain.select_repo(options).all() @@ -401,7 +579,7 @@ defmodule Explorer.Chain.Address do Returns `:ok` if found - iex> {:ok, %Explorer.Chain.Address{hash: hash}} = Explorer.Chain.create_address( + iex> {:ok, %Explorer.Chain.Address{hash: hash}} = Explorer.Chain.Address.create( ...> %{hash: "0x5aaeb6053f3e94c9b9a09f33669435e7ef1beaed"} ...> ) iex> Explorer.Address.check_address_exists(hash) @@ -426,7 +604,7 @@ defmodule Explorer.Chain.Address do Returns `true` if found - iex> {:ok, %Explorer.Chain.Address{hash: hash}} = Explorer.Chain.create_address( + iex> {:ok, %Explorer.Chain.Address{hash: hash}} = Explorer.Chain.Address.create( ...> %{hash: "0x5aaeb6053f3e94c9b9a09f33669435e7ef1beaed"} ...> ) iex> Explorer.Chain.Address.address_exists?(hash) @@ -451,7 +629,18 @@ defmodule Explorer.Chain.Address do end @doc """ - Sets contract_code for the given Explorer.Chain.Address + Sets the contract code for the given address. + + This function updates the contract code and the `updated_at` timestamp for an + address in the database. + + ## Parameters + - `address_hash`: The hash of the address to update. + - `contract_code`: The new contract code to set. + + ## Returns + A tuple `{count, nil}`, where `count` is the number of rows updated + (typically 1 if the address exists, 0 otherwise). """ @spec set_contract_code(Hash.Address.t(), binary()) :: {non_neg_integer(), nil} def set_contract_code(address_hash, contract_code) when not is_nil(address_hash) and is_binary(contract_code) do @@ -483,4 +672,26 @@ defmodule Explorer.Chain.Address do {[], nil} end end + + @doc """ + Retrieves the creation transaction for a given address. + + ## Parameters + - `address`: The address for which to find the creation transaction. + + ## Returns + - `nil` if no creation transaction is found. + - `%InternalTransaction{}` if the creation transaction is an internal transaction. + - `%Transaction{}` if the creation transaction is a regular transaction. + """ + @spec creation_transaction(any()) :: nil | InternalTransaction.t() | Transaction.t() + def creation_transaction(%__MODULE__{contracts_creation_internal_transaction: %InternalTransaction{}} = address) do + address.contracts_creation_internal_transaction + end + + def creation_transaction(%__MODULE__{contracts_creation_transaction: %Transaction{}} = address) do + address.contracts_creation_transaction + end + + def creation_transaction(_address), do: nil end diff --git a/apps/explorer/lib/explorer/chain/address/counters.ex b/apps/explorer/lib/explorer/chain/address/counters.ex index 9ac501b..5b873f0 100644 --- a/apps/explorer/lib/explorer/chain/address/counters.ex +++ b/apps/explorer/lib/explorer/chain/address/counters.ex @@ -38,8 +38,8 @@ defmodule Explorer.Chain.Address.Counters do @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] + @types [:validations, :transactions, :token_transfers, :token_balances, :logs, :withdrawals, :internal_transactions] + @transactions_types [:transactions_from, :transactions_to, :transactions_contract] defp address_hash_to_logs_query(address_hash) do from(l in Log, where: l.address_hash == ^address_hash) @@ -181,6 +181,20 @@ defmodule Explorer.Chain.Address.Counters do Repo.aggregate(to_address_query, :count, :hash, timeout: :infinity) end + @doc """ + Calculates the total gas used by incoming transactions to a given address. + + This function queries the database for all transactions where the + `to_address_hash` matches the provided `address_hash`, and sums up the + `gas_used` for these transactions. + + ## Parameters + - `address_hash`: The address hash to query for incoming transactions. + + ## Returns + - The total gas used by incoming transactions, or `nil` if no transactions + are found or if the sum is null. + """ @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 = @@ -192,6 +206,19 @@ defmodule Explorer.Chain.Address.Counters do Repo.aggregate(to_address_query, :sum, :gas_used, timeout: :infinity) end + @doc """ + Calculates the total gas used by outgoing transactions from a given address. + + This function queries the database for all transactions where the + `from_address_hash` matches the provided `address_hash`, and sums up the + `gas_used` for these transactions. + + ## Parameters + - `address_hash`: the address to query. + + ## Returns + - The total gas used, or `nil` if no transactions are found or if the sum is null. + """ @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 = @@ -226,9 +253,29 @@ defmodule Explorer.Chain.Address.Counters do ) end + @doc """ + Calculates the total gas usage for a given address. + + This function determines the appropriate gas usage calculation based on the + address type: + + - For smart contracts (excluding EOAs with code), it first checks the gas + usage of incoming transactions. If there are no incoming transactions or + their gas usage is zero, it falls back to the gas usage of outgoing + transactions. + - For regular addresses and EOAs with code, it calculates the gas usage of + outgoing transactions. + + ## Parameters + - `address`: The address to calculate gas usage for. + + ## Returns + - The total gas usage for the address. + - `nil` if no relevant transactions are found or if the sum is null. + """ @spec address_to_gas_usage_count(Address.t()) :: Decimal.t() | nil def address_to_gas_usage_count(address) do - if Address.smart_contract?(address) do + if Address.smart_contract?(address) and not Address.eoa_with_code?(address) do incoming_transaction_gas_usage = address_to_incoming_transaction_gas_usage(address.hash) cond do @@ -246,7 +293,7 @@ defmodule Explorer.Chain.Address.Counters do end end - defp address_hash_to_internal_txs_limited_count_query(address_hash) do + defp address_hash_to_internal_transactions_limited_count_query(address_hash) do query_to_address_hash_wrapped = InternalTransaction |> InternalTransaction.where_nonpending_block() @@ -353,7 +400,7 @@ defmodule Explorer.Chain.Address.Counters do ) transactions_from_count_task = - run_or_ignore(cached_counters[:txs], :txs_from, address_hash, fn -> + run_or_ignore(cached_counters[:transactions], :transactions_from, address_hash, fn -> result = Transaction |> where([t], t.from_address_hash == ^address_hash) @@ -367,14 +414,18 @@ defmodule Explorer.Chain.Address.Counters do 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) + AddressesTabsCounters.save_transactions_counter_progress(address_hash, %{ + transactions_types: [:transactions_from], + transactions_from: result + }) + + AddressesTabsCounters.drop_task(:transactions_from, address_hash) - {:txs_from, result} + {:transactions_from, result} end) transactions_to_count_task = - run_or_ignore(cached_counters[:txs], :txs_to, address_hash, fn -> + run_or_ignore(cached_counters[:transactions], :transactions_to, address_hash, fn -> result = Transaction |> where([t], t.to_address_hash == ^address_hash) @@ -388,14 +439,18 @@ defmodule Explorer.Chain.Address.Counters do 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) + AddressesTabsCounters.save_transactions_counter_progress(address_hash, %{ + transactions_types: [:transactions_to], + transactions_to: result + }) + + AddressesTabsCounters.drop_task(:transactions_to, address_hash) - {:txs_to, result} + {:transactions_to, result} end) transactions_created_contract_count_task = - run_or_ignore(cached_counters[:txs], :txs_contract, address_hash, fn -> + run_or_ignore(cached_counters[:transactions], :transactions_contract, address_hash, fn -> result = Transaction |> where([t], t.created_contract_address_hash == ^address_hash) @@ -409,14 +464,14 @@ defmodule Explorer.Chain.Address.Counters do 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.save_transactions_counter_progress(address_hash, %{ + transactions_types: [:transactions_contract], + transactions_contract: result }) - AddressesTabsCounters.drop_task(:txs_contract, address_hash) + AddressesTabsCounters.drop_task(:transactions_contract, address_hash) - {:txs_contract, result} + {:transactions_contract, result} end) token_transfers_count_task = @@ -455,11 +510,11 @@ defmodule Explorer.Chain.Address.Counters do options ) - internal_txs_count_task = + internal_transactions_count_task = configure_task( - :internal_txs, + :internal_transactions, cached_counters, - address_hash_to_internal_txs_limited_count_query(address_hash), + address_hash_to_internal_transactions_limited_count_query(address_hash), address_hash, options ) @@ -487,44 +542,46 @@ defmodule Explorer.Chain.Address.Counters do token_balances_count_task, logs_count_task, withdrawals_count_task, - internal_txs_count_task, + internal_transactions_count_task, celo_election_rewards_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.warning(fn -> - [ - "Query fetching address counters for #{address_hash} terminated: #{inspect(reason)}" - ] - end) - - acc - - nil -> - Logger.warning(fn -> - [ - "Query fetching address counters for #{address_hash} timed out." - ] - end) - - Task.ignore(task) - - acc + |> Enum.reduce( + Map.merge(prepare_cache_values(cached_counters), %{transactions_types: [], transactions_hashes: []}), + fn {task, res}, acc -> + case res do + {:ok, {transactions_type, transactions_hashes}} when transactions_type in @transactions_types -> + acc + |> (&Map.put(&1, :transactions_types, [transactions_type | &1[:transactions_types]])).() + |> (&Map.put(&1, :transactions_hashes, &1[:transactions_hashes] ++ transactions_hashes)).() + + {:ok, {type, counter}} -> + Map.put(acc, type, counter) + + {:exit, reason} -> + Logger.warning(fn -> + [ + "Query fetching address counters for #{address_hash} terminated: #{inspect(reason)}" + ] + end) + + acc + + nil -> + Logger.warning(fn -> + [ + "Query fetching address counters for #{address_hash} timed out." + ] + end) + + Task.ignore(task) + + acc + end end - end) - |> process_txs_counter() + ) + |> process_transactions_counter() map end @@ -561,17 +618,19 @@ defmodule Explorer.Chain.Address.Counters do end) end - defp process_txs_counter(%{txs_types: [_ | _] = txs_types, txs_hashes: hashes} = map) do + defp process_transactions_counter( + %{transactions_types: [_ | _] = transactions_types, transactions_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) + if Enum.count(transactions_types) == 3 || counter == @counters_limit do + map |> Map.put(:transactions, counter) else map end end - defp process_txs_counter(map), do: map + defp process_transactions_counter(map), do: map defp prepare_cache_values(cached_counters) do Enum.reduce(cached_counters, %{}, fn @@ -586,8 +645,8 @@ defmodule Explorer.Chain.Address.Counters do @doc """ Returns all possible transactions type """ - @spec txs_types :: list(atom) - def txs_types, do: @txs_types + @spec transactions_types :: list(atom) + def transactions_types, do: @transactions_types @doc """ Returns max counter value diff --git a/apps/explorer/lib/explorer/chain/address/metadata_preloader.ex b/apps/explorer/lib/explorer/chain/address/metadata_preloader.ex index 2ee81f2..0159591 100644 --- a/apps/explorer/lib/explorer/chain/address/metadata_preloader.ex +++ b/apps/explorer/lib/explorer/chain/address/metadata_preloader.ex @@ -200,12 +200,12 @@ defmodule Explorer.Chain.Address.MetadataPreloader do to_address_hash: to_address_hash, created_contract_address_hash: created_contract_address_hash, from_address_hash: from_address_hash - } = tx, + } = transaction, names, field_to_put_info ) do token_transfers = - case tx.token_transfers do + case transaction.token_transfers do token_transfers_list when is_list(token_transfers_list) -> Enum.map(token_transfers_list, &put_meta_to_item(&1, names, field_to_put_info)) @@ -214,11 +214,11 @@ defmodule Explorer.Chain.Address.MetadataPreloader do end %Transaction{ - tx - | to_address: alter_address(tx.to_address, to_address_hash, names, field_to_put_info), + transaction + | to_address: alter_address(transaction.to_address, to_address_hash, names, field_to_put_info), created_contract_address: - alter_address(tx.created_contract_address, created_contract_address_hash, names, field_to_put_info), - from_address: alter_address(tx.from_address, from_address_hash, names, field_to_put_info), + alter_address(transaction.created_contract_address, created_contract_address_hash, names, field_to_put_info), + from_address: alter_address(transaction.from_address, from_address_hash, names, field_to_put_info), token_transfers: token_transfers } end @@ -243,16 +243,16 @@ defmodule Explorer.Chain.Address.MetadataPreloader do to_address_hash: to_address_hash, created_contract_address_hash: created_contract_address_hash, from_address_hash: from_address_hash - } = tx, + } = transaction, names, field_to_put_info ) do %InternalTransaction{ - tx - | to_address: alter_address(tx.to_address, to_address_hash, names, field_to_put_info), + transaction + | to_address: alter_address(transaction.to_address, to_address_hash, names, field_to_put_info), created_contract_address: - alter_address(tx.created_contract_address, created_contract_address_hash, names, field_to_put_info), - from_address: alter_address(tx.from_address, from_address_hash, names, field_to_put_info) + alter_address(transaction.created_contract_address, created_contract_address_hash, names, field_to_put_info), + from_address: alter_address(transaction.from_address, from_address_hash, names, field_to_put_info) } end diff --git a/apps/explorer/lib/explorer/chain/address/scam_badge_to_address.ex b/apps/explorer/lib/explorer/chain/address/scam_badge_to_address.ex new file mode 100644 index 0000000..e911cfb --- /dev/null +++ b/apps/explorer/lib/explorer/chain/address/scam_badge_to_address.ex @@ -0,0 +1,101 @@ +defmodule Explorer.Chain.Address.ScamBadgeToAddress do + @moduledoc """ + Defines Address.ScamBadgeToAddress.t() mapping with Address.t() + """ + + use Explorer.Schema + + import Ecto.Changeset + + alias Explorer.{Chain, Repo} + alias Explorer.Chain.{Address, Hash} + + import Ecto.Query, only: [from: 2] + + @typedoc """ + * `address` - the `t:Explorer.Chain.Address.t/0`. + * `address_hash` - foreign key for `address`. + """ + @primary_key false + typed_schema "scam_address_badge_mappings" do + belongs_to(:address, Address, foreign_key: :address_hash, references: :hash, type: Hash.Address, null: false) + + timestamps() + end + + @required_fields ~w(address_hash)a + @allowed_fields @required_fields + + def changeset(%__MODULE__{} = struct, params \\ %{}) do + struct + |> cast(params, @allowed_fields) + |> validate_required(@required_fields) + |> foreign_key_constraint(:address_hash) + end + + @doc """ + Adds Address.ScamBadgeToAddress.t() by the list of Hash.Address.t() + """ + @spec add([Hash.Address.t()]) :: {non_neg_integer(), [__MODULE__.t()]} + def add(address_hashes) do + now = DateTime.utc_now() + + insert_params = + address_hashes + |> Enum.map(fn address_hash_string -> + case Chain.string_to_address_hash(address_hash_string) do + {:ok, address_hash} -> %{address_hash: address_hash, inserted_at: now, updated_at: now} + :error -> nil + end + end) + |> Enum.filter(&(!is_nil(&1))) + + safe_add(insert_params) + end + + defp safe_add(insert_params) do + Repo.insert_all(__MODULE__, insert_params, on_conflict: :nothing, returning: [:address_hash]) + rescue + # if at least one of the address hashes didn't yet exist in the `addresses` table by the moment of scam badge assignment, we'll receive foreign key violation error. + # In this case, we'll try to insert the missing addresses and then try to insert the scam badge mappings again. + error -> + address_insert_params = + insert_params + |> Enum.map(fn scam_address -> + scam_address + |> Map.put(:hash, scam_address.address_hash) + |> Map.drop([:address_hash]) + end) + + with %Postgrex.Error{postgres: %{code: :foreign_key_violation}} <- error, + {_number_of_inserted_addresses, [_term]} <- Address.create_multiple(address_insert_params) do + safe_add(insert_params) + else + _ -> {0, []} + end + end + + @doc """ + Deletes Address.ScamBadgeToAddress.t() by the list of Hash.Address.t() + """ + @spec delete([Hash.Address.t()]) :: {non_neg_integer(), [__MODULE__.t()]} + def delete(address_hashes) do + query = + from( + bta in __MODULE__, + where: bta.address_hash in ^address_hashes, + select: bta + ) + + Repo.delete_all(query) + end + + @doc """ + Gets the list of Address.ScamBadgeToAddress.t() + """ + @spec get([Chain.necessity_by_association_option() | Chain.api?()]) :: [__MODULE__.t()] + def get(options) do + __MODULE__ + |> Chain.select_repo(options).all() + end +end diff --git a/apps/explorer/lib/explorer/chain/address/token_balance.ex b/apps/explorer/lib/explorer/chain/address/token_balance.ex index e0aef47..85a7314 100644 --- a/apps/explorer/lib/explorer/chain/address/token_balance.ex +++ b/apps/explorer/lib/explorer/chain/address/token_balance.ex @@ -25,6 +25,8 @@ defmodule Explorer.Chain.Address.TokenBalance do * `value` - The value that's represents the balance. * `token_id` - The token_id of the transferred token (applicable for ERC-1155, ERC-721 and ERC-404 tokens) * `token_type` - The type of the token + * `refetch_after` - when to refetch the token balance + * `retries_count` - number of times the token balance has been retried """ typed_schema "address_token_balances" do field(:value, :decimal) @@ -32,6 +34,8 @@ defmodule Explorer.Chain.Address.TokenBalance do field(:value_fetched_at, :utc_datetime_usec) field(:token_id, :decimal) field(:token_type, :string, null: false) + field(:refetch_after, :utc_datetime_usec) + field(:retries_count, :integer) belongs_to(:address, Address, foreign_key: :address_hash, references: :hash, type: Hash.Address, null: false) @@ -47,7 +51,7 @@ defmodule Explorer.Chain.Address.TokenBalance do timestamps() end - @optional_fields ~w(value value_fetched_at token_id)a + @optional_fields ~w(value value_fetched_at token_id refetch_after retries_count)a @required_fields ~w(address_hash block_number token_contract_address_hash token_type)a @allowed_fields @optional_fields ++ @required_fields @@ -77,7 +81,8 @@ defmodule Explorer.Chain.Address.TokenBalance do 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" or tb.token_type == "ERC-404") and - (is_nil(tb.value_fetched_at) or is_nil(tb.value)) + (is_nil(tb.value_fetched_at) or is_nil(tb.value)) and + (is_nil(tb.refetch_after) or tb.refetch_after < ^Timex.now()) ) else from( @@ -87,7 +92,8 @@ defmodule Explorer.Chain.Address.TokenBalance do where: ((tb.address_hash != ^@burn_address_hash and t.type == "ERC-721") or t.type == "ERC-20" or t.type == "ERC-1155" or t.type == "ERC-404") and - (is_nil(tb.value_fetched_at) or is_nil(tb.value)) + (is_nil(tb.value_fetched_at) or is_nil(tb.value)) and + (is_nil(tb.refetch_after) or tb.refetch_after < ^Timex.now()) ) end end diff --git a/apps/explorer/lib/explorer/chain/advanced_filter.ex b/apps/explorer/lib/explorer/chain/advanced_filter.ex index 1161030..62cdc16 100644 --- a/apps/explorer/lib/explorer/chain/advanced_filter.ex +++ b/apps/explorer/lib/explorer/chain/advanced_filter.ex @@ -8,7 +8,7 @@ defmodule Explorer.Chain.AdvancedFilter do import Ecto.Query alias Explorer.{Chain, Helper, PagingOptions} - alias Explorer.Chain.{Address, Data, Hash, InternalTransaction, TokenTransfer, Transaction} + alias Explorer.Chain.{Address, Data, DenormalizationHelper, Hash, InternalTransaction, TokenTransfer, Transaction} @primary_key false typed_embedded_schema null: false do @@ -54,7 +54,7 @@ defmodule Explorer.Chain.AdvancedFilter do field(:token_transfer_batch_index, :integer, null: true) end - @typep tx_types :: {:tx_types, [String.t()] | nil} + @typep transaction_types :: {:transaction_types, [String.t()] | nil} @typep methods :: {:methods, [String.t()] | nil} @typep age :: {:age, [{:from, DateTime.t() | nil} | {:to, DateTime.t() | nil}] | nil} @typep from_address_hashes :: {:from_address_hashes, [Hash.Address.t()] | nil} @@ -64,7 +64,7 @@ defmodule Explorer.Chain.AdvancedFilter do @typep token_contract_address_hashes :: {:token_contract_address_hashes, [{:include, [Hash.Address.t()]} | {:include, [Hash.Address.t()]}] | nil} @type options :: [ - tx_types() + transaction_types() | methods() | age() | from_address_hashes() @@ -74,19 +74,31 @@ defmodule Explorer.Chain.AdvancedFilter do | token_contract_address_hashes() | Chain.paging_options() | Chain.api?() + | {:timeout, timeout()} ] @spec list(options()) :: [__MODULE__.t()] def list(options \\ []) do paging_options = Keyword.get(options, :paging_options) + timeout = Keyword.get(options, :timeout, :timer.seconds(60)) + + age = Keyword.get(options, :age) + + block_numbers_age = + [ + from: age[:from] && Chain.timestamp_to_block_number(age[:from], :after, Keyword.get(options, :api?, false)), + to: age[:to] && Chain.timestamp_to_block_number(age[:to], :before, Keyword.get(options, :api?, false)) + ] + tasks = options + |> Keyword.put(:block_numbers_age, block_numbers_age) |> queries(paging_options) - |> Enum.map(fn query -> Task.async(fn -> Chain.select_repo(options).all(query) end) end) + |> Enum.map(fn query -> Task.async(fn -> Chain.select_repo(options).all(query, timeout: timeout) end) end) tasks - |> Task.yield_many(:timer.seconds(60)) + |> Task.yield_many(timeout: timeout, on_timeout: :kill_task) |> Enum.flat_map(fn {_task, res} -> case res do {:ok, result} -> @@ -102,55 +114,57 @@ defmodule Explorer.Chain.AdvancedFilter do |> Enum.map(&to_advanced_filter/1) |> Enum.sort(&sort_function/2) |> take_page_size(paging_options) + |> Chain.select_repo(options).preload( + from_address: [:scam_badge, :names, :smart_contract, :proxy_implementations], + to_address: [:scam_badge, :names, :smart_contract, :proxy_implementations], + created_contract_address: [:names, :smart_contract, :proxy_implementations] + ) end defp queries(options, paging_options) do - cond do - only_transactions?(options) -> - [transactions_query(paging_options, options), internal_transactions_query(paging_options, options)] - - only_token_transfers?(options) -> - [token_transfers_query(paging_options, options)] - - true -> - [ - transactions_query(paging_options, options), - internal_transactions_query(paging_options, options), - token_transfers_query(paging_options, options) - ] - end + [] + |> maybe_add_transactions_queries(options, paging_options) + |> maybe_add_token_transfers_queries(options, paging_options) end - defp only_transactions?(options) do - transaction_types = options[:tx_types] - tokens_to_include = options[:token_contract_address_hashes][:include] + defp maybe_add_transactions_queries(queries, options, paging_options) do + transaction_types = options[:transaction_types] || [] + tokens_to_include = options[:token_contract_address_hashes][:include] || [] + tokens_to_exclude = options[:token_contract_address_hashes][:exclude] || [] - transaction_types == ["COIN_TRANSFER"] or tokens_to_include == ["native"] + if (transaction_types == [] or "COIN_TRANSFER" in transaction_types) and + (tokens_to_include == [] or "native" in tokens_to_include) and + "native" not in tokens_to_exclude do + [transactions_query(paging_options, options), internal_transactions_query(paging_options, options) | queries] + else + queries + end end - defp only_token_transfers?(options) do - transaction_types = options[:tx_types] - tokens_to_include = options[:token_contract_address_hashes][:include] - tokens_to_exclude = options[:token_contract_address_hashes][:exclude] + defp maybe_add_token_transfers_queries(queries, options, paging_options) do + transaction_types = options[:transaction_types] || [] + tokens_to_include = options[:token_contract_address_hashes][:include] || [] - (is_list(transaction_types) and length(transaction_types) > 0 and "COIN_TRANSFER" not in transaction_types) or - (is_list(tokens_to_include) and length(tokens_to_include) > 0 and "native" not in tokens_to_include) or - (is_list(tokens_to_exclude) and "native" in tokens_to_exclude) + if (transaction_types == [] or not (transaction_types |> Enum.reject(&(&1 == "COIN_TRANSFER")) |> Enum.empty?())) and + (tokens_to_include == [] or not (tokens_to_include |> Enum.reject(&(&1 == "native")) |> Enum.empty?())) do + [token_transfers_query(paging_options, options) | queries] + else + queries + end end defp to_advanced_filter(%Transaction{} = transaction) do + %{value: decimal_transaction_value} = transaction.value + %__MODULE__{ hash: transaction.hash, type: "coin_transfer", input: transaction.input, timestamp: transaction.block_timestamp, - from_address: transaction.from_address, from_address_hash: transaction.from_address_hash, - to_address: transaction.to_address, to_address_hash: transaction.to_address_hash, - created_contract_address: transaction.created_contract_address, created_contract_address_hash: transaction.created_contract_address_hash, - value: transaction.value.value, + value: decimal_transaction_value, fee: transaction |> Transaction.fee(:wei) |> elem(1), block_number: transaction.block_number, transaction_index: transaction.index @@ -158,18 +172,17 @@ defmodule Explorer.Chain.AdvancedFilter do end defp to_advanced_filter(%InternalTransaction{} = internal_transaction) do + %{value: decimal_internal_transaction_value} = internal_transaction.value + %__MODULE__{ hash: internal_transaction.transaction.hash, type: "coin_transfer", input: internal_transaction.input, timestamp: internal_transaction.transaction.block_timestamp, - from_address: internal_transaction.from_address, from_address_hash: internal_transaction.from_address_hash, - to_address: internal_transaction.to_address, to_address_hash: internal_transaction.to_address_hash, - created_contract_address: internal_transaction.created_contract_address, created_contract_address_hash: internal_transaction.created_contract_address_hash, - value: internal_transaction.value.value, + value: decimal_internal_transaction_value, fee: internal_transaction.transaction.gas_price && internal_transaction.gas_used && Decimal.mult(internal_transaction.transaction.gas_price.value, internal_transaction.gas_used), @@ -185,11 +198,8 @@ defmodule Explorer.Chain.AdvancedFilter do type: token_transfer.token_type, input: token_transfer.transaction.input, timestamp: token_transfer.transaction.block_timestamp, - from_address: token_transfer.from_address, from_address_hash: token_transfer.from_address_hash, - to_address: token_transfer.to_address, to_address_hash: token_transfer.to_address_hash, - created_contract_address: nil, created_contract_address_hash: nil, fee: token_transfer.transaction |> Transaction.fee(:wei) |> elem(1), token_transfer: %TokenTransfer{ @@ -247,19 +257,27 @@ defmodule Explorer.Chain.AdvancedFilter do defp transactions_query(paging_options, options) do query = - from(transaction in Transaction, - as: :transaction, - preload: [ - :block, - from_address: [:names, :smart_contract, :proxy_implementations], - to_address: [:names, :smart_contract, :proxy_implementations], - created_contract_address: [:names, :smart_contract, :proxy_implementations] - ], - order_by: [ - desc: transaction.block_number, - desc: transaction.index - ] - ) + if DenormalizationHelper.transactions_denormalization_finished?() do + from(transaction in Transaction, + as: :transaction, + where: transaction.block_consensus == true, + order_by: [ + desc: transaction.block_number, + desc: transaction.index + ] + ) + else + from(transaction in Transaction, + as: :transaction, + join: block in assoc(transaction, :block), + as: :block, + where: block.consensus == true, + order_by: [ + desc: transaction.block_number, + desc: transaction.index + ] + ) + end query |> page_transactions(paging_options) @@ -270,11 +288,14 @@ defmodule Explorer.Chain.AdvancedFilter do defp page_transactions(query, %PagingOptions{ key: %{ block_number: block_number, - transaction_index: tx_index + transaction_index: transaction_index } }) do dynamic_condition = - dynamic(^page_block_number_dynamic(:transaction, block_number) or ^page_tx_index_dynamic(block_number, tx_index)) + dynamic( + ^page_block_number_dynamic(:transaction, block_number) or + ^page_transaction_index_dynamic(block_number, transaction_index) + ) query |> where(^dynamic_condition) end @@ -283,67 +304,117 @@ defmodule Explorer.Chain.AdvancedFilter do defp internal_transactions_query(paging_options, options) do query = - from(internal_transaction in InternalTransaction, - as: :internal_transaction, - join: transaction in assoc(internal_transaction, :transaction), - as: :transaction, - preload: [ - from_address: [:names, :smart_contract, :proxy_implementations], - to_address: [:names, :smart_contract, :proxy_implementations], - created_contract_address: [:names, :smart_contract, :proxy_implementations], - transaction: transaction - ], - order_by: [ - desc: transaction.block_number, - desc: transaction.index, - desc: internal_transaction.index - ] - ) + if DenormalizationHelper.transactions_denormalization_finished?() do + from(internal_transaction in InternalTransaction, + as: :internal_transaction, + join: transaction in assoc(internal_transaction, :transaction), + as: :transaction, + where: transaction.block_consensus == true, + where: + (internal_transaction.type == :call and internal_transaction.index > 0) or + internal_transaction.type != :call, + order_by: [ + desc: internal_transaction.block_number, + desc: internal_transaction.transaction_index, + desc: internal_transaction.index + ] + ) + else + from(internal_transaction in InternalTransaction, + as: :internal_transaction, + join: transaction in assoc(internal_transaction, :transaction), + as: :transaction, + join: block in assoc(internal_transaction, :block), + as: :block, + where: block.consensus == true, + where: + (internal_transaction.type == :call and internal_transaction.index > 0) or + internal_transaction.type != :call, + order_by: [ + desc: internal_transaction.block_number, + desc: internal_transaction.transaction_index, + desc: internal_transaction.index + ] + ) + end query |> page_internal_transactions(paging_options) |> limit_query(paging_options) - |> apply_transactions_filters(options) + |> apply_internal_transactions_filters(options) + |> limit_query(paging_options) + |> preload([:transaction]) end defp page_internal_transactions(query, %PagingOptions{ key: %{ block_number: block_number, - transaction_index: tx_index, + transaction_index: _transaction_index, internal_transaction_index: nil } - }) do - case {block_number, tx_index} do - {0, 0} -> - query |> where(as(:transaction).block_number == ^block_number and as(:transaction).index == ^tx_index) + }) + when block_number < 0 do + query |> where(false) + end - {0, tx_index} -> - query - |> where(as(:transaction).block_number == ^block_number and as(:transaction).index <= ^tx_index) + defp page_internal_transactions(query, %PagingOptions{ + key: %{ + block_number: block_number, + transaction_index: transaction_index, + internal_transaction_index: nil + } + }) + when block_number > 0 and transaction_index <= 0 do + query |> where(as(:transaction).block_number < ^block_number) + end + + defp page_internal_transactions(query, %PagingOptions{ + key: %{ + block_number: 0, + transaction_index: 0, + internal_transaction_index: nil + } + }) do + query |> where(as(:transaction).block_number == 0 and as(:transaction).index == 0) + end - {block_number, 0} -> - query |> where(as(:transaction).block_number < ^block_number) + defp page_internal_transactions(query, %PagingOptions{ + key: %{ + block_number: 0, + transaction_index: transaction_index, + internal_transaction_index: nil + } + }) do + query + |> where(as(:transaction).block_number == 0 and as(:transaction).index <= ^transaction_index) + end - _ -> - query - |> where( - as(:transaction).block_number < ^block_number or - (as(:transaction).block_number == ^block_number and as(:transaction).index <= ^tx_index) - ) - end + defp page_internal_transactions(query, %PagingOptions{ + key: %{ + block_number: block_number, + transaction_index: transaction_index, + internal_transaction_index: nil + } + }) do + query + |> where( + as(:transaction).block_number < ^block_number or + (as(:transaction).block_number == ^block_number and as(:transaction).index <= ^transaction_index) + ) end defp page_internal_transactions(query, %PagingOptions{ key: %{ block_number: block_number, - transaction_index: tx_index, + transaction_index: transaction_index, internal_transaction_index: it_index } }) do dynamic_condition = dynamic( - ^page_block_number_dynamic(:transaction, block_number) or ^page_tx_index_dynamic(block_number, tx_index) or - ^page_it_index_dynamic(block_number, tx_index, it_index) + ^page_block_number_dynamic(:transaction, block_number) or + ^page_transaction_index_dynamic(block_number, transaction_index) or + ^page_it_index_dynamic(block_number, transaction_index, it_index) ) query @@ -354,80 +425,166 @@ defmodule Explorer.Chain.AdvancedFilter do defp token_transfers_query(paging_options, options) do token_transfer_query = - from(token_transfer in TokenTransfer, - as: :token_transfer, - join: transaction in assoc(token_transfer, :transaction), - as: :transaction, - join: token in assoc(token_transfer, :token), - as: :token, - select: %TokenTransfer{ - token_transfer - | token_id: fragment("UNNEST(?)", token_transfer.token_ids), - amount: - fragment("UNNEST(COALESCE(?, ARRAY[COALESCE(?, 1)]))", token_transfer.amounts, token_transfer.amount), - reverse_index_in_batch: - fragment("GENERATE_SERIES(COALESCE(ARRAY_LENGTH(?, 1), 1), 1, -1)", token_transfer.amounts), - token_decimals: token.decimals - }, - order_by: [ - desc: token_transfer.block_number, - desc: token_transfer.log_index - ] - ) + if DenormalizationHelper.transactions_denormalization_finished?() do + from(token_transfer in TokenTransfer, + as: :token_transfer, + join: transaction in assoc(token_transfer, :transaction), + as: :transaction, + join: token in assoc(token_transfer, :token), + as: :token, + select: %TokenTransfer{ + token_transfer + | token_id: fragment("UNNEST(?)", token_transfer.token_ids), + amount: + fragment("UNNEST(COALESCE(?, ARRAY[COALESCE(?, 1)]))", token_transfer.amounts, token_transfer.amount), + reverse_index_in_batch: + fragment("GENERATE_SERIES(COALESCE(ARRAY_LENGTH(?, 1), 1), 1, -1)", token_transfer.amounts), + token_decimals: token.decimals + }, + where: transaction.block_consensus == true, + order_by: [ + desc: token_transfer.block_number, + desc: token_transfer.log_index + ] + ) + else + from(token_transfer in TokenTransfer, + as: :token_transfer, + join: transaction in assoc(token_transfer, :transaction), + as: :transaction, + join: token in assoc(token_transfer, :token), + as: :token, + join: block in assoc(token_transfer, :block), + as: :block, + select: %TokenTransfer{ + token_transfer + | token_id: fragment("UNNEST(?)", token_transfer.token_ids), + amount: + fragment("UNNEST(COALESCE(?, ARRAY[COALESCE(?, 1)]))", token_transfer.amounts, token_transfer.amount), + reverse_index_in_batch: + fragment("GENERATE_SERIES(COALESCE(ARRAY_LENGTH(?, 1), 1), 1, -1)", token_transfer.amounts), + token_decimals: token.decimals + }, + where: block.consensus == true, + order_by: [ + desc: token_transfer.block_number, + desc: token_transfer.log_index + ] + ) + end + + query_function = + (&make_token_transfer_query_unnested/2) + |> apply_token_transfers_filters(options) + |> page_token_transfers(paging_options) token_transfer_query - |> apply_token_transfers_filters(options) - |> page_token_transfers(paging_options) - |> filter_token_transfers_by_amount(options[:amount][:from], options[:amount][:to]) - |> make_token_transfer_query_unnested() |> limit_query(paging_options) + |> query_function.(false) + |> limit_query(paging_options) + |> preload([:transaction, :token]) + |> select_merge([token_transfer], %{token_ids: [token_transfer.token_id], amounts: [token_transfer.amount]}) end - defp page_token_transfers(query, %PagingOptions{ + defp page_token_transfers(query_function, %PagingOptions{ key: %{ block_number: block_number, - transaction_index: tx_index, + transaction_index: _transaction_index, token_transfer_index: nil, internal_transaction_index: nil } - }) do - case {block_number, tx_index} do - {0, 0} -> - query |> where(as(:transaction).block_number == ^block_number and as(:transaction).index == ^tx_index) + }) + when block_number < 0 do + fn query, unnested? -> + query |> where(false) |> query_function.(unnested?) + end + end - {0, tx_index} -> - query - |> where([token_transfer], token_transfer.block_number == ^block_number and as(:transaction).index < ^tx_index) + defp page_token_transfers(query_function, %PagingOptions{ + key: %{ + block_number: block_number, + transaction_index: transaction_index, + token_transfer_index: nil, + internal_transaction_index: nil + } + }) + when block_number > 0 and transaction_index <= 0 do + fn query, unnested? -> + query |> where([token_transfer], token_transfer.block_number < ^block_number) |> query_function.(unnested?) + end + end - {block_number, 0} -> - query |> where([token_transfer], token_transfer.block_number < ^block_number) + defp page_token_transfers(query_function, %PagingOptions{ + key: %{ + block_number: 0, + transaction_index: 0, + token_transfer_index: nil, + internal_transaction_index: nil + } + }) do + fn query, unnested? -> + query + |> where(as(:transaction).block_number == 0 and as(:transaction).index == 0) + |> query_function.(unnested?) + end + end - {block_number, tx_index} -> - query - |> where( - [token_transfer], - token_transfer.block_number < ^block_number or - (token_transfer.block_number == ^block_number and as(:transaction).index <= ^tx_index) - ) + defp page_token_transfers(query_function, %PagingOptions{ + key: %{ + block_number: 0, + transaction_index: transaction_index, + token_transfer_index: nil, + internal_transaction_index: nil + } + }) do + fn query, unnested? -> + query + |> where( + [token_transfer], + token_transfer.block_number == 0 and as(:transaction).index < ^transaction_index + ) + |> query_function.(unnested?) end end - defp page_token_transfers(query, %PagingOptions{ + defp page_token_transfers(query_function, %PagingOptions{ key: %{ block_number: block_number, - transaction_index: tx_index, + transaction_index: transaction_index, + token_transfer_index: nil, + internal_transaction_index: nil + } + }) do + fn query, unnested? -> + query + |> where( + [token_transfer], + token_transfer.block_number < ^block_number or + (token_transfer.block_number == ^block_number and as(:transaction).index <= ^transaction_index) + ) + |> query_function.(unnested?) + end + end + + defp page_token_transfers(query_function, %PagingOptions{ + key: %{ + block_number: block_number, + transaction_index: transaction_index, token_transfer_index: nil } }) do dynamic_condition = dynamic( - ^page_block_number_dynamic(:token_transfer, block_number) or ^page_tx_index_dynamic(block_number, tx_index) + ^page_block_number_dynamic(:token_transfer, block_number) or + ^page_transaction_index_dynamic(block_number, transaction_index) ) - query |> where(^dynamic_condition) + fn query, unnested? -> + query |> where(^dynamic_condition) |> query_function.(unnested?) + end end - defp page_token_transfers(query, %PagingOptions{ + defp page_token_transfers(query_function, %PagingOptions{ key: %{ block_number: block_number, token_transfer_index: tt_index, @@ -440,20 +597,21 @@ defmodule Explorer.Chain.AdvancedFilter do ^page_tt_index_dynamic(:token_transfer, block_number, tt_index, tt_batch_index) ) - paged_query = query |> where(^dynamic_condition) - - paged_query - |> make_token_transfer_query_unnested() - |> where( - ^page_tt_batch_index_dynamic( - block_number, - tt_index, - tt_batch_index + fn query, unnested? -> + query + |> where(^dynamic_condition) + |> query_function.(unnested?) + |> where( + ^page_tt_batch_index_dynamic( + block_number, + tt_index, + tt_batch_index + ) ) - ) + end end - defp page_token_transfers(query, _), do: query + defp page_token_transfers(query_function, _), do: query_function defp page_block_number_dynamic(binding, block_number) when block_number > 0 do dynamic(as(^binding).block_number < ^block_number) @@ -463,18 +621,23 @@ defmodule Explorer.Chain.AdvancedFilter do dynamic(false) end - defp page_tx_index_dynamic(block_number, tx_index) when tx_index > 0 do - dynamic([transaction: tx], tx.block_number == ^block_number and tx.index < ^tx_index) + defp page_transaction_index_dynamic(block_number, transaction_index) + when block_number >= 0 and transaction_index > 0 do + dynamic( + [transaction: transaction], + transaction.block_number == ^block_number and transaction.index < ^transaction_index + ) end - defp page_tx_index_dynamic(_, _) do + defp page_transaction_index_dynamic(_, _) do dynamic(false) end - defp page_it_index_dynamic(block_number, tx_index, it_index) when it_index > 0 do + defp page_it_index_dynamic(block_number, transaction_index, it_index) + when block_number >= 0 and transaction_index >= 0 and it_index > 0 do dynamic( - [transaction: tx, internal_transaction: it], - tx.block_number == ^block_number and tx.index == ^tx_index and + [transaction: transaction, internal_transaction: it], + transaction.block_number == ^block_number and transaction.index == ^transaction_index and it.index < ^it_index ) end @@ -484,11 +647,12 @@ defmodule Explorer.Chain.AdvancedFilter do end defp page_tt_index_dynamic(binding, block_number, tt_index, tt_batch_index) - when tt_index > 0 and tt_batch_index > 1 do + when block_number >= 0 and tt_index > 0 and tt_batch_index > 1 do dynamic(as(^binding).block_number == ^block_number and as(^binding).log_index <= ^tt_index) end - defp page_tt_index_dynamic(binding, block_number, tt_index, _tt_batch_index) when tt_index > 0 do + defp page_tt_index_dynamic(binding, block_number, tt_index, _tt_batch_index) + when block_number >= 0 and tt_index > 0 do dynamic(as(^binding).block_number == ^block_number and as(^binding).log_index < ^tt_index) end @@ -496,7 +660,8 @@ defmodule Explorer.Chain.AdvancedFilter do dynamic(false) end - defp page_tt_batch_index_dynamic(block_number, tt_index, tt_batch_index) when tt_batch_index > 1 do + defp page_tt_batch_index_dynamic(block_number, tt_index, tt_batch_index) + when block_number >= 0 and tt_index >= 0 and tt_batch_index > 1 do dynamic( [unnested_token_transfer: tt], ^page_block_number_dynamic(:unnested_token_transfer, block_number) or @@ -518,38 +683,59 @@ defmodule Explorer.Chain.AdvancedFilter do defp limit_query(query, _), do: query - defp apply_token_transfers_filters(query, options) do - query - |> filter_by_tx_type(options[:tx_types]) + defp apply_token_transfers_filters(query_function, options) do + query_function + |> filter_by_transaction_type(options[:transaction_types]) |> filter_token_transfers_by_methods(options[:methods]) - |> filter_by_token(options[:token_contract_address_hashes][:include], :include) - |> filter_by_token(options[:token_contract_address_hashes][:exclude], :exclude) - |> apply_common_filters(options) + |> filter_token_transfers_by_age(options) + |> filter_by_token(options[:token_contract_address_hashes]) + |> filter_token_transfers_by_addresses( + options[:from_address_hashes], + options[:to_address_hashes], + options[:address_relation] + ) + |> filter_token_transfers_by_amount(options[:amount][:from], options[:amount][:to]) end defp apply_transactions_filters(query, options) do query |> filter_transactions_by_amount(options[:amount][:from], options[:amount][:to]) |> filter_transactions_by_methods(options[:methods]) - |> apply_common_filters(options) + |> only_collated_transactions() + |> filter_by_addresses(options[:from_address_hashes], options[:to_address_hashes], options[:address_relation]) + |> filter_by_age(:transaction, options) end - defp apply_common_filters(query, options) do + defp apply_internal_transactions_filters(query, options) do query + |> filter_transactions_by_amount(options[:amount][:from], options[:amount][:to]) + |> filter_transactions_by_methods(options[:methods]) |> only_collated_transactions() - |> filter_by_timestamp(options[:age][:from], options[:age][:to]) - |> filter_by_addresses(options[:from_address_hashes], options[:to_address_hashes], options[:address_relation]) + |> filter_by_age(:transaction, options) + |> filter_internal_transactions_by_addresses( + options[:from_address_hashes], + options[:to_address_hashes], + options[:address_relation] + ) end defp only_collated_transactions(query) do query |> where(not is_nil(as(:transaction).block_number) and not is_nil(as(:transaction).index)) end - defp filter_by_tx_type(query, [_ | _] = tx_types) do - query |> where([token_transfer], token_transfer.token_type in ^tx_types) + defp filter_by_transaction_type(query_function, [_ | _] = transaction_types) do + if DenormalizationHelper.tt_denormalization_finished?() do + fn query, unnested? -> + query |> where([token_transfer], token_transfer.token_type in ^transaction_types) |> query_function.(unnested?) + end + else + fn query, unnested? -> + query |> where([token: token], token.type in ^transaction_types) |> query_function.(unnested?) + end + end end - defp filter_by_tx_type(query, _), do: query + defp filter_by_transaction_type(query_function, _), do: query_function defp filter_transactions_by_methods(query, [_ | _] = methods) do prepared_methods = prepare_methods(methods) @@ -559,13 +745,17 @@ defmodule Explorer.Chain.AdvancedFilter do defp filter_transactions_by_methods(query, _), do: query - defp filter_token_transfers_by_methods(query, [_ | _] = methods) do + defp filter_token_transfers_by_methods(query_function, [_ | _] = methods) do prepared_methods = prepare_methods(methods) - query |> where(fragment("substring(? FOR 4)", as(:transaction).input) in ^prepared_methods) + fn query, unnested? -> + query + |> where(fragment("substring(? FOR 4)", as(:transaction).input) in ^prepared_methods) + |> query_function.(unnested?) + end end - defp filter_token_transfers_by_methods(query, _), do: query + defp filter_token_transfers_by_methods(query_function, _), do: query_function defp prepare_methods(methods) do methods @@ -578,16 +768,48 @@ defmodule Explorer.Chain.AdvancedFilter do end) end - defp filter_by_timestamp(query, %DateTime{} = from, %DateTime{} = to) do - query |> where(as(:transaction).block_timestamp >= ^from and as(:transaction).block_timestamp <= ^to) + defp filter_token_transfers_by_age(query_function, options) do + fn query, unnested? -> query |> filter_by_age(:token_transfer, options) |> query_function.(unnested?) end + end + + defp filter_by_age(query, entity, options) do + query + |> do_filter_by_age(options[:block_numbers_age][:from], options[:age][:from], entity, :from) + |> do_filter_by_age(options[:block_numbers_age][:to], options[:age][:to], entity, :to) + end + + defp do_filter_by_age(query, {:ok, block_number}, _timestamp, entity, direction) do + filter_by_block_number(query, block_number, entity, direction) + end + + defp do_filter_by_age(query, _block_number, timestamp, _entity, direction) do + filter_by_timestamp(query, timestamp, direction) + end + + defp filter_by_block_number(query, from, entity, :from) when not is_nil(from) do + query |> where(as(^entity).block_number >= ^from) + end + + defp filter_by_block_number(query, to, entity, :to) when not is_nil(to) do + query |> where(as(^entity).block_number <= ^to) end - defp filter_by_timestamp(query, %DateTime{} = from, _to) do - query |> where(as(:transaction).block_timestamp >= ^from) + defp filter_by_block_number(query, _, _, _), do: query + + defp filter_by_timestamp(query, %DateTime{} = from, :from) do + if DenormalizationHelper.transactions_denormalization_finished?() do + query |> where(as(:transaction).block_timestamp >= ^from) + else + query |> where(as(:block).timestamp >= ^from) + end end - defp filter_by_timestamp(query, _from, %DateTime{} = to) do - query |> where(as(:transaction).block_timestamp <= ^to) + defp filter_by_timestamp(query, %DateTime{} = to, :to) do + if DenormalizationHelper.transactions_denormalization_finished?() do + query |> where(as(:transaction).block_timestamp <= ^to) + else + query |> where(as(:block).timestamp <= ^to) + end end defp filter_by_timestamp(query, _, _), do: query @@ -645,84 +867,566 @@ defmodule Explorer.Chain.AdvancedFilter do dynamic([t], ^from_addresses_dynamic and ^to_addresses_dynamic) end - @eth_decimals 1000_000_000_000_000_000 + defp filter_token_transfers_by_addresses(query_function, from_addresses_params, to_addresses_params, relation) do + case {process_address_inclusion(from_addresses_params), process_address_inclusion(to_addresses_params)} do + {nil, nil} -> query_function + {from, nil} -> do_filter_token_transfers_by_address(query_function, from, :from_address_hash) + {nil, to} -> do_filter_token_transfers_by_address(query_function, to, :to_address_hash) + {from, to} -> do_filter_token_transfers_by_both_addresses(query_function, from, to, relation) + end + end - defp filter_transactions_by_amount(query, from, to) when not is_nil(from) and not is_nil(to) and from < to do - query |> where([t], t.value / @eth_decimals >= ^from and t.value / @eth_decimals <= ^to) + defp do_filter_token_transfers_by_address(query_function, {:include, addresses}, field) do + fn query, _unnested? -> + queries = + addresses + |> Enum.map(fn address -> + query |> where([token_transfer], field(token_transfer, ^field) == ^address) |> query_function.(true) + end) + |> map_first(&subquery/1) + |> Enum.reduce(fn query, acc -> union_all(acc, ^query) end) + + from(token_transfer in subquery(queries), + as: :unnested_token_transfer, + order_by: [desc: token_transfer.block_number, desc: token_transfer.log_index] + ) + end end - defp filter_transactions_by_amount(query, _from, to) when not is_nil(to) do - query |> where([t], t.value / @eth_decimals <= ^to) + defp do_filter_token_transfers_by_address(query_function, {:exclude, addresses}, field) do + fn query, unnested? -> + query |> where([t], field(t, ^field) not in ^addresses) |> query_function.(unnested?) + end end - defp filter_transactions_by_amount(query, from, _to) when not is_nil(from) do - query |> where([t], t.value / @eth_decimals >= ^from) + defp do_filter_token_transfers_by_both_addresses(query_function, {:include, from}, {:include, to}, relation) do + fn query, _unnested? -> + from_queries = + from + |> Enum.map(fn from_address -> + query |> where([token_transfer], token_transfer.from_address_hash == ^from_address) |> query_function.(true) + end) + + to_queries = + to + |> Enum.map(fn to_address -> + query |> where([token_transfer], token_transfer.to_address_hash == ^to_address) |> query_function.(true) + end) + + do_filter_token_transfers_by_both_addresses_to_include(from_queries, to_queries, relation) + end end - defp filter_transactions_by_amount(query, _, _), do: query + defp do_filter_token_transfers_by_both_addresses(query_function, {:include, from}, {:exclude, to}, :and) do + fn query, _unnested? -> + from_queries = + from + |> Enum.map(fn from_address -> + query + |> where( + [token_transfer], + token_transfer.from_address_hash == ^from_address and token_transfer.to_address_hash not in ^to + ) + |> query_function.(true) + end) + |> map_first(&subquery/1) + |> Enum.reduce(fn query, acc -> union_all(acc, ^query) end) + + from(token_transfer in subquery(from_queries), + as: :unnested_token_transfer, + order_by: [desc: token_transfer.block_number, desc: token_transfer.log_index] + ) + end + end - defp filter_token_transfers_by_amount(query, from, to) when not is_nil(from) and not is_nil(to) and from < to do - unnested_query = make_token_transfer_query_unnested(query) + defp do_filter_token_transfers_by_both_addresses(query_function, {:include, from}, {:exclude, to}, _relation) do + fn query, _unnested? -> + from_queries = + from + |> Enum.map(fn from_address -> + query + |> where( + [token_transfer], + token_transfer.from_address_hash == ^from_address or token_transfer.to_address_hash not in ^to + ) + |> query_function.(true) + end) + |> map_first(&subquery/1) + |> Enum.reduce(fn query, acc -> union_all(acc, ^query) end) + + from(token_transfer in subquery(from_queries), + as: :unnested_token_transfer, + order_by: [desc: token_transfer.block_number, desc: token_transfer.log_index] + ) + end + end - unnested_query - |> where( - [unnested_token_transfer: tt], - tt.amount / fragment("10 ^ COALESCE(?, 0)", tt.token_decimals) >= ^from and - tt.amount / fragment("10 ^ COALESCE(?, 0)", tt.token_decimals) <= ^to + defp do_filter_token_transfers_by_both_addresses(query_function, {:exclude, from}, {:include, to}, :and) do + fn query, _unnested? -> + to_queries = + to + |> Enum.map(fn to_address -> + query + |> where( + [token_transfer], + token_transfer.to_address_hash == ^to_address and token_transfer.from_address_hash not in ^from + ) + |> query_function.(true) + end) + |> map_first(&subquery/1) + |> Enum.reduce(fn query, acc -> union_all(acc, ^query) end) + + from(token_transfer in subquery(to_queries), + as: :unnested_token_transfer, + order_by: [desc: token_transfer.block_number, desc: token_transfer.log_index] + ) + end + end + + defp do_filter_token_transfers_by_both_addresses(query_function, {:exclude, from}, {:include, to}, _relation) do + fn query, _unnested? -> + to_queries = + to + |> Enum.map(fn to_address -> + query + |> where( + [token_transfer], + token_transfer.to_address_hash == ^to_address or token_transfer.from_address_hash not in ^from + ) + |> query_function.(true) + end) + |> map_first(&subquery/1) + |> Enum.reduce(fn query, acc -> union_all(acc, ^query) end) + + from(token_transfer in subquery(to_queries), + as: :unnested_token_transfer, + order_by: [desc: token_transfer.block_number, desc: token_transfer.log_index] + ) + end + end + + defp do_filter_token_transfers_by_both_addresses(query_function, {:exclude, from}, {:exclude, to}, :and) do + fn query, unnested? -> + query + |> where([t], t.from_address_hash not in ^from and t.to_address_hash not in ^to) + |> query_function.(unnested?) + end + end + + defp do_filter_token_transfers_by_both_addresses(query_function, {:exclude, from}, {:exclude, to}, _relation) do + fn query, unnested? -> + query + |> where([t], t.from_address_hash not in ^from or t.to_address_hash not in ^to) + |> query_function.(unnested?) + end + end + + defp do_filter_token_transfers_by_both_addresses_to_include(from_queries, to_queries, relation) do + case relation do + :and -> + united_from_queries = + from_queries |> map_first(&subquery/1) |> Enum.reduce(fn query, acc -> union_all(acc, ^query) end) + + united_to_queries = + to_queries |> map_first(&subquery/1) |> Enum.reduce(fn query, acc -> union_all(acc, ^query) end) + + from(token_transfer in subquery(intersect_all(united_from_queries, ^united_to_queries)), + as: :unnested_token_transfer, + order_by: [desc: token_transfer.block_number, desc: token_transfer.log_index] + ) + + _ -> + union_query = + from_queries + |> Kernel.++(to_queries) + |> map_first(&subquery/1) + |> Enum.reduce(fn query, acc -> union(acc, ^query) end) + + from(token_transfer in subquery(union_query), + as: :unnested_token_transfer, + order_by: [desc: token_transfer.block_number, desc: token_transfer.log_index] + ) + end + end + + defp filter_internal_transactions_by_addresses(query, from_addresses, to_addresses, relation) do + case {process_address_inclusion(from_addresses), process_address_inclusion(to_addresses)} do + {nil, nil} -> query + {from, nil} -> do_filter_internal_transactions_by_address(query, from, :from_address_hash) + {nil, to} -> do_filter_internal_transactions_by_address(query, to, :to_address_hash) + {from, to} -> do_filter_internal_transactions_by_both_addresses(query, from, to, relation) + end + end + + defp do_filter_internal_transactions_by_address(query, {:include, addresses}, field) do + queries = + addresses + |> Enum.map(fn address -> + query |> where([token_transfer], field(token_transfer, ^field) == ^address) + end) + |> map_first(&subquery/1) + |> Enum.reduce(fn query, acc -> union_all(acc, ^query) end) + + from(internal_transaction in subquery(queries), + order_by: [ + desc: internal_transaction.block_number, + desc: internal_transaction.transaction_index, + desc: internal_transaction.index + ] ) end - defp filter_token_transfers_by_amount(query, _from, to) when not is_nil(to) do - unnested_query = make_token_transfer_query_unnested(query) + defp do_filter_internal_transactions_by_address(query, {:exclude, addresses}, field) do + query |> where([t], field(t, ^field) not in ^addresses) + end - unnested_query - |> where( - [unnested_token_transfer: tt], - tt.amount / fragment("10 ^ COALESCE(?, 0)", tt.token_decimals) <= ^to + defp do_filter_internal_transactions_by_both_addresses(query, {:include, from}, {:include, to}, relation) do + from_queries = + from + |> Enum.map(fn from_address -> + query |> where([internal_transaction], internal_transaction.from_address_hash == ^from_address) + end) + + to_queries = + to + |> Enum.map(fn to_address -> + query |> where([internal_transaction], internal_transaction.to_address_hash == ^to_address) + end) + + do_filter_internal_transactions_by_both_addresses_to_include(from_queries, to_queries, relation) + end + + defp do_filter_internal_transactions_by_both_addresses(query, {:include, from}, {:exclude, to}, :and) do + from_queries = + from + |> Enum.map(fn from_address -> + query + |> where( + [internal_transaction], + internal_transaction.from_address_hash == ^from_address and internal_transaction.to_address_hash not in ^to + ) + end) + |> map_first(&subquery/1) + |> Enum.reduce(fn query, acc -> union_all(acc, ^query) end) + + from(internal_transaction in subquery(from_queries), + order_by: [ + desc: internal_transaction.block_number, + desc: internal_transaction.transaction_index, + desc: internal_transaction.index + ] + ) + end + + defp do_filter_internal_transactions_by_both_addresses(query, {:include, from}, {:exclude, to}, _relation) do + from_queries = + from + |> Enum.map(fn from_address -> + query + |> where( + [internal_transaction], + internal_transaction.from_address_hash == ^from_address or internal_transaction.to_address_hash not in ^to + ) + end) + |> map_first(&subquery/1) + |> Enum.reduce(fn query, acc -> union_all(acc, ^query) end) + + from(internal_transaction in subquery(from_queries), + order_by: [ + desc: internal_transaction.block_number, + desc: internal_transaction.transaction_index, + desc: internal_transaction.index + ] ) end - defp filter_token_transfers_by_amount(query, from, _to) when not is_nil(from) do - unnested_query = make_token_transfer_query_unnested(query) + defp do_filter_internal_transactions_by_both_addresses(query, {:exclude, from}, {:include, to}, :and) do + to_queries = + to + |> Enum.map(fn to_address -> + query + |> where( + [internal_transaction], + internal_transaction.to_address_hash == ^to_address and internal_transaction.from_address_hash not in ^from + ) + end) + |> map_first(&subquery/1) + |> Enum.reduce(fn query, acc -> union_all(acc, ^query) end) + + from(internal_transaction in subquery(to_queries), + order_by: [ + desc: internal_transaction.block_number, + desc: internal_transaction.transaction_index, + desc: internal_transaction.index + ] + ) + end - unnested_query - |> where( - [unnested_token_transfer: tt], - tt.amount / fragment("10 ^ COALESCE(?, 0)", tt.token_decimals) >= ^from + defp do_filter_internal_transactions_by_both_addresses(query, {:exclude, from}, {:include, to}, _relation) do + to_queries = + to + |> Enum.map(fn to_address -> + query + |> where( + [internal_transaction], + internal_transaction.to_address_hash == ^to_address or internal_transaction.from_address_hash not in ^from + ) + end) + |> map_first(&subquery/1) + |> Enum.reduce(fn query, acc -> union_all(acc, ^query) end) + + from(internal_transaction in subquery(to_queries), + order_by: [ + desc: internal_transaction.block_number, + desc: internal_transaction.transaction_index, + desc: internal_transaction.index + ] ) end - defp filter_token_transfers_by_amount(query, _, _), do: query + defp do_filter_internal_transactions_by_both_addresses(query, {:exclude, from}, {:exclude, to}, :and) do + query + |> where([t], t.from_address_hash not in ^from and t.to_address_hash not in ^to) + end + + defp do_filter_internal_transactions_by_both_addresses(query, {:exclude, from}, {:exclude, to}, _relation) do + query + |> where([t], t.from_address_hash not in ^from or t.to_address_hash not in ^to) + end - defp make_token_transfer_query_unnested(query) do - if has_named_binding?(query, :unnested_token_transfer) do + defp do_filter_internal_transactions_by_both_addresses_to_include(from_queries, to_queries, relation) do + case relation do + :and -> + united_from_queries = + from_queries |> map_first(&subquery/1) |> Enum.reduce(fn query, acc -> union_all(acc, ^query) end) + + united_to_queries = + to_queries |> map_first(&subquery/1) |> Enum.reduce(fn query, acc -> union_all(acc, ^query) end) + + from(internal_transaction in subquery(intersect_all(united_from_queries, ^united_to_queries)), + order_by: [ + desc: internal_transaction.block_number, + desc: internal_transaction.transaction_index, + desc: internal_transaction.index + ] + ) + + _ -> + union_query = + from_queries + |> Kernel.++(to_queries) + |> map_first(&subquery/1) + |> Enum.reduce(fn query, acc -> union(acc, ^query) end) + + from(internal_transaction in subquery(union_query), + order_by: [ + desc: internal_transaction.block_number, + desc: internal_transaction.transaction_index, + desc: internal_transaction.index + ] + ) + end + end + + @eth_decimals 1_000_000_000_000_000_000 + + defp filter_transactions_by_amount(query, from, to) when not is_nil(from) and not is_nil(to) do + if Decimal.positive?(to) and Decimal.lt?(from, to) do + query |> where([t], t.value / @eth_decimals >= ^from and t.value / @eth_decimals <= ^to) + else + query |> where(false) + end + end + + defp filter_transactions_by_amount(query, _from, to) when not is_nil(to) do + if Decimal.positive?(to) do + query |> where([t], t.value / @eth_decimals <= ^to) + else + query |> where(false) + end + end + + defp filter_transactions_by_amount(query, from, _to) when not is_nil(from) do + if Decimal.positive?(from) do + query |> where([t], t.value / @eth_decimals >= ^from) + else + query + end + end + + defp filter_transactions_by_amount(query, _, _), do: query + + defp filter_token_transfers_by_amount(query_function, from, to) do + fn query, unnested? -> + query + |> filter_token_transfers_by_amount_before_subquery(from, to) + |> query_function.(unnested?) + |> filter_token_transfers_by_amount_after_subquery(from, to) + end + end + + defp filter_token_transfers_by_amount_before_subquery(query, from, to) + when not is_nil(from) and not is_nil(to) and from < to do + if Decimal.positive?(to) and Decimal.lt?(from, to) do + query + |> where( + [tt, token: token], + ^to * fragment("10 ^ COALESCE(?, 0)", token.decimals) >= + fragment("ANY(COALESCE(?, ARRAY[COALESCE(?, 1)]))", tt.amounts, tt.amount) and + ^from * fragment("10 ^ COALESCE(?, 0)", token.decimals) <= + fragment("ANY(COALESCE(?, ARRAY[COALESCE(?, 1)]))", tt.amounts, tt.amount) + ) + else + query |> where(false) + end + end + + defp filter_token_transfers_by_amount_before_subquery(query, _from, to) when not is_nil(to) do + if Decimal.positive?(to) do + query + |> where( + [tt, token: token], + ^to * fragment("10 ^ COALESCE(?, 0)", token.decimals) >= + fragment("ANY(COALESCE(?, ARRAY[COALESCE(?, 1)]))", tt.amounts, tt.amount) + ) + else + query |> where(false) + end + end + + defp filter_token_transfers_by_amount_before_subquery(query, from, _to) when not is_nil(from) do + if Decimal.positive?(from) do query + |> where( + [tt, token: token], + ^from * fragment("10 ^ COALESCE(?, 0)", token.decimals) <= + fragment("ANY(COALESCE(?, ARRAY[COALESCE(?, 1)]))", tt.amounts, tt.amount) + ) else + query + end + end + + defp filter_token_transfers_by_amount_before_subquery(query, _, _), do: query + + defp filter_token_transfers_by_amount_after_subquery(unnested_query, from, to) + when not is_nil(from) and not is_nil(to) and from < to do + if Decimal.positive?(to) and Decimal.lt?(from, to) do + unnested_query + |> where( + [unnested_token_transfer: tt], + tt.amount / fragment("10 ^ COALESCE(?, 0)", tt.token_decimals) >= ^from and + tt.amount / fragment("10 ^ COALESCE(?, 0)", tt.token_decimals) <= ^to + ) + else + unnested_query |> where(false) + end + end + + defp filter_token_transfers_by_amount_after_subquery(unnested_query, _from, to) when not is_nil(to) do + if Decimal.positive?(to) do + unnested_query + |> where( + [unnested_token_transfer: tt], + tt.amount / fragment("10 ^ COALESCE(?, 0)", tt.token_decimals) <= ^to + ) + else + unnested_query |> where(false) + end + end + + defp filter_token_transfers_by_amount_after_subquery(unnested_query, from, _to) when not is_nil(from) do + if Decimal.positive?(from) do + unnested_query + |> where( + [unnested_token_transfer: tt], + tt.amount / fragment("10 ^ COALESCE(?, 0)", tt.token_decimals) >= ^from + ) + else + unnested_query + end + end + + defp filter_token_transfers_by_amount_after_subquery(query, _, _), do: query + + defp make_token_transfer_query_unnested(query, false) do + with_named_binding(query, :unnested_token_transfer, fn query, binding -> from(token_transfer in subquery(query), + as: ^binding + ) + end) + end + + defp make_token_transfer_query_unnested(query, _), do: query + + defp filter_by_token(query_function, token_contract_address_hashes) when is_list(token_contract_address_hashes) do + case process_address_inclusion(token_contract_address_hashes) do + nil -> + query_function + + {include_or_exclude, token_contract_address_hashes} -> + filtered = token_contract_address_hashes |> Enum.reject(&(&1 == "native")) + + if Enum.empty?(filtered) do + query_function + else + do_filter_by_token(query_function, {include_or_exclude, filtered}) + end + end + end + + defp filter_by_token(query_function, _), do: query_function + + defp do_filter_by_token(query_function, {:include, token_contract_address_hashes}) do + fn query, _unnested? -> + queries = + token_contract_address_hashes + |> Enum.map(fn address -> + query + |> where([token_transfer], token_transfer.token_contract_address_hash == ^address) + |> query_function.(true) + end) + |> map_first(&subquery/1) + |> Enum.reduce(fn query, acc -> union_all(acc, ^query) end) + + from(token_transfer in subquery(queries), as: :unnested_token_transfer, - preload: [ - :transaction, - :token, - from_address: [:names, :smart_contract, :proxy_implementations], - to_address: [:names, :smart_contract, :proxy_implementations] - ], - select_merge: %{ - token_ids: [token_transfer.token_id], - amounts: [token_transfer.amount] - } + order_by: [desc: token_transfer.block_number, desc: token_transfer.log_index] ) end end - defp filter_by_token(query, [_ | _] = token_contract_address_hashes, :include) do - filtered = token_contract_address_hashes |> Enum.reject(&(&1 == "native")) - query |> where([token_transfer], token_transfer.token_contract_address_hash in ^filtered) + defp do_filter_by_token(query_function, {:exclude, token_contract_address_hashes}) do + fn query, unnested? -> + query_function.( + from(token_transfer in query, + left_join: to_exclude in fragment("UNNEST(?)", type(^token_contract_address_hashes, {:array, Hash.Address})), + on: token_transfer.token_contract_address_hash == to_exclude, + where: is_nil(to_exclude) + ), + unnested? + ) + end end - defp filter_by_token(query, [_ | _] = token_contract_address_hashes, :exclude) do - filtered = token_contract_address_hashes |> Enum.reject(&(&1 == "native")) - query |> where([token_transfer], token_transfer.token_contract_address_hash not in ^filtered) + defp process_address_inclusion(addresses) when is_list(addresses) do + case {Keyword.get(addresses, :include, []), Keyword.get(addresses, :exclude, [])} do + {to_include, to_exclude} when to_include in [nil, []] and to_exclude in [nil, []] -> + nil + + {to_include, to_exclude} when to_include in [nil, []] and is_list(to_exclude) -> + {:exclude, to_exclude} + + {to_include, to_exclude} when is_list(to_include) -> + case to_include -- (to_exclude || []) do + [] -> nil + to_include -> {:include, to_include} + end + end end - defp filter_by_token(query, _, _), do: query + defp process_address_inclusion(_), do: nil + + defp map_first([h | t], f), do: [f.(h) | t] + defp map_first([], _f), do: [] end diff --git a/apps/explorer/lib/explorer/chain/arbitrum/batch_transaction.ex b/apps/explorer/lib/explorer/chain/arbitrum/batch_transaction.ex index d1487ce..dca5ed9 100644 --- a/apps/explorer/lib/explorer/chain/arbitrum/batch_transaction.ex +++ b/apps/explorer/lib/explorer/chain/arbitrum/batch_transaction.ex @@ -14,21 +14,21 @@ defmodule Explorer.Chain.Arbitrum.BatchTransaction do alias Explorer.Chain.Arbitrum.L1Batch alias Explorer.Chain.{Hash, Transaction} - @required_attrs ~w(batch_number tx_hash)a + @required_attrs ~w(batch_number transaction_hash)a @typedoc """ Descriptor of the rollup transaction included in an Arbitrum batch: * `batch_number` - The number of the Arbitrum batch. - * `tx_hash` - The hash of the rollup transaction. + * `transaction_hash` - The hash of the rollup transaction. """ @type to_import :: %{ :batch_number => non_neg_integer(), - :tx_hash => binary() + :transaction_hash => binary() } @typedoc """ - * `tx_hash` - The hash of the rollup transaction. - * `l2_transaction` - An instance of `Explorer.Chain.Transaction` referenced by `tx_hash`. + * `transaction_hash` - The hash of the rollup transaction. + * `l2_transaction` - An instance of `Explorer.Chain.Transaction` referenced by `transaction_hash`. * `batch_number` - The number of the Arbitrum batch. * `batch` - An instance of `Explorer.Chain.Arbitrum.L1Batch` referenced by `batch_number`. """ @@ -37,7 +37,7 @@ defmodule Explorer.Chain.Arbitrum.BatchTransaction do belongs_to(:batch, L1Batch, foreign_key: :batch_number, references: :number, type: :integer) belongs_to(:l2_transaction, Transaction, - foreign_key: :tx_hash, + foreign_key: :transaction_hash, primary_key: true, references: :hash, type: Hash.Full @@ -56,6 +56,6 @@ defmodule Explorer.Chain.Arbitrum.BatchTransaction do |> validate_required(@required_attrs) |> foreign_key_constraint(:batch_number) |> foreign_key_constraint(:block_hash) - |> unique_constraint(:tx_hash) + |> unique_constraint(:transaction_hash) end end diff --git a/apps/explorer/lib/explorer/chain/arbitrum/da_multi_purpose_record.ex b/apps/explorer/lib/explorer/chain/arbitrum/da_multi_purpose_record.ex index 36409e6..c7ef42e 100644 --- a/apps/explorer/lib/explorer/chain/arbitrum/da_multi_purpose_record.ex +++ b/apps/explorer/lib/explorer/chain/arbitrum/da_multi_purpose_record.ex @@ -84,22 +84,23 @@ defmodule Explorer.Chain.Arbitrum.DaMultiPurposeRecord.Helper do ## Parameters - `height`: The height of the block in the Celestia network. - - `tx_commitment`: The transaction commitment. + - `transaction_commitment`: The transaction commitment. ## Returns - A binary representing the calculated data key for the record containing Celestia blob data. """ @spec calculate_celestia_data_key(binary() | non_neg_integer(), binary() | Explorer.Chain.Hash.t()) :: binary() - def calculate_celestia_data_key(height, tx_commitment) when is_binary(height) do - calculate_celestia_data_key(String.to_integer(height), tx_commitment) + def calculate_celestia_data_key(height, transaction_commitment) when is_binary(height) do + calculate_celestia_data_key(String.to_integer(height), transaction_commitment) end - def calculate_celestia_data_key(height, %Hash{} = tx_commitment) when is_integer(height) do - calculate_celestia_data_key(height, tx_commitment.bytes) + def calculate_celestia_data_key(height, %Hash{} = transaction_commitment) when is_integer(height) do + calculate_celestia_data_key(height, transaction_commitment.bytes) end - def calculate_celestia_data_key(height, tx_commitment) when is_integer(height) and is_binary(tx_commitment) do - :crypto.hash(:sha256, :binary.encode_unsigned(height) <> tx_commitment) + def calculate_celestia_data_key(height, transaction_commitment) + when is_integer(height) and is_binary(transaction_commitment) do + :crypto.hash(:sha256, :binary.encode_unsigned(height) <> transaction_commitment) end end diff --git a/apps/explorer/lib/explorer/chain/arbitrum/reader.ex b/apps/explorer/lib/explorer/chain/arbitrum/reader.ex index 881ebe3..cdb7093 100644 --- a/apps/explorer/lib/explorer/chain/arbitrum/reader.ex +++ b/apps/explorer/lib/explorer/chain/arbitrum/reader.ex @@ -23,9 +23,11 @@ defmodule Explorer.Chain.Arbitrum.Reader do # https://github.com/OffchainLabs/go-ethereum/blob/dff302de66598c36b964b971f72d35a95148e650/core/types/transaction.go#L44C2-L50 @message_to_l2_eth_deposit 100 - @message_to_l2_submit_retryable_tx 105 - - @zero_wei 0 + @message_to_l2_submit_retryable_transaction 105 + @to_l2_messages_transaction_types [ + @message_to_l2_eth_deposit, + @message_to_l2_submit_retryable_transaction + ] @doc """ Retrieves the number of the latest L1 block where an L1-to-L2 message was discovered. @@ -110,7 +112,7 @@ defmodule Explorer.Chain.Arbitrum.Reader do missed_messages_to_l2_query() |> order_by(desc: :block_number) |> limit(1) - |> select([rollup_tx], rollup_tx.block_number) + |> select([rollup_transaction], rollup_transaction.block_number) |> Repo.one(timeout: :infinity) end @@ -193,7 +195,7 @@ defmodule Explorer.Chain.Arbitrum.Reader do Reads a list of L1 transactions by their hashes from the `arbitrum_lifecycle_l1_transactions` table and returns their IDs. ## Parameters - - `l1_tx_hashes`: A list of hashes to retrieve L1 transactions for. + - `l1_transaction_hashes`: A list of hashes to retrieve L1 transactions for. ## Returns - A list of tuples containing transaction hashes and IDs for the transaction @@ -201,12 +203,12 @@ defmodule Explorer.Chain.Arbitrum.Reader do list. """ @spec lifecycle_transaction_ids([binary()]) :: [{Hash.t(), non_neg_integer}] - def lifecycle_transaction_ids(l1_tx_hashes) when is_list(l1_tx_hashes) do + def lifecycle_transaction_ids(l1_transaction_hashes) when is_list(l1_transaction_hashes) do query = from( lt in LifecycleTransaction, select: {lt.hash, lt.id}, - where: lt.hash in ^l1_tx_hashes + where: lt.hash in ^l1_transaction_hashes ) Repo.all(query) @@ -216,7 +218,7 @@ defmodule Explorer.Chain.Arbitrum.Reader do Reads a list of L1 transactions by their hashes from the `arbitrum_lifecycle_l1_transactions` table. ## Parameters - - `l1_tx_hashes`: A list of hashes to retrieve L1 transactions for. + - `l1_transaction_hashes`: A list of hashes to retrieve L1 transactions for. ## Returns - A list of `Explorer.Chain.Arbitrum.LifecycleTransaction` corresponding to the @@ -224,11 +226,11 @@ defmodule Explorer.Chain.Arbitrum.Reader do list. """ @spec lifecycle_transactions([binary()]) :: [LifecycleTransaction.t()] - def lifecycle_transactions(l1_tx_hashes) when is_list(l1_tx_hashes) do + def lifecycle_transactions(l1_transaction_hashes) when is_list(l1_transaction_hashes) do query = from( lt in LifecycleTransaction, - where: lt.hash in ^l1_tx_hashes + where: lt.hash in ^l1_transaction_hashes ) Repo.all(query) @@ -475,11 +477,11 @@ defmodule Explorer.Chain.Arbitrum.Reader do def l1_block_of_latest_execution do query = from( - tx in LifecycleTransaction, + transaction in LifecycleTransaction, inner_join: ex in L1Execution, - on: tx.id == ex.execution_id, - select: tx.block_number, - order_by: [desc: tx.block_number], + on: transaction.id == ex.execution_id, + select: transaction.block_number, + order_by: [desc: transaction.block_number], limit: 1 ) @@ -497,11 +499,11 @@ defmodule Explorer.Chain.Arbitrum.Reader do def l1_block_of_earliest_execution do query = from( - tx in LifecycleTransaction, + transaction in LifecycleTransaction, inner_join: ex in L1Execution, - on: tx.id == ex.execution_id, - select: tx.block_number, - order_by: [asc: tx.block_number], + on: transaction.id == ex.execution_id, + select: transaction.block_number, + order_by: [asc: transaction.block_number], limit: 1 ) @@ -670,11 +672,11 @@ defmodule Explorer.Chain.Arbitrum.Reader do main_query = from( subquery in subquery(confirmed_combined_ranges_query), - inner_join: tx_cur in LifecycleTransaction, - on: subquery.confirmation_id == tx_cur.id, - left_join: tx_prev in LifecycleTransaction, - on: subquery.prev_confirmation_id == tx_prev.id, - select: {tx_prev.block_number, tx_cur.block_number}, + inner_join: current_transaction in LifecycleTransaction, + on: subquery.confirmation_id == current_transaction.id, + left_join: previous_transaction in LifecycleTransaction, + on: subquery.prev_confirmation_id == previous_transaction.id, + select: {previous_transaction.block_number, current_transaction.block_number}, where: subquery.min_block_num - 1 != subquery.prev_max_number or is_nil(subquery.prev_max_number), order_by: [desc: subquery.min_block_num], limit: 1 @@ -824,9 +826,12 @@ defmodule Explorer.Chain.Arbitrum.Reader do @spec transactions_for_missed_messages_to_l2(non_neg_integer(), non_neg_integer()) :: [Hash.t()] def transactions_for_missed_messages_to_l2(start_block, end_block) do missed_messages_to_l2_query() - |> where([rollup_tx], rollup_tx.block_number >= ^start_block and rollup_tx.block_number <= ^end_block) + |> where( + [rollup_transaction], + rollup_transaction.block_number >= ^start_block and rollup_transaction.block_number <= ^end_block + ) |> order_by(desc: :block_timestamp) - |> select([rollup_tx], rollup_tx.hash) + |> select([rollup_transaction], rollup_transaction.hash) |> Repo.all() end @@ -838,21 +843,14 @@ defmodule Explorer.Chain.Arbitrum.Reader do # table. A message is considered missed if there is a transaction without a # matching message record. # - # For transactions that could be considered ETH deposits, it checks - # that the message value is not zero, as transactions with a zero value - # cannot be a deposit. - # # ## Returns # - A query to retrieve missed L1-to-L2 messages. @spec missed_messages_to_l2_query() :: Ecto.Query.t() defp missed_messages_to_l2_query do - from(rollup_tx in Transaction, + from(rollup_transaction in Transaction, left_join: msg in Message, - on: rollup_tx.hash == msg.completion_transaction_hash and msg.direction == :to_l2, - where: - (rollup_tx.type == ^@message_to_l2_submit_retryable_tx or - (rollup_tx.type == ^@message_to_l2_eth_deposit and rollup_tx.value != ^@zero_wei)) and - is_nil(msg.completion_transaction_hash) + on: rollup_transaction.hash == msg.completion_transaction_hash and msg.direction == :to_l2, + where: rollup_transaction.type in @to_l2_messages_transaction_types and is_nil(msg.completion_transaction_hash) ) end @@ -1057,7 +1055,7 @@ defmodule Explorer.Chain.Arbitrum.Reader do """ @spec batch_transactions(non_neg_integer() | binary(), api?: boolean()) :: [BatchTransaction.t()] def batch_transactions(batch_number, options) do - query = from(tx in BatchTransaction, where: tx.batch_number == ^batch_number) + query = from(transaction in BatchTransaction, where: transaction.batch_number == ^batch_number) select_repo(options).all(query) end @@ -1337,4 +1335,21 @@ defmodule Explorer.Chain.Arbitrum.Reader do |> Chain.join_associations(%{:transactions => :optional}) |> Repo.all() end + + @doc """ + Retrieves the message IDs of uncompleted L1-to-L2 messages. + + ## Returns + - A list of the message IDs of uncompleted L1-to-L2 messages. + """ + @spec get_uncompleted_l1_to_l2_messages_ids() :: [non_neg_integer()] + def get_uncompleted_l1_to_l2_messages_ids do + query = + from(msg in Message, + where: msg.direction == :to_l2 and is_nil(msg.completion_transaction_hash), + select: msg.message_id + ) + + Repo.all(query) + end end diff --git a/apps/explorer/lib/explorer/chain/blackfort/validator.ex b/apps/explorer/lib/explorer/chain/blackfort/validator.ex new file mode 100644 index 0000000..e36a87d --- /dev/null +++ b/apps/explorer/lib/explorer/chain/blackfort/validator.ex @@ -0,0 +1,201 @@ +defmodule Explorer.Chain.Blackfort.Validator do + @moduledoc """ + Blackfort validators + """ + + use Explorer.Schema + + alias Explorer.Chain.{Address, Import} + alias Explorer.Chain.Hash.Address, as: HashAddress + alias Explorer.{Chain, Repo, SortingHelper} + + require Logger + + @default_sorting [ + asc: :address_hash + ] + + @primary_key false + typed_schema "validators_blackfort" do + field(:address_hash, HashAddress, primary_key: true) + field(:name, :binary) + field(:commission, :integer) + field(:self_bonded_amount, :decimal) + field(:delegated_amount, :decimal) + field(:slashing_status_is_slashed, :boolean, default: false) + field(:slashing_status_by_block, :integer) + field(:slashing_status_multiplier, :integer) + + has_one(:address, Address, foreign_key: :hash, references: :address_hash) + timestamps() + end + + @required_attrs ~w(address_hash)a + @optional_attrs ~w(name commission self_bonded_amount delegated_amount)a + def changeset(%__MODULE__{} = validator, attrs) do + validator + |> cast(attrs, @required_attrs ++ @optional_attrs) + |> validate_required(@required_attrs) + |> unique_constraint(:address_hash) + end + + @doc """ + Get validators list. + Keyword could contain: + - paging_options + - necessity_by_association + - sorting (supported by `Explorer.SortingHelper` module) + - state (one of `@state_enum`) + """ + @spec get_paginated_validators(keyword()) :: [t()] + def get_paginated_validators(options \\ []) do + paging_options = Keyword.get(options, :paging_options, Chain.default_paging_options()) + necessity_by_association = Keyword.get(options, :necessity_by_association, %{}) + sorting = Keyword.get(options, :sorting, []) + + __MODULE__ + |> Chain.join_associations(necessity_by_association) + |> SortingHelper.apply_sorting(sorting, @default_sorting) + |> SortingHelper.page_with_sorting(paging_options, sorting, @default_sorting) + |> Chain.select_repo(options).all() + end + + @doc """ + Get all validators + """ + @spec get_all_validators(keyword()) :: [t()] + def get_all_validators(options \\ []) do + __MODULE__ + |> Chain.select_repo(options).all() + end + + @doc """ + Delete validators by address hashes + """ + @spec delete_validators_by_address_hashes([binary() | HashAddress.t()]) :: {non_neg_integer(), nil | []} | :ignore + def delete_validators_by_address_hashes(list) when is_list(list) and length(list) > 0 do + __MODULE__ + |> where([vs], vs.address_hash in ^list) + |> Repo.delete_all() + end + + def delete_validators_by_address_hashes(_), do: :ignore + + @doc """ + Insert validators + """ + @spec insert_validators([map()]) :: {non_neg_integer(), nil | []} + def insert_validators(validators) do + Repo.insert_all(__MODULE__, validators, + on_conflict: {:replace_all_except, [:inserted_at]}, + conflict_target: [:address_hash] + ) + end + + @doc """ + Append timestamps (:inserted_at, :updated_at) + """ + @spec append_timestamps(map()) :: map() + def append_timestamps(validator) do + Map.merge(validator, Import.timestamps()) + end + + @doc """ + Derive next page params from %Explorer.Chain.Blackfort.Validator{} + """ + @spec next_page_params(t()) :: map() + def next_page_params(%__MODULE__{address_hash: address_hash}) do + %{"address_hash" => address_hash} + end + + @doc """ + Returns dynamic query for validated blocks count. Needed for SortingHelper + """ + @spec dynamic_validated_blocks() :: Ecto.Query.dynamic_expr() + def dynamic_validated_blocks do + dynamic( + [vs], + fragment( + "SELECT count(*) FROM blocks WHERE miner_hash = ?", + vs.address_hash + ) + ) + end + + @doc """ + Returns total count of validators. + """ + @spec count_validators() :: integer() + def count_validators do + Repo.aggregate(__MODULE__, :count, :address_hash) + end + + @doc """ + Returns count of new validators (inserted withing last 24h). + """ + @spec count_new_validators() :: integer() + def count_new_validators do + __MODULE__ + |> where([vs], vs.inserted_at >= ago(1, "day")) + |> Repo.aggregate(:count, :address_hash) + end + + @doc """ + Fetch list of Blackfort validators + """ + @spec fetch_validators_list() :: {:ok, list()} | :error + def fetch_validators_list do + case HTTPoison.get(validator_url(), [], follow_redirect: true) do + {:ok, %HTTPoison.Response{status_code: 200, body: body}} -> + body |> Jason.decode() |> parse_validators_info() + + error -> + Logger.error("Failed to fetch blackfort validator info: #{inspect(error)}") + :error + end + end + + defp parse_validators_info({:ok, validators}) do + {:ok, + validators + |> Enum.map(fn %{ + "address" => address_hash_string, + "name" => name, + "commission" => commission, + "self_bonded_amount" => self_bonded_amount, + "delegated_amount" => delegated_amount, + "slashing_status" => %{ + "is_slashed" => slashing_status_is_slashed, + "by_block" => slashing_status_by_block, + "multiplier" => slashing_status_multiplier + } + } -> + {:ok, address_hash} = HashAddress.cast(address_hash_string) + + %{ + address_hash: address_hash, + name: name, + commission: parse_number(commission), + self_bonded_amount: parse_number(self_bonded_amount), + delegated_amount: parse_number(delegated_amount), + slashing_status_is_slashed: slashing_status_is_slashed, + slashing_status_by_block: slashing_status_by_block, + slashing_status_multiplier: slashing_status_multiplier + } + end)} + end + + defp parse_validators_info({:error, error}) do + Logger.error("Failed to parse blackfort validator info: #{inspect(error)}") + :error + end + + defp validator_url do + Application.get_env(:explorer, __MODULE__)[:api_url] + end + + defp parse_number(string) do + {number, _} = Integer.parse(string) + number + end +end diff --git a/apps/explorer/lib/explorer/chain/block.ex b/apps/explorer/lib/explorer/chain/block.ex index 60f6025..6cc8e04 100644 --- a/apps/explorer/lib/explorer/chain/block.ex +++ b/apps/explorer/lib/explorer/chain/block.ex @@ -18,7 +18,7 @@ defmodule Explorer.Chain.Block.Schema do alias Explorer.Chain.Arbitrum.BatchBlock, as: ArbitrumBatchBlock alias Explorer.Chain.Block.{Reward, SecondDegreeRelation} alias Explorer.Chain.Celo.EpochReward, as: CeloEpochReward - alias Explorer.Chain.Optimism.TxnBatch, as: OptimismTxnBatch + alias Explorer.Chain.Optimism.TransactionBatch, as: OptimismTransactionBatch alias Explorer.Chain.ZkSync.BatchBlock, as: ZkSyncBatchBlock @chain_type_fields (case Application.compile_env(:explorer, :chain_type) do @@ -34,7 +34,7 @@ defmodule Explorer.Chain.Block.Schema do :optimism -> elem( quote do - has_one(:op_transaction_batch, OptimismTxnBatch, + has_one(:op_transaction_batch, OptimismTransactionBatch, foreign_key: :l2_block_number, references: :number ) diff --git a/apps/explorer/lib/explorer/chain/bridged_token.ex b/apps/explorer/lib/explorer/chain/bridged_token.ex index 64beb98..8e5c62e 100644 --- a/apps/explorer/lib/explorer/chain/bridged_token.ex +++ b/apps/explorer/lib/explorer/chain/bridged_token.ex @@ -213,7 +213,7 @@ defmodule Explorer.Chain.BridgedToken do """ def fetch_omni_bridged_tokens_metadata(token_addresses) do Enum.each(token_addresses, fn token_address_hash -> - created_from_int_tx_success_query = + created_from_internal_transaction_success_query = from( it in InternalTransaction, inner_join: t in assoc(it, :transaction), @@ -221,42 +221,42 @@ defmodule Explorer.Chain.BridgedToken do where: t.status == ^1 ) - created_from_int_tx_success = - created_from_int_tx_success_query + created_from_internal_transaction_success = + created_from_internal_transaction_success_query |> limit(1) |> Repo.one() - created_from_tx_query = + created_from_transaction_query = from( t in Transaction, where: t.created_contract_address_hash == ^token_address_hash ) - created_from_tx = - created_from_tx_query + created_from_transaction = + created_from_transaction_query |> Repo.all() |> Enum.count() > 0 - created_from_int_tx_query = + created_from_internal_transaction_query = from( it in InternalTransaction, where: it.created_contract_address_hash == ^token_address_hash ) - created_from_int_tx = - created_from_int_tx_query + created_from_internal_transaction = + created_from_internal_transaction_query |> Repo.all() |> Enum.count() > 0 cond do - created_from_tx -> + created_from_transaction -> set_token_bridged_status(token_address_hash, false) - created_from_int_tx && !created_from_int_tx_success -> + created_from_internal_transaction && !created_from_internal_transaction_success -> set_token_bridged_status(token_address_hash, false) - created_from_int_tx && created_from_int_tx_success -> - proceed_with_set_omni_status(token_address_hash, created_from_int_tx_success) + created_from_internal_transaction && created_from_internal_transaction_success -> + proceed_with_set_omni_status(token_address_hash, created_from_internal_transaction_success) true -> :ok @@ -266,11 +266,11 @@ defmodule Explorer.Chain.BridgedToken do :ok end - defp proceed_with_set_omni_status(token_address_hash, created_from_int_tx_success) do + defp proceed_with_set_omni_status(token_address_hash, created_from_internal_transaction_success) do {:ok, eth_omni_status} = extract_omni_bridged_token_metadata_wrapper( token_address_hash, - created_from_int_tx_success, + created_from_internal_transaction_success, :eth_omni_bridge_mediator ) @@ -280,7 +280,7 @@ defmodule Explorer.Chain.BridgedToken do else extract_omni_bridged_token_metadata_wrapper( token_address_hash, - created_from_int_tx_success, + created_from_internal_transaction_success, :bsc_omni_bridge_mediator ) end @@ -291,7 +291,7 @@ defmodule Explorer.Chain.BridgedToken do else extract_omni_bridged_token_metadata_wrapper( token_address_hash, - created_from_int_tx_success, + created_from_internal_transaction_success, :poa_omni_bridge_mediator ) end @@ -301,9 +301,13 @@ defmodule Explorer.Chain.BridgedToken do end end - defp extract_omni_bridged_token_metadata_wrapper(token_address_hash, created_from_int_tx_success, mediator) do + defp extract_omni_bridged_token_metadata_wrapper( + token_address_hash, + created_from_internal_transaction_success, + mediator + ) do omni_bridge_mediator = Application.get_env(:explorer, __MODULE__)[mediator] - %{transaction_hash: transaction_hash} = created_from_int_tx_success + %{transaction_hash: transaction_hash} = created_from_internal_transaction_success if omni_bridge_mediator && omni_bridge_mediator !== "" do {:ok, omni_bridge_mediator_hash} = Chain.string_to_address_hash(omni_bridge_mediator) diff --git a/apps/explorer/lib/explorer/chain/cache/address_sum.ex b/apps/explorer/lib/explorer/chain/cache/address_sum.ex index 8e2d67f..694cb89 100644 --- a/apps/explorer/lib/explorer/chain/cache/address_sum.ex +++ b/apps/explorer/lib/explorer/chain/cache/address_sum.ex @@ -17,9 +17,10 @@ defmodule Explorer.Chain.Cache.AddressSum do alias Explorer.Etherscan defp handle_fallback(:sum) do - # This will get the task PID if one exists and launch a new task if not + # This will get the task PID if one exists, check if it's running and launch + # a new task if task doesn't exist or it's not running. # See next `handle_fallback` definition - get_async_task() + safe_get_async_task() {:return, Decimal.new(0)} end @@ -49,7 +50,7 @@ defmodule Explorer.Chain.Cache.AddressSum do # By setting this as a `callback` an async task will be started each time the # `sum` expires (unless there is one already running) - defp async_task_on_deletion({:delete, _, :sum}), do: get_async_task() + defp async_task_on_deletion({:delete, _, :sum}), do: safe_get_async_task() defp async_task_on_deletion(_data), do: nil end diff --git a/apps/explorer/lib/explorer/chain/cache/address_sum_minus_burnt.ex b/apps/explorer/lib/explorer/chain/cache/address_sum_minus_burnt.ex index 6e8dfa9..c1f637a 100644 --- a/apps/explorer/lib/explorer/chain/cache/address_sum_minus_burnt.ex +++ b/apps/explorer/lib/explorer/chain/cache/address_sum_minus_burnt.ex @@ -17,9 +17,10 @@ defmodule Explorer.Chain.Cache.AddressSumMinusBurnt do alias Explorer.Chain.Cache.Helper defp handle_fallback(:sum_minus_burnt) do - # This will get the task PID if one exists and launch a new task if not + # This will get the task PID if one exists, check if it's running and launch + # a new task if task doesn't exist or it's not running. # See next `handle_fallback` definition - get_async_task() + safe_get_async_task() {:return, Decimal.new(0)} end @@ -56,7 +57,7 @@ defmodule Explorer.Chain.Cache.AddressSumMinusBurnt do # By setting this as a `callback` an async task will be started each time the # `sum_minus_burnt` expires (unless there is one already running) - defp async_task_on_deletion({:delete, _, :sum_minus_burnt}), do: get_async_task() + defp async_task_on_deletion({:delete, _, :sum_minus_burnt}), do: safe_get_async_task() defp async_task_on_deletion(_data), do: nil 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 index 64801ca..e144f88 100644 --- a/apps/explorer/lib/explorer/chain/cache/addresses_tabs_counters.ex +++ b/apps/explorer/lib/explorer/chain/cache/addresses_tabs_counters.ex @@ -11,7 +11,14 @@ defmodule Explorer.Chain.Cache.AddressesTabsCounters do @cache_name :addresses_tabs_counters - @typep counter_type :: :validations | :txs | :token_transfers | :token_balances | :logs | :withdrawals | :internal_txs + @typep counter_type :: + :validations + | :transactions + | :token_transfers + | :token_balances + | :logs + | :withdrawals + | :internal_transactions @typep response_status :: :limit_value | :stale | :up_to_date @spec get_counter(counter_type, String.t()) :: {DateTime.t(), non_neg_integer(), response_status} | nil @@ -41,8 +48,8 @@ defmodule Explorer.Chain.Cache.AddressesTabsCounters do address_hash |> task_cache_key(counter_type) |> fetch_from_ets_cache(@cache_name, nil) end - def save_txs_counter_progress(address_hash, results) do - GenServer.cast(__MODULE__, {:set_txs_state, address_hash, results}) + def save_transactions_counter_progress(address_hash, results) do + GenServer.cast(__MODULE__, {:set_transactions_state, address_hash, results}) end def start_link(_) do @@ -63,21 +70,21 @@ defmodule Explorer.Chain.Cache.AddressesTabsCounters do end @impl true - def handle_cast({:set_txs_state, address_hash, %{txs_types: txs_types} = results}, state) do + def handle_cast({:set_transactions_state, address_hash, %{transactions_types: transactions_types} = results}, state) do address_hash = lowercased_string(address_hash) if 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]) + transactions_types + |> Enum.reduce(state[address_hash] || %{}, fn transaction_type, acc -> + Map.put(acc, transaction_type, results[transaction_type]) end) - |> (&Map.put(&1, :txs_types, (txs_types ++ (&1[:txs_types] || [])) |> Enum.uniq())).() + |> (&Map.put(&1, :transactions_types, (transactions_types ++ (&1[:transactions_types] || [])) |> Enum.uniq())).() counter = - Counters.txs_types() + Counters.transactions_types() |> Enum.reduce([], fn type, acc -> (address_state[type] || []) ++ acc end) @@ -86,12 +93,12 @@ defmodule Explorer.Chain.Cache.AddressesTabsCounters do |> min(Counters.counters_limit()) cond do - Enum.count(address_state[:txs_types]) == 3 -> - set_counter(:txs, address_hash, counter) + Enum.count(address_state[:transactions_types]) == 3 -> + set_counter(:transactions, address_hash, counter) {:noreply, Map.put(state, address_hash, nil)} counter == Counters.counters_limit() -> - set_counter(:txs, address_hash, counter) + set_counter(:transactions, address_hash, counter) {:noreply, Map.put(state, address_hash, :limit_value)} true -> diff --git a/apps/explorer/lib/explorer/chain/cache/background_migrations.ex b/apps/explorer/lib/explorer/chain/cache/background_migrations.ex index 3d716b7..ebe0f19 100644 --- a/apps/explorer/lib/explorer/chain/cache/background_migrations.ex +++ b/apps/explorer/lib/explorer/chain/cache/background_migrations.ex @@ -10,13 +10,15 @@ defmodule Explorer.Chain.Cache.BackgroundMigrations do key: :transactions_denormalization_finished, key: :tb_token_type_finished, key: :ctb_token_type_finished, - key: :tt_denormalization_finished + key: :tt_denormalization_finished, + key: :sanitize_duplicated_log_index_logs_finished @dialyzer :no_match alias Explorer.Migrator.{ AddressCurrentTokenBalanceTokenType, AddressTokenBalanceTokenType, + SanitizeDuplicatedLogIndexLogs, TokenTransferTokenType, TransactionsDenormalization } @@ -52,4 +54,12 @@ defmodule Explorer.Chain.Cache.BackgroundMigrations do {:return, false} end + + defp handle_fallback(:sanitize_duplicated_log_index_logs_finished) do + Task.start_link(fn -> + set_sanitize_duplicated_log_index_logs_finished(SanitizeDuplicatedLogIndexLogs.migration_finished?()) + end) + + {:return, false} + end end diff --git a/apps/explorer/lib/explorer/chain/cache/blackfort_validators_counters.ex b/apps/explorer/lib/explorer/chain/cache/blackfort_validators_counters.ex new file mode 100644 index 0000000..cf2a774 --- /dev/null +++ b/apps/explorer/lib/explorer/chain/cache/blackfort_validators_counters.ex @@ -0,0 +1,97 @@ +defmodule Explorer.Chain.Cache.BlackfortValidatorsCounters do + @moduledoc """ + Counts and store counters of validators blackfort. + + It loads the count asynchronously and in a time interval of 30 minutes. + """ + + use GenServer + + alias Explorer.Chain + alias Explorer.Chain.Blackfort.Validator, as: ValidatorBlackfort + + @validators_counter_key "blackfort_validators_counter" + @new_validators_counter_key "new_blackfort_validators_counter" + + # It is undesirable to automatically start the consolidation in all environments. + # Consider the test environment: if the consolidation initiates but does not + # finish before a test ends, that test will fail. This way, hundreds of + # tests were failing before disabling the consolidation and the scheduler in + # the test env. + config = Application.compile_env(:explorer, __MODULE__) + @enable_consolidation Keyword.get(config, :enable_consolidation) + + @update_interval_in_milliseconds Keyword.get(config, :update_interval_in_milliseconds) + + @doc """ + Starts a process to periodically update validators blackfort counters + """ + @spec start_link(term()) :: GenServer.on_start() + def start_link(_) do + GenServer.start_link(__MODULE__, [], name: __MODULE__) + end + + @impl true + def init(_args) do + {:ok, %{consolidate?: @enable_consolidation}, {:continue, :ok}} + end + + defp schedule_next_consolidation do + Process.send_after(self(), :consolidate, @update_interval_in_milliseconds) + end + + @impl true + def handle_continue(:ok, %{consolidate?: true} = state) do + consolidate() + schedule_next_consolidation() + + {:noreply, state} + end + + @impl true + def handle_continue(:ok, state) do + {:noreply, state} + end + + @impl true + def handle_info(:consolidate, state) do + consolidate() + schedule_next_consolidation() + + {:noreply, state} + end + + @doc """ + Fetches values for a blackfort validators counters from the `last_fetched_counters` table. + """ + @spec get_counters(Keyword.t()) :: map() + def get_counters(options) do + %{ + validators_counter: Chain.get_last_fetched_counter(@validators_counter_key, options), + new_validators_counter: Chain.get_last_fetched_counter(@new_validators_counter_key, options) + } + end + + @doc """ + Consolidates the info by populating the `last_fetched_counters` table with the current database information. + """ + @spec consolidate() :: any() + def consolidate do + tasks = [ + Task.async(fn -> ValidatorBlackfort.count_validators() end), + Task.async(fn -> ValidatorBlackfort.count_new_validators() end) + ] + + [validators_counter, new_validators_counter] = Task.await_many(tasks, :infinity) + + Chain.upsert_last_fetched_counter(%{ + counter_type: @validators_counter_key, + value: validators_counter + }) + + Chain.upsert_last_fetched_counter(%{ + counter_type: @new_validators_counter_key, + value: new_validators_counter + }) + end +end diff --git a/apps/explorer/lib/explorer/chain/cache/block.ex b/apps/explorer/lib/explorer/chain/cache/block.ex index edb97c4..68ea799 100644 --- a/apps/explorer/lib/explorer/chain/cache/block.ex +++ b/apps/explorer/lib/explorer/chain/cache/block.ex @@ -56,9 +56,10 @@ defmodule Explorer.Chain.Cache.Block do end defp handle_fallback(:count) do - # This will get the task PID if one exists and launch a new task if not + # This will get the task PID if one exists, check if it's running and launch + # a new task if task doesn't exist or it's not running. # See next `handle_fallback` definition - get_async_task() + safe_get_async_task() {:return, nil} end @@ -95,7 +96,7 @@ defmodule Explorer.Chain.Cache.Block do # 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({:delete, _, :count}), do: safe_get_async_task() defp async_task_on_deletion(_data), do: nil diff --git a/apps/explorer/lib/explorer/chain/cache/celo_core_contracts.ex b/apps/explorer/lib/explorer/chain/cache/celo_core_contracts.ex index 42e86fc..d822a5f 100644 --- a/apps/explorer/lib/explorer/chain/cache/celo_core_contracts.ex +++ b/apps/explorer/lib/explorer/chain/cache/celo_core_contracts.ex @@ -11,7 +11,7 @@ defmodule Explorer.Chain.Cache.CeloCoreContracts do For details on the structure of the `CELO_CORE_CONTRACTS` environment variable, see `app/explorer/lib/fetch_celo_core_contracts.ex`. """ - @dialyzer :no_match + @dialyzer {:nowarn_function, get_address_updates: 1} require Logger @@ -142,9 +142,6 @@ defmodule Explorer.Chain.Cache.CeloCoreContracts do {:error, :event_name_not_found} - nil -> - {:error, :event_does_not_exist} - {:contract_address, :error} -> Logger.error(fn -> [ @@ -161,7 +158,33 @@ defmodule Explorer.Chain.Cache.CeloCoreContracts do end end - defp get_address_updates(contract_atom) do + @doc """ + Retrieves all address updates for a specified core contract. + + ## Parameters + + - `contract_atom` (`atom()`): The atom representing the core contract (e.g., + `:accounts`, `:validators`). + + ## Returns + + - `{:ok, [map()]}`: On success, returns a list of maps containing address + updates for the contract. + - `{:error, reason}`: Returns an error tuple with one of the following + reasons: `:contract_atom_not_found`, `:contract_name_not_found` + + ## Examples + + iex> Explorer.Chain.Cache.CeloCoreContracts.get_address_updates(:validators) + {:ok, [%{"address" => "0x123...", "updated_at_block_number" => 1000000}, ...]} + + iex> Explorer.Chain.Cache.CeloCoreContracts.get_address_updates(:unknown_contract) + {:error, :contract_atom_not_found} + + """ + @spec get_address_updates(atom()) :: + {:ok, [map()]} | {:error, :contract_atom_not_found | :contract_name_not_found} + def get_address_updates(contract_atom) do with {:atom, {:ok, contract_name}} <- {:atom, Map.fetch(@atom_to_contract_name, contract_atom)}, {:addresses, {:ok, contract_name_to_addresses}} <- @@ -223,6 +246,23 @@ defmodule Explorer.Chain.Cache.CeloCoreContracts do end end + @doc """ + Retrieves the block number of the first address update for a given core + contract. + + ## Parameters + + - `contract_atom` (`atom()`): The atom representing the core contract. + + ## Returns + + - `{:ok, Block.block_number()}`: The block number of the first update. + - `{:error, :contract_atom_not_found}`: If the contract atom is not + recognized. + """ + @spec get_first_update_block_number(atom()) :: + {:ok, Block.block_number() | nil} + | {:error, :contract_atom_not_found} def get_first_update_block_number(contract_atom) do with {:ok, address_updates} <- get_address_updates(contract_atom), %{"updated_at_block_number" => updated_at_block_number} <- 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 12c615d..bc9b590 100644 --- a/apps/explorer/lib/explorer/chain/cache/gas_price_oracle.ex +++ b/apps/explorer/lib/explorer/chain/cache/gas_price_oracle.ex @@ -310,9 +310,10 @@ defmodule Explorer.Chain.Cache.GasPriceOracle do defp fast_time_coefficient, do: Application.get_env(:explorer, __MODULE__)[:fast_time_coefficient] defp handle_fallback(:gas_prices) do - # This will get the task PID if one exists and launch a new task if not + # This will get the task PID if one exists, check if it's running and launch + # a new task if task doesn't exist or it's not running. # See next `handle_fallback` definition - get_async_task() + safe_get_async_task() {:return, get_old_gas_prices()} end @@ -353,7 +354,7 @@ defmodule Explorer.Chain.Cache.GasPriceOracle do # `gas_prices` expires (unless there is one already running) defp async_task_on_deletion({:delete, _, :gas_prices}) do set_old_gas_prices(get_gas_prices()) - get_async_task() + safe_get_async_task() end defp async_task_on_deletion(_data), do: nil diff --git a/apps/explorer/lib/explorer/chain/cache/gas_usage.ex b/apps/explorer/lib/explorer/chain/cache/gas_usage.ex index 6a632c9..ddff60d 100644 --- a/apps/explorer/lib/explorer/chain/cache/gas_usage.ex +++ b/apps/explorer/lib/explorer/chain/cache/gas_usage.ex @@ -37,9 +37,10 @@ defmodule Explorer.Chain.Cache.GasUsage do end defp handle_fallback(:sum) do - # This will get the task PID if one exists and launch a new task if not + # This will get the task PID if one exists, check if it's running and launch + # a new task if task doesn't exist or it's not running. # See next `handle_fallback` definition - get_async_task() + safe_get_async_task() {:return, nil} end @@ -73,7 +74,7 @@ defmodule Explorer.Chain.Cache.GasUsage do # By setting this as a `callback` an async task will be started each time the # `sum` expires (unless there is one already running) - defp async_task_on_deletion({:delete, _, :sum}), do: get_async_task() + defp async_task_on_deletion({:delete, _, :sum}), do: safe_get_async_task() defp async_task_on_deletion(_data), do: nil diff --git a/apps/explorer/lib/explorer/chain/cache/pending_block_operation.ex b/apps/explorer/lib/explorer/chain/cache/pending_block_operation.ex index 7dccbe0..a746664 100644 --- a/apps/explorer/lib/explorer/chain/cache/pending_block_operation.ex +++ b/apps/explorer/lib/explorer/chain/cache/pending_block_operation.ex @@ -35,9 +35,10 @@ defmodule Explorer.Chain.Cache.PendingBlockOperation do end defp handle_fallback(:count) do - # This will get the task PID if one exists and launch a new task if not + # This will get the task PID if one exists, check if it's running and launch + # a new task if task doesn't exist or it's not running. # See next `handle_fallback` definition - get_async_task() + safe_get_async_task() {:return, nil} end @@ -67,7 +68,7 @@ defmodule Explorer.Chain.Cache.PendingBlockOperation do # 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({:delete, _, :count}), do: safe_get_async_task() defp async_task_on_deletion(_data), do: nil end diff --git a/apps/explorer/lib/explorer/chain/cache/state_changes.ex b/apps/explorer/lib/explorer/chain/cache/state_changes.ex index f11f48e..5c9abc4 100644 --- a/apps/explorer/lib/explorer/chain/cache/state_changes.ex +++ b/apps/explorer/lib/explorer/chain/cache/state_changes.ex @@ -21,8 +21,8 @@ defmodule Explorer.Chain.Cache.StateChanges do @type id :: Hash.t() - def element_to_id(%__MODULE__{transaction_hash: tx_hash}) do - tx_hash + def element_to_id(%__MODULE__{transaction_hash: transaction_hash}) do + transaction_hash end # in order to always keep just requested changes diff --git a/apps/explorer/lib/explorer/chain/cache/transaction.ex b/apps/explorer/lib/explorer/chain/cache/transaction.ex index 6ac24f5..969aa53 100644 --- a/apps/explorer/lib/explorer/chain/cache/transaction.ex +++ b/apps/explorer/lib/explorer/chain/cache/transaction.ex @@ -36,9 +36,10 @@ defmodule Explorer.Chain.Cache.Transaction do end defp handle_fallback(:count) do - # This will get the task PID if one exists and launch a new task if not + # This will get the task PID if one exists, check if it's running and launch + # a new task if task doesn't exist or it's not running. # See next `handle_fallback` definition - get_async_task() + safe_get_async_task() {:return, nil} end @@ -68,7 +69,7 @@ defmodule Explorer.Chain.Cache.Transaction do # 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({:delete, _, :count}), do: safe_get_async_task() defp async_task_on_deletion(_data), do: nil end diff --git a/apps/explorer/lib/explorer/chain/cache/transaction_action_tokens_data.ex b/apps/explorer/lib/explorer/chain/cache/transaction_action_tokens_data.ex index 27fc043..976bfba 100644 --- a/apps/explorer/lib/explorer/chain/cache/transaction_action_tokens_data.ex +++ b/apps/explorer/lib/explorer/chain/cache/transaction_action_tokens_data.ex @@ -4,7 +4,7 @@ defmodule Explorer.Chain.Cache.TransactionActionTokensData do """ use GenServer - @cache_name :tx_actions_tokens_data_cache + @cache_name :transaction_actions_tokens_data_cache @spec start_link(term()) :: GenServer.on_start() def start_link(_) do diff --git a/apps/explorer/lib/explorer/chain/cache/transaction_action_uniswap_pools.ex b/apps/explorer/lib/explorer/chain/cache/transaction_action_uniswap_pools.ex index f254e95..4e77197 100644 --- a/apps/explorer/lib/explorer/chain/cache/transaction_action_uniswap_pools.ex +++ b/apps/explorer/lib/explorer/chain/cache/transaction_action_uniswap_pools.ex @@ -4,7 +4,7 @@ defmodule Explorer.Chain.Cache.TransactionActionUniswapPools do """ use GenServer - @cache_name :tx_actions_uniswap_pools_cache + @cache_name :transaction_actions_uniswap_pools_cache @spec start_link(term()) :: GenServer.on_start() def start_link(_) do diff --git a/apps/explorer/lib/explorer/chain/celo/election_reward.ex b/apps/explorer/lib/explorer/chain/celo/election_reward.ex index f31898b..e45d563 100644 --- a/apps/explorer/lib/explorer/chain/celo/election_reward.ex +++ b/apps/explorer/lib/explorer/chain/celo/election_reward.ex @@ -32,8 +32,9 @@ defmodule Explorer.Chain.Celo.ElectionReward do import Ecto.Query, only: [from: 2, where: 3] import Explorer.Helper, only: [safe_parse_non_negative_integer: 1] + alias Explorer.Chain.Cache.CeloCoreContracts alias Explorer.{Chain, PagingOptions} - alias Explorer.Chain.{Address, Block, Hash, Wei} + alias Explorer.Chain.{Address, Block, Hash, Token, Wei} @type type :: :voter | :validator | :group | :delegated_payment @types_enum ~w(voter validator group delegated_payment)a @@ -96,6 +97,8 @@ defmodule Explorer.Chain.Celo.ElectionReward do null: false ) + field(:token, :any, virtual: true) :: Token.t() | nil + timestamps() end @@ -243,10 +246,76 @@ defmodule Explorer.Chain.Celo.ElectionReward do ) end + @doc """ + Joins the token table to the query based on the reward type. + + ## Parameters + - `query` (`Ecto.Query.t()`): The query to join the token table. + + ## Returns + - An Ecto query with the token table joined. + """ + @spec join_token(Ecto.Query.t()) :: Ecto.Query.t() + def join_token(query) do + # This match should never fail + %{ + voter: [voter_token_address_hash], + validator: [validator_token_address_hash], + group: [group_token_address_hash], + delegated_payment: [delegated_payment_token_address_hash] + } = + Map.new( + @reward_type_atom_to_token_atom, + fn {type, token_atom} -> + addresses = + token_atom + |> CeloCoreContracts.get_address_updates() + |> case do + {:ok, addresses} -> addresses + _ -> [] + end + |> Enum.map(fn %{"address" => address_hash_string} -> + {:ok, address_hash} = Hash.Address.cast(address_hash_string) + address_hash + end) + + {type, addresses} + end + ) + + from( + r in query, + join: t in Token, + on: + t.contract_address_hash == + fragment( + """ + CASE ? + WHEN ? THEN ?::bytea + WHEN ? THEN ?::bytea + WHEN ? THEN ?::bytea + WHEN ? THEN ?::bytea + ELSE NULL + END + """, + r.type, + ^"voter", + ^voter_token_address_hash.bytes, + ^"validator", + ^validator_token_address_hash.bytes, + ^"group", + ^group_token_address_hash.bytes, + ^"delegated_payment", + ^delegated_payment_token_address_hash.bytes + ), + select_merge: %{token: t} + ) + end + @doc """ Makes Explorer.PagingOptions map for election rewards. """ - @spec address_paging_options(map()) :: [Chain.paging_options()] + @spec block_paging_options(map()) :: [Chain.paging_options()] def block_paging_options(params) do with %{ "amount" => amount_string, diff --git a/apps/explorer/lib/explorer/chain/celo/epoch_reward.ex b/apps/explorer/lib/explorer/chain/celo/epoch_reward.ex index 8356456..f08d59f 100644 --- a/apps/explorer/lib/explorer/chain/celo/epoch_reward.ex +++ b/apps/explorer/lib/explorer/chain/celo/epoch_reward.ex @@ -101,8 +101,8 @@ defmodule Explorer.Chain.Celo.EpochReward do select: {tt.log_index, tt}, preload: [ :token, - [from_address: [:names, :smart_contract, :proxy_implementations]], - [to_address: [:names, :smart_contract, :proxy_implementations]] + [from_address: [:scam_badge, :names, :smart_contract, :proxy_implementations]], + [to_address: [:scam_badge, :names, :smart_contract, :proxy_implementations]] ] ) diff --git a/apps/explorer/lib/explorer/chain/celo/reader.ex b/apps/explorer/lib/explorer/chain/celo/reader.ex index d1c2dff..ac49b87 100644 --- a/apps/explorer/lib/explorer/chain/celo/reader.ex +++ b/apps/explorer/lib/explorer/chain/celo/reader.ex @@ -9,11 +9,13 @@ defmodule Explorer.Chain.Celo.Reader do only: [ select_repo: 1, join_associations: 2, - default_paging_options: 0 + default_paging_options: 0, + max_consensus_block_number: 1 ] - alias Explorer.Chain.Cache.CeloCoreContracts - alias Explorer.Chain.Celo.ElectionReward + alias Explorer.Chain.Block + alias Explorer.Chain.Cache.{Blocks, CeloCoreContracts} + alias Explorer.Chain.Celo.{ElectionReward, Helper} alias Explorer.Chain.{Hash, Token, Wei} @election_reward_types ElectionReward.types() @@ -50,6 +52,7 @@ defmodule Explorer.Chain.Celo.Reader do address_hash |> ElectionReward.address_hash_to_ordered_rewards_query() + |> ElectionReward.join_token() |> ElectionReward.paginate(paging_options) |> limit(^paging_options.page_size) |> join_associations(necessity_by_association) @@ -190,4 +193,24 @@ defmodule Explorer.Chain.Celo.Reader do {reward_type_atom, Map.get(token_atom_to_token, token_atom)} end) end + + @doc """ + Retrieves the epoch number of the last fetched block. + """ + @spec last_block_epoch_number(Keyword.t()) :: Block.block_number() | nil + def last_block_epoch_number(options \\ []) do + block_number = + 1 + |> Blocks.atomic_take_enough() + |> case do + [%Block{number: number}] -> {:ok, number} + nil -> max_consensus_block_number(options) + end + |> case do + {:ok, number} -> number + _ -> nil + end + + block_number && Helper.block_number_to_epoch_number(block_number) + end end diff --git a/apps/explorer/lib/explorer/chain/celo/validator_group_vote.ex b/apps/explorer/lib/explorer/chain/celo/validator_group_vote.ex index dcb639e..221b1c6 100644 --- a/apps/explorer/lib/explorer/chain/celo/validator_group_vote.ex +++ b/apps/explorer/lib/explorer/chain/celo/validator_group_vote.ex @@ -24,12 +24,14 @@ defmodule Explorer.Chain.Celo.ValidatorGroupVote do typed_schema "celo_validator_group_votes" do belongs_to(:account_address, Address, foreign_key: :account_address_hash, + primary_key: true, references: :hash, type: Hash.Address ) belongs_to(:group_address, Address, foreign_key: :group_address_hash, + primary_key: true, references: :hash, type: Hash.Address ) @@ -43,7 +45,6 @@ defmodule Explorer.Chain.Celo.ValidatorGroupVote do belongs_to(:block, Block, foreign_key: :block_hash, - primary_key: true, references: :hash, type: Hash.Full, null: false @@ -70,7 +71,6 @@ defmodule Explorer.Chain.Celo.ValidatorGroupVote do |> validate_required(@required_attrs) |> foreign_key_constraint(:account_address_hash) |> foreign_key_constraint(:group_address_hash) - |> foreign_key_constraint(:block_hash) |> foreign_key_constraint(:transaction_hash) end end diff --git a/apps/explorer/lib/explorer/chain/contract_method.ex b/apps/explorer/lib/explorer/chain/contract_method.ex index 8d1c381..021e62d 100644 --- a/apps/explorer/lib/explorer/chain/contract_method.ex +++ b/apps/explorer/lib/explorer/chain/contract_method.ex @@ -109,14 +109,19 @@ defmodule Explorer.Chain.ContractMethod do @doc """ Finds contract methods by selector id """ - @spec find_contract_methods(binary(), [Chain.api?()]) :: [__MODULE__.t()] + @spec find_contract_methods([binary()], [Chain.api?()]) :: [__MODULE__.t()] + def find_contract_methods(method_ids, options) + + def find_contract_methods([], _), do: [] + def find_contract_methods(method_ids, options) do query = from( contract_method in __MODULE__, distinct: contract_method.identifier, where: contract_method.abi["type"] == "function", - where: contract_method.identifier in ^method_ids + where: contract_method.identifier in ^method_ids, + order_by: [asc: contract_method.identifier, asc: contract_method.inserted_at] ) Chain.select_repo(options).all(query) 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 88496d4..d1dc032 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 @@ -19,7 +19,6 @@ defmodule Explorer.Chain.CSVExport.AddressTransactionCsvExporter do transactions |> Transaction.decode_transactions(true, api?: true) - |> elem(0) |> Enum.zip(transactions) |> to_csv_format(address_hash, exchange_rate) |> Helper.dump_to_stream() @@ -61,8 +60,8 @@ defmodule Explorer.Chain.CSVExport.AddressTransactionCsvExporter do ] date_to_prices = - Enum.reduce(transactions_with_decoded_data, %{}, fn {_decoded_data, tx}, acc -> - date = tx |> Transaction.block_timestamp() |> DateTime.to_date() + Enum.reduce(transactions_with_decoded_data, %{}, fn {_decoded_data, transaction}, acc -> + date = transaction |> Transaction.block_timestamp() |> DateTime.to_date() if Map.has_key?(acc, date) do acc diff --git a/apps/explorer/lib/explorer/chain/csv_export/helper.ex b/apps/explorer/lib/explorer/chain/csv_export/helper.ex index 973f9f6..1b3774d 100644 --- a/apps/explorer/lib/explorer/chain/csv_export/helper.ex +++ b/apps/explorer/lib/explorer/chain/csv_export/helper.ex @@ -115,10 +115,4 @@ defmodule Explorer.Chain.CSVExport.Helper do true end end - - @spec captcha_helper() :: module() - def captcha_helper do - :block_scout_web - |> Application.get_env(:captcha_helper) - end end 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 1394f8a..d46b3f6 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 @@ -40,11 +40,11 @@ defmodule Explorer.Chain.Fetcher.LookUpSmartContractSourcesOnDemand do defp fetch_sources(address_hash_string, address_contract_code, only_full?) do Publisher.broadcast(%{eth_bytecode_db_lookup_started: [address_hash_string]}, :on_demand) - creation_tx_input = contract_creation_input(address_hash_string) + creation_transaction_input = contract_creation_input(address_hash_string) with {:ok, %{"sourceType" => type, "matchType" => match_type} = source} <- %{} - |> prepare_bytecode_for_microservice(creation_tx_input, Data.to_string(address_contract_code)) + |> prepare_bytecode_for_microservice(creation_transaction_input, Data.to_string(address_contract_code)) |> EthBytecodeDBInterface.search_contract(address_hash_string), :ok <- check_match_type(match_type, only_full?), {:ok, _} <- process_contract_source(type, source, address_hash_string) do diff --git a/apps/explorer/lib/explorer/chain/filecoin/id.ex b/apps/explorer/lib/explorer/chain/filecoin/id.ex new file mode 100644 index 0000000..74ce571 --- /dev/null +++ b/apps/explorer/lib/explorer/chain/filecoin/id.ex @@ -0,0 +1,157 @@ +defmodule Explorer.Chain.Filecoin.IDAddress do + @moduledoc """ + Handles Filecoin ID addresses, wrapping the `NativeAddress` type. + """ + + alias Explorer.Chain.Filecoin.NativeAddress + alias Poison.Encoder.BitString + + require Integer + + defstruct ~w(value)a + + @protocol_indicator 0 + + use Ecto.Type + + @type t :: %__MODULE__{value: binary()} + + @impl Ecto.Type + @spec type() :: :binary + def type, do: :binary + + defp to_native_address(%__MODULE__{value: value}) do + %NativeAddress{ + protocol_indicator: @protocol_indicator, + payload: value + } + end + + @doc """ + Casts a binary string to a `Explorer.Chain.Filecoin.IDAddress`. + + ## Examples + + iex> Explorer.Chain.Filecoin.IDAddress.cast("f01729") + {:ok, %Explorer.Chain.Filecoin.IDAddress{value: <<193, 13>>}} + + iex> Explorer.Chain.Filecoin.IDAddress.cast(%Explorer.Chain.Filecoin.IDAddress{value: <<193, 13>>}) + {:ok, %Explorer.Chain.Filecoin.IDAddress{value: <<193, 13>>}} + + iex> Explorer.Chain.Filecoin.IDAddress.cast("invalid") + :error + """ + @impl Ecto.Type + def cast(address_string) when is_binary(address_string) do + address_string + |> NativeAddress.cast() + |> case do + {:ok, + %NativeAddress{ + protocol_indicator: @protocol_indicator, + payload: value + }} -> + {:ok, %__MODULE__{value: value}} + + :error -> + :error + end + end + + @impl Ecto.Type + def cast(%__MODULE__{} = address), do: {:ok, address} + + @impl Ecto.Type + def cast(_), do: :error + + @doc """ + Dumps an `Explorer.Chain.Filecoin.IDAddress` to its binary representation. + + ## Examples + + iex> address = %Explorer.Chain.Filecoin.IDAddress{value: <<193, 13>>} + iex> Explorer.Chain.Filecoin.IDAddress.dump(address) + {:ok, <<0, 193, 13>>} + + iex> Explorer.Chain.Filecoin.IDAddress.dump("invalid") + :error + """ + @impl Ecto.Type + def dump(%__MODULE__{} = address) do + address + |> to_native_address() + |> NativeAddress.dump() + end + + def dump(_), do: :error + + @doc """ + Loads a binary representation of an `Explorer.Chain.Filecoin.IDAddress`. + + ## Examples + + iex> Explorer.Chain.Filecoin.IDAddress.load(<<0, 193, 13>>) + {:ok, %Explorer.Chain.Filecoin.IDAddress{value: <<193, 13>>}} + + iex> Explorer.Chain.Filecoin.IDAddress.load("invalid") + :error + """ + @impl Ecto.Type + def load(bytes) when is_binary(bytes) do + bytes + |> NativeAddress.load() + |> case do + {:ok, + %NativeAddress{ + protocol_indicator: @protocol_indicator, + payload: value + }} -> + {:ok, %__MODULE__{value: value}} + + _ -> + :error + end + end + + def load(_), do: :error + + @doc """ + Converts an `Explorer.Chain.Filecoin.IDAddress` to its string representation. + + ## Examples + + iex> address = %Explorer.Chain.Filecoin.IDAddress{value: <<193, 13>>} + iex> Explorer.Chain.Filecoin.IDAddress.to_string(address) + "f01729" + """ + @spec to_string(t()) :: String.t() + def to_string(%__MODULE__{} = address) do + address + |> to_native_address() + |> NativeAddress.to_string() + end + + defimpl String.Chars do + def to_string(address) do + @for.to_string(address) + end + end + + defimpl Poison.Encoder do + def encode(address, options) do + address + |> to_string() + |> BitString.encode(options) + end + end + + defimpl Jason.Encoder do + alias Jason.Encode + + def encode(address, opts) do + address + |> to_string() + |> Encode.string(opts) + end + end +end diff --git a/apps/explorer/lib/explorer/chain/filecoin/native_address.ex b/apps/explorer/lib/explorer/chain/filecoin/native_address.ex new file mode 100644 index 0000000..aaeac00 --- /dev/null +++ b/apps/explorer/lib/explorer/chain/filecoin/native_address.ex @@ -0,0 +1,408 @@ +defmodule Explorer.Chain.Filecoin.NativeAddress do + @moduledoc """ + Handles Filecoin addresses by parsing, validating, and converting them to and + from their binary representations. + + Addresses are encoded to binary according to the [Filecoin Address + spec](https://spec.filecoin.io/appendix/address/#section-appendix.address.validatechecksum). + Details about f4 addresses are provided in + [FIP-0048](https://github.com/filecoin-project/FIPs/blob/master/FIPS/fip-0048.md). + + Internally, f0/f1/f2/f3 addresses are stored as a binary with the following structure: + + |--------------------|---------| + | protocol indicator | payload | + |--------------------|---------| + | 1 byte | n bytes | + |--------------------|---------| + + 1. The first byte is the protocol indicator. The values are: + - `0` for f0 addresses + - `1` for f1 addresses + - `2` for f2 addresses + - `3` for f3 addresses + + 2. The remaining bytes are the payload. + + f4 addresses are stored as a binary with the following structure: + + |--------------------|----------|---------| + | protocol indicator | actor id | payload | + |--------------------|----------|---------| + | 1 byte | 1 byte | n bytes | + |--------------------|----------|---------| + + 1. The first byte is the protocol indicator. The value is `4`. + 2. The second byte is the actor id. + 3. The remaining bytes are the payload. + """ + + alias Explorer.Chain.Hash + alias Poison.Encoder.BitString + alias Varint.LEB128 + + use Ecto.Type + + defstruct ~w(protocol_indicator actor_id payload checksum)a + + @checksum_bytes_count 4 + + @protocol_indicator_bytes_count 1 + @max_protocol_indicator 2 ** (@protocol_indicator_bytes_count * Hash.bits_per_byte()) - 1 + + @min_address_string_length 3 + + # Payload sizes: + # f1 -- 20 bytes + # f2 -- 20 bytes + # f3 -- 48 bytes + @protocol_indicator_to_payload_byte_count %{ + 1 => 20, + # For some reason, specs tell that payload for f2 is a SHA256 hash, which is + # 32 bytes long. However, in practice, it is 20 bytes long... + # + # https://spec.filecoin.io/appendix/address/#section-appendix.address.protocol-2-actor + 2 => 20, + 3 => 48 + } + @standard_protocol_indicators Map.keys(@protocol_indicator_to_payload_byte_count) + + @type t :: %__MODULE__{ + protocol_indicator: non_neg_integer(), + actor_id: non_neg_integer() | nil, + payload: binary(), + checksum: binary() | nil + } + + @impl Ecto.Type + @spec type() :: :binary + def type, do: :binary + + defp network_prefix do + Atom.to_string(Application.get_env(:explorer, __MODULE__)[:network_prefix]) + end + + @doc """ + Casts `term` to `t:t/0`. + + If the term is already in `t:t/0`, then it is returned + + iex> Explorer.Chain.Filecoin.NativeAddress.cast( + ...> %Explorer.Chain.Filecoin.NativeAddress{ + ...> protocol_indicator: 0, + ...> actor_id: nil, + ...> payload: <<193, 13>>, + ...> checksum: nil + ...> } + ...> ) + { + :ok, + %Explorer.Chain.Filecoin.NativeAddress{ + protocol_indicator: 0, + actor_id: nil, + payload: <<193, 13>>, + checksum: nil + } + } + + If the term is a binary, then it is parsed to `t:t/0` + + iex> Explorer.Chain.Filecoin.NativeAddress.cast("f01729") + { + :ok, + %Explorer.Chain.Filecoin.NativeAddress{ + protocol_indicator: 0, + actor_id: nil, + payload: <<193, 13>>, + checksum: nil + } + } + + iex> Explorer.Chain.Filecoin.NativeAddress.cast("f01729") + { + :ok, + %Explorer.Chain.Filecoin.NativeAddress{ + protocol_indicator: 0, + actor_id: nil, + payload: <<193, 13>>, + checksum: nil + } + } + + iex> NativeAddress.cast("f410fabpafjfjgqkc3douo3yzfug5tq4bwfvuhsewxji") + { + :ok, + %Explorer.Chain.Filecoin.NativeAddress{ + protocol_indicator: 4, + actor_id: 10, + payload: <<0, 94, 2, 164, 169, 52, 20, 45, 141, 212, 118, 241, 146, 208, 221, 156, 56, 27, 22, 180>>, + checksum: <<60, 137, 107, 165>> + } + } + """ + @impl Ecto.Type + @spec cast(t() | String.t()) :: {:ok, t()} | :error + def cast(%__MODULE__{} = address), do: {:ok, address} + + def cast(address_string) when is_binary(address_string) do + network = network_prefix() + + with true <- String.length(address_string) >= @min_address_string_length, + ^network <> protocol_indicator_and_payload <- address_string, + {:ok, address} <- cast_protocol_indicator_and_payload(protocol_indicator_and_payload), + :ok <- verify_checksum(address) do + {:ok, address} + else + _ -> + :error + end + end + + defp cast_protocol_indicator_and_payload("0" <> id_string) do + id_string + |> Integer.parse() + |> case do + {id, ""} when is_integer(id) and id >= 0 -> + payload = LEB128.encode(id) + + {:ok, + %__MODULE__{ + protocol_indicator: 0, + actor_id: nil, + payload: payload, + checksum: nil + }} + + _ -> + :error + end + end + + defp cast_protocol_indicator_and_payload("4" <> rest) do + with [actor_id_string, base32_digits] <- String.split(rest, "f", parts: 2), + {actor_id, ""} when is_integer(actor_id) <- Integer.parse(actor_id_string), + {:ok, {payload, checksum}} <- cast_base32_digits(base32_digits) do + {:ok, + %__MODULE__{ + protocol_indicator: 4, + actor_id: actor_id, + payload: payload, + checksum: checksum + }} + else + _ -> :error + end + end + + defp cast_protocol_indicator_and_payload(protocol_indicator_and_payload) do + with {protocol_indicator_string, base32_digits} <- + String.split_at( + protocol_indicator_and_payload, + 1 + ), + {protocol_indicator, ""} when protocol_indicator in @standard_protocol_indicators <- + Integer.parse(protocol_indicator_string), + {:ok, byte_count} <- + Map.fetch( + @protocol_indicator_to_payload_byte_count, + protocol_indicator + ), + {:ok, {payload, checksum}} <- cast_base32_digits(base32_digits, byte_count) do + {:ok, + %__MODULE__{ + protocol_indicator: protocol_indicator, + actor_id: nil, + payload: payload, + checksum: checksum + }} + else + _ -> :error + end + end + + defp cast_base32_digits(digits) do + with {:ok, bytes} <- Base.decode32(digits, case: :lower, padding: false), + << + payload::binary-size(byte_size(bytes) - @checksum_bytes_count), + checksum::binary-size(@checksum_bytes_count) + >> <- bytes do + {:ok, {payload, checksum}} + else + _ -> :error + end + end + + defp cast_base32_digits(digits, expected_bytes_count) do + with {:ok, {payload, checksum}} <- cast_base32_digits(digits), + true <- byte_size(payload) == expected_bytes_count do + {:ok, {payload, checksum}} + else + _ -> :error + end + end + + @doc """ + Dumps the address to `:binary` (`bytea`) representation format used in + database. + """ + @impl Ecto.Type + @spec dump(t()) :: {:ok, binary()} | :error + def dump(%__MODULE__{protocol_indicator: 4, actor_id: actor_id, payload: payload}) + when is_integer(actor_id) and + is_binary(payload) and + actor_id >= 0 and + actor_id <= @max_protocol_indicator do + {:ok, <<4, actor_id, payload::binary>>} + end + + def dump(%__MODULE__{protocol_indicator: protocol_indicator, payload: payload}) + when is_integer(protocol_indicator) and + is_binary(payload) and + protocol_indicator >= 0 and + protocol_indicator <= @max_protocol_indicator do + {:ok, <>} + end + + def dump(_), do: :error + + @doc """ + Loads the address from `:binary` representation used in database. + """ + @impl Ecto.Type + @spec load(binary()) :: {:ok, t()} | :error + def load(<> = bytes) do + case protocol_indicator do + 0 -> + {:ok, + %__MODULE__{ + protocol_indicator: 0, + actor_id: nil, + payload: rest, + checksum: nil + }} + + 4 -> + checksum = to_checksum(bytes) + <> = rest + + {:ok, + %__MODULE__{ + protocol_indicator: 4, + actor_id: actor_id, + payload: payload, + checksum: checksum + }} + + protocol_indicator when protocol_indicator in @standard_protocol_indicators -> + checksum = to_checksum(bytes) + + {:ok, + %__MODULE__{ + protocol_indicator: protocol_indicator, + actor_id: nil, + payload: rest, + checksum: checksum + }} + + _ -> + :error + end + end + + def load(_), do: :error + + @doc """ + Converts the address to a string representation. + + iex> Explorer.Chain.Filecoin.NativeAddress.to_string( + ...> %Explorer.Chain.Filecoin.NativeAddress{ + ...> protocol_indicator: 0, + ...> actor_id: nil, + ...> payload: <<193, 13>>, + ...> checksum: nil + ...> } + ...> ) + "f01729" + + iex> Explorer.Chain.Filecoin.NativeAddress.to_string( + ...> %Explorer.Chain.Filecoin.NativeAddress{ + ...> protocol_indicator: 4, + ...> actor_id: 10, + ...> payload: <<0, 94, 2, 164, 169, 52, 20, 45, 141, 212, 118, 241, 146, 208, 221, 156, 56, 27, 22, 180>>, + ...> checksum: <<60, 137, 107, 165>> + ...> } + ...> ) + "f410fabpafjfjgqkc3douo3yzfug5tq4bwfvuhsewxji" + """ + @spec to_string(t) :: String.t() + def to_string(%__MODULE__{protocol_indicator: 0, payload: payload}) do + {id, <<>>} = LEB128.decode(payload) + network_prefix() <> "0" <> Integer.to_string(id) + end + + @spec to_string(t) :: String.t() + def to_string(%__MODULE__{ + protocol_indicator: protocol_indicator, + payload: payload, + actor_id: actor_id, + checksum: checksum + }) do + payload_with_checksum = + Base.encode32( + payload <> checksum, + case: :lower, + padding: false + ) + + protocol_indicator_part = + protocol_indicator + |> case do + indicator when indicator in @standard_protocol_indicators -> + Integer.to_string(indicator) + + 4 -> + "4" <> Integer.to_string(actor_id) <> "f" + end + + network_prefix() <> protocol_indicator_part <> payload_with_checksum + end + + defp verify_checksum(%__MODULE__{protocol_indicator: 0, checksum: nil}), do: :ok + + defp verify_checksum(%__MODULE__{checksum: checksum} = address) + when not is_nil(checksum) do + with {:ok, bytes} <- dump(address), + ^checksum <- to_checksum(bytes) do + :ok + else + _ -> :error + end + end + + defp to_checksum(bytes), + do: Blake2.hash2b(bytes, @checksum_bytes_count) + + defimpl String.Chars do + def to_string(hash) do + @for.to_string(hash) + end + end + + defimpl Poison.Encoder do + def encode(hash, options) do + hash + |> to_string() + |> BitString.encode(options) + end + end + + defimpl Jason.Encoder do + alias Jason.Encode + + def encode(hash, opts) do + hash + |> to_string() + |> Encode.string(opts) + end + end +end diff --git a/apps/explorer/lib/explorer/chain/filecoin/pending_address_operation.ex b/apps/explorer/lib/explorer/chain/filecoin/pending_address_operation.ex new file mode 100644 index 0000000..63e61ab --- /dev/null +++ b/apps/explorer/lib/explorer/chain/filecoin/pending_address_operation.ex @@ -0,0 +1,75 @@ +defmodule Explorer.Chain.Filecoin.PendingAddressOperation do + @moduledoc """ + Tracks an address that is pending for fetching of filecoin address info. + """ + + use Explorer.Schema + + import Explorer.Chain, only: [add_fetcher_limit: 2] + alias Explorer.Chain.{Address, Hash} + alias Explorer.Repo + + @http_error_codes 400..526 + + @optional_attrs ~w(http_status_code)a + @required_attrs ~w(address_hash)a + + @attrs @optional_attrs ++ @required_attrs + + @typedoc """ + * `address_hash` - the hash of the address that is pending to be fetched. + * `http_status_code` - the unsuccessful (non-200) http code returned by Beryx + API if the fetcher failed to fetch the address, set to `nil` if the fetcher + did not attempt to fetch the address yet. + """ + @primary_key false + typed_schema "filecoin_pending_address_operations" do + belongs_to(:address, Address, + foreign_key: :address_hash, + references: :hash, + type: Hash.Address, + primary_key: true + ) + + field(:http_status_code, :integer) + + timestamps() + end + + @spec changeset( + Explorer.Chain.Filecoin.PendingAddressOperation.t(), + :invalid | %{optional(:__struct__) => none(), optional(atom() | binary()) => any()} + ) :: Ecto.Changeset.t() + def changeset(%__MODULE__{} = pending_ops, attrs) do + pending_ops + |> cast(attrs, @attrs) + |> validate_required(@required_attrs) + |> foreign_key_constraint(:address_hash, name: :filecoin_pending_address_operations_address_hash_fkey) + |> unique_constraint(:address_hash, name: :filecoin_pending_address_operations_pkey) + |> validate_inclusion(:http_status_code, @http_error_codes) + end + + @doc """ + Returns a stream of pending operations. + """ + @spec stream( + initial :: accumulator, + reducer :: (entry :: term(), accumulator -> accumulator), + limited? :: boolean() + ) :: {:ok, accumulator} + when accumulator: term() + def stream(initial, reducer, limited? \\ false) + when is_function(reducer, 2) do + query = + from( + op in __MODULE__, + select: op, + where: is_nil(op.http_status_code), + order_by: [desc: op.address_hash] + ) + + query + |> add_fetcher_limit(limited?) + |> Repo.stream_reduce(initial, reducer) + end +end diff --git a/apps/explorer/lib/explorer/chain/hash.ex b/apps/explorer/lib/explorer/chain/hash.ex index 8bac3ff..255861c 100644 --- a/apps/explorer/lib/explorer/chain/hash.ex +++ b/apps/explorer/lib/explorer/chain/hash.ex @@ -121,7 +121,7 @@ defmodule Explorer.Chain.Hash do """ @spec to_integer(t()) :: pos_integer() def to_integer(%__MODULE__{byte_count: byte_count, bytes: bytes}) do - <> = bytes + <> = bytes integer end diff --git a/apps/explorer/lib/explorer/chain/import.ex b/apps/explorer/lib/explorer/chain/import.ex index d0101ed..4886ab9 100644 --- a/apps/explorer/lib/explorer/chain/import.ex +++ b/apps/explorer/lib/explorer/chain/import.ex @@ -15,7 +15,8 @@ defmodule Explorer.Chain.Import do Import.Stage.BlockRelated, Import.Stage.BlockReferencing, Import.Stage.BlockFollowing, - Import.Stage.BlockPending + Import.Stage.BlockPending, + Import.Stage.ChainTypeSpecific ] # in order so that foreign keys are inserted before being referenced diff --git a/apps/explorer/lib/explorer/chain/import/runner.ex b/apps/explorer/lib/explorer/chain/import/runner.ex index 0a543d6..97d36a2 100644 --- a/apps/explorer/lib/explorer/chain/import/runner.ex +++ b/apps/explorer/lib/explorer/chain/import/runner.ex @@ -22,7 +22,7 @@ defmodule Explorer.Chain.Import.Runner do @type changes_list :: [changes] @type changeset_function_name :: atom - @type on_conflict :: :nothing | :replace_all | Ecto.Query.t() + @type on_conflict :: :nothing | :replace_all | {:replace, [atom()]} | Ecto.Query.t() @typedoc """ Runner-specific options under `c:option_key/0` in all options passed to `c:run/3`. 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 1ea20e0..cc0dd4d 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 @@ -108,17 +108,17 @@ defmodule Explorer.Chain.Import.Runner.Address.CurrentTokenBalances do |> Map.put(:timestamps, timestamps) multi - |> Multi.run(:filter_placeholders, fn _, _ -> + |> Multi.run(:filter_ctb_placeholders, fn _, _ -> Instrumenter.block_import_stage_runner( fn -> TokenBalances.filter_placeholders(changes_list) end, :block_following, :current_token_balances, - :filter_placeholders + :filter_ctb_placeholders ) end) - |> Multi.run(:address_current_token_balances, fn repo, _ -> + |> Multi.run(:address_current_token_balances, fn repo, %{filter_ctb_placeholders: filtered_changes_list} -> Instrumenter.block_import_stage_runner( - fn -> insert(repo, changes_list, insert_options) end, + fn -> insert(repo, filtered_changes_list, insert_options) end, :block_following, :current_token_balances, :address_current_token_balances diff --git a/apps/explorer/lib/explorer/chain/import/runner/address/token_balances.ex b/apps/explorer/lib/explorer/chain/import/runner/address/token_balances.ex index af07a27..932b6cc 100644 --- a/apps/explorer/lib/explorer/chain/import/runner/address/token_balances.ex +++ b/apps/explorer/lib/explorer/chain/import/runner/address/token_balances.ex @@ -148,6 +148,8 @@ defmodule Explorer.Chain.Import.Runner.Address.TokenBalances do value: fragment("COALESCE(EXCLUDED.value, ?)", token_balance.value), value_fetched_at: fragment("EXCLUDED.value_fetched_at"), token_type: fragment("EXCLUDED.token_type"), + refetch_after: fragment("EXCLUDED.refetch_after"), + retries_count: fragment("EXCLUDED.retries_count"), inserted_at: fragment("LEAST(EXCLUDED.inserted_at, ?)", token_balance.inserted_at), updated_at: fragment("GREATEST(EXCLUDED.updated_at, ?)", token_balance.updated_at) ] diff --git a/apps/explorer/lib/explorer/chain/import/runner/addresses.ex b/apps/explorer/lib/explorer/chain/import/runner/addresses.ex index 23a2e7a..c74b73c 100644 --- a/apps/explorer/lib/explorer/chain/import/runner/addresses.ex +++ b/apps/explorer/lib/explorer/chain/import/runner/addresses.ex @@ -3,14 +3,16 @@ defmodule Explorer.Chain.Import.Runner.Addresses do Bulk imports `t:Explorer.Chain.Address.t/0`. """ - require Ecto.Query + import Ecto.Query, only: [from: 2] + import Explorer.Chain.Import.Runner.Helper, only: [chain_type_dependent_import: 3] alias Ecto.{Multi, Repo} - alias Explorer.Chain.{Address, Hash, Import, Transaction} + alias Explorer.Chain.Filecoin.PendingAddressOperation, as: FilecoinPendingAddressOperation alias Explorer.Chain.Import.Runner + alias Explorer.Chain.{Address, Hash, Import, Transaction} alias Explorer.Prometheus.Instrumenter - import Ecto.Query, only: [from: 2] + require Ecto.Query @behaviour Import.Runner @@ -98,6 +100,21 @@ defmodule Explorer.Chain.Import.Runner.Addresses do :created_address_code_indexed_at_transactions ) end) + |> chain_type_dependent_import( + :filecoin, + &Multi.run( + &1, + :filecoin_pending_address_operations, + fn repo, _ -> + Instrumenter.block_import_stage_runner( + fn -> filecoin_pending_address_operations(repo, ordered_changes_list, insert_options) end, + :addresses, + :addresses, + :filecoin_pending_address_operations + ) + end + ) + ) end @impl Import.Runner @@ -151,8 +168,8 @@ defmodule Explorer.Chain.Import.Runner.Addresses do required(:timeout) => timeout, required(:timestamps) => Import.timestamps() }) :: {:ok, [Address.t()]} - defp insert(repo, ordered_changes_list, %{timeout: timeout, timestamps: timestamps} = options) - when is_list(ordered_changes_list) do + def 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) Import.insert_changes_list( @@ -261,4 +278,23 @@ defmodule Explorer.Chain.Import.Runner.Addresses do end end end + + defp filecoin_pending_address_operations(repo, addresses, %{timeout: timeout, timestamps: timestamps}) do + ordered_addresses = + addresses + |> Enum.map(&%{address_hash: &1.hash}) + |> Enum.sort_by(& &1.address_hash) + |> Enum.dedup_by(& &1.address_hash) + + Import.insert_changes_list( + repo, + ordered_addresses, + conflict_target: :address_hash, + on_conflict: :nothing, + for: FilecoinPendingAddressOperation, + returning: true, + timeout: timeout, + timestamps: timestamps + ) + end end diff --git a/apps/explorer/lib/explorer/chain/import/runner/arbitrum/batch_transactions.ex b/apps/explorer/lib/explorer/chain/import/runner/arbitrum/batch_transactions.ex index f4cda64..17cda3f 100644 --- a/apps/explorer/lib/explorer/chain/import/runner/arbitrum/batch_transactions.ex +++ b/apps/explorer/lib/explorer/chain/import/runner/arbitrum/batch_transactions.ex @@ -60,7 +60,7 @@ defmodule Explorer.Chain.Import.Runner.Arbitrum.BatchTransactions do | {:error, [Changeset.t()]} def insert(repo, changes_list, %{timeout: timeout, timestamps: timestamps} = _options) when is_list(changes_list) do # Enforce Arbitrum.BatchTransaction ShareLocks order (see docs: sharelock.md) - ordered_changes_list = Enum.sort_by(changes_list, & &1.tx_hash) + ordered_changes_list = Enum.sort_by(changes_list, & &1.transaction_hash) {:ok, inserted} = Import.insert_changes_list( @@ -70,7 +70,7 @@ defmodule Explorer.Chain.Import.Runner.Arbitrum.BatchTransactions do returning: true, timeout: timeout, timestamps: timestamps, - conflict_target: :tx_hash, + conflict_target: :transaction_hash, on_conflict: :nothing ) diff --git a/apps/explorer/lib/explorer/chain/import/runner/arbitrum/lifecycle_transactions.ex b/apps/explorer/lib/explorer/chain/import/runner/arbitrum/lifecycle_transactions.ex index f5a2c07..b6ab03b 100644 --- a/apps/explorer/lib/explorer/chain/import/runner/arbitrum/lifecycle_transactions.ex +++ b/apps/explorer/lib/explorer/chain/import/runner/arbitrum/lifecycle_transactions.ex @@ -83,24 +83,24 @@ defmodule Explorer.Chain.Import.Runner.Arbitrum.LifecycleTransactions do defp default_on_conflict do from( - tx in LifecycleTransaction, + transaction 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 timestamp: fragment("EXCLUDED.timestamp"), block_number: fragment("EXCLUDED.block_number"), - status: fragment("GREATEST(?, EXCLUDED.status)", tx.status), - inserted_at: fragment("LEAST(?, EXCLUDED.inserted_at)", tx.inserted_at), - updated_at: fragment("GREATEST(?, EXCLUDED.updated_at)", tx.updated_at) + status: fragment("GREATEST(?, EXCLUDED.status)", transaction.status), + inserted_at: fragment("LEAST(?, EXCLUDED.inserted_at)", transaction.inserted_at), + updated_at: fragment("GREATEST(?, EXCLUDED.updated_at)", transaction.updated_at) ] ], where: fragment( "(EXCLUDED.timestamp, EXCLUDED.block_number, EXCLUDED.status) IS DISTINCT FROM (?, ?, ?)", - tx.timestamp, - tx.block_number, - tx.status + transaction.timestamp, + transaction.block_number, + transaction.status ) ) end diff --git a/apps/explorer/lib/explorer/chain/import/runner/blocks.ex b/apps/explorer/lib/explorer/chain/import/runner/blocks.ex index 0af2896..f6f435a 100644 --- a/apps/explorer/lib/explorer/chain/import/runner/blocks.ex +++ b/apps/explorer/lib/explorer/chain/import/runner/blocks.ex @@ -6,6 +6,7 @@ defmodule Explorer.Chain.Import.Runner.Blocks do require Ecto.Query import Ecto.Query, only: [from: 2, where: 3, subquery: 1] + import Explorer.Chain.Import.Runner.Helper, only: [chain_type_dependent_import: 3] alias Ecto.{Changeset, Multi, Repo} @@ -30,7 +31,7 @@ defmodule Explorer.Chain.Import.Runner.Blocks do alias Explorer.Chain.Block.Reward alias Explorer.Chain.Import.Runner alias Explorer.Chain.Import.Runner.Address.CurrentTokenBalances - alias Explorer.Chain.Import.Runner.{TokenInstances, Tokens} + alias Explorer.Chain.Import.Runner.{Addresses, TokenInstances, Tokens} alias Explorer.Prometheus.Instrumenter alias Explorer.Utility.MissingRangesManipulator @@ -159,6 +160,23 @@ defmodule Explorer.Chain.Import.Runner.Blocks do :derive_transaction_forks ) end) + |> Multi.run(:delete_address_coin_balances, fn repo, %{lose_consensus: non_consensus_blocks} -> + Instrumenter.block_import_stage_runner( + fn -> delete_address_coin_balances(repo, non_consensus_blocks, insert_options) end, + :address_referencing, + :blocks, + :delete_address_coin_balances + ) + end) + |> Multi.run(:derive_address_fetched_coin_balances, fn repo, + %{delete_address_coin_balances: delete_address_coin_balances} -> + Instrumenter.block_import_stage_runner( + fn -> derive_address_fetched_coin_balances(repo, delete_address_coin_balances, insert_options) end, + :address_referencing, + :blocks, + :derive_address_fetched_coin_balances + ) + end) |> Multi.run(:delete_address_token_balances, fn repo, %{lose_consensus: non_consensus_blocks} -> Instrumenter.block_import_stage_runner( fn -> delete_address_token_balances(repo, non_consensus_blocks, insert_options) end, @@ -228,14 +246,6 @@ defmodule Explorer.Chain.Import.Runner.Blocks do @impl Runner def timeout, do: @timeout - def chain_type_dependent_import(multi, chain_type, multi_run) do - if Application.get_env(:explorer, :chain_type) == chain_type do - multi_run.(multi) - else - multi - end - end - defp fork_transactions(%{ repo: repo, timeout: timeout, @@ -471,6 +481,66 @@ defmodule Explorer.Chain.Import.Runner.Blocks do ) end + defp delete_address_coin_balances(_repo, [], _options), do: {:ok, []} + + defp delete_address_coin_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(cb in Address.CoinBalance, + where: cb.block_number in ^non_consensus_block_numbers, + select: map(cb, [:address_hash, :block_number]), + # Enforce TokenBalance ShareLocks order (see docs: sharelocks.md) + order_by: [cb.address_hash, cb.block_number], + lock: "FOR UPDATE" + ) + + query = + from(cb in Address.CoinBalance, + select: cb.address_hash, + inner_join: ordered_address_coin_balance in subquery(ordered_query), + on: + ordered_address_coin_balance.address_hash == cb.address_hash and + ordered_address_coin_balance.block_number == cb.block_number + ) + + try do + {_count, deleted_coin_balances_address_hashes} = repo.delete_all(query, timeout: timeout) + + {:ok, deleted_coin_balances_address_hashes} + rescue + postgrex_error in Postgrex.Error -> + {:error, %{exception: postgrex_error, block_numbers: non_consensus_block_numbers}} + end + end + + defp derive_address_fetched_coin_balances(_repo, [], _options), do: {:ok, []} + + defp derive_address_fetched_coin_balances(repo, deleted_balances_address_hashes, options) do + last_balances_query = + from(cb in Address.CoinBalance, + where: cb.address_hash in ^deleted_balances_address_hashes, + where: not is_nil(cb.value), + distinct: cb.address_hash, + order_by: [asc: cb.address_hash, desc: cb.block_number], + select: %{ + hash: cb.address_hash, + fetched_coin_balance: cb.value, + fetched_coin_balance_block_number: cb.block_number + } + ) + + addresses_params = + last_balances_query + |> repo.all() + |> Enum.sort_by(& &1.hash) + + addresses_options = + Map.put(options, :on_conflict, {:replace, [:fetched_coin_balance, :fetched_coin_balance_block_number]}) + + Addresses.insert(repo, addresses_params, addresses_options) + end + defp delete_address_token_balances(_, [], _), do: {:ok, []} defp delete_address_token_balances(repo, non_consensus_blocks, %{timeout: timeout}) do @@ -537,10 +607,8 @@ defmodule Explorer.Chain.Import.Runner.Blocks do select: map(ctb, [ :address_hash, - :block_number, :token_contract_address_hash, :token_id, - :token_type, # Used to determine if `address_hash` was a holder of `token_contract_address_hash` before # `address_current_token_balance` is deleted in `update_tokens_holder_count`. @@ -573,28 +641,43 @@ defmodule Explorer.Chain.Import.Runner.Blocks do %{timeout: timeout} = options ) when is_list(deleted_address_current_token_balances) do - new_current_token_balances_placeholders = - Enum.map(deleted_address_current_token_balances, fn deleted_balance -> - now = DateTime.utc_now() + final_query = derive_address_current_token_balances_grouped_query(deleted_address_current_token_balances) - %{ - address_hash: deleted_balance.address_hash, - token_contract_address_hash: deleted_balance.token_contract_address_hash, - token_id: deleted_balance.token_id, - token_type: deleted_balance.token_type, - block_number: deleted_balance.block_number, - value: nil, - value_fetched_at: nil, - inserted_at: now, - updated_at: now - } - end) + new_current_token_balance_query = + from(new_current_token_balance in subquery(final_query), + inner_join: tb in Address.TokenBalance, + on: + tb.address_hash == new_current_token_balance.address_hash and + tb.token_contract_address_hash == new_current_token_balance.token_contract_address_hash and + ((is_nil(tb.token_id) and is_nil(new_current_token_balance.token_id)) or + (tb.token_id == new_current_token_balance.token_id and + not is_nil(tb.token_id) and not is_nil(new_current_token_balance.token_id))) and + tb.block_number == new_current_token_balance.block_number, + select: %{ + 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) + }, + windows: [ + w: [partition_by: [tb.address_hash, tb.token_contract_address_hash, tb.token_id]] + ] + ) + + current_token_balance = + new_current_token_balance_query + |> repo.all() timestamps = Import.timestamps() result = CurrentTokenBalances.insert_changes_list_with_and_without_token_id( - new_current_token_balances_placeholders, + current_token_balance, repo, timestamps, timeout, @@ -745,61 +828,27 @@ defmodule Explorer.Chain.Import.Runner.Blocks do end defp refs_to_token_transfers_query(historical_token_transfers_query, filtered_query) do - if Application.get_env(:explorer, :chain_type) == :polygon_zkevm do - 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), - inner_join: t in Transaction, - on: tt.transaction_hash == t.hash, - select: %{ - token_contract_address_hash: tt.token_contract_address_hash, - token_id: historical_tt.token_id, - block_number: tt.block_number, - transaction_hash: t.hash, - log_index: tt.log_index, - position: - over(row_number(), - partition_by: [tt.token_contract_address_hash, historical_tt.token_id, tt.block_number], - order_by: [desc: t.index, desc: tt.log_index] - ) - } - ) - else - 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] - ) - end + 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] + ) end defp derived_token_transfers_query(refs_to_token_transfers, filtered_query) do - if Application.get_env(:explorer, :chain_type) == :polygon_zkevm do - 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 and - tt_1.transaction_hash == tt.transaction_hash, - where: tt_1.position == 1 - ) - else - 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 - ) - end + 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 + ) end defp token_instances_on_conflict do @@ -819,6 +868,43 @@ defmodule Explorer.Chain.Import.Runner.Blocks do ) end + defp derive_address_current_token_balances_grouped_query(deleted_address_current_token_balances) do + initial_query = + from(tb in Address.TokenBalance, + select: %{ + address_hash: tb.address_hash, + token_contract_address_hash: tb.token_contract_address_hash, + token_id: tb.token_id, + block_number: max(tb.block_number) + }, + group_by: [tb.address_hash, tb.token_contract_address_hash, tb.token_id] + ) + + Enum.reduce(deleted_address_current_token_balances, initial_query, fn %{ + address_hash: address_hash, + token_contract_address_hash: + token_contract_address_hash, + token_id: token_id + }, + acc_query -> + if token_id do + from(tb in acc_query, + or_where: + tb.address_hash == ^address_hash and + tb.token_contract_address_hash == ^token_contract_address_hash and + tb.token_id == ^token_id + ) + else + from(tb in acc_query, + or_where: + tb.address_hash == ^address_hash and + tb.token_contract_address_hash == ^token_contract_address_hash and + is_nil(tb.token_id) + ) + end + end) + end + # `block_rewards` are linked to `blocks.hash`, but fetched by `blocks.number`, so when a block with the same number is # inserted, the old block rewards need to be deleted, so that the old and new rewards aren't combined. defp delete_rewards(repo, blocks_changes, %{timeout: timeout}) do diff --git a/apps/explorer/lib/explorer/chain/import/runner/celo/validator_group_votes.ex b/apps/explorer/lib/explorer/chain/import/runner/celo/validator_group_votes.ex index 618b0ba..10cd12c 100644 --- a/apps/explorer/lib/explorer/chain/import/runner/celo/validator_group_votes.ex +++ b/apps/explorer/lib/explorer/chain/import/runner/celo/validator_group_votes.ex @@ -78,7 +78,11 @@ defmodule Explorer.Chain.Import.Runner.Celo.ValidatorGroupVotes do returning: true, timeout: timeout, timestamps: timestamps, - conflict_target: :transaction_hash, + conflict_target: [ + :transaction_hash, + :account_address_hash, + :group_address_hash + ], on_conflict: :replace_all ) diff --git a/apps/explorer/lib/explorer/chain/import/runner/helper.ex b/apps/explorer/lib/explorer/chain/import/runner/helper.ex new file mode 100644 index 0000000..6fd5f53 --- /dev/null +++ b/apps/explorer/lib/explorer/chain/import/runner/helper.ex @@ -0,0 +1,22 @@ +defmodule Explorer.Chain.Import.Runner.Helper do + @moduledoc """ + Provides utility functions for the chain import runners. + """ + + @doc """ + Executes the import function if the configured chain type matches the + specified `chain_type`. + """ + @spec chain_type_dependent_import( + Ecto.Multi.t(), + chain_type :: atom(), + (Ecto.Multi.t() -> Ecto.Multi.t()) + ) :: Ecto.Multi.t() + def chain_type_dependent_import(multi, chain_type, multi_run) do + if Application.get_env(:explorer, :chain_type) == chain_type do + multi_run.(multi) + else + multi + end + end +end 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 1446ec9..55f67f3 100644 --- a/apps/explorer/lib/explorer/chain/import/runner/internal_transactions.ex +++ b/apps/explorer/lib/explorer/chain/import/runner/internal_transactions.ex @@ -65,15 +65,15 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactions do :acquire_blocks ) end) - |> Multi.run(:acquire_pending_internal_txs, fn repo, %{acquire_blocks: block_hashes} -> + |> Multi.run(:acquire_pending_internal_transactions, fn repo, %{acquire_blocks: block_hashes} -> Instrumenter.block_import_stage_runner( - fn -> acquire_pending_internal_txs(repo, block_hashes) end, + fn -> acquire_pending_internal_transactions(repo, block_hashes) end, :block_pending, :internal_transactions, - :acquire_pending_internal_txs + :acquire_pending_internal_transactions ) end) - |> Multi.run(:acquire_transactions, fn repo, %{acquire_pending_internal_txs: pending_block_hashes} -> + |> Multi.run(:acquire_transactions, fn repo, %{acquire_pending_internal_transactions: pending_block_hashes} -> Instrumenter.block_import_stage_runner( fn -> acquire_transactions(repo, pending_block_hashes) end, :block_pending, @@ -171,7 +171,7 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactions do end) |> Multi.run(:update_pending_blocks_status, fn repo, %{ - acquire_pending_internal_txs: pending_block_hashes, + acquire_pending_internal_transactions: pending_block_hashes, set_refetch_needed_for_invalid_blocks: invalid_block_hashes } -> Instrumenter.block_import_stage_runner( @@ -309,7 +309,7 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactions do {:ok, repo.all(query)} end - defp acquire_pending_internal_txs(repo, block_hashes) do + defp acquire_pending_internal_transactions(repo, block_hashes) do query = from( pending_ops in PendingBlockOperation, @@ -340,8 +340,8 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactions do defp invalid_block_numbers(transactions, internal_transactions_params) do # Finds all mismatches between transactions and internal transactions # for a block number: - # - there are no internal txs for some transactions - # - there are internal txs with a different block number than their transactions + # - there are no internal transactions for some transactions + # - there are internal transactions with a different block number than their transactions # Returns block numbers where any of these issues is found # Note: the case "# - there are no transactions for some internal transactions" was removed because it caused the issue https://github.com/blockscout/blockscout/issues/3367 @@ -350,7 +350,7 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactions do # |> MapSet.difference(internal_transactions_tuples) should be replaced with |> MapSet.difference(common_tuples) # Note: for zetachain or if empty traces are explicitly allowed, - # the case "# - there are no internal txs for some transactions" is removed since + # the case "# - there are no internal transactions for some transactions" is removed since # there are may be non-traceable transactions transactions_tuples = MapSet.new(transactions, &{&1.hash, &1.block_number}) @@ -391,7 +391,7 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactions do else blocks_map = Map.new(transactions, &{&1.block_number, &1.block_hash}) - valid_internal_txs = + valid_internal_transactions = internal_transactions_params |> Enum.group_by(& &1.block_number) |> Map.drop(invalid_block_numbers) @@ -399,7 +399,7 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactions do compose_entry_wrapper(item, blocks_map) end) - {:ok, valid_internal_txs} + {:ok, valid_internal_transactions} end end @@ -484,8 +484,8 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactions do else params = valid_internal_transactions - |> Enum.filter(fn internal_tx -> - internal_tx[:index] == 0 + |> Enum.filter(fn internal_transaction -> + internal_transaction[:index] == 0 end) |> Enum.map(fn trace -> %{ @@ -602,12 +602,12 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactions do end end - defp get_trivial_tx_hashes_with_error_in_internal_tx(internal_transactions) do + defp get_trivial_transaction_hashes_with_error_in_internal_transaction(internal_transactions) do internal_transactions - |> Enum.filter(fn internal_tx -> - internal_tx[:index] != 0 && !is_nil(internal_tx[:error]) + |> Enum.filter(fn internal_transaction -> + internal_transaction[:index] != 0 && !is_nil(internal_transaction[:error]) end) - |> Enum.map(fn internal_tx -> internal_tx[:transaction_hash] end) + |> Enum.map(fn internal_transaction -> internal_transaction[:transaction_hash] end) |> MapSet.new() end @@ -650,7 +650,9 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactions do transaction_receipt_from_node \\ nil ) do valid_internal_transactions_count = Enum.count(valid_internal_transactions) - txs_with_error_in_internal_txs = get_trivial_tx_hashes_with_error_in_internal_tx(valid_internal_transactions) + + transactions_with_error_in_internal_transactions = + get_trivial_transaction_hashes_with_error_in_internal_transaction(valid_internal_transactions) set = generate_transaction_set_to_update( @@ -658,7 +660,7 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactions do transaction_from_db, transaction_receipt_from_node, timestamps, - txs_with_error_in_internal_txs + transactions_with_error_in_internal_transactions ) update_query = @@ -692,7 +694,7 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactions do transaction_from_db, transaction_receipt_from_node, timestamps, - txs_with_error_in_internal_txs + transactions_with_error_in_internal_transactions ) do default_set = [ created_contract_address_hash: first_trace.created_contract_address_hash, @@ -712,8 +714,11 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactions do transaction_receipt_from_node && transaction_receipt_from_node.cumulative_gas_used ) |> Keyword.put_new( - :has_error_in_internal_txs, - if(Enum.member?(txs_with_error_in_internal_txs, first_trace.transaction_hash), do: true, else: false) + :has_error_in_internal_transactions, + if(Enum.member?(transactions_with_error_in_internal_transactions, first_trace.transaction_hash), + do: true, + else: false + ) ) set_with_gas_used = @@ -794,7 +799,7 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactions do ) try do - # ShareLocks order already enforced by `acquire_pending_internal_txs` (see docs: sharelocks.md) + # ShareLocks order already enforced by `acquire_pending_internal_transactions` (see docs: sharelocks.md) {_count, deleted} = repo.delete_all(delete_query, []) {:ok, deleted} @@ -809,7 +814,7 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactions 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) + _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 diff --git a/apps/explorer/lib/explorer/chain/import/runner/optimism/txn_batches.ex b/apps/explorer/lib/explorer/chain/import/runner/optimism/transaction_batches.ex similarity index 78% rename from apps/explorer/lib/explorer/chain/import/runner/optimism/txn_batches.ex rename to apps/explorer/lib/explorer/chain/import/runner/optimism/transaction_batches.ex index 0814b34..ae84611 100644 --- a/apps/explorer/lib/explorer/chain/import/runner/optimism/txn_batches.ex +++ b/apps/explorer/lib/explorer/chain/import/runner/optimism/transaction_batches.ex @@ -1,13 +1,13 @@ -defmodule Explorer.Chain.Import.Runner.Optimism.TxnBatches do +defmodule Explorer.Chain.Import.Runner.Optimism.TransactionBatches do @moduledoc """ - Bulk imports `t:Explorer.Chain.Optimism.TxnBatch.t/0`. + Bulk imports `t:Explorer.Chain.Optimism.TransactionBatch.t/0`. """ require Ecto.Query alias Ecto.{Changeset, Multi, Repo} alias Explorer.Chain.Import - alias Explorer.Chain.Optimism.TxnBatch + alias Explorer.Chain.Optimism.TransactionBatch alias Explorer.Prometheus.Instrumenter import Ecto.Query, only: [from: 2] @@ -17,13 +17,13 @@ defmodule Explorer.Chain.Import.Runner.Optimism.TxnBatches do # milliseconds @timeout 60_000 - @type imported :: [TxnBatch.t()] + @type imported :: [TransactionBatch.t()] @impl Import.Runner - def ecto_schema_module, do: TxnBatch + def ecto_schema_module, do: TransactionBatch @impl Import.Runner - def option_key, do: :optimism_txn_batches + def option_key, do: :optimism_transaction_batches @impl Import.Runner def imported_table_row do @@ -42,12 +42,12 @@ defmodule Explorer.Chain.Import.Runner.Optimism.TxnBatches do |> Map.put_new(:timeout, @timeout) |> Map.put(:timestamps, timestamps) - Multi.run(multi, :insert_txn_batches, fn repo, _ -> + Multi.run(multi, :insert_transaction_batches, fn repo, _ -> Instrumenter.block_import_stage_runner( fn -> insert(repo, changes_list, insert_options) end, :block_referencing, - :optimism_txn_batches, - :optimism_txn_batches + :optimism_transaction_batches, + :optimism_transaction_batches ) end) end @@ -56,19 +56,19 @@ defmodule Explorer.Chain.Import.Runner.Optimism.TxnBatches do def timeout, do: @timeout @spec insert(Repo.t(), [map()], %{required(:timeout) => timeout(), required(:timestamps) => Import.timestamps()}) :: - {:ok, [TxnBatch.t()]} + {: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 TxnBatch ShareLocks order (see docs: sharelock.md) + # Enforce TransactionBatch ShareLocks order (see docs: sharelock.md) ordered_changes_list = Enum.sort_by(changes_list, & &1.l2_block_number) {:ok, inserted} = Import.insert_changes_list( repo, ordered_changes_list, - for: TxnBatch, + for: TransactionBatch, returning: true, timeout: timeout, timestamps: timestamps, @@ -81,7 +81,7 @@ defmodule Explorer.Chain.Import.Runner.Optimism.TxnBatches do defp default_on_conflict do from( - tb in TxnBatch, + tb in TransactionBatch, update: [ set: [ # don't update `l2_block_number` as it is a primary key and used for the conflict target diff --git a/apps/explorer/lib/explorer/chain/import/runner/polygon_zkevm/lifecycle_transactions.ex b/apps/explorer/lib/explorer/chain/import/runner/polygon_zkevm/lifecycle_transactions.ex index 3b12c4c..a7260a7 100644 --- a/apps/explorer/lib/explorer/chain/import/runner/polygon_zkevm/lifecycle_transactions.ex +++ b/apps/explorer/lib/explorer/chain/import/runner/polygon_zkevm/lifecycle_transactions.ex @@ -83,20 +83,20 @@ defmodule Explorer.Chain.Import.Runner.PolygonZkevm.LifecycleTransactions do defp default_on_conflict do from( - tx in LifecycleTransaction, + transaction 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) + inserted_at: fragment("LEAST(?, EXCLUDED.inserted_at)", transaction.inserted_at), + updated_at: fragment("GREATEST(?, EXCLUDED.updated_at)", transaction.updated_at) ] ], where: fragment( "(EXCLUDED.is_verify) IS DISTINCT FROM (?)", - tx.is_verify + transaction.is_verify ) ) end diff --git a/apps/explorer/lib/explorer/chain/import/runner/scroll/batch_bundles.ex b/apps/explorer/lib/explorer/chain/import/runner/scroll/batch_bundles.ex new file mode 100644 index 0000000..20633de --- /dev/null +++ b/apps/explorer/lib/explorer/chain/import/runner/scroll/batch_bundles.ex @@ -0,0 +1,106 @@ +defmodule Explorer.Chain.Import.Runner.Scroll.BatchBundles do + @moduledoc """ + Bulk imports `Explorer.Chain.Scroll.BatchBundle`. + """ + + require Ecto.Query + + import Ecto.Query, only: [from: 2] + + alias Ecto.{Changeset, Multi, Repo} + alias Explorer.Chain.Import + alias Explorer.Chain.Scroll.BatchBundle, as: ScrollBatchBundle + alias Explorer.Prometheus.Instrumenter + + @behaviour Import.Runner + + # milliseconds + @timeout 60_000 + + @type imported :: [ScrollBatchBundle.t()] + + @impl Import.Runner + def ecto_schema_module, do: ScrollBatchBundle + + @impl Import.Runner + def option_key, do: :scroll_batch_bundles + + @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, :insert_scroll_batch_bundles, fn repo, _ -> + Instrumenter.block_import_stage_runner( + fn -> insert(repo, changes_list, insert_options) end, + :block_referencing, + :scroll_batch_bundles, + :scroll_batch_bundles + ) + end) + end + + @impl Import.Runner + def timeout, do: @timeout + + @spec insert(Repo.t(), [map()], %{required(:timeout) => timeout(), required(:timestamps) => Import.timestamps()}) :: + {:ok, [ScrollBatchBundle.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 ScrollBatchBundle ShareLocks order (see docs: sharelock.md) + ordered_changes_list = Enum.sort_by(changes_list, & &1.final_batch_number) + + {:ok, inserted} = + Import.insert_changes_list( + repo, + ordered_changes_list, + conflict_target: :id, + on_conflict: on_conflict, + for: ScrollBatchBundle, + returning: true, + timeout: timeout, + timestamps: timestamps + ) + + {:ok, inserted} + end + + defp default_on_conflict do + from( + sbb in ScrollBatchBundle, + update: [ + set: [ + # Don't update `id` as it is a primary key and used for the conflict target + final_batch_number: fragment("EXCLUDED.final_batch_number"), + finalize_transaction_hash: fragment("EXCLUDED.finalize_transaction_hash"), + finalize_block_number: fragment("EXCLUDED.finalize_block_number"), + finalize_timestamp: fragment("EXCLUDED.finalize_timestamp"), + inserted_at: fragment("LEAST(?, EXCLUDED.inserted_at)", sbb.inserted_at), + updated_at: fragment("GREATEST(?, EXCLUDED.updated_at)", sbb.updated_at) + ] + ], + where: + fragment( + "(EXCLUDED.final_batch_number, EXCLUDED.finalize_transaction_hash, EXCLUDED.finalize_block_number, EXCLUDED.finalize_timestamp) IS DISTINCT FROM (?, ?, ?, ?)", + sbb.final_batch_number, + sbb.finalize_transaction_hash, + sbb.finalize_block_number, + sbb.finalize_timestamp + ) + ) + end +end diff --git a/apps/explorer/lib/explorer/chain/import/runner/scroll/batches.ex b/apps/explorer/lib/explorer/chain/import/runner/scroll/batches.ex new file mode 100644 index 0000000..710e22c --- /dev/null +++ b/apps/explorer/lib/explorer/chain/import/runner/scroll/batches.ex @@ -0,0 +1,110 @@ +defmodule Explorer.Chain.Import.Runner.Scroll.Batches do + @moduledoc """ + Bulk imports `Explorer.Chain.Scroll.Batch`. + """ + + require Ecto.Query + + import Ecto.Query, only: [from: 2] + + alias Ecto.{Changeset, Multi, Repo} + alias Explorer.Chain.Import + alias Explorer.Chain.Scroll.Batch, as: ScrollBatch + alias Explorer.Prometheus.Instrumenter + + @behaviour Import.Runner + + # milliseconds + @timeout 60_000 + + @type imported :: [ScrollBatch.t()] + + @impl Import.Runner + def ecto_schema_module, do: ScrollBatch + + @impl Import.Runner + def option_key, do: :scroll_batches + + @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, :insert_scroll_batches, fn repo, _ -> + Instrumenter.block_import_stage_runner( + fn -> insert(repo, changes_list, insert_options) end, + :block_referencing, + :scroll_batches, + :scroll_batches + ) + end) + end + + @impl Import.Runner + def timeout, do: @timeout + + @spec insert(Repo.t(), [map()], %{required(:timeout) => timeout(), required(:timestamps) => Import.timestamps()}) :: + {:ok, [ScrollBatch.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 ScrollBatch 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, + conflict_target: :number, + on_conflict: on_conflict, + for: ScrollBatch, + returning: true, + timeout: timeout, + timestamps: timestamps + ) + + {:ok, inserted} + end + + defp default_on_conflict do + from( + sb in ScrollBatch, + update: [ + set: [ + # Don't update `number` as it is a primary key and used for the conflict target + number: fragment("EXCLUDED.number"), + commit_transaction_hash: fragment("EXCLUDED.commit_transaction_hash"), + commit_block_number: fragment("EXCLUDED.commit_block_number"), + commit_timestamp: fragment("EXCLUDED.commit_timestamp"), + l2_block_range: fragment("EXCLUDED.l2_block_range"), + container: fragment("EXCLUDED.container"), + inserted_at: fragment("LEAST(?, EXCLUDED.inserted_at)", sb.inserted_at), + updated_at: fragment("GREATEST(?, EXCLUDED.updated_at)", sb.updated_at) + ] + ], + where: + fragment( + "(EXCLUDED.number, EXCLUDED.commit_transaction_hash, EXCLUDED.commit_block_number, EXCLUDED.commit_timestamp, EXCLUDED.l2_block_range, EXCLUDED.container) IS DISTINCT FROM (?, ?, ?, ?, ?, ?)", + sb.number, + sb.commit_transaction_hash, + sb.commit_block_number, + sb.commit_timestamp, + sb.l2_block_range, + sb.container + ) + ) + end +end diff --git a/apps/explorer/lib/explorer/chain/import/runner/scroll/bridge_operations.ex b/apps/explorer/lib/explorer/chain/import/runner/scroll/bridge_operations.ex new file mode 100644 index 0000000..44c8cc4 --- /dev/null +++ b/apps/explorer/lib/explorer/chain/import/runner/scroll/bridge_operations.ex @@ -0,0 +1,111 @@ +defmodule Explorer.Chain.Import.Runner.Scroll.BridgeOperations do + @moduledoc """ + Bulk imports `Explorer.Chain.Scroll.Bridge`. + """ + + require Ecto.Query + + import Ecto.Query, only: [from: 2] + + alias Ecto.{Changeset, Multi, Repo} + alias Explorer.Chain.Import + alias Explorer.Chain.Scroll.Bridge, as: ScrollBridge + alias Explorer.Prometheus.Instrumenter + + @behaviour Import.Runner + + # milliseconds + @timeout 60_000 + + @type imported :: [ScrollBridge.t()] + + @impl Import.Runner + def ecto_schema_module, do: ScrollBridge + + @impl Import.Runner + def option_key, do: :scroll_bridge_operations + + @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, :insert_scroll_bridge_operations, fn repo, _ -> + Instrumenter.block_import_stage_runner( + fn -> insert(repo, changes_list, insert_options) end, + :block_referencing, + :scroll_bridge_operations, + :scroll_bridge_operations + ) + end) + end + + @impl Import.Runner + def timeout, do: @timeout + + @spec insert(Repo.t(), [map()], %{required(:timeout) => timeout(), required(:timestamps) => Import.timestamps()}) :: + {:ok, [ScrollBridge.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 ScrollBridge ShareLocks order (see docs: sharelock.md) + ordered_changes_list = Enum.sort_by(changes_list, &{&1.type, &1.message_hash}) + + {:ok, inserted} = + Import.insert_changes_list( + repo, + ordered_changes_list, + conflict_target: [:type, :message_hash], + on_conflict: on_conflict, + for: ScrollBridge, + returning: true, + timeout: timeout, + timestamps: timestamps + ) + + {:ok, inserted} + end + + defp default_on_conflict do + from( + sb in ScrollBridge, + update: [ + set: [ + # Don't update `type` as it is part of the composite primary key and used for the conflict target + # Don't update `message_hash` as it is part of the composite primary key and used for the conflict target + index: fragment("COALESCE(EXCLUDED.index, ?)", sb.index), + l1_transaction_hash: fragment("COALESCE(EXCLUDED.l1_transaction_hash, ?)", sb.l1_transaction_hash), + l2_transaction_hash: fragment("COALESCE(EXCLUDED.l2_transaction_hash, ?)", sb.l2_transaction_hash), + amount: fragment("COALESCE(EXCLUDED.amount, ?)", sb.amount), + block_number: fragment("COALESCE(EXCLUDED.block_number, ?)", sb.block_number), + block_timestamp: fragment("COALESCE(EXCLUDED.block_timestamp, ?)", sb.block_timestamp), + inserted_at: fragment("LEAST(?, EXCLUDED.inserted_at)", sb.inserted_at), + updated_at: fragment("GREATEST(?, EXCLUDED.updated_at)", sb.updated_at) + ] + ], + where: + fragment( + "(EXCLUDED.index, EXCLUDED.l1_transaction_hash, EXCLUDED.l2_transaction_hash, EXCLUDED.amount, EXCLUDED.block_number, EXCLUDED.block_timestamp) IS DISTINCT FROM (?, ?, ?, ?, ?, ?)", + sb.index, + sb.l1_transaction_hash, + sb.l2_transaction_hash, + sb.amount, + sb.block_number, + sb.block_timestamp + ) + ) + end +end diff --git a/apps/explorer/lib/explorer/chain/import/runner/scroll/l1_fee_params.ex b/apps/explorer/lib/explorer/chain/import/runner/scroll/l1_fee_params.ex new file mode 100644 index 0000000..525fd0f --- /dev/null +++ b/apps/explorer/lib/explorer/chain/import/runner/scroll/l1_fee_params.ex @@ -0,0 +1,102 @@ +defmodule Explorer.Chain.Import.Runner.Scroll.L1FeeParams do + @moduledoc """ + Bulk imports `Explorer.Chain.Scroll.L1FeeParam`. + """ + + require Ecto.Query + + alias Ecto.{Changeset, Multi, Repo} + alias Explorer.Chain.Import + alias Explorer.Chain.Scroll.L1FeeParam + alias Explorer.Prometheus.Instrumenter + + import Ecto.Query, only: [from: 2] + + @behaviour Import.Runner + + # milliseconds + @timeout 60_000 + + @type imported :: [L1FeeParam.t()] + + @impl Import.Runner + def ecto_schema_module, do: L1FeeParam + + @impl Import.Runner + def option_key, do: :scroll_l1_fee_params + + @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, :insert_l1_fee_params, fn repo, _ -> + Instrumenter.block_import_stage_runner( + fn -> insert(repo, changes_list, insert_options) end, + :block_referencing, + :scroll_l1_fee_params, + :scroll_l1_fee_params + ) + end) + end + + @impl Import.Runner + def timeout, do: @timeout + + @spec insert(Repo.t(), [map()], %{required(:timeout) => timeout(), required(:timestamps) => Import.timestamps()}) :: + {:ok, [L1FeeParam.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 L1FeeParam ShareLocks order (see docs: sharelock.md) + ordered_changes_list = Enum.sort_by(changes_list, &{&1.block_number, &1.transaction_index, &1.name}) + + {:ok, inserted} = + Import.insert_changes_list( + repo, + ordered_changes_list, + for: L1FeeParam, + returning: true, + timeout: timeout, + timestamps: timestamps, + conflict_target: [:block_number, :transaction_index, :name], + on_conflict: on_conflict + ) + + {:ok, inserted} + end + + defp default_on_conflict do + from( + param in L1FeeParam, + update: [ + set: [ + # Don't update `block_number` as it is part of the composite primary key and used for the conflict target + # Don't update `transaction_index` as it is part of the composite primary key and used for the conflict target + # Don't update `name` as it is part of the composite primary key and used for the conflict target + value: fragment("EXCLUDED.value"), + inserted_at: fragment("LEAST(?, EXCLUDED.inserted_at)", param.inserted_at), + updated_at: fragment("GREATEST(?, EXCLUDED.updated_at)", param.updated_at) + ] + ], + where: + fragment( + "(EXCLUDED.value) IS DISTINCT FROM (?)", + param.value + ) + ) + end +end diff --git a/apps/explorer/lib/explorer/chain/import/runner/signed_authorizations.ex b/apps/explorer/lib/explorer/chain/import/runner/signed_authorizations.ex new file mode 100644 index 0000000..147f5c8 --- /dev/null +++ b/apps/explorer/lib/explorer/chain/import/runner/signed_authorizations.ex @@ -0,0 +1,110 @@ +defmodule Explorer.Chain.Import.Runner.SignedAuthorizations do + @moduledoc """ + Bulk imports `t:Explorer.Chain.SignedAuthorization.t/0`. + """ + + require Ecto.Query + + import Ecto.Query, only: [from: 2] + + alias Ecto.{Changeset, Multi, Repo} + alias Explorer.Chain.{Import, SignedAuthorization} + alias Explorer.Prometheus.Instrumenter + + @behaviour Import.Runner + + # milliseconds + @timeout 60_000 + + @type imported :: [SignedAuthorization.t()] + + @impl Import.Runner + def ecto_schema_module, do: SignedAuthorization + + @impl Import.Runner + def option_key, do: :signed_authorizations + + @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, :signed_authorizations, fn repo, _ -> + Instrumenter.block_import_stage_runner( + fn -> insert(repo, changes_list, insert_options) end, + :block_referencing, + :signed_authorizations, + :signed_authorizations + ) + 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, [SignedAuthorization.t()]} + | {:error, [Changeset.t()]} + 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) + conflict_target = [:transaction_hash, :index] + + {:ok, _} = + Import.insert_changes_list( + repo, + changes_list, + for: SignedAuthorization, + on_conflict: on_conflict, + conflict_target: conflict_target, + returning: true, + timeout: timeout, + timestamps: timestamps + ) + end + + defp default_on_conflict do + from( + authorization in SignedAuthorization, + update: [ + set: [ + chain_id: fragment("EXCLUDED.chain_id"), + address: fragment("EXCLUDED.address"), + nonce: fragment("EXCLUDED.nonce"), + r: fragment("EXCLUDED.r"), + s: fragment("EXCLUDED.s"), + v: fragment("EXCLUDED.v"), + authority: fragment("EXCLUDED.authority"), + inserted_at: fragment("LEAST(?, EXCLUDED.inserted_at)", authorization.inserted_at), + updated_at: fragment("GREATEST(?, EXCLUDED.updated_at)", authorization.updated_at) + ] + ], + where: + fragment( + "(EXCLUDED.chain_id, EXCLUDED.address, EXCLUDED.nonce, EXCLUDED.r, EXCLUDED.s, EXCLUDED.v, EXCLUDED.authority) IS DISTINCT FROM (?, ?, ?, ?, ?, ?, ?)", + authorization.chain_id, + authorization.address, + authorization.nonce, + authorization.r, + authorization.s, + authorization.v, + authorization.authority + ) + ) + end +end diff --git a/apps/explorer/lib/explorer/chain/import/runner/transactions.ex b/apps/explorer/lib/explorer/chain/import/runner/transactions.ex index aee4679..d16e442 100644 --- a/apps/explorer/lib/explorer/chain/import/runner/transactions.ex +++ b/apps/explorer/lib/explorer/chain/import/runner/transactions.ex @@ -244,7 +244,7 @@ defmodule Explorer.Chain.Import.Runner.Transactions do l1_fee_scalar: fragment("EXCLUDED.l1_fee_scalar"), l1_gas_price: fragment("EXCLUDED.l1_gas_price"), l1_gas_used: fragment("EXCLUDED.l1_gas_used"), - l1_tx_origin: fragment("EXCLUDED.l1_tx_origin"), + l1_transaction_origin: fragment("EXCLUDED.l1_transaction_origin"), l1_block_number: fragment("EXCLUDED.l1_block_number"), # 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), @@ -253,7 +253,7 @@ defmodule Explorer.Chain.Import.Runner.Transactions do ], 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.l1_fee, EXCLUDED.l1_fee_scalar, EXCLUDED.l1_gas_price, EXCLUDED.l1_gas_used, EXCLUDED.l1_tx_origin, EXCLUDED.l1_block_number) IS DISTINCT FROM (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", + "(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.l1_fee, EXCLUDED.l1_fee_scalar, EXCLUDED.l1_gas_price, EXCLUDED.l1_gas_used, EXCLUDED.l1_transaction_origin, EXCLUDED.l1_block_number) IS DISTINCT FROM (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", transaction.block_hash, transaction.block_number, transaction.block_consensus, @@ -283,7 +283,7 @@ defmodule Explorer.Chain.Import.Runner.Transactions do transaction.l1_fee_scalar, transaction.l1_gas_price, transaction.l1_gas_used, - transaction.l1_tx_origin, + transaction.l1_transaction_origin, transaction.l1_block_number ) ) @@ -438,6 +438,80 @@ defmodule Explorer.Chain.Import.Runner.Transactions do ) end + :scroll -> + 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"), + 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"), + l1_fee: fragment("EXCLUDED.l1_fee"), + queue_index: fragment("EXCLUDED.queue_index"), + # 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.l1_fee, EXCLUDED.queue_index) 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.l1_fee, + transaction.queue_index + ) + ) + end + _ -> defp default_on_conflict do from( diff --git a/apps/explorer/lib/explorer/chain/import/runner/zksync/batch_transactions.ex b/apps/explorer/lib/explorer/chain/import/runner/zksync/batch_transactions.ex index 39804aa..a7dca22 100644 --- a/apps/explorer/lib/explorer/chain/import/runner/zksync/batch_transactions.ex +++ b/apps/explorer/lib/explorer/chain/import/runner/zksync/batch_transactions.ex @@ -60,7 +60,7 @@ defmodule Explorer.Chain.Import.Runner.ZkSync.BatchTransactions do | {:error, [Changeset.t()]} def insert(repo, changes_list, %{timeout: timeout, timestamps: timestamps} = _options) when is_list(changes_list) do # Enforce ZkSync.BatchTransaction ShareLocks order (see docs: sharelock.md) - ordered_changes_list = Enum.sort_by(changes_list, & &1.tx_hash) + ordered_changes_list = Enum.sort_by(changes_list, & &1.transaction_hash) {:ok, inserted} = Import.insert_changes_list( @@ -70,7 +70,7 @@ defmodule Explorer.Chain.Import.Runner.ZkSync.BatchTransactions do returning: true, timeout: timeout, timestamps: timestamps, - conflict_target: :tx_hash, + conflict_target: :transaction_hash, on_conflict: :nothing ) diff --git a/apps/explorer/lib/explorer/chain/import/runner/zksync/lifecycle_transactions.ex b/apps/explorer/lib/explorer/chain/import/runner/zksync/lifecycle_transactions.ex index b5b5e74..7011423 100644 --- a/apps/explorer/lib/explorer/chain/import/runner/zksync/lifecycle_transactions.ex +++ b/apps/explorer/lib/explorer/chain/import/runner/zksync/lifecycle_transactions.ex @@ -83,20 +83,20 @@ defmodule Explorer.Chain.Import.Runner.ZkSync.LifecycleTransactions do defp default_on_conflict do from( - tx in LifecycleTransaction, + transaction 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 timestamp: fragment("EXCLUDED.timestamp"), - inserted_at: fragment("LEAST(?, EXCLUDED.inserted_at)", tx.inserted_at), - updated_at: fragment("GREATEST(?, EXCLUDED.updated_at)", tx.updated_at) + inserted_at: fragment("LEAST(?, EXCLUDED.inserted_at)", transaction.inserted_at), + updated_at: fragment("GREATEST(?, EXCLUDED.updated_at)", transaction.updated_at) ] ], where: fragment( "(EXCLUDED.timestamp) IS DISTINCT FROM (?)", - tx.timestamp + transaction.timestamp ) ) end diff --git a/apps/explorer/lib/explorer/chain/import/runner/zksync/transaction_batches.ex b/apps/explorer/lib/explorer/chain/import/runner/zksync/transaction_batches.ex index 2c4639a..11c85ff 100644 --- a/apps/explorer/lib/explorer/chain/import/runner/zksync/transaction_batches.ex +++ b/apps/explorer/lib/explorer/chain/import/runner/zksync/transaction_batches.ex @@ -88,8 +88,8 @@ defmodule Explorer.Chain.Import.Runner.ZkSync.TransactionBatches do set: [ # don't update `number` as it is a primary key and used for the conflict target timestamp: fragment("EXCLUDED.timestamp"), - l1_tx_count: fragment("EXCLUDED.l1_tx_count"), - l2_tx_count: fragment("EXCLUDED.l2_tx_count"), + l1_transaction_count: fragment("EXCLUDED.l1_transaction_count"), + l2_transaction_count: fragment("EXCLUDED.l2_transaction_count"), root_hash: fragment("EXCLUDED.root_hash"), l1_gas_price: fragment("EXCLUDED.l1_gas_price"), l2_fair_gas_price: fragment("EXCLUDED.l2_fair_gas_price"), @@ -104,10 +104,10 @@ defmodule Explorer.Chain.Import.Runner.ZkSync.TransactionBatches do ], where: fragment( - "(EXCLUDED.timestamp, EXCLUDED.l1_tx_count, EXCLUDED.l2_tx_count, EXCLUDED.root_hash, EXCLUDED.l1_gas_price, EXCLUDED.l2_fair_gas_price, EXCLUDED.start_block, EXCLUDED.end_block, EXCLUDED.commit_id, EXCLUDED.prove_id, EXCLUDED.execute_id) IS DISTINCT FROM (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", + "(EXCLUDED.timestamp, EXCLUDED.l1_transaction_count, EXCLUDED.l2_transaction_count, EXCLUDED.root_hash, EXCLUDED.l1_gas_price, EXCLUDED.l2_fair_gas_price, EXCLUDED.start_block, EXCLUDED.end_block, EXCLUDED.commit_id, EXCLUDED.prove_id, EXCLUDED.execute_id) IS DISTINCT FROM (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", tb.timestamp, - tb.l1_tx_count, - tb.l2_tx_count, + tb.l1_transaction_count, + tb.l2_transaction_count, tb.root_hash, tb.l1_gas_price, tb.l2_fair_gas_price, 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 72b62a2..0000000 --- a/apps/explorer/lib/explorer/chain/import/stage/address_referencing.ex +++ /dev/null @@ -1,30 +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 all_runners, - do: runners() - - @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 fe91366..0000000 --- a/apps/explorer/lib/explorer/chain/import/stage/addresses.ex +++ /dev/null @@ -1,26 +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] - - @impl Stage - def all_runners, - do: runners() - - @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/block_following.ex b/apps/explorer/lib/explorer/chain/import/stage/block_following.ex index b59dc20..9848926 100644 --- a/apps/explorer/lib/explorer/chain/import/stage/block_following.ex +++ b/apps/explorer/lib/explorer/chain/import/stage/block_following.ex @@ -13,6 +13,7 @@ defmodule Explorer.Chain.Import.Stage.BlockFollowing do do: [ Runner.Block.SecondDegreeRelations, Runner.Block.Rewards, + Runner.Address.TokenBalances, Runner.Address.CurrentTokenBalances ] 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 0d339a7..af647c2 100644 --- a/apps/explorer/lib/explorer/chain/import/stage/block_referencing.ex +++ b/apps/explorer/lib/explorer/chain/import/stage/block_referencing.ex @@ -8,85 +8,22 @@ defmodule Explorer.Chain.Import.Stage.BlockReferencing do @behaviour Stage - @default_runners [ - Runner.Transaction.Forks, - Runner.Logs, - Runner.Tokens, - Runner.TokenInstances, - Runner.Address.TokenBalances, - Runner.TransactionActions, - Runner.Withdrawals - ] - - @extra_runners_by_chain_type %{ - optimism: [ - Runner.Optimism.FrameSequences, - Runner.Optimism.FrameSequenceBlobs, - Runner.Optimism.TxnBatches, - Runner.Optimism.OutputRoots, - Runner.Optimism.DisputeGames, - Runner.Optimism.Deposits, - Runner.Optimism.Withdrawals, - Runner.Optimism.WithdrawalEvents - ], - polygon_edge: [ - Runner.PolygonEdge.Deposits, - Runner.PolygonEdge.DepositExecutes, - Runner.PolygonEdge.Withdrawals, - Runner.PolygonEdge.WithdrawalExits - ], - polygon_zkevm: [ - Runner.PolygonZkevm.LifecycleTransactions, - Runner.PolygonZkevm.TransactionBatches, - Runner.PolygonZkevm.BatchTransactions, - Runner.PolygonZkevm.BridgeL1Tokens, - Runner.PolygonZkevm.BridgeOperations - ], - zksync: [ - Runner.ZkSync.LifecycleTransactions, - Runner.ZkSync.TransactionBatches, - Runner.ZkSync.BatchTransactions, - Runner.ZkSync.BatchBlocks - ], - shibarium: [ - Runner.Shibarium.BridgeOperations - ], - ethereum: [ - Runner.Beacon.BlobTransactions - ], - arbitrum: [ - Runner.Arbitrum.Messages, - Runner.Arbitrum.LifecycleTransactions, - Runner.Arbitrum.L1Executions, - Runner.Arbitrum.L1Batches, - Runner.Arbitrum.BatchBlocks, - Runner.Arbitrum.BatchTransactions, - Runner.Arbitrum.DaMultiPurposeRecords - ], - celo: [ - Runner.Celo.ValidatorGroupVotes, - Runner.Celo.ElectionRewards, - Runner.Celo.EpochRewards - ] - } - @impl Stage def runners do - chain_type = Application.get_env(:explorer, :chain_type) - chain_type_runners = Map.get(@extra_runners_by_chain_type, chain_type, []) - - @default_runners ++ chain_type_runners + [ + Runner.Transaction.Forks, + Runner.Logs, + Runner.Tokens, + Runner.TokenInstances, + Runner.TransactionActions, + Runner.Withdrawals, + Runner.SignedAuthorizations + ] end @impl Stage - def all_runners do - all_extra_runners = - @extra_runners_by_chain_type - |> Map.values() - |> Enum.concat() - - @default_runners ++ all_extra_runners - end + def all_runners, + do: runners() @impl Stage def multis(runner_to_changes_list, options) do diff --git a/apps/explorer/lib/explorer/chain/import/stage/block_related.ex b/apps/explorer/lib/explorer/chain/import/stage/block_related.ex index a9c25cf..b18808f 100644 --- a/apps/explorer/lib/explorer/chain/import/stage/block_related.ex +++ b/apps/explorer/lib/explorer/chain/import/stage/block_related.ex @@ -10,8 +10,8 @@ defmodule Explorer.Chain.Import.Stage.BlockRelated do @addresses_runner Runner.Addresses @rest_runners [ - Runner.Address.CoinBalances, Runner.Blocks, + Runner.Address.CoinBalances, Runner.Address.CoinBalancesDaily, Runner.Transactions, Runner.TokenTransfers diff --git a/apps/explorer/lib/explorer/chain/import/stage/chain_type_specific.ex b/apps/explorer/lib/explorer/chain/import/stage/chain_type_specific.ex new file mode 100644 index 0000000..f897810 --- /dev/null +++ b/apps/explorer/lib/explorer/chain/import/stage/chain_type_specific.ex @@ -0,0 +1,88 @@ +defmodule Explorer.Chain.Import.Stage.ChainTypeSpecific do + @moduledoc """ + Imports any chain type specific tables. + """ + + alias Explorer.Chain.Import.{Runner, Stage} + + @behaviour Stage + + @runners_by_chain_type %{ + optimism: [ + Runner.Optimism.FrameSequences, + Runner.Optimism.FrameSequenceBlobs, + Runner.Optimism.TransactionBatches, + Runner.Optimism.OutputRoots, + Runner.Optimism.DisputeGames, + Runner.Optimism.Deposits, + Runner.Optimism.Withdrawals, + Runner.Optimism.WithdrawalEvents + ], + polygon_edge: [ + Runner.PolygonEdge.Deposits, + Runner.PolygonEdge.DepositExecutes, + Runner.PolygonEdge.Withdrawals, + Runner.PolygonEdge.WithdrawalExits + ], + polygon_zkevm: [ + Runner.PolygonZkevm.LifecycleTransactions, + Runner.PolygonZkevm.TransactionBatches, + Runner.PolygonZkevm.BatchTransactions, + Runner.PolygonZkevm.BridgeL1Tokens, + Runner.PolygonZkevm.BridgeOperations + ], + zksync: [ + Runner.ZkSync.LifecycleTransactions, + Runner.ZkSync.TransactionBatches, + Runner.ZkSync.BatchTransactions, + Runner.ZkSync.BatchBlocks + ], + shibarium: [ + Runner.Shibarium.BridgeOperations + ], + ethereum: [ + Runner.Beacon.BlobTransactions + ], + arbitrum: [ + Runner.Arbitrum.Messages, + Runner.Arbitrum.LifecycleTransactions, + Runner.Arbitrum.L1Executions, + Runner.Arbitrum.L1Batches, + Runner.Arbitrum.BatchBlocks, + Runner.Arbitrum.BatchTransactions, + Runner.Arbitrum.DaMultiPurposeRecords + ], + scroll: [ + Runner.Scroll.BatchBundles, + Runner.Scroll.Batches, + Runner.Scroll.BridgeOperations, + Runner.Scroll.L1FeeParams + ], + celo: [ + Runner.Celo.ValidatorGroupVotes, + Runner.Celo.ElectionRewards, + Runner.Celo.EpochRewards + ] + } + + @impl Stage + def runners do + chain_type = Application.get_env(:explorer, :chain_type) + Map.get(@runners_by_chain_type, chain_type, []) + end + + @impl Stage + def all_runners do + @runners_by_chain_type + |> Map.values() + |> Enum.concat() + end + + @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/internal_transaction.ex b/apps/explorer/lib/explorer/chain/internal_transaction.ex index 2661c63..5d6b452 100644 --- a/apps/explorer/lib/explorer/chain/internal_transaction.ex +++ b/apps/explorer/lib/explorer/chain/internal_transaction.ex @@ -5,8 +5,12 @@ defmodule Explorer.Chain.InternalTransaction do alias Explorer.{Chain, PagingOptions} alias Explorer.Chain.{Address, Block, Data, Hash, PendingBlockOperation, Transaction, Wei} + alias Explorer.Chain.DenormalizationHelper alias Explorer.Chain.InternalTransaction.{Action, CallType, Result, Type} + @typep paging_options :: {:paging_options, PagingOptions.t()} + @typep api? :: {:api?, true | false} + @default_paging_options %PagingOptions{page_size: 50} @typedoc """ @@ -813,6 +817,39 @@ defmodule Explorer.Chain.InternalTransaction do ) end + @doc """ + Returns the ordered paginated list of internal transactions (consensus blocks only) from the DB with address, block preloads + """ + @spec fetch([paging_options | api?]) :: [] + def fetch(options) do + paging_options = Keyword.get(options, :paging_options, @default_paging_options) + + case paging_options do + %PagingOptions{key: {0, 0}} -> + [] + + _ -> + preloads = + DenormalizationHelper.extend_transaction_preload([ + :block, + [from_address: [:scam_badge, :names, :smart_contract, :proxy_implementations]], + [to_address: [:scam_badge, :names, :smart_contract, :proxy_implementations]] + ]) + + __MODULE__ + |> where_nonpending_block() + |> Chain.page_internal_transaction(paging_options, %{index_internal_transaction_desc_order: true}) + |> order_by([internal_transaction], + desc: internal_transaction.block_number, + desc: internal_transaction.transaction_index, + desc: internal_transaction.index + ) + |> limit(^paging_options.page_size) + |> preload(^preloads) + |> Chain.select_repo(options).all() + end + end + defp page_block_internal_transaction(query, %PagingOptions{key: %{block_index: block_index}}) do query |> where([internal_transaction], internal_transaction.block_index > ^block_index) diff --git a/apps/explorer/lib/explorer/chain/log.ex b/apps/explorer/lib/explorer/chain/log.ex index 7ef3588..92018eb 100644 --- a/apps/explorer/lib/explorer/chain/log.ex +++ b/apps/explorer/lib/explorer/chain/log.ex @@ -394,9 +394,9 @@ defmodule Explorer.Chain.Log do |> Base.decode16!(case: :lower) end - def fetch_log_by_tx_hash_and_first_topic(tx_hash, first_topic, options \\ []) do + def fetch_log_by_transaction_hash_and_first_topic(transaction_hash, first_topic, options \\ []) do __MODULE__ - |> where([l], l.transaction_hash == ^tx_hash and l.first_topic == ^first_topic) + |> where([l], l.transaction_hash == ^transaction_hash and l.first_topic == ^first_topic) |> limit(1) |> Chain.select_repo(options).one() end diff --git a/apps/explorer/lib/explorer/chain/map_cache.ex b/apps/explorer/lib/explorer/chain/map_cache.ex index 6dfbe6b..fbe38db 100644 --- a/apps/explorer/lib/explorer/chain/map_cache.ex +++ b/apps/explorer/lib/explorer/chain/map_cache.ex @@ -201,6 +201,35 @@ defmodule Explorer.Chain.MapCache do end end + defp named_functions(:async_task) do + quote do + def get_async_task, do: get(:async_task) + + @doc """ + Checks if the asynchronous task has stalled (has finished, but still in cache), + and if so, creates a new replacement task to continue the operation. + """ + def safe_get_async_task do + case get_async_task() do + pid when is_pid(pid) -> + if Process.alive?(pid) do + pid + else + set_async_task(nil) + get_async_task() + end + + not_pid -> + not_pid + end + end + + def set_async_task(value), do: set(:async_task, value) + + def update_async_task(value), do: update(:async_task, value) + end + end + # sobelow_skip ["DOS"] defp named_functions(key) do quote do diff --git a/apps/explorer/lib/explorer/chain/metrics/queries.ex b/apps/explorer/lib/explorer/chain/metrics/queries.ex index c68b829..496b6ea 100644 --- a/apps/explorer/lib/explorer/chain/metrics/queries.ex +++ b/apps/explorer/lib/explorer/chain/metrics/queries.ex @@ -35,17 +35,17 @@ defmodule Explorer.Chain.Metrics.Queries do def success_transactions_number_query do if DenormalizationHelper.transactions_denormalization_finished?() do Transaction - |> where([tx], tx.block_timestamp >= ago(^update_period_hours(), "hour")) - |> where([tx], tx.block_consensus == true) - |> where([tx], tx.status == ^1) - |> select([tx], count(tx.hash)) + |> where([transaction], transaction.block_timestamp >= ago(^update_period_hours(), "hour")) + |> where([transaction], transaction.block_consensus == true) + |> where([transaction], transaction.status == ^1) + |> select([transaction], count(transaction.hash)) else Transaction - |> join(:inner, [tx], block in assoc(tx, :block)) - |> where([tx, block], block.timestamp >= ago(^update_period_hours(), "hour")) - |> where([tx, block], block.consensus == true) - |> where([tx, block], tx.status == ^1) - |> select([tx, block], count(tx.hash)) + |> join(:inner, [transaction], block in assoc(transaction, :block)) + |> where([transaction, block], block.timestamp >= ago(^update_period_hours(), "hour")) + |> where([transaction, block], block.consensus == true) + |> where([transaction, block], transaction.status == ^1) + |> select([transaction, block], count(transaction.hash)) end end @@ -57,30 +57,30 @@ defmodule Explorer.Chain.Metrics.Queries do transactions_query = if DenormalizationHelper.transactions_denormalization_finished?() do Transaction - |> where([tx], not is_nil(tx.created_contract_address_hash)) - |> where([tx], tx.block_timestamp >= ago(^update_period_hours(), "hour")) - |> where([tx], tx.block_consensus == true) - |> where([tx], tx.status == ^1) - |> select([tx], tx.created_contract_address_hash) + |> where([transaction], not is_nil(transaction.created_contract_address_hash)) + |> where([transaction], transaction.block_timestamp >= ago(^update_period_hours(), "hour")) + |> where([transaction], transaction.block_consensus == true) + |> where([transaction], transaction.status == ^1) + |> select([transaction], transaction.created_contract_address_hash) else Transaction - |> join(:inner, [tx], block in assoc(tx, :block)) - |> where([tx], not is_nil(tx.created_contract_address_hash)) - |> where([tx, block], block.consensus == true) - |> where([tx, block], block.timestamp >= ago(^update_period_hours(), "hour")) - |> where([tx, block], tx.status == ^1) - |> select([tx, block], tx.created_contract_address_hash) + |> join(:inner, [transaction], block in assoc(transaction, :block)) + |> where([transaction], not is_nil(transaction.created_contract_address_hash)) + |> where([transaction, block], block.consensus == true) + |> where([transaction, block], block.timestamp >= ago(^update_period_hours(), "hour")) + |> where([transaction, block], transaction.status == ^1) + |> select([transaction, block], transaction.created_contract_address_hash) end # todo: this part is too slow, need to optimize # internal_transactions_query = # InternalTransaction # |> join(:inner, [it], transaction in assoc(it, :transaction)) - # |> where([it, tx], not is_nil(it.created_contract_address_hash)) - # |> where([it, tx], tx.block_timestamp >= ago(^update_period_hours(), "hour")) - # |> where([it, tx], tx.block_consensus == true) - # |> where([it, tx], tx.status == ^1) - # |> select([it, tx], it.created_contract_address_hash) + # |> where([it, transaction], not is_nil(it.created_contract_address_hash)) + # |> where([it, transaction], transaction.block_timestamp >= ago(^update_period_hours(), "hour")) + # |> where([it, transaction], transaction.block_consensus == true) + # |> where([it, transaction], transaction.status == ^1) + # |> select([it, transaction], it.created_contract_address_hash) # |> wrapped_union_subquery() # query = @@ -143,15 +143,15 @@ defmodule Explorer.Chain.Metrics.Queries do def simplified_active_addresses_number_query do if DenormalizationHelper.transactions_denormalization_finished?() do Transaction - |> where([tx], tx.block_timestamp >= ago(^update_period_hours(), "hour")) - |> where([tx], tx.block_consensus == true) - |> select([tx], fragment("COUNT(DISTINCT(?))", tx.from_address_hash)) + |> where([transaction], transaction.block_timestamp >= ago(^update_period_hours(), "hour")) + |> where([transaction], transaction.block_consensus == true) + |> select([transaction], fragment("COUNT(DISTINCT(?))", transaction.from_address_hash)) else Transaction - |> join(:inner, [tx], block in assoc(tx, :block)) - |> where([tx, block], block.timestamp >= ago(^update_period_hours(), "hour")) - |> where([tx, block], block.consensus == true) - |> select([tx], fragment("COUNT(DISTINCT(?))", tx.from_address_hash)) + |> join(:inner, [transaction], block in assoc(transaction, :block)) + |> where([transaction, block], block.timestamp >= ago(^update_period_hours(), "hour")) + |> where([transaction, block], block.consensus == true) + |> select([transaction], fragment("COUNT(DISTINCT(?))", transaction.from_address_hash)) end end @@ -164,31 +164,31 @@ defmodule Explorer.Chain.Metrics.Queries do transactions_query = if DenormalizationHelper.transactions_denormalization_finished?() do Transaction - |> where([tx], tx.block_timestamp >= ago(^update_period_hours(), "hour")) - |> where([tx], tx.block_consensus == true) + |> where([transaction], transaction.block_timestamp >= ago(^update_period_hours(), "hour")) + |> where([transaction], transaction.block_consensus == true) |> distinct(true) - |> select([tx], %{ + |> select([transaction], %{ address_hash: fragment( "UNNEST(ARRAY[?, ?, ?])", - tx.from_address_hash, - tx.to_address_hash, - tx.created_contract_address_hash + transaction.from_address_hash, + transaction.to_address_hash, + transaction.created_contract_address_hash ) }) else Transaction - |> join(:inner, [tx], block in assoc(tx, :block)) - |> where([tx, block], block.timestamp >= ago(^update_period_hours(), "hour")) - |> where([tx, block], block.consensus == true) + |> join(:inner, [transaction], block in assoc(transaction, :block)) + |> where([transaction, block], block.timestamp >= ago(^update_period_hours(), "hour")) + |> where([transaction, block], block.consensus == true) |> distinct(true) - |> select([tx, block], %{ + |> select([transaction, block], %{ address_hash: fragment( "UNNEST(ARRAY[?, ?, ?])", - tx.from_address_hash, - tx.to_address_hash, - tx.created_contract_address_hash + transaction.from_address_hash, + transaction.to_address_hash, + transaction.created_contract_address_hash ) }) end @@ -197,10 +197,10 @@ defmodule Explorer.Chain.Metrics.Queries do if DenormalizationHelper.transactions_denormalization_finished?() do InternalTransaction |> join(:inner, [it], transaction in assoc(it, :transaction)) - |> where([it, tx], tx.block_timestamp >= ago(^update_period_hours(), "hour")) - |> where([it, tx], tx.block_consensus == true) - |> where([it, tx], tx.status == ^1) - |> select([it, tx], %{ + |> where([it, transaction], transaction.block_timestamp >= ago(^update_period_hours(), "hour")) + |> where([it, transaction], transaction.block_consensus == true) + |> where([it, transaction], transaction.status == ^1) + |> select([it, transaction], %{ address_hash: fragment( "UNNEST(ARRAY[?, ?, ?])", @@ -213,11 +213,11 @@ defmodule Explorer.Chain.Metrics.Queries do else InternalTransaction |> join(:inner, [it], transaction in assoc(it, :transaction)) - |> join(:inner, [tx], block in assoc(tx, :block)) - |> where([it, tx, block], tx.block_timestamp >= ago(^update_period_hours(), "hour")) - |> where([it, tx, block], block.consensus == true) - |> where([it, tx, block], tx.status == ^1) - |> select([it, tx, block], %{ + |> join(:inner, [transaction], block in assoc(transaction, :block)) + |> where([it, transaction, block], transaction.block_timestamp >= ago(^update_period_hours(), "hour")) + |> where([it, transaction, block], block.consensus == true) + |> where([it, transaction, block], transaction.status == ^1) + |> select([it, transaction, block], %{ address_hash: fragment( "UNNEST(ARRAY[?, ?, ?])", @@ -233,10 +233,10 @@ defmodule Explorer.Chain.Metrics.Queries do if DenormalizationHelper.transactions_denormalization_finished?() do TokenTransfer |> join(:inner, [tt], transaction in assoc(tt, :transaction)) - |> where([tt, tx], tx.block_timestamp >= ago(^update_period_hours(), "hour")) - |> where([tt, tx], tx.block_consensus == true) - |> where([tt, tx], tx.status == ^1) - |> select([tt, tx], %{ + |> where([tt, transaction], transaction.block_timestamp >= ago(^update_period_hours(), "hour")) + |> where([tt, transaction], transaction.block_consensus == true) + |> where([tt, transaction], transaction.status == ^1) + |> select([tt, transaction], %{ address_hash: fragment("UNNEST(ARRAY[?, ?, ?])", tt.from_address_hash, tt.to_address_hash, tt.token_contract_address_hash) }) @@ -244,11 +244,11 @@ defmodule Explorer.Chain.Metrics.Queries do else TokenTransfer |> join(:inner, [tt], transaction in assoc(tt, :transaction)) - |> join(:inner, [tx], block in assoc(tx, :block)) - |> where([tt, tx, block], tx.block_timestamp >= ago(^update_period_hours(), "hour")) - |> where([tt, tx, block], block.consensus == true) - |> where([tt, tx, block], tx.status == ^1) - |> select([tt, tx, block], %{ + |> join(:inner, [transaction], block in assoc(transaction, :block)) + |> where([tt, transaction, block], transaction.block_timestamp >= ago(^update_period_hours(), "hour")) + |> where([tt, transaction, block], block.consensus == true) + |> where([tt, transaction, block], transaction.status == ^1) + |> select([tt, transaction, block], %{ address_hash: fragment("UNNEST(ARRAY[?, ?, ?])", tt.from_address_hash, tt.to_address_hash, tt.token_contract_address_hash) }) diff --git a/apps/explorer/lib/explorer/chain/mud.ex b/apps/explorer/lib/explorer/chain/mud.ex index 386cec0..c6eee86 100644 --- a/apps/explorer/lib/explorer/chain/mud.ex +++ b/apps/explorer/lib/explorer/chain/mud.ex @@ -10,10 +10,11 @@ defmodule Explorer.Chain.Mud do order_by: 3, select: 3, where: 3, - limit: 2 + limit: 2, + join: 5 ] - alias ABI.TypeDecoder + alias ABI.{FunctionSelector, TypeDecoder} alias Explorer.{Chain, PagingOptions, Repo, SortingHelper} alias Explorer.Chain.{ @@ -23,13 +24,12 @@ defmodule Explorer.Chain.Mud do Hash, Mud, Mud.Schema, - Mud.Schema.FieldSchema + Mud.Schema.FieldSchema, + SmartContract } require Logger - @schema_prefix "mud" - @store_tables_table_id Base.decode16!("746273746f72650000000000000000005461626c657300000000000000000000", case: :lower ) @@ -42,6 +42,42 @@ defmodule Explorer.Chain.Mud do value_names: ["fieldLayout", "keySchema", "valueSchema", "abiEncodedKeyNames", "abiEncodedValueNames"] } + @world_system_registry_table_id Base.decode16!("7462776f726c6400000000000000000053797374656d52656769737472790000", + case: :lower + ) + + # https://github.com/latticexyz/mud/blob/5a6c03c6bc02c980ca051dadd8e20560ac25c771/packages/world/src/codegen/tables/SystemRegistry.sol#L24-L32 + @world_system_registry_schema %Schema{ + key_schema: FieldSchema.from("0x0014010061000000000000000000000000000000000000000000000000000000"), + value_schema: FieldSchema.from("0x002001005f000000000000000000000000000000000000000000000000000000"), + key_names: ["system"], + value_names: ["systemId"] + } + + @world_function_selector_table_id Base.decode16!("7462776f726c6400000000000000000046756e6374696f6e53656c6563746f72", + case: :lower + ) + + # https://github.com/latticexyz/mud/blob/5a6c03c6bc02c980ca051dadd8e20560ac25c771/packages/world/src/codegen/tables/FunctionSelectors.sol#L24-L32 + @world_function_selector_schema %Schema{ + key_schema: FieldSchema.from("0x0004010043000000000000000000000000000000000000000000000000000000"), + value_schema: FieldSchema.from("0x002402005f430000000000000000000000000000000000000000000000000000"), + key_names: ["worldFunctionSelector"], + value_names: ["systemId", "systemFunctionSelector"] + } + + @world_function_signature_table_id Base.decode16!("6f74776f726c6400000000000000000046756e6374696f6e5369676e61747572", + case: :lower + ) + + # https://github.com/latticexyz/mud/blob/5a6c03c6bc02c980ca051dadd8e20560ac25c771/packages/world/src/codegen/tables/FunctionSignatures.sol#L21-L29 + @world_function_signature_schema %Schema{ + key_schema: FieldSchema.from("0x0004010043000000000000000000000000000000000000000000000000000000"), + value_schema: FieldSchema.from("0x00000001c5000000000000000000000000000000000000000000000000000000"), + key_names: ["functionSelector"], + value_names: ["functionSignature"] + } + @primary_key false typed_schema "records" do field(:address, Hash.Address, null: false) @@ -193,6 +229,128 @@ defmodule Explorer.Chain.Mud do |> Repo.Mud.all() end + @doc """ + Returns the list of first 1000 MUD systems registered for the given world. + """ + @spec world_systems(Hash.Address.t()) :: [{Hash.Full.t(), Hash.Address.t()}] + def world_systems(world) do + Mud + |> where([r], r.address == ^world and r.table_id == ^@world_system_registry_table_id and r.is_deleted == false) + |> limit(1000) + |> Repo.Mud.all() + |> Enum.map(&decode_record(&1, @world_system_registry_schema)) + |> Enum.map(fn s -> + with {:ok, system_id} <- Hash.Full.cast(s["systemId"]), + {:ok, system} <- Hash.Address.cast(s["system"]) do + {system_id, system} + end + end) + |> Enum.reject(&(&1 == :error)) + end + + @doc """ + Returns reconstructed ABI of the MUD system in the given world. + """ + @spec world_system(Hash.Address.t(), Hash.Address.t(), Keyword.t()) :: + {:ok, Hash.Full.t(), [FunctionSelector.t()]} | {:error, :not_found} + # credo:disable-for-next-line Credo.Check.Refactor.CyclomaticComplexity + def world_system(world, system, options \\ []) do + # pad to 32 bytes + padded_system_address_hash = %Data{bytes: <<0::size(96)>> <> system.bytes} + + # If we were to access MUD tables in SQL, it would look like: + # SELECT sr.*, fsl.*, fsg.* + # FROM tb.world.SystemRegistry sr + # JOIN tb.world.FunctionSelector fsl ON fsl.systemId = sr.systemId + # JOIN tb.world.FunctionSignature fsg ON fsg.functionSelector = fsl.worldFunctionSelector + # WHERE sr.system = $1 + {system_records, function_selector_signature_records} = + Mud + |> where( + [r], + r.address == ^world and r.table_id == ^@world_system_registry_table_id and r.is_deleted == false and + r.key_bytes == ^padded_system_address_hash + ) + |> join( + :left, + [r], + r2 in Mud, + on: + r2.address == ^world and r2.table_id == ^@world_function_selector_table_id and r2.is_deleted == false and + fragment("substring(? FOR 32)", r2.static_data) == r.static_data + ) + |> join( + :left, + [r, r2], + r3 in Mud, + on: + r3.address == ^world and r3.table_id == ^@world_function_signature_table_id and r3.is_deleted == false and + r3.key_bytes == r2.key_bytes + ) + |> select([r, r2, r3], {r, {r2, r3}}) + |> limit(1000) + |> Repo.Mud.all() + |> Enum.unzip() + + with false <- Enum.empty?(system_records), + system_record = Enum.at(system_records, 0), + {:ok, system_id} <- Hash.Full.cast(system_record.static_data.bytes) do + {:ok, system_id, reconstruct_system_abi(system, function_selector_signature_records, options)} + else + _ -> {:error, :not_found} + end + end + + @spec reconstruct_system_abi(Hash.Address.t(), [{Mud.t(), Mud.t()}], Keyword.t()) :: [FunctionSelector.t()] + defp reconstruct_system_abi(system, function_selector_signature_records, options) do + system_contract = SmartContract.address_hash_to_smart_contract(system, options) + + # fetch verified contract ABI, if any + verified_system_abi = + ((system_contract && system_contract.abi) || []) + |> ABI.parse_specification() + |> Enum.filter(&(&1.type == :function)) + |> Enum.into(%{}, fn selector -> {"0x" <> Base.encode16(selector.method_id, case: :lower), selector} end) + + function_selector_signature_records + |> Enum.reject(&(&1 == {nil, nil})) + |> Enum.map(fn {function_selector_record, function_signature_record} -> + function_selector = function_selector_record |> decode_record(@world_function_selector_schema) + + # if the external world function selector is present in the verified ABI, we use it + world_function_selector = + verified_system_abi |> Map.get(function_selector |> Map.get("worldFunctionSelector")) + + if world_function_selector do + world_function_selector + else + abi_method = parse_function_signature(function_signature_record) + + # if the internal system function selector is present in the verified ABI, + # then it has the same arguments as the external world function, but a different name, + # so we use it after replacing the function name accordingly. + # in case neither of the selectors were found in the verified ABI, we use the ABI crafted from the method signature + verified_system_abi + |> Map.get(function_selector |> Map.get("systemFunctionSelector"), abi_method) + |> Map.put(:function, abi_method.function) + end + end) + end + + @spec parse_function_signature(Mud.t()) :: FunctionSelector.t() + defp parse_function_signature(function_signature_record) do + raw_abi_method = + function_signature_record + |> decode_record(@world_function_signature_schema) + |> Map.get("functionSignature") + |> FunctionSelector.decode() + + raw_abi_method + |> Map.put(:type, :function) + |> Map.put(:state_mutability, :payable) + |> Map.put(:input_names, 0..(Enum.count(raw_abi_method.types) - 1) |> Enum.map(&"arg#{&1}")) + end + @doc """ Preloads last modification timestamps for the list of raw MUD records. @@ -330,7 +488,8 @@ defmodule Explorer.Chain.Mud do |> Enum.zip(static_types) |> Enum.reduce({%{}, (static_data && static_data.bytes) || <<>>}, fn {field, type}, {acc, data} -> type_size = static_type_size(type) - <> = data + {enc, rest} = split_binary(data, type_size) + decoded = decode_type(type, enc) {Map.put(acc, field, decoded), rest} end) @@ -349,7 +508,7 @@ defmodule Explorer.Chain.Mud do [dynamic_fields, dynamic_types, dynamic_type_lengths] |> Enum.zip() |> Enum.reduce({res, (dynamic_data && dynamic_data.bytes) || <<>>}, fn {field, type, length}, {acc, data} -> - <> = data + {enc, rest} = split_binary(data, length) decoded = decode_type(type, enc) {Map.put(acc, field, decoded), rest} @@ -366,6 +525,12 @@ defmodule Explorer.Chain.Mud do end end + defp split_binary(binary, size) do + if byte_size(binary) >= size, + do: :erlang.split_binary(binary, size), + else: {<<0::size(size * 8)>>, <<>>} + end + # credo:disable-for-next-line Credo.Check.Refactor.CyclomaticComplexity defp decode_type(type, raw) do case type do @@ -400,3 +565,80 @@ defmodule Explorer.Chain.Mud do end end end + +defimpl Jason.Encoder, for: ABI.FunctionSelector do + alias Jason.Encode + + def encode(data, opts) do + function_inputs = encode_arguments(data.types, data.input_names) + + inputs = + if data.inputs_indexed do + function_inputs + |> Enum.zip(data.inputs_indexed) + |> Enum.map(fn {r, indexed} -> Map.put(r, "indexed", indexed) end) + else + function_inputs + end + + Encode.map( + %{ + "type" => data.type, + "name" => data.function, + "inputs" => inputs, + "outputs" => encode_arguments(data.returns, data.return_names), + "stateMutability" => encode_state_mutability(data.state_mutability) + }, + opts + ) + end + + defp encode_arguments(types, names) do + types + |> Enum.zip(names) + |> Enum.map(fn {type, name} -> + %{ + "type" => encode_type(type), + "name" => name, + "components" => encode_components(type) + } + |> Map.reject(fn {key, value} -> {key, value} == {"components", nil} end) + end) + end + + defp encode_type(:bool), do: "bool" + defp encode_type(:string), do: "string" + defp encode_type(:bytes), do: "bytes" + defp encode_type(:address), do: "address" + defp encode_type(:function), do: "function" + defp encode_type({:int, size}), do: "int#{size}" + defp encode_type({:uint, size}), do: "uint#{size}" + defp encode_type({:fixed, element_count, precision}), do: "fixed#{element_count}x#{precision}" + defp encode_type({:ufixed, element_count, precision}), do: "ufixed#{element_count}x#{precision}" + defp encode_type({:bytes, size}), do: "bytes#{size}" + defp encode_type({:array, type}), do: "#{encode_type(type)}[]" + defp encode_type({:array, type, element_count}), do: "#{encode_type(type)}[#{element_count}]" + defp encode_type({:tuple, _types}), do: "tuple" + + defp encode_components({:array, type}), do: encode_components(type) + defp encode_components({:array, type, _element_count}), do: encode_components(type) + + defp encode_components({:tuple, types}), + do: + types + |> Enum.with_index() + |> Enum.map(fn {type, index} -> + %{ + "type" => encode_type(type), + "name" => "arg#{index}" + } + end) + + defp encode_components(_), do: nil + + defp encode_state_mutability(:pure), do: "pure" + defp encode_state_mutability(:view), do: "view" + defp encode_state_mutability(:non_payable), do: "nonpayable" + defp encode_state_mutability(:payable), do: "payable" + defp encode_state_mutability(_), do: nil +end diff --git a/apps/explorer/lib/explorer/chain/optimism/deposit.ex b/apps/explorer/lib/explorer/chain/optimism/deposit.ex index 48c6dc3..36543fb 100644 --- a/apps/explorer/lib/explorer/chain/optimism/deposit.ex +++ b/apps/explorer/lib/explorer/chain/optimism/deposit.ex @@ -61,7 +61,7 @@ defmodule Explorer.Chain.Optimism.Deposit do paging_options = Keyword.get(options, :paging_options, default_paging_options()) case paging_options do - %PagingOptions{key: {0, _l2_tx_hash}} -> + %PagingOptions{key: {0, _l2_transaction_hash}} -> [] _ -> @@ -80,10 +80,10 @@ defmodule Explorer.Chain.Optimism.Deposit do defp page_deposits(query, %PagingOptions{key: nil}), do: query - defp page_deposits(query, %PagingOptions{key: {block_number, l2_tx_hash}}) do + defp page_deposits(query, %PagingOptions{key: {block_number, l2_transaction_hash}}) do from(d in query, where: d.l1_block_number < ^block_number, - or_where: d.l1_block_number == ^block_number and d.l2_transaction_hash < ^l2_tx_hash + or_where: d.l1_block_number == ^block_number and d.l2_transaction_hash < ^l2_transaction_hash ) end end diff --git a/apps/explorer/lib/explorer/chain/optimism/frame_sequence.ex b/apps/explorer/lib/explorer/chain/optimism/frame_sequence.ex index 8a5788e..d78646c 100644 --- a/apps/explorer/lib/explorer/chain/optimism/frame_sequence.ex +++ b/apps/explorer/lib/explorer/chain/optimism/frame_sequence.ex @@ -16,7 +16,7 @@ defmodule Explorer.Chain.Optimism.FrameSequence do import Explorer.Chain, only: [default_paging_options: 0, select_repo: 1] alias Explorer.Chain.{Hash, Transaction} - alias Explorer.Chain.Optimism.{FrameSequenceBlob, TxnBatch} + alias Explorer.Chain.Optimism.{FrameSequenceBlob, TransactionBatch} alias Explorer.PagingOptions @required_attrs ~w(id l1_transaction_hashes l1_timestamp)a @@ -25,7 +25,7 @@ defmodule Explorer.Chain.Optimism.FrameSequence do * `l1_transaction_hashes` - The list of L1 transaction hashes where the frame sequence is stored. * `l1_timestamp` - UTC timestamp of the last L1 transaction of `l1_transaction_hashes` list. * `view_ready` - Boolean flag indicating if the frame sequence is ready for displaying on UI. - * `transaction_batches` - Instances of `Explorer.Chain.Optimism.TxnBatch` bound with this frame sequence. + * `transaction_batches` - Instances of `Explorer.Chain.Optimism.TransactionBatch` bound with this frame sequence. * `blobs` - Instances of `Explorer.Chain.Optimism.FrameSequenceBlob` bound with this frame sequence. """ @primary_key {:id, :integer, autogenerate: false} @@ -34,7 +34,7 @@ defmodule Explorer.Chain.Optimism.FrameSequence do field(:l1_timestamp, :utc_datetime_usec) field(:view_ready, :boolean) - has_many(:transaction_batches, TxnBatch, foreign_key: :frame_sequence_id) + has_many(:transaction_batches, TransactionBatch, foreign_key: :frame_sequence_id) has_many(:blobs, FrameSequenceBlob, foreign_key: :frame_sequence_id) timestamps() @@ -104,9 +104,9 @@ defmodule Explorer.Chain.Optimism.FrameSequence do batch = select_repo(options).one(query) if not is_nil(batch) do - l2_block_number_from = TxnBatch.edge_l2_block_number(internal_id, :min) - l2_block_number_to = TxnBatch.edge_l2_block_number(internal_id, :max) - tx_count = Transaction.tx_count_for_block_range(l2_block_number_from..l2_block_number_to) + l2_block_number_from = TransactionBatch.edge_l2_block_number(internal_id, :min) + l2_block_number_to = TransactionBatch.edge_l2_block_number(internal_id, :max) + transaction_count = Transaction.transaction_count_for_block_range(l2_block_number_from..l2_block_number_to) {batch_data_container, blobs} = FrameSequenceBlob.list(internal_id, options) @@ -115,8 +115,10 @@ defmodule Explorer.Chain.Optimism.FrameSequence do "l1_timestamp" => batch.l1_timestamp, "l2_block_start" => l2_block_number_from, "l2_block_end" => l2_block_number_to, - "tx_count" => tx_count, - "l1_tx_hashes" => batch.l1_transaction_hashes, + "transaction_count" => transaction_count, + # todo: keep next line for compatibility with frontend and remove when new frontend is bound to `transaction_count` property + "tx_count" => transaction_count, + "l1_transaction_hashes" => batch.l1_transaction_hashes, "batch_data_container" => batch_data_container } diff --git a/apps/explorer/lib/explorer/chain/optimism/txn_batch.ex b/apps/explorer/lib/explorer/chain/optimism/transaction_batch.ex similarity index 95% rename from apps/explorer/lib/explorer/chain/optimism/txn_batch.ex rename to apps/explorer/lib/explorer/chain/optimism/transaction_batch.ex index a94ef6d..2ce8032 100644 --- a/apps/explorer/lib/explorer/chain/optimism/txn_batch.ex +++ b/apps/explorer/lib/explorer/chain/optimism/transaction_batch.ex @@ -1,9 +1,9 @@ -defmodule Explorer.Chain.Optimism.TxnBatch do +defmodule Explorer.Chain.Optimism.TransactionBatch do @moduledoc """ Models a batch of transactions for Optimism. Changes in the schema should be reflected in the bulk import module: - - Explorer.Chain.Import.Runner.Optimism.TxnBatches + - Explorer.Chain.Import.Runner.Optimism.TransactionBatches Migrations: - Explorer.Repo.Migrations.AddOpTransactionBatchesTable @@ -96,7 +96,7 @@ defmodule Explorer.Chain.Optimism.TxnBatch do end @doc """ - Lists `t:Explorer.Chain.Optimism.TxnBatch.t/0`'s' in descending order based on l2_block_number. + Lists `t:Explorer.Chain.Optimism.TransactionBatch.t/0`'s' in descending order based on l2_block_number. ## Parameters - `options`: A keyword list of options that may include whether to use a replica database, @@ -131,7 +131,7 @@ defmodule Explorer.Chain.Optimism.TxnBatch do base_query |> join_association(:frame_sequence, :required) - |> page_txn_batches(paging_options) + |> page_transaction_batches(paging_options) |> limit(^paging_options.page_size) |> select_repo(options).all() end @@ -275,9 +275,9 @@ defmodule Explorer.Chain.Optimism.TxnBatch do bytes_before <> <> <> bytes_after end - defp page_txn_batches(query, %PagingOptions{key: nil}), do: query + defp page_transaction_batches(query, %PagingOptions{key: nil}), do: query - defp page_txn_batches(query, %PagingOptions{key: {block_number}}) do + defp page_transaction_batches(query, %PagingOptions{key: {block_number}}) do from(tb in query, where: tb.l2_block_number < ^block_number) end end diff --git a/apps/explorer/lib/explorer/chain/optimism/withdrawal.ex b/apps/explorer/lib/explorer/chain/optimism/withdrawal.ex index d397329..011273f 100644 --- a/apps/explorer/lib/explorer/chain/optimism/withdrawal.ex +++ b/apps/explorer/lib/explorer/chain/optimism/withdrawal.ex @@ -64,8 +64,8 @@ defmodule Explorer.Chain.Optimism.Withdrawal do base_query = from(w in __MODULE__, order_by: [desc: w.msg_nonce], - left_join: l2_tx in Transaction, - on: w.l2_transaction_hash == l2_tx.hash, + left_join: l2_transaction in Transaction, + on: w.l2_transaction_hash == l2_transaction.hash, left_join: l2_block in Block, on: w.l2_block_number == l2_block.number, left_join: we in WithdrawalEvent, @@ -77,7 +77,7 @@ defmodule Explorer.Chain.Optimism.Withdrawal do l2_timestamp: l2_block.timestamp, l2_transaction_hash: w.l2_transaction_hash, l1_transaction_hash: we.l1_transaction_hash, - from: l2_tx.from_address_hash + from: l2_transaction.from_address_hash } ) diff --git a/apps/explorer/lib/explorer/chain/ordered_cache.ex b/apps/explorer/lib/explorer/chain/ordered_cache.ex index 9d2e797..46b1f8f 100644 --- a/apps/explorer/lib/explorer/chain/ordered_cache.ex +++ b/apps/explorer/lib/explorer/chain/ordered_cache.ex @@ -112,6 +112,11 @@ defmodule Explorer.Chain.OrderedCache do """ @callback take_enough(integer()) :: [element] | nil + @doc """ + Behaves like `take_enough/1`, but addresses [#10445](https://github.com/blockscout/blockscout/issues/10445). + """ + @callback atomic_take_enough(integer()) :: [element] | nil + @doc """ Adds an element, or a list of elements, to the cache. When the cache is full, only the most prevailing elements will be stored, based @@ -204,6 +209,22 @@ defmodule Explorer.Chain.OrderedCache do end end + @impl OrderedCache + def atomic_take_enough(amount) do + items = + cache_name() + |> ConCache.ets() + |> :ets.tab2list() + + if amount <= Enum.count(items) - 1 do + items + |> Enum.reject(fn {key, _value} -> key == ids_list_key() end) + |> Enum.sort(&prevails?/2) + |> Enum.take(amount) + |> Enum.map(fn {_key, value} -> value end) + end + end + ### Updating function def remove_deleted_from_index({:delete, _cache_pid, id}) do @@ -226,6 +247,7 @@ defmodule Explorer.Chain.OrderedCache do ConCache.update(cache_name(), ids_list_key(), fn ids -> updated_list = elements + |> do_preloads() |> Enum.map(&{element_to_id(&1), &1}) |> Enum.sort(&prevails?(&1, &2)) |> merge_and_update(ids || [], max_size()) @@ -237,6 +259,14 @@ defmodule Explorer.Chain.OrderedCache do def update(element), do: update([element]) + defp do_preloads(elements) do + if Enum.empty?(preloads()) do + elements + else + Explorer.Repo.preload(elements, preloads()) + end + end + defp merge_and_update(_candidates, existing, 0) do # if there is no more space in the list remove the remaining existing # elements and return an empty list @@ -302,17 +332,10 @@ defmodule Explorer.Chain.OrderedCache do end defp put_element(element_id, element) do - full_element = - if Enum.empty?(preloads()) do - element - else - Explorer.Repo.preload(element, preloads()) - end - # dirty puts are a little faster than puts with locks. # this is not a problem because this is the only function modifying rows # and it only gets called inside `update`, which works isolated - ConCache.dirty_put(cache_name(), element_id, full_element) + ConCache.dirty_put(cache_name(), element_id, element) end ### Supervisor's child specification diff --git a/apps/explorer/lib/explorer/chain/pending_block_operation.ex b/apps/explorer/lib/explorer/chain/pending_block_operation.ex index 0852e31..9a1b5b4 100644 --- a/apps/explorer/lib/explorer/chain/pending_block_operation.ex +++ b/apps/explorer/lib/explorer/chain/pending_block_operation.ex @@ -35,19 +35,6 @@ defmodule Explorer.Chain.PendingBlockOperation do |> unique_constraint(:block_hash, name: :pending_block_operations_pkey) end - @doc """ - Returns all pending block operations with the `block_hash` in the given list, - using "FOR UPDATE" to grab ShareLocks in order (see docs: sharelocks.md) - """ - def fetch_and_lock_by_hashes(hashes) when is_list(hashes) do - from( - pending_ops in __MODULE__, - where: pending_ops.block_hash in ^hashes, - order_by: [asc: pending_ops.block_hash], - lock: "FOR UPDATE" - ) - end - def block_hashes do from( pending_ops in __MODULE__, diff --git a/apps/explorer/lib/explorer/chain/polygon_zkevm/lifecycle_transaction.ex b/apps/explorer/lib/explorer/chain/polygon_zkevm/lifecycle_transaction.ex index 504cdce..e1ec0a7 100644 --- a/apps/explorer/lib/explorer/chain/polygon_zkevm/lifecycle_transaction.ex +++ b/apps/explorer/lib/explorer/chain/polygon_zkevm/lifecycle_transaction.ex @@ -24,8 +24,8 @@ defmodule Explorer.Chain.PolygonZkevm.LifecycleTransaction do Validates that the `attrs` are valid. """ @spec changeset(Ecto.Schema.t(), map()) :: Ecto.Schema.t() - def changeset(%__MODULE__{} = txn, attrs \\ %{}) do - txn + def changeset(%__MODULE__{} = transaction, attrs \\ %{}) do + transaction |> cast(attrs, @required_attrs) |> validate_required(@required_attrs) |> unique_constraint(:id) diff --git a/apps/explorer/lib/explorer/chain/polygon_zkevm/reader.ex b/apps/explorer/lib/explorer/chain/polygon_zkevm/reader.ex index 2e5daf8..e34301b 100644 --- a/apps/explorer/lib/explorer/chain/polygon_zkevm/reader.ex +++ b/apps/explorer/lib/explorer/chain/polygon_zkevm/reader.ex @@ -190,12 +190,12 @@ defmodule Explorer.Chain.PolygonZkevm.Reader do Reads a list of L1 transactions by their hashes from `polygon_zkevm_lifecycle_l1_transactions` table. """ @spec lifecycle_transactions(list()) :: list() - def lifecycle_transactions(l1_tx_hashes) do + def lifecycle_transactions(l1_transaction_hashes) do query = from( lt in LifecycleTransaction, select: {lt.hash, lt.id}, - where: lt.hash in ^l1_tx_hashes + where: lt.hash in ^l1_transaction_hashes ) Repo.all(query, timeout: :infinity) @@ -337,6 +337,14 @@ defmodule Explorer.Chain.PolygonZkevm.Reader do end end + @doc """ + Filters token symbol (cannot be longer than 16 characters). + """ + @spec sanitize_symbol(String.t()) :: String.t() + def sanitize_symbol(symbol) do + String.slice(symbol, 0, 16) + end + defp page_batches(query, %PagingOptions{key: nil}), do: query defp page_batches(query, %PagingOptions{key: {number}}) do diff --git a/apps/explorer/lib/explorer/chain/rollup_reorg_monitor_queue.ex b/apps/explorer/lib/explorer/chain/rollup_reorg_monitor_queue.ex new file mode 100644 index 0000000..7a73659 --- /dev/null +++ b/apps/explorer/lib/explorer/chain/rollup_reorg_monitor_queue.ex @@ -0,0 +1,91 @@ +defmodule Explorer.Chain.RollupReorgMonitorQueue do + @moduledoc """ + A module containing (encapsulating) the reorg monitor queue and functions to manage it. + Mostly used by the `Indexer.Fetcher.RollupL1ReorgMonitor` module. + """ + + alias Explorer.BoundQueue + + @doc """ + Pops the number of reorg block from the front of the queue for the specified rollup module. + + ## Parameters + - `module`: The module for which the block number is popped from the queue. + + ## Returns + - The popped block number. + - `nil` if the reorg queue is empty. + """ + @spec reorg_block_pop(module()) :: non_neg_integer() | nil + def reorg_block_pop(module) do + table_name = reorg_table_name(module) + + 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 + + @doc """ + Pushes the number of reorg block to the back of the queue for the specified rollup module. + + ## Parameters + - `block_number`: The reorg block number. + - `module`: The module for which the block number is pushed to the queue. + + ## Returns + - Nothing is returned. + """ + @spec reorg_block_push(non_neg_integer(), module()) :: any() + def reorg_block_push(block_number, module) do + table_name = reorg_table_name(module) + {:ok, updated_queue} = BoundQueue.push_back(reorg_queue_get(table_name), block_number) + :ets.insert(table_name, {:queue, updated_queue}) + end + + # Reads a block number queue instance from the ETS table associated with the queue. + # The table name depends on the module name and formed by the `reorg_table_name` function. + # + # ## Parameters + # - `table_name`: The ETS table name of the queue. + # + # ## Returns + # - `BoundQueue` instance for the queue. The queue may be empty (then %BoundQueue{} is returned). + @spec reorg_queue_get(atom()) :: BoundQueue.t(any()) + 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 + + # Forms an ETS table name for the block number queue for the given module name. + # + # ## Parameters + # - `module`: The module name (instance) for which the ETS table name should be formed. + # + # ## Returns + # - An atom defining the table name. + # + # sobelow_skip ["DOS.BinToAtom"] + @spec reorg_table_name(module()) :: atom() + defp reorg_table_name(module) do + :"#{module}#{:_reorgs}" + end +end diff --git a/apps/explorer/lib/explorer/chain/scroll/batch.ex b/apps/explorer/lib/explorer/chain/scroll/batch.ex new file mode 100644 index 0000000..d4d3636 --- /dev/null +++ b/apps/explorer/lib/explorer/chain/scroll/batch.ex @@ -0,0 +1,82 @@ +defmodule Explorer.Chain.Scroll.Batch do + @moduledoc """ + Models a batch for Scroll. + + Changes in the schema should be reflected in the bulk import module: + - Explorer.Chain.Import.Runner.Scroll.Batches + + Migrations: + - Explorer.Repo.Scroll.Migrations.AddBatchesTables + """ + + use Explorer.Schema + + alias Explorer.Chain.Block.Range, as: BlockRange + alias Explorer.Chain.Hash + alias Explorer.Chain.Scroll.BatchBundle + + @optional_attrs ~w(bundle_id)a + + @required_attrs ~w(number commit_transaction_hash commit_block_number commit_timestamp l2_block_range container)a + + @typedoc """ + Descriptor of the batch: + * `number` - A unique batch number. + * `commit_transaction_hash` - A hash of the commit transaction on L1. + * `commit_block_number` - A block number of the commit transaction on L1. + * `commit_timestamp` - A timestamp of the commit block. + * `bundle_id` - An identifier of the batch bundle from the `scroll_batch_bundles` database table. + * `l2_block_range` - A range of L2 blocks included into the batch. + * `container` - A container where the batch info is mostly located (can be :in_calldata, :in_blob4844). + """ + @type to_import :: %{ + number: non_neg_integer(), + commit_transaction_hash: binary(), + commit_block_number: non_neg_integer(), + commit_timestamp: DateTime.t(), + bundle_id: non_neg_integer() | nil, + l2_block_range: BlockRange.t(), + container: :in_calldata | :in_blob4844 + } + + @typedoc """ + * `number` - A unique batch number. + * `commit_transaction_hash` - A hash of the commit transaction on L1. + * `commit_block_number` - A block number of the commit transaction on L1. + * `commit_timestamp` - A timestamp of the commit block. + * `bundle_id` - An identifier of the batch bundle from the `scroll_batch_bundles` database table. + * `l2_block_range` - A range of L2 blocks included into the batch. + * `container` - A container where the batch info is mostly located (can be :in_calldata, :in_blob4844). + """ + @primary_key false + typed_schema "scroll_batches" do + field(:number, :integer, primary_key: true) + field(:commit_transaction_hash, Hash.Full) + field(:commit_block_number, :integer) + field(:commit_timestamp, :utc_datetime_usec) + + belongs_to(:bundle, BatchBundle, + foreign_key: :bundle_id, + references: :id, + type: :integer, + null: true + ) + + field(:l2_block_range, BlockRange, null: false) + field(:container, Ecto.Enum, values: [:in_blob4844, :in_calldata]) + + timestamps() + end + + @doc """ + Checks 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) + |> unique_constraint(:number) + |> foreign_key_constraint(:bundle_id) + end +end diff --git a/apps/explorer/lib/explorer/chain/scroll/batch_bundle.ex b/apps/explorer/lib/explorer/chain/scroll/batch_bundle.ex new file mode 100644 index 0000000..5a36bf7 --- /dev/null +++ b/apps/explorer/lib/explorer/chain/scroll/batch_bundle.ex @@ -0,0 +1,58 @@ +defmodule Explorer.Chain.Scroll.BatchBundle do + @moduledoc """ + Models a batch bundle for Scroll. + + Changes in the schema should be reflected in the bulk import module: + - Explorer.Chain.Import.Runner.Scroll.BatchBundles + + Migrations: + - Explorer.Repo.Scroll.Migrations.AddBatchesTables + """ + + use Explorer.Schema + + alias Explorer.Chain.Hash + + @required_attrs ~w(final_batch_number finalize_transaction_hash finalize_block_number finalize_timestamp)a + + @typedoc """ + Descriptor of the batch bundle: + * `final_batch_number` - The last batch number finalized in this bundle. + * `finalize_transaction_hash` - A hash of the finalize transaction on L1. + * `finalize_block_number` - A block number of the finalize transaction on L1. + * `finalize_timestamp` - A timestamp of the finalize block. + """ + @type to_import :: %{ + final_batch_number: non_neg_integer(), + finalize_transaction_hash: binary(), + finalize_block_number: non_neg_integer(), + finalize_timestamp: DateTime.t() + } + + @typedoc """ + * `id` - An internal ID of the bundle. + * `final_batch_number` - The last batch number finalized in this bundle. + * `finalize_transaction_hash` - A hash of the finalize transaction on L1. + * `finalize_block_number` - A block number of the finalize transaction on L1. + * `finalize_timestamp` - A timestamp of the finalize block. + """ + @primary_key {:id, :id, autogenerate: true} + typed_schema "scroll_batch_bundles" do + field(:final_batch_number, :integer) + field(:finalize_transaction_hash, Hash.Full) + field(:finalize_block_number, :integer) + field(:finalize_timestamp, :utc_datetime_usec) + timestamps() + end + + @doc """ + Checks that the `attrs` are valid. + """ + @spec changeset(Ecto.Schema.t(), map()) :: Ecto.Schema.t() + def changeset(%__MODULE__{} = bundles, attrs \\ %{}) do + bundles + |> cast(attrs, @required_attrs) + |> validate_required(@required_attrs) + |> unique_constraint(:id) + end +end diff --git a/apps/explorer/lib/explorer/chain/scroll/bridge.ex b/apps/explorer/lib/explorer/chain/scroll/bridge.ex new file mode 100644 index 0000000..6cfd280 --- /dev/null +++ b/apps/explorer/lib/explorer/chain/scroll/bridge.ex @@ -0,0 +1,78 @@ +defmodule Explorer.Chain.Scroll.Bridge do + @moduledoc """ + Models a bridge operation for Scroll. + + Changes in the schema should be reflected in the bulk import module: + - Explorer.Chain.Import.Runner.Scroll.BridgeOperations + + Migrations: + - Explorer.Repo.Scroll.Migrations.AddBridgeTable + """ + + use Explorer.Schema + + alias Explorer.Chain.Hash + + @optional_attrs ~w(index l1_transaction_hash l2_transaction_hash amount block_number block_timestamp)a + + @required_attrs ~w(type message_hash)a + + @typedoc """ + Descriptor of the Scroll bridge message: + * `type` - Type of the bridge operation (:deposit or :withdrawal). + * `index` - Index of the deposit or index of the withdrawal (can be nil). + * `l1_transaction_hash` - L1 transaction hash of the bridge operation (can be nil). + * `l2_transaction_hash` - L2 transaction hash of the bridge operation (can be nil). + * `amount` - Amount of the operation in native token (can be nil). + * `block_number` - Block number of deposit operation for `l1_transaction_hash` + or withdrawal operation for `l2_transaction_hash` (can be nil). + * `block_timestamp` - Timestamp of the block `block_number` (can be nil). + * `message_hash` - Unique hash of the operation (message). + """ + @type to_import :: %{ + type: :deposit | :withdrawal, + index: non_neg_integer() | nil, + l1_transaction_hash: binary() | nil, + l2_transaction_hash: binary() | nil, + amount: non_neg_integer() | nil, + block_number: non_neg_integer() | nil, + block_timestamp: DateTime.t() | nil, + message_hash: binary() + } + + @typedoc """ + * `type` - Type of the bridge operation (:deposit or :withdrawal). + * `index` - Index of the deposit or index of the withdrawal (can be nil). + * `l1_transaction_hash` - L1 transaction hash of the bridge operation (can be nil). + * `l2_transaction_hash` - L2 transaction hash of the bridge operation (can be nil). + * `amount` - Amount of the operation in native token (can be nil). + * `block_number` - Block number of deposit operation for `l1_transaction_hash` + or withdrawal operation for `l2_transaction_hash` (can be nil). + * `block_timestamp` - Timestamp of the block `block_number` (can be nil). + * `message_hash` - Unique hash of the operation (message). + """ + @primary_key false + typed_schema "scroll_bridge" do + field(:type, Ecto.Enum, values: [:deposit, :withdrawal], primary_key: true) + field(:index, :integer) + field(:l1_transaction_hash, Hash.Full) + field(:l2_transaction_hash, Hash.Full) + field(:amount, :decimal) + field(:block_number, :integer) + field(:block_timestamp, :utc_datetime_usec) + field(:message_hash, Hash.Full, primary_key: true) + + timestamps() + end + + @doc """ + Checks that the `attrs` are valid. + """ + @spec changeset(Ecto.Schema.t(), map()) :: Ecto.Schema.t() + def changeset(%__MODULE__{} = operations, attrs \\ %{}) do + operations + |> cast(attrs, @required_attrs ++ @optional_attrs) + |> validate_required(@required_attrs) + |> unique_constraint([:type, :message_hash]) + end +end diff --git a/apps/explorer/lib/explorer/chain/scroll/l1_fee_param.ex b/apps/explorer/lib/explorer/chain/scroll/l1_fee_param.ex new file mode 100644 index 0000000..1864931 --- /dev/null +++ b/apps/explorer/lib/explorer/chain/scroll/l1_fee_param.ex @@ -0,0 +1,135 @@ +defmodule Explorer.Chain.Scroll.L1FeeParam do + @moduledoc """ + Models an L1 fee parameter for Scroll. + + Changes in the schema should be reflected in the bulk import module: + - Explorer.Chain.Import.Runner.Scroll.L1FeeParams + + Migrations: + - Explorer.Repo.Scroll.Migrations.AddFeeFields + """ + + use Explorer.Schema + + import Explorer.Chain, only: [get_last_fetched_counter: 1, upsert_last_fetched_counter: 1] + + alias Explorer.Chain.Transaction + + @counter_type "scroll_l1_fee_params_fetcher_last_block_number" + @required_attrs ~w(block_number transaction_index name value)a + + @typedoc """ + Descriptor of the L1 Fee Parameter change: + * `block_number` - A block number of the transaction where the given parameter value was changed. + * `transaction_index` - An index of the transaction (within the block) where the given parameter value was changed. + * `name` - A name of the parameter (can be one of: `overhead`, `scalar`, `commit_scalar`, `blob_scalar`, `l1_base_fee`, `l1_blob_base_fee`). + * `value` - A new value of the parameter. + """ + @type to_import :: %{ + block_number: non_neg_integer(), + transaction_index: non_neg_integer(), + name: :overhead | :scalar | :commit_scalar | :blob_scalar | :l1_base_fee | :l1_blob_base_fee, + value: non_neg_integer() + } + + @typedoc """ + * `block_number` - A block number of the transaction where the given parameter was changed. + * `transaction_index` - An index of the transaction (within the block) where the given parameter was changed. + * `name` - A name of the parameter (can be one of: `overhead`, `scalar`, `commit_scalar`, `blob_scalar`, `l1_base_fee`, `l1_blob_base_fee`). + * `value` - A new value of the parameter. + """ + @primary_key false + typed_schema "scroll_l1_fee_params" do + field(:block_number, :integer, primary_key: true) + field(:transaction_index, :integer, primary_key: true) + + field(:name, Ecto.Enum, + values: [:overhead, :scalar, :commit_scalar, :blob_scalar, :l1_base_fee, :l1_blob_base_fee], + primary_key: true + ) + + field(:value, :integer) + + timestamps() + end + + @doc """ + Validates that the `attrs` are valid. + """ + @spec changeset(Ecto.Schema.t(), map()) :: Ecto.Schema.t() + def changeset(%__MODULE__{} = params, attrs \\ %{}) do + params + |> cast(attrs, @required_attrs) + |> validate_required(@required_attrs) + |> unique_constraint([:block_number, :transaction_index]) + end + + @doc """ + Calculates gas used on L1 for the specified L2 transaction. + The returning value depends on the transaction block number: + if that's after Curie upgrade, the function returns 0 as all transactions are put into blob. + Otherwise, the value is calculated based on transaction data (and includes the given overhead). + See https://github.com/scroll-tech/go-ethereum/blob/9ec83a509ac7f6dd2d0beb054eb14c19f3e67a72/rollup/fees/rollup_fee.go#L171-L195 + for the implementation and https://scroll.io/blog/compressing-the-gas-scrolls-curie-upgrade for the Curie upgrade description. + + ## Parameters + - `transaction`: Transaction structure containing block number and transaction data. + - `l1_fee_overhead`: The overhead to add to the gas used in case the transaction was created before Curie upgrade. + + ## Returns + - Calculated L1 gas used value (can be 0). + """ + @spec l1_gas_used(Transaction.t(), non_neg_integer()) :: non_neg_integer() + def l1_gas_used(transaction, l1_fee_overhead) do + if transaction.block_number > Application.get_all_env(:explorer)[__MODULE__][:curie_upgrade_block] do + 0 + else + total = + transaction.input.bytes + |> :binary.bin_to_list() + |> Enum.reduce(0, fn byte, acc -> + # credo:disable-for-next-line Credo.Check.Refactor.Nesting + if byte == 0 do + acc + 4 + else + acc + 16 + end + end) + + total + l1_fee_overhead + 4 * 16 + end + end + + @doc """ + Reads the block number from the `last_fetched_counters` table which was + the last handled L2 block on the previous launch of Indexer.Fetcher.Scroll.L1FeeParam module. + + ## Returns + - The last L2 block number. + - Zero if this is the first launch of the module. + """ + @spec last_l2_block_number() :: non_neg_integer() + def last_l2_block_number do + @counter_type + |> get_last_fetched_counter() + |> Decimal.to_integer() + end + + @doc """ + Updates the last handled L2 block by the Indexer.Fetcher.Scroll.L1FeeParam module. + The new block number is written to the `last_fetched_counters` table. + + ## Parameters + - `block_number`: The number of the L2 block. + + ## Returns + - nothing + """ + @spec set_last_l2_block_number(non_neg_integer()) :: any() + def set_last_l2_block_number(block_number) do + upsert_last_fetched_counter(%{ + counter_type: @counter_type, + value: block_number + }) + end +end diff --git a/apps/explorer/lib/explorer/chain/scroll/reader.ex b/apps/explorer/lib/explorer/chain/scroll/reader.ex new file mode 100644 index 0000000..4c25b77 --- /dev/null +++ b/apps/explorer/lib/explorer/chain/scroll/reader.ex @@ -0,0 +1,427 @@ +defmodule Explorer.Chain.Scroll.Reader do + @moduledoc "Contains read functions for Scroll modules." + + import Ecto.Query, + only: [ + from: 2, + limit: 2, + order_by: 2, + order_by: 3, + select: 3, + where: 2, + where: 3 + ] + + import Explorer.Chain, only: [select_repo: 1] + + alias Explorer.Chain.Scroll.{Batch, BatchBundle, Bridge, L1FeeParam} + alias Explorer.{Chain, PagingOptions, Repo} + alias Explorer.Chain.{Block, Transaction} + + @doc """ + Reads a batch by its number from database. + + ## Parameters + - `number`: The batch number. If `:latest`, the function gets + the latest batch from the `scroll_batches` table. + - `options`: A keyword list of options that may include whether to use a replica database. + + ## Returns + - {:ok, batch} when the batch is found in the table. + - {:error, :not_found} when the batch is not found. + """ + @spec batch(non_neg_integer() | :latest, + necessity_by_association: %{atom() => :optional | :required}, + api?: boolean() + ) :: {:ok, Batch.t()} | {:error, :not_found} + @spec batch(non_neg_integer() | :latest) :: {:ok, Batch.t()} | {:error, :not_found} + def batch(number, options \\ []) + + def batch(:latest, options) when is_list(options) do + necessity_by_association = Keyword.get(options, :necessity_by_association, %{}) + + Batch + |> order_by(desc: :number) + |> limit(1) + |> Chain.join_associations(necessity_by_association) + |> 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, %{}) + + Batch + |> 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 """ + Lists `t:Explorer.Chain.Scroll.Batch.t/0`'s' in descending order based on the `number`. + + ## Parameters + - `options`: A keyword list of options that may include whether to use a replica database and paging options. + + ## Returns + - A list of found entities sorted by `number` in descending order. + """ + @spec batches(paging_options: PagingOptions.t(), api?: boolean()) :: [Batch.t()] + @spec batches() :: [Batch.t()] + def batches(options \\ []) do + paging_options = Keyword.get(options, :paging_options, Chain.default_paging_options()) + + case paging_options do + %PagingOptions{key: {0}} -> + [] + + _ -> + base_query = + from(b in Batch, + order_by: [desc: b.number] + ) + + base_query + |> Chain.join_association(:bundle, :optional) + |> page_batches(paging_options) + |> limit(^paging_options.page_size) + |> select_repo(options).all() + end + end + + @doc """ + Retrieves a list of rollup blocks included into a specified batch. + + This function constructs and executes a database query to retrieve a list of rollup blocks, + considering pagination options specified in the `options` parameter. These options dictate + the number of items to retrieve and how many items to skip from the top. + + ## Parameters + - `batch_number`: The batch number. + - `options`: A keyword list of options specifying pagination, association necessity, and + whether to use a replica database. + + ## Returns + - A list of `Explorer.Chain.Block` entries belonging to the specified batch. + """ + @spec batch_blocks(non_neg_integer() | binary(), + necessity_by_association: %{atom() => :optional | :required}, + api?: boolean(), + paging_options: PagingOptions.t() + ) :: [Block.t()] + def batch_blocks(batch_number, options) do + necessity_by_association = Keyword.get(options, :necessity_by_association, %{}) + paging_options = Keyword.get(options, :paging_options, Chain.default_paging_options()) + api = Keyword.get(options, :api?, false) + + case batch(batch_number, api?: api) do + {:ok, batch} -> + query = + from( + b in Block, + where: + b.number >= ^batch.l2_block_range.from and b.number <= ^batch.l2_block_range.to and b.consensus == true + ) + + query + |> page_batch_blocks(paging_options) + |> limit(^paging_options.page_size) + |> order_by(desc: :number) + |> Chain.join_associations(necessity_by_association) + |> select_repo(options).all() + + _ -> + [] + end + end + + @doc """ + Gets batch number and its bundle id (if defined) by the L2 block number. + + ## Parameters + - `block_number`: The L2 block number for which the batch should be determined. + - `options`: A keyword list of options that may include whether to use a replica database. + + ## Returns + - A tuple `{batch_number, bundle_id}`. + - `nil` if the batch is not found. + """ + @spec batch_by_l2_block_number(non_neg_integer()) :: {non_neg_integer(), non_neg_integer() | nil} | nil + def batch_by_l2_block_number(block_number, options \\ []) do + select_repo(options).one( + from( + b in Batch, + where: fragment("int8range(?, ?) <@ l2_block_range", ^block_number, ^(block_number + 1)), + select: {b.number, b.bundle_id} + ) + ) + end + + @doc """ + Gets last known L1 batch item from the `scroll_batches` table. + + ## Returns + - A tuple `{block_number, transaction_hash}` - the block number and L1 transaction hash bound to the batch. + - If the batch is not found, returns `{0, nil}`. + """ + @spec last_l1_batch_item() :: {non_neg_integer(), binary() | nil} + def last_l1_batch_item do + query = + from(b in Batch, + select: {b.commit_block_number, b.commit_transaction_hash}, + order_by: [desc: b.number], + limit: 1 + ) + + query + |> Repo.one() + |> Kernel.||({0, nil}) + end + + @doc """ + Gets `final_batch_number` from the last known L1 bundle. + + ## Returns + - The `final_batch_number` of the last L1 bundle. + - If there are no bundles, returns -1. + """ + @spec last_final_batch_number() :: integer() + def last_final_batch_number do + query = + from(bb in BatchBundle, + select: bb.final_batch_number, + order_by: [desc: bb.id], + limit: 1 + ) + + query + |> Repo.one() + |> Kernel.||(-1) + end + + @doc """ + Gets the last known L1 bridge item (deposit) from the `scroll_bridge` table. + + ## Returns + - A tuple `{block_number, transaction_hash}` - the block number and L1 transaction hash bound to the deposit. + - If the deposit is not found, returns `{0, nil}`. + """ + @spec last_l1_bridge_item() :: {non_neg_integer(), binary() | nil} + def last_l1_bridge_item do + query = + from(b in Bridge, + select: {b.block_number, b.l1_transaction_hash}, + where: b.type == :deposit and not is_nil(b.block_number), + order_by: [desc: b.index], + limit: 1 + ) + + query + |> Repo.one() + |> Kernel.||({0, nil}) + end + + @doc """ + Gets the last known L2 bridge item (withdrawal) from the `scroll_bridge` table. + + ## Returns + - A tuple `{block_number, transaction_hash}` - the block number and L2 transaction hash bound to the withdrawal. + - If the withdrawal is not found, returns `{0, nil}`. + """ + @spec last_l2_bridge_item() :: {non_neg_integer(), binary() | nil} + def last_l2_bridge_item do + query = + from(b in Bridge, + select: {b.block_number, b.l2_transaction_hash}, + where: b.type == :withdrawal and not is_nil(b.block_number), + order_by: [desc: b.index], + limit: 1 + ) + + query + |> Repo.one() + |> Kernel.||({0, nil}) + end + + @doc """ + Gets a value of the specified L1 Fee parameter for the given transaction from database. + If a parameter is not defined for the transaction block number and index, the function returns `nil`. + + ## Parameters + - `name`: A name of the parameter. + - `transaction`: Transaction structure containing block number and transaction index within the block. + - `options`: A keyword list of options that may include whether to use a replica database. + + ## Returns + - The parameter value, or `nil` if not defined. + """ + @spec get_l1_fee_param_for_transaction( + :overhead | :scalar | :commit_scalar | :blob_scalar | :l1_base_fee | :l1_blob_base_fee, + Transaction.t(), + api?: boolean() + ) :: non_neg_integer() | nil + @spec get_l1_fee_param_for_transaction( + :overhead | :scalar | :commit_scalar | :blob_scalar | :l1_base_fee | :l1_blob_base_fee, + Transaction.t() + ) :: non_neg_integer() | nil + def get_l1_fee_param_for_transaction(name, transaction, options \\ []) + + def get_l1_fee_param_for_transaction(_name, %{block_number: 0, index: 0}, _options), do: nil + + # credo:disable-for-next-line /Complexity/ + def get_l1_fee_param_for_transaction(name, transaction, options) + when name in [:overhead, :scalar, :commit_scalar, :blob_scalar, :l1_base_fee, :l1_blob_base_fee] do + base_query = + L1FeeParam + |> select([p], p.value) + |> order_by([p], desc: p.block_number, desc: p.transaction_index) + |> limit(1) + + query = + cond do + transaction.block_number == 0 -> + # transaction.index is greater than 0 here + where(base_query, [p], p.name == ^name and p.block_number == 0 and p.transaction_index < ^transaction.index) + + transaction.index == 0 -> + # transaction.block_number is greater than 0 here + where(base_query, [p], p.name == ^name and p.block_number < ^transaction.block_number) + + true -> + where( + base_query, + [p], + p.name == ^name and + (p.block_number < ^transaction.block_number or + (p.block_number == ^transaction.block_number and p.transaction_index < ^transaction.index)) + ) + end + + select_repo(options).one(query) + end + + @doc """ + Retrieves a list of Scroll deposits (both completed and unclaimed) + sorted in descending order of the index. + + ## Parameters + - `options`: A keyword list of options that may include whether to use a replica database and paging options. + + ## Returns + - A list of deposits. + """ + @spec deposits(paging_options: PagingOptions.t(), api?: boolean()) :: [Bridge.t()] + @spec deposits() :: [Bridge.t()] + def deposits(options \\ []) do + paging_options = Keyword.get(options, :paging_options, Chain.default_paging_options()) + + case paging_options do + %PagingOptions{key: {0}} -> + [] + + _ -> + base_query = + from( + b in Bridge, + where: b.type == :deposit and not is_nil(b.l1_transaction_hash), + order_by: [desc: b.index] + ) + + base_query + |> page_deposits_or_withdrawals(paging_options) + |> limit(^paging_options.page_size) + |> select_repo(options).all() + end + end + + @doc """ + Returns a total number of Scroll deposits (both completed and unclaimed). + """ + @spec deposits_count(api?: boolean()) :: non_neg_integer() | nil + @spec deposits_count() :: non_neg_integer() | nil + def deposits_count(options \\ []) do + query = + from( + b in Bridge, + where: b.type == :deposit and not is_nil(b.l1_transaction_hash) + ) + + select_repo(options).aggregate(query, :count, timeout: :infinity) + end + + @doc """ + Retrieves a list of Scroll withdrawals (both completed and unclaimed) + sorted in descending order of the index. + + ## Parameters + - `options`: A keyword list of options that may include whether to use a replica database. + + ## Returns + - A list of withdrawals. + """ + @spec withdrawals(paging_options: PagingOptions.t(), api?: boolean()) :: [Bridge.t()] + @spec withdrawals() :: [Bridge.t()] + def withdrawals(options \\ []) do + paging_options = Keyword.get(options, :paging_options, Chain.default_paging_options()) + + case paging_options do + %PagingOptions{key: {0}} -> + [] + + _ -> + base_query = + from( + b in Bridge, + where: b.type == :withdrawal and not is_nil(b.l2_transaction_hash), + order_by: [desc: b.index] + ) + + base_query + |> page_deposits_or_withdrawals(paging_options) + |> limit(^paging_options.page_size) + |> select_repo(options).all() + end + end + + @doc """ + Returns a total number of Scroll withdrawals (both completed and unclaimed). + """ + @spec withdrawals_count(api?: boolean()) :: non_neg_integer() | nil + @spec withdrawals_count() :: non_neg_integer() | nil + def withdrawals_count(options \\ []) do + query = + from( + b in Bridge, + where: b.type == :withdrawal and not is_nil(b.l2_transaction_hash) + ) + + select_repo(options).aggregate(query, :count, timeout: :infinity) + end + + defp page_batches(query, %PagingOptions{key: nil}), do: query + + defp page_batches(query, %PagingOptions{key: {number}}) do + from(b in query, where: b.number < ^number) + end + + defp page_batch_blocks(query, %PagingOptions{key: nil}), do: query + + defp page_batch_blocks(query, %PagingOptions{key: {0}}), do: query + + defp page_batch_blocks(query, %PagingOptions{key: {block_number}}) do + from(b in query, where: b.number < ^block_number) + end + + defp page_deposits_or_withdrawals(query, %PagingOptions{key: nil}), do: query + + defp page_deposits_or_withdrawals(query, %PagingOptions{key: {index}}) do + from(b in query, where: b.index < ^index) + end +end diff --git a/apps/explorer/lib/explorer/chain/search.ex b/apps/explorer/lib/explorer/chain/search.ex index 3405753..20d0d35 100644 --- a/apps/explorer/lib/explorer/chain/search.ex +++ b/apps/explorer/lib/explorer/chain/search.ex @@ -83,10 +83,12 @@ defmodule Explorer.Chain.Search do end def base_joint_query(string, term) do - tokens_query = search_token_query(string, term) - contracts_query = search_contract_query(term) + tokens_query = + string |> search_token_query(term) |> ExplorerHelper.maybe_hide_scam_addresses(:contract_address_hash) + + contracts_query = term |> search_contract_query() |> ExplorerHelper.maybe_hide_scam_addresses(:address_hash) labels_query = search_label_query(term) - address_query = search_address_query(string) + address_query = string |> search_address_query() |> ExplorerHelper.maybe_hide_scam_addresses(:hash) block_query = search_block_query(string) basic_query = @@ -102,30 +104,30 @@ defmodule Explorer.Chain.Search do |> union(^address_query) valid_full_hash?(string) -> - tx_query = search_tx_query(string) + transaction_query = search_transaction_query(string) - tx_block_query = + transaction_block_query = basic_query - |> union(^tx_query) + |> union(^transaction_query) |> union(^block_query) - tx_block_op_query = + transaction_block_op_query = if UserOperation.enabled?() do user_operation_query = search_user_operation_query(string) - tx_block_query + transaction_block_query |> union(^user_operation_query) else - tx_block_query + transaction_block_query end if Application.get_env(:explorer, :chain_type) == :ethereum do blob_query = search_blob_query(string) - tx_block_op_query + transaction_block_op_query |> union(^blob_query) else - tx_block_op_query + transaction_block_op_query end block_query -> @@ -161,6 +163,7 @@ defmodule Explorer.Chain.Search do tokens_result = search_query |> search_token_query(term) + |> ExplorerHelper.maybe_hide_scam_addresses(:contract_address_hash) |> order_by([token], desc_nulls_last: token.circulating_market_cap, desc_nulls_last: token.fiat_value, @@ -175,6 +178,7 @@ defmodule Explorer.Chain.Search do contracts_result = term |> search_contract_query() + |> ExplorerHelper.maybe_hide_scam_addresses(:address_hash) |> order_by([items], asc: items.name, desc: items.inserted_at) |> limit(^paging_options.page_size) |> select_repo(options).all() @@ -186,10 +190,10 @@ defmodule Explorer.Chain.Search do |> limit(^paging_options.page_size) |> select_repo(options).all() - tx_result = + transaction_result = if valid_full_hash?(search_query) do search_query - |> search_tx_query() + |> search_transaction_query() |> select_repo(options).all() else [] @@ -216,6 +220,7 @@ defmodule Explorer.Chain.Search do address_result = if query = search_address_query(search_query) do query + |> ExplorerHelper.maybe_hide_scam_addresses(:hash) |> select_repo(options).all() else [] @@ -237,7 +242,7 @@ defmodule Explorer.Chain.Search do tokens_result, contracts_result, labels_result, - tx_result, + transaction_result, op_result, blob_result, address_result, @@ -405,16 +410,16 @@ defmodule Explorer.Chain.Search do defp valid_full_hash?(string_input) do case Chain.string_to_transaction_hash(string_input) do - {:ok, _tx_hash} -> true + {:ok, _transaction_hash} -> true _ -> false end end - defp search_tx_query(term) do + defp search_transaction_query(term) do if DenormalizationHelper.transactions_denormalization_finished?() do transaction_search_fields = search_fields() - |> Map.put(:tx_hash, dynamic([transaction], transaction.hash)) + |> Map.put(:transaction_hash, dynamic([transaction], transaction.hash)) |> Map.put(:block_hash, dynamic([transaction], transaction.block_hash)) |> Map.put(:type, "transaction") |> Map.put(:block_number, dynamic([transaction], transaction.block_number)) @@ -428,7 +433,7 @@ defmodule Explorer.Chain.Search do else transaction_search_fields = search_fields() - |> Map.put(:tx_hash, dynamic([transaction, _], transaction.hash)) + |> Map.put(:transaction_hash, dynamic([transaction, _], transaction.hash)) |> Map.put(:block_hash, dynamic([transaction, _], transaction.block_hash)) |> Map.put(:type, "transaction") |> Map.put(:block_number, dynamic([transaction, _], transaction.block_number)) @@ -508,7 +513,7 @@ defmodule Explorer.Chain.Search do 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} + key: {_address_hash, _transaction_hash, _block_hash, holder_count, name, inserted_at, item_type} }) when holder_count in [nil, ""] do where( @@ -523,7 +528,7 @@ defmodule Explorer.Chain.Search do # 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} + key: {_address_hash, _transaction_hash, _block_hash, holder_count, name, inserted_at, item_type} }) do where( query, @@ -587,7 +592,7 @@ defmodule Explorer.Chain.Search do end end - # For some reasons timestamp for blocks and txs returns as ~N[2023-06-25 19:39:47.339493] + # For some reasons timestamp for blocks and transactions returns as ~N[2023-06-25 19:39:47.339493] defp format_timestamp(result) do if result.timestamp do result @@ -602,6 +607,7 @@ defmodule Explorer.Chain.Search do [ result[:address_hash] |> search_address_query() + |> ExplorerHelper.maybe_hide_scam_addresses(:hash) |> select_repo(options).all() |> merge_address_search_result_with_ens_info(result) ] @@ -647,7 +653,7 @@ defmodule Explorer.Chain.Search do defp search_fields do %{ address_hash: dynamic([_], type(^nil, :binary)), - tx_hash: dynamic([_], type(^nil, :binary)), + transaction_hash: dynamic([_], type(^nil, :binary)), user_operation_hash: dynamic([_], type(^nil, :binary)), blob_hash: dynamic([_], type(^nil, :binary)), block_hash: dynamic([_], type(^nil, :binary)), diff --git a/apps/explorer/lib/explorer/chain/signed_authorization.ex b/apps/explorer/lib/explorer/chain/signed_authorization.ex new file mode 100644 index 0000000..f43d847 --- /dev/null +++ b/apps/explorer/lib/explorer/chain/signed_authorization.ex @@ -0,0 +1,80 @@ +defmodule Explorer.Chain.SignedAuthorization do + @moduledoc "Models a transaction extension with authorization tuples from eip7702 set code transactions." + + use Explorer.Schema + + alias Explorer.Chain.{Hash, Transaction} + + @optional_attrs ~w(authority)a + @required_attrs ~w(transaction_hash index chain_id address nonce r s v)a + + @typedoc """ + Descriptor of the signed authorization tuple from EIP-7702 set code transactions: + * `transaction_hash` - the hash of the associated transaction. + * `index` - the index of this authorization in the authorization list. + * `chain_id` - the ID of the chain for which the authorization was created. + * `address` - the address of the delegate contract. + * `nonce` - the signature nonce. + * `v` - the 'v' component of the signature. + * `r` - the 'r' component of the signature. + * `s` - the 's' component of the signature. + * `authority` - the signer of the authorization. + """ + @type to_import :: %__MODULE__{ + transaction_hash: binary(), + index: non_neg_integer(), + chain_id: non_neg_integer(), + address: binary(), + nonce: non_neg_integer(), + r: non_neg_integer(), + s: non_neg_integer(), + v: non_neg_integer(), + authority: binary() | nil + } + + @typedoc """ + * `transaction_hash` - the hash of the associated transaction. + * `index` - the index of this authorization in the authorization list. + * `chain_id` - the ID of the chain for which the authorization was created. + * `address` - the address of the delegate contract. + * `nonce` - the signature nonce. + * `v` - the 'v' component of the signature. + * `r` - the 'r' component of the signature. + * `s` - the 's' component of the signature. + * `authority` - the signer of the authorization. + * `inserted_at` - timestamp indicating when the signed authorization was created. + * `updated_at` - timestamp indicating when the signed authorization was last updated. + * `transaction` - an instance of `Explorer.Chain.Transaction` referenced by `transaction_hash`. + """ + @primary_key false + typed_schema "signed_authorizations" do + field(:index, :integer, primary_key: true, null: false) + field(:chain_id, :integer, null: false) + field(:address, Hash.Address, null: false) + field(:nonce, :integer, null: false) + field(:r, :decimal, null: false) + field(:s, :decimal, null: false) + field(:v, :integer, null: false) + field(:authority, Hash.Address, null: true) + + belongs_to(:transaction, Transaction, + foreign_key: :transaction_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__{} = struct, attrs \\ %{}) do + struct + |> cast(attrs, @required_attrs ++ @optional_attrs) + |> validate_required(@required_attrs) + |> foreign_key_constraint(:transaction_hash) + end +end diff --git a/apps/explorer/lib/explorer/chain/smart_contract.ex b/apps/explorer/lib/explorer/chain/smart_contract.ex index d6a8811..6cfe684 100644 --- a/apps/explorer/lib/explorer/chain/smart_contract.ex +++ b/apps/explorer/lib/explorer/chain/smart_contract.ex @@ -95,6 +95,7 @@ defmodule Explorer.Chain.SmartContract do use Explorer.Schema + alias ABI.FunctionSelector alias Ecto.{Changeset, Multi} alias Explorer.{Chain, Repo, SortingHelper} @@ -113,6 +114,7 @@ defmodule Explorer.Chain.SmartContract do alias Explorer.Chain.SmartContract.Proxy alias Explorer.Chain.SmartContract.Proxy.Models.Implementation + alias Explorer.Helper, as: ExplorerHelper alias Explorer.SmartContract.Helper alias Explorer.SmartContract.Solidity.Verifier @@ -136,6 +138,31 @@ defmodule Explorer.Chain.SmartContract do ~w()a end) + @create_zksync_abi [ + %{ + "inputs" => [ + %{"internalType" => "bytes32", "name" => "_salt", "type" => "bytes32"}, + %{"internalType" => "bytes32", "name" => "_bytecodeHash", "type" => "bytes32"}, + %{"internalType" => "bytes", "name" => "_input", "type" => "bytes"} + ], + "name" => "create2", + "outputs" => [%{"internalType" => "address", "name" => "", "type" => "address"}], + "stateMutability" => "payable", + "type" => "function" + }, + %{ + "inputs" => [ + %{"internalType" => "bytes32", "name" => "_salt", "type" => "bytes32"}, + %{"internalType" => "bytes32", "name" => "_bytecodeHash", "type" => "bytes32"}, + %{"internalType" => "bytes", "name" => "_input", "type" => "bytes"} + ], + "name" => "create", + "outputs" => [%{"internalType" => "address", "name" => "", "type" => "address"}], + "stateMutability" => "payable", + "type" => "function" + } + ] + @doc """ Returns burn address hash """ @@ -565,30 +592,32 @@ defmodule Explorer.Chain.SmartContract do @doc """ Extracts creation bytecode (`init`) and transaction (`tx`) or - internal transaction (`internal_tx`) where the contract was created. + internal transaction (`internal_transaction`) 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 = + @spec creation_transaction_with_bytecode(binary() | Hash.t()) :: + %{init: binary(), transaction: Transaction.t()} + | %{init: binary(), internal_transaction: InternalTransaction.t()} + | nil + def creation_transaction_with_bytecode(address_hash) do + creation_transaction_query = from( - tx in Transaction, - where: tx.created_contract_address_hash == ^address_hash, - where: tx.status == ^1, - order_by: [desc: tx.block_number], + transaction in Transaction, + where: transaction.created_contract_address_hash == ^address_hash, + where: transaction.status == ^1, + order_by: [desc: transaction.block_number], limit: ^1 ) - tx = - creation_tx_query + transaction = + creation_transaction_query |> Repo.one() - if tx do - with %{input: input} <- tx do - %{init: Data.to_string(input), tx: tx} + if transaction do + with %{input: input} <- transaction do + %{init: Data.to_string(input), transaction: transaction} end else - creation_int_tx_query = + creation_int_transaction_query = from( itx in InternalTransaction, join: t in assoc(itx, :transaction), @@ -596,12 +625,12 @@ defmodule Explorer.Chain.SmartContract do where: t.status == ^1 ) - internal_tx = creation_int_tx_query |> Repo.one() + internal_transaction = creation_int_transaction_query |> Repo.one() - case internal_tx do + case internal_transaction do %{init: init} -> init_str = Data.to_string(init) - %{init: init_str, internal_tx: internal_tx} + %{init: init_str, internal_transaction: internal_transaction} _ -> nil @@ -699,6 +728,10 @@ defmodule Explorer.Chain.SmartContract do """ @spec get_verified_bytecode_twin_contract(Address.t(), any()) :: SmartContract.t() | nil def get_verified_bytecode_twin_contract(%Address{} = target_address, options \\ []) do + necessity_by_association = %{ + :smart_contract_additional_sources => :optional + } + case target_address do %{contract_code: %Chain.Data{bytes: contract_code_bytes}} -> target_address_hash = target_address.hash @@ -715,6 +748,7 @@ defmodule Explorer.Chain.SmartContract do ) verified_bytecode_twin_contract_query + |> Chain.join_associations(necessity_by_association) |> Chain.select_repo(options).one(timeout: 10_000) _ -> @@ -1268,6 +1302,7 @@ defmodule Explorer.Chain.SmartContract do query = from(contract in __MODULE__) query + |> ExplorerHelper.maybe_hide_scam_addresses(:address_hash) |> filter_contracts(filter) |> search_contracts(search_string) |> SortingHelper.apply_sorting(sorting_options, @default_sorting) @@ -1301,4 +1336,28 @@ defmodule Explorer.Chain.SmartContract do end defp filter_contracts(basic_query, _), do: basic_query + + @doc """ + Retrieves the constructor arguments for a zkSync smart contract. + Using @create_zksync_abi function decodes transaction input of contract creation + + ## Parameters + - `binary()`: The binary data representing the smart contract. + + ## Returns + - `nil`: If the constructor arguments cannot be retrieved. + - `binary()`: The constructor arguments in binary format. + """ + @spec zksync_get_constructor_arguments(binary()) :: nil | binary() + def zksync_get_constructor_arguments(address_hash_string) do + creation_input = Chain.contract_creation_input_data_from_transaction(address_hash_string) + + case @create_zksync_abi |> ABI.parse_specification() |> ABI.find_and_decode(creation_input) do + {%FunctionSelector{}, [_, _, constructor_args]} -> + Base.encode16(constructor_args, case: :lower) + + _ -> + nil + end + end end diff --git a/apps/explorer/lib/explorer/chain/smart_contract/audit_report.ex b/apps/explorer/lib/explorer/chain/smart_contract/audit_report.ex index e92a4cf..124255b 100644 --- a/apps/explorer/lib/explorer/chain/smart_contract/audit_report.ex +++ b/apps/explorer/lib/explorer/chain/smart_contract/audit_report.ex @@ -39,7 +39,7 @@ defmodule Explorer.Chain.SmartContract.AuditReport do waste_fields = association_fields ++ @local_fields chain = - Application.get_env(:block_scout_web, BlockScoutWeb.Endpoint)[:url][:host] <> + Helper.get_app_host() <> Application.get_env(:block_scout_web, BlockScoutWeb.Endpoint)[:url][:path] request |> Map.from_struct() |> Map.drop(waste_fields) |> Map.put(:chain, chain) diff --git a/apps/explorer/lib/explorer/chain/smart_contract/proxy.ex b/apps/explorer/lib/explorer/chain/smart_contract/proxy.ex index f4ea719..2412874 100644 --- a/apps/explorer/lib/explorer/chain/smart_contract/proxy.ex +++ b/apps/explorer/lib/explorer/chain/smart_contract/proxy.ex @@ -15,6 +15,7 @@ defmodule Explorer.Chain.SmartContract.Proxy do EIP1822, EIP1967, EIP2535, + EIP7702, EIP930, MasterCopy } @@ -116,7 +117,20 @@ defmodule Explorer.Chain.SmartContract.Proxy do end @doc """ - Decodes address output into 20 bytes address hash + Decodes and formats an address output from a smart contract ABI. + + This function handles various input formats and edge cases when decoding + address outputs from smart contract function calls or events. + + ## Parameters + - `address`: The address output to decode. Can be `nil`, `"0x"`, a binary string, or `:error`. + + ## Returns + - `nil` if the input is `nil`. + - The burn address hash string if the input is `"0x"`. + - A formatted address string if the input is a valid binary string. + - `:error` if the input is `:error`. + - `nil` for any other input type. """ @spec abi_decode_address_output(any()) :: nil | :error | binary() def abi_decode_address_output(nil), do: nil @@ -154,16 +168,6 @@ defmodule Explorer.Chain.SmartContract.Proxy do 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 @@ -260,6 +264,20 @@ defmodule Explorer.Chain.SmartContract.Proxy do proxy_abi, go_to_fallback? ], + :get_implementation_address_hash_string_eip7702 + ) + end + + @doc """ + Returns EIP-7702 implementation address or tries next proxy pattern + """ + @spec get_implementation_address_hash_string_eip7702(Hash.Address.t(), any(), bool()) :: + %{implementation_address_hash_strings: [String.t()] | :error | nil, proxy_type: atom() | :unknown} + def get_implementation_address_hash_string_eip7702(proxy_address_hash, proxy_abi, go_to_fallback?) do + get_implementation_address_hash_string_by_module( + EIP7702, + :eip7702, + [proxy_address_hash, proxy_abi, go_to_fallback?], :get_implementation_address_hash_string_eip1967 ) end @@ -292,14 +310,64 @@ defmodule Explorer.Chain.SmartContract.Proxy do proxy_type: atom() } def get_implementation_address_hash_string_eip1822(proxy_address_hash, proxy_abi, go_to_fallback?) do - get_implementation_address_hash_string_by_module(EIP1822, :eip1822, [proxy_address_hash, proxy_abi, go_to_fallback?]) + get_implementation_address_hash_string_by_module( + EIP1822, + :eip1822, + [ + proxy_address_hash, + proxy_abi, + go_to_fallback? + ], + :get_implementation_address_hash_string_eip2535 + ) + end + + @doc """ + Returns EIP-2535 implementation address or tries next proxy pattern + """ + @spec get_implementation_address_hash_string_eip2535(Hash.Address.t(), any(), bool()) :: %{ + implementation_address_hash_strings: [String.t() | :error | nil], + proxy_type: atom() + } + def get_implementation_address_hash_string_eip2535(proxy_address_hash, proxy_abi, go_to_fallback?) do + get_implementation_address_hash_string_by_module(EIP2535, :eip2535, [proxy_address_hash, proxy_abi, go_to_fallback?]) end defp get_implementation_address_hash_string_by_module( module, proxy_type, - [proxy_address_hash, proxy_abi, go_to_fallback?] = args, + args, next_func \\ :fallback_proxy_detection + ) + + defp get_implementation_address_hash_string_by_module( + EIP2535 = module, + :eip2535 = proxy_type, + [proxy_address_hash, proxy_abi, go_to_fallback?] = args, + next_func + ) do + implementation_address_hash_strings = module.get_implementation_address_hash_strings(proxy_address_hash) + + if !is_nil(implementation_address_hash_strings) && implementation_address_hash_strings !== [] && + implementation_address_hash_strings !== :error do + %{implementation_address_hash_strings: implementation_address_hash_strings, proxy_type: proxy_type} + else + do_get_implementation_address_hash_string_by_module( + implementation_address_hash_strings, + proxy_address_hash, + proxy_abi, + go_to_fallback?, + next_func, + args + ) + end + end + + defp get_implementation_address_hash_string_by_module( + module, + proxy_type, + [proxy_address_hash, proxy_abi, go_to_fallback?] = args, + next_func ) do implementation_address_hash_string = module.get_implementation_address_hash_string(proxy_address_hash) @@ -307,23 +375,41 @@ defmodule Explorer.Chain.SmartContract.Proxy do implementation_address_hash_string !== :error do %{implementation_address_hash_strings: [implementation_address_hash_string], proxy_type: proxy_type} else - cond do - next_func !== :fallback_proxy_detection -> - apply(__MODULE__, next_func, args) + do_get_implementation_address_hash_string_by_module( + implementation_address_hash_string, + proxy_address_hash, + proxy_abi, + go_to_fallback?, + next_func, + args + ) + end + end - go_to_fallback? && next_func == :fallback_proxy_detection -> - fallback_value = implementation_fallback_value(implementation_address_hash_string) + defp do_get_implementation_address_hash_string_by_module( + implementation_value, + proxy_address_hash, + proxy_abi, + go_to_fallback?, + next_func, + args + ) do + cond do + next_func !== :fallback_proxy_detection -> + apply(__MODULE__, next_func, args) - apply(__MODULE__, :fallback_proxy_detection, [proxy_address_hash, proxy_abi, fallback_value]) + go_to_fallback? && next_func == :fallback_proxy_detection -> + fallback_value = implementation_fallback_value(implementation_value) - true -> - implementation_fallback_value(implementation_address_hash_string) - end + apply(__MODULE__, :fallback_proxy_detection, [proxy_address_hash, proxy_abi, fallback_value]) + + true -> + implementation_fallback_value(implementation_value) end end - defp implementation_fallback_value(implementation_address_hash_string) do - value = if implementation_address_hash_string == :error, do: :error, else: [] + defp implementation_fallback_value(implementation_value) do + value = if implementation_value == :error, do: :error, else: [] %{implementation_address_hash_strings: value, proxy_type: :unknown} end @@ -413,16 +499,14 @@ defmodule Explorer.Chain.SmartContract.Proxy do 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 + def combine_proxy_implementation_abi( + smart_contract, + options \\ [] + ) do + proxy_abi = (smart_contract && smart_contract.abi) || [] 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 - [] + proxy_abi ++ implementation_abi end defp find_input_by_name(inputs, name) do diff --git a/apps/explorer/lib/explorer/chain/smart_contract/proxy/basic.ex b/apps/explorer/lib/explorer/chain/smart_contract/proxy/basic.ex index d0d6a4f..aff7b41 100644 --- a/apps/explorer/lib/explorer/chain/smart_contract/proxy/basic.ex +++ b/apps/explorer/lib/explorer/chain/smart_contract/proxy/basic.ex @@ -9,7 +9,8 @@ defmodule Explorer.Chain.SmartContract.Proxy.Basic do @doc """ Gets implementation hash string of proxy contract from getter. """ - @spec get_implementation_address_hash_string(binary, binary, SmartContract.abi()) :: nil | binary() | [binary()] + @spec get_implementation_address_hash_string(binary, binary, SmartContract.abi()) :: + nil | :error | binary() | [binary()] def get_implementation_address_hash_string(signature, proxy_address_hash, abi) do implementation_address = case Reader.query_contract( @@ -20,8 +21,14 @@ defmodule Explorer.Chain.SmartContract.Proxy.Basic do }, false ) do - %{^signature => {:ok, [result]}} -> result - _ -> nil + %{^signature => {:ok, [result]}} -> + result + + %{^signature => {:error, _}} -> + :error + + _ -> + nil end adds_0x_to_address(implementation_address) @@ -30,9 +37,11 @@ defmodule Explorer.Chain.SmartContract.Proxy.Basic do @doc """ Adds 0x to address at the beginning """ - @spec adds_0x_to_address(nil | binary()) :: nil | binary() | [binary()] + @spec adds_0x_to_address(nil | :error | binary()) :: nil | :error | binary() | [binary()] def adds_0x_to_address(nil), do: nil + def adds_0x_to_address(:error), do: :error + def adds_0x_to_address(addresses) when is_list(addresses) do addresses |> Enum.map(fn address -> adds_0x_to_address(address) end) diff --git a/apps/explorer/lib/explorer/chain/smart_contract/proxy/eip_2535.ex b/apps/explorer/lib/explorer/chain/smart_contract/proxy/eip_2535.ex index cc0d374..4e9aaf9 100644 --- a/apps/explorer/lib/explorer/chain/smart_contract/proxy/eip_2535.ex +++ b/apps/explorer/lib/explorer/chain/smart_contract/proxy/eip_2535.ex @@ -19,7 +19,7 @@ defmodule Explorer.Chain.SmartContract.Proxy.EIP2535 do } ] - @spec get_implementation_address_hash_strings(Hash.Address.t()) :: nil | [binary] + @spec get_implementation_address_hash_strings(Hash.Address.t()) :: nil | :error | [binary] def get_implementation_address_hash_strings(proxy_address_hash) do case @facet_addresses_signature |> Basic.get_implementation_address_hash_string( @@ -29,6 +29,9 @@ defmodule Explorer.Chain.SmartContract.Proxy.EIP2535 do implementation_addresses when is_list(implementation_addresses) -> implementation_addresses + :error -> + :error + _ -> nil end diff --git a/apps/explorer/lib/explorer/chain/smart_contract/proxy/eip_7702.ex b/apps/explorer/lib/explorer/chain/smart_contract/proxy/eip_7702.ex new file mode 100644 index 0000000..eb34946 --- /dev/null +++ b/apps/explorer/lib/explorer/chain/smart_contract/proxy/eip_7702.ex @@ -0,0 +1,73 @@ +defmodule Explorer.Chain.SmartContract.Proxy.EIP7702 do + @moduledoc """ + Module for fetching EOA delegate from https://eips.ethereum.org/EIPS/eip-7702 + """ + + alias Explorer.Chain + alias Explorer.Chain.{Address, Hash} + alias Explorer.Chain.SmartContract.Proxy + + @doc """ + Retrieves the delegate address hash string for an EIP-7702 compatible EOA. + + This function fetches the contract code for the given address and extracts + the delegate address according to the EIP-7702 specification. + + ## Parameters + - `address_hash`: The address of the contract to check. + - `options`: Optional keyword list of options (default: `[]`). + + ## Returns + - The delegate address in the hex string format if found and successfully decoded. + - `nil` if the address doesn't exist, has no contract code, or the delegate address + couldn't be extracted or decoded. + """ + @spec get_implementation_address_hash_string(Hash.Address.t(), Keyword.t()) :: String.t() | nil + @spec get_implementation_address_hash_string(Hash.Address.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_code_bytes |> get_delegate_address() |> Proxy.abi_decode_address_output() + + _ -> + nil + end + end + end + + @doc """ + Extracts the EIP-7702 delegate address from the bytecode. + + This function analyzes the given bytecode to identify and extract the delegate + address according to the EIP-7702 specification. + + ## Parameters + - `contract_code_bytes`: The binary representation of the contract bytecode. + + ## Returns + - A string representation of the delegate address prefixed with "0x" if found. + - `nil` if the delegate address is not present in the bytecode. + + ## Examples + iex> get_delegate_address(<<239, 1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20>>) + "0x0102030405060708090a0b0c0d0e0f10111213" + + iex> get_delegate_address(<<1, 2, 3>>) + nil + """ + @spec get_delegate_address(binary()) :: String.t() | nil + def get_delegate_address(contract_code_bytes) do + case contract_code_bytes do + # 0xef0100 <> address + <<239, 1, 0>> <> <> -> "0x" <> Base.encode16(address, case: :lower) + _ -> nil + end + end +end diff --git a/apps/explorer/lib/explorer/chain/smart_contract/proxy/models/implementation.ex b/apps/explorer/lib/explorer/chain/smart_contract/proxy/models/implementation.ex index f6e4d9c..a60fb55 100644 --- a/apps/explorer/lib/explorer/chain/smart_contract/proxy/models/implementation.ex +++ b/apps/explorer/lib/explorer/chain/smart_contract/proxy/models/implementation.ex @@ -17,7 +17,6 @@ defmodule Explorer.Chain.SmartContract.Proxy.Models.Implementation do alias Explorer.Chain.{Address, Hash, SmartContract} alias Explorer.Chain.SmartContract.Proxy - alias Explorer.Chain.SmartContract.Proxy.Models.Implementation alias Explorer.Counters.AverageBlockTime alias Explorer.Repo alias Timex.Duration @@ -50,6 +49,7 @@ defmodule Explorer.Chain.SmartContract.Proxy.Models.Implementation do :comptroller, :eip2535, :clone_with_immutable_arguments, + :eip7702, :unknown ], null: true @@ -91,10 +91,24 @@ defmodule Explorer.Chain.SmartContract.Proxy.Models.Implementation do |> select_repo(options).one() end + @doc """ + Returns all implementations for the given smart-contract address hashes + """ + @spec get_proxy_implementations_for_multiple_proxies([Hash.Address.t()], Keyword.t()) :: [__MODULE__.t()] + def get_proxy_implementations_for_multiple_proxies(proxy_address_hashes, options \\ []) + + def get_proxy_implementations_for_multiple_proxies([], _), do: [] + + def get_proxy_implementations_for_multiple_proxies(proxy_address_hashes, options) do + proxy_address_hashes + |> get_proxy_implementations_by_multiple_hashes_query() + |> select_repo(options).all() + end + @doc """ Returns the last implementation updated_at for the given smart-contract address hash """ - @spec get_proxy_implementation_updated_at(Hash.Address.t() | nil, Keyword.t()) :: DateTime.t() + @spec get_proxy_implementation_updated_at(Hash.Address.t() | nil, Keyword.t()) :: DateTime.t() | nil def get_proxy_implementation_updated_at(proxy_address_hash, options) do proxy_address_hash |> get_proxy_implementations_query() @@ -109,6 +123,13 @@ defmodule Explorer.Chain.SmartContract.Proxy.Models.Implementation do ) end + defp get_proxy_implementations_by_multiple_hashes_query(proxy_address_hashes) do + from( + p in __MODULE__, + where: p.proxy_address_hash in ^proxy_address_hashes + ) + end + @doc """ Returns implementation address, name and proxy type for the given SmartContract """ @@ -140,9 +161,20 @@ defmodule Explorer.Chain.SmartContract.Proxy.Models.Implementation do {updated_smart_contract, implementation_address_fetched?} = if check_implementation_refetch_necessity(implementation_updated_at) do - SmartContract.address_hash_to_smart_contract_with_bytecode_twin(address_hash, options) + {smart_contract_with_bytecode_twin, implementation_address_fetched?} = + SmartContract.address_hash_to_smart_contract_with_bytecode_twin(address_hash, options) + + if smart_contract_with_bytecode_twin do + {smart_contract_with_bytecode_twin, implementation_address_fetched?} + else + {smart_contract, implementation_address_fetched?} + end else - {smart_contract, false} + if implementation_updated_at do + {smart_contract, true} + else + {smart_contract, false} + end end get_implementation( @@ -199,7 +231,7 @@ defmodule Explorer.Chain.SmartContract.Proxy.Models.Implementation do {:ok, :error} -> format_proxy_implementations_response(proxy_implementations) - {:ok, %Implementation{} = result} -> + {:ok, %__MODULE__{} = result} -> format_proxy_implementations_response(result) _ -> @@ -278,7 +310,7 @@ defmodule Explorer.Chain.SmartContract.Proxy.Models.Implementation do Saves proxy's implementation into the DB """ @spec save_implementation_data([String.t()], Hash.Address.t(), atom() | nil, Keyword.t()) :: - Implementation.t() | :empty | :error + __MODULE__.t() | :empty | :error def save_implementation_data(:error, _proxy_address_hash, _proxy_type, _options) do :error end diff --git a/apps/explorer/lib/explorer/chain/supply/rsk.ex b/apps/explorer/lib/explorer/chain/supply/rsk.ex index 2eb9caa..14635a1 100644 --- a/apps/explorer/lib/explorer/chain/supply/rsk.ex +++ b/apps/explorer/lib/explorer/chain/supply/rsk.ex @@ -113,7 +113,7 @@ defmodule Explorer.Chain.Supply.RSK do json_rpc_named_arguments = Application.get_env(:explorer, :json_rpc_named_arguments) - case EthereumJSONRPC.fetch_balances(params, json_rpc_named_arguments) do + case EthereumJSONRPC.fetch_balances(params, json_rpc_named_arguments, max_number) do {:ok, %FetchedBalances{ errors: [], diff --git a/apps/explorer/lib/explorer/chain/token.ex b/apps/explorer/lib/explorer/chain/token.ex index b756d2f..f97e0f5 100644 --- a/apps/explorer/lib/explorer/chain/token.ex +++ b/apps/explorer/lib/explorer/chain/token.ex @@ -81,6 +81,7 @@ defmodule Explorer.Chain.Token do alias Ecto.Changeset alias Explorer.{Chain, SortingHelper} alias Explorer.Chain.{BridgedToken, Hash, Search, Token} + alias Explorer.Helper, as: ExplorerHelper alias Explorer.Repo alias Explorer.SmartContract.Helper @@ -210,6 +211,7 @@ defmodule Explorer.Chain.Token do sorted_paginated_query = query + |> ExplorerHelper.maybe_hide_scam_addresses(:contract_address_hash) |> apply_filter(token_type) |> SortingHelper.apply_sorting(sorting, @default_sorting) |> SortingHelper.page_with_sorting(paging_options, sorting, @default_sorting) diff --git a/apps/explorer/lib/explorer/chain/token_transfer.ex b/apps/explorer/lib/explorer/chain/token_transfer.ex index bafd52e..674efce 100644 --- a/apps/explorer/lib/explorer/chain/token_transfer.ex +++ b/apps/explorer/lib/explorer/chain/token_transfer.ex @@ -239,8 +239,8 @@ defmodule Explorer.Chain.TokenTransfer do DenormalizationHelper.extend_transaction_preload([ :transaction, :token, - [from_address: [:names, :smart_contract, :proxy_implementations]], - [to_address: [:names, :smart_contract, :proxy_implementations]] + [from_address: [:scam_badge, :names, :smart_contract, :proxy_implementations]], + [to_address: [:scam_badge, :names, :smart_contract, :proxy_implementations]] ]) only_consensus_transfers_query() @@ -266,8 +266,8 @@ defmodule Explorer.Chain.TokenTransfer do DenormalizationHelper.extend_transaction_preload([ :transaction, :token, - [from_address: [:names, :smart_contract, :proxy_implementations]], - [to_address: [:names, :smart_contract, :proxy_implementations]] + [from_address: [:scam_badge, :names, :smart_contract, :proxy_implementations]], + [to_address: [:scam_badge, :names, :smart_contract, :proxy_implementations]] ]) only_consensus_transfers_query() @@ -282,6 +282,52 @@ defmodule Explorer.Chain.TokenTransfer do end end + @doc """ + Returns the ordered paginated list of consensus token transfers (consensus blocks only) from the DB with address, token, transaction preloads + """ + @spec fetch([paging_options | api?]) :: [] + def fetch(options) do + paging_options = Keyword.get(options, :paging_options, @default_paging_options) + token_type = Keyword.get(options, :token_type) + + case paging_options do + %PagingOptions{key: {0, 0}} -> + [] + + _ -> + preloads = + DenormalizationHelper.extend_transaction_preload([ + :transaction, + :token, + [from_address: [:scam_badge, :names, :smart_contract, :proxy_implementations]], + [to_address: [:scam_badge, :names, :smart_contract, :proxy_implementations]] + ]) + + only_consensus_transfers_query() + |> preload(^preloads) + |> order_by([tt], desc: tt.block_number, desc: tt.log_index) + |> maybe_filter_by_token_type(token_type) + |> page_token_transfer(paging_options) + |> limit(^paging_options.page_size) + |> Chain.select_repo(options).all() + end + end + + defp maybe_filter_by_token_type(query, token_types) do + if Enum.empty?(token_types) do + query + else + if DenormalizationHelper.tt_denormalization_finished?() do + query + |> where([tt], tt.token_type in ^token_types) + else + query + |> join(:inner, [tt], token in assoc(tt, :token), as: :token) + |> where([tt, block, token], token.type in ^token_types) + end + end + end + @spec count_token_transfers_from_token_hash(Hash.t()) :: non_neg_integer() def count_token_transfers_from_token_hash(token_address_hash) do query = @@ -442,7 +488,46 @@ defmodule Explorer.Chain.TokenTransfer do |> order_by([tt], desc: tt.block_number, desc: tt.log_index) end - def token_transfers_by_address_hash(direction, address_hash, token_types, paging_options) do + @doc """ + Retrieves token transfers associated with a given address, optionally filtered + by direction and token types. + + ## Parameters + + - `address_hash` (`Hash.Address.t()`): The address hash for which to retrieve + token transfers. + - `direction` (`nil | :to | :from`): The direction of the transfers to filter. + - `:to` - transfers where `to_address` matches `address_hash`. + - `:from` - transfers where `from_address` matches `address_hash`. + - `nil` - includes both incoming and outgoing transfers. + - `token_types` (`[binary()]`): The token types to filter, e.g `["ERC20", "ERC721"]`. + - `paging_options` (`nil | Explorer.PagingOptions.t()`): Pagination options to + limit the result set. + + ## Returns + + An `Ecto.Query` for `TokenTransfer.t()`. + + ## Examples + + Fetch all incoming ERC20 token transfers for a specific address: + + # iex> query = token_transfers_by_address_hash(address_hash, :to, ["ERC20"], paging_options) + # iex> Repo.all(query) + + Fetch both incoming and outgoing token transfers for a specific address + without pagination, token type filtering, and direction filtering: + + # iex> query = token_transfers_by_address_hash(address_hash, nil, [], nil) + # iex> Repo.all(query) + """ + @spec token_transfers_by_address_hash( + Hash.Address.t(), + nil | :to | :from, + [binary()], + nil | Explorer.PagingOptions.t() + ) :: Ecto.Query.t() + def token_transfers_by_address_hash(address_hash, direction, token_types, paging_options) do if direction == :to || direction == :from do only_consensus_transfers_query() |> filter_by_direction(direction, address_hash) @@ -474,7 +559,7 @@ defmodule Explorer.Chain.TokenTransfer do |> union(^from_address_hash_query) |> Chain.wrapped_union_subquery() |> order_by([tt], desc: tt.block_number, desc: tt.log_index) - |> limit(^paging_options.page_size) + |> handle_paging_options(paging_options) end end diff --git a/apps/explorer/lib/explorer/chain/transaction.ex b/apps/explorer/lib/explorer/chain/transaction.ex index e6e48e2..67529bb 100644 --- a/apps/explorer/lib/explorer/chain/transaction.ex +++ b/apps/explorer/lib/explorer/chain/transaction.ex @@ -6,6 +6,8 @@ defmodule Explorer.Chain.Transaction.Schema do - Explorer.Chain.Import.Runner.Transactions """ + alias Explorer.Chain + alias Explorer.Chain.{ Address, Beacon.BlobTransaction, @@ -14,6 +16,7 @@ defmodule Explorer.Chain.Transaction.Schema do Hash, InternalTransaction, Log, + SignedAuthorization, TokenTransfer, TransactionAction, Wei @@ -42,12 +45,21 @@ defmodule Explorer.Chain.Transaction.Schema do field(:l1_fee_scalar, :decimal) field(:l1_gas_price, Wei) field(:l1_gas_used, :decimal) - field(:l1_tx_origin, Hash.Full) + field(:l1_transaction_origin, Hash.Full) field(:l1_block_number, :integer) end, 2 ) + :scroll -> + elem( + quote do + field(:l1_fee, Wei) + field(:queue_index, :integer) + end, + 2 + ) + :suave -> elem( quote do @@ -110,7 +122,7 @@ defmodule Explorer.Chain.Transaction.Schema do elem( quote do has_one(:zksync_batch_transaction, ZkSyncBatchTransaction, - foreign_key: :tx_hash, + foreign_key: :transaction_hash, references: :hash ) @@ -150,7 +162,7 @@ defmodule Explorer.Chain.Transaction.Schema do field(:gas_used_for_l1, :decimal) has_one(:arbitrum_batch_transaction, ArbitrumBatchTransaction, - foreign_key: :tx_hash, + foreign_key: :transaction_hash, references: :hash ) @@ -214,7 +226,7 @@ defmodule Explorer.Chain.Transaction.Schema do field(:max_priority_fee_per_gas, Wei) field(:max_fee_per_gas, Wei) field(:type, :integer) - field(:has_error_in_internal_txs, :boolean) + field(:has_error_in_internal_transactions, :boolean) field(:has_token_transfers, :boolean, virtual: true) # stability virtual fields @@ -268,6 +280,11 @@ defmodule Explorer.Chain.Transaction.Schema do type: Hash.Address ) + has_many(:signed_authorizations, SignedAuthorization, + foreign_key: :transaction_hash, + references: :hash + ) + unquote_splicing(@chain_type_fields) end end @@ -295,24 +312,28 @@ defmodule Explorer.Chain.Transaction do Data, DenormalizationHelper, Hash, - SmartContract, SmartContract.Proxy, TokenTransfer, Transaction, Wei } + alias Explorer.Chain.SmartContract.Proxy.Models.Implementation + alias Explorer.SmartContract.SigProviderInterface @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 r s v)a + to_address_hash revert_reason type has_error_in_internal_transactions r s v)a @chain_type_optional_attrs (case Application.compile_env(:explorer, :chain_type) do :optimism -> - ~w(l1_fee l1_fee_scalar l1_gas_price l1_gas_used l1_tx_origin l1_block_number)a + ~w(l1_fee l1_fee_scalar l1_gas_price l1_gas_used l1_transaction_origin l1_block_number)a + + :scroll -> + ~w(l1_fee queue_index)a :suave -> ~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 @@ -430,7 +451,7 @@ defmodule Explorer.Chain.Transaction do processing. * `error` - the `error` from the last `t:Explorer.Chain.InternalTransaction.t/0` in `internal_transactions` that caused `status` to be `:error`. Only set after `internal_transactions_index_at` is set AND if there was an error. - Also, `error` is set if transaction is replaced/dropped + Also, `error` is set if transaction is dropped/replaced * `forks` - copies of this transactions that were collated into `uncles` not on the primary consensus of the chain. * `from_address` - the source of `value` * `from_address_hash` - foreign key of `from_address` @@ -473,7 +494,7 @@ defmodule Explorer.Chain.Transaction do * `max_priority_fee_per_gas` - User defined maximum fee (tip) per unit of gas paid to validator for transaction prioritization. * `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 + * `has_error_in_internal_transactions` - 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) @@ -727,17 +748,14 @@ defmodule Explorer.Chain.Transaction do {:ok, identifier, text, mapping} _ -> - {result, _, _} = - 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 + ) end _ -> @@ -750,31 +768,60 @@ defmodule Explorer.Chain.Transaction do 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(), + methods_map, + smart_contract_full_abi_map + ) :: error_type | success_type + when methods_map: map(), + smart_contract_full_abi_map: 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( + transaction, + skip_sig_provider? \\ false, + options, + methods_map \\ %{}, + smart_contract_full_abi_map \\ %{} + ) - def decoded_input_data(%__MODULE__{to_address: nil}, _, _, full_abi_acc, methods_acc), - do: {{:error, :no_to_address}, full_abi_acc, methods_acc} + # skip decoding if there is no to_address + def decoded_input_data( + %__MODULE__{to_address: nil}, + _, + _, + _, + _ + ), + do: {:error, :no_to_address} - def decoded_input_data(%NotLoaded{}, _, _, full_abi_acc, methods_acc), - do: {{:error, :not_loaded}, full_abi_acc, methods_acc} + # skip decoding if transaction is not loaded + def decoded_input_data(%NotLoaded{}, _, _, _, _), + do: {:error, :not_loaded} - 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} + # skip decoding if input is empty + def decoded_input_data( + %__MODULE__{input: %{bytes: bytes}}, + _, + _, + _, + _ + ) + when bytes in [nil, <<>>] do + {:error, :no_input_data} + end + # skip decoding if to_address is not a contract unless DECODE_NOT_A_CONTRACT_CALLS is set if not Application.compile_env(:explorer, :decode_not_a_contract_calls) do - 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} + def decoded_input_data( + %__MODULE__{to_address: %{contract_code: nil}}, + _, + _, + _, + _ + ), + do: {:error, :not_a_contract_call} end + # if to_address's smart_contract is nil reduce to the case when to_address is not loaded def decoded_input_data( %__MODULE__{ to_address: %{smart_contract: nil}, @@ -783,8 +830,8 @@ defmodule Explorer.Chain.Transaction do }, skip_sig_provider?, options, - full_abi_acc, - methods_acc + methods_map, + smart_contract_full_abi_map ) do decoded_input_data( %__MODULE__{ @@ -794,11 +841,12 @@ defmodule Explorer.Chain.Transaction do }, skip_sig_provider?, options, - full_abi_acc, - methods_acc + methods_map, + smart_contract_full_abi_map ) end + # if to_address's smart_contract is not loaded reduce to the case when to_address is not loaded def decoded_input_data( %__MODULE__{ to_address: %{smart_contract: %NotLoaded{}}, @@ -807,8 +855,8 @@ defmodule Explorer.Chain.Transaction do }, skip_sig_provider?, options, - full_abi_acc, - methods_acc + methods_map, + smart_contract_full_abi_map ) do decoded_input_data( %__MODULE__{ @@ -818,11 +866,12 @@ defmodule Explorer.Chain.Transaction do }, skip_sig_provider?, options, - full_abi_acc, - methods_acc + methods_map, + smart_contract_full_abi_map ) end + # if to_address is not loaded try decoding by method candidates in the DB def decoded_input_data( %__MODULE__{ to_address: %NotLoaded{}, @@ -831,29 +880,37 @@ defmodule Explorer.Chain.Transaction do }, skip_sig_provider?, options, - full_abi_acc, - methods_acc + methods_map, + _smart_contract_full_abi_map ) do - {methods, methods_acc} = - method_id - |> check_methods_cache(methods_acc, options) + methods = check_methods_cache(method_id, methods_map, options) candidates = 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, + [candidate.abi], + hash + ) 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)}, - full_abi_acc, methods_acc} + {:error, :contract_not_verified, + if(candidates == [], do: decode_function_call_via_sig_provider(input, hash, skip_sig_provider?), else: candidates)} end - def decoded_input_data(%__MODULE__{to_address: %NotLoaded{}}, _, _, full_abi_acc, methods_acc) do - {{:error, :contract_not_verified, []}, full_abi_acc, methods_acc} + # if to_address is not loaded and input is not a method call return error + def decoded_input_data( + %__MODULE__{to_address: %NotLoaded{}}, + _, + _, + _, + _ + ) do + {:error, :contract_not_verified, []} end def decoded_input_data( @@ -864,12 +921,14 @@ defmodule Explorer.Chain.Transaction do }, skip_sig_provider?, options, - full_abi_acc, - methods_acc + methods_map, + smart_contract_full_abi_map ) do - case do_decoded_input_data(data, smart_contract, hash, options, full_abi_acc) do + full_abi = check_full_abi_cache(smart_contract, smart_contract_full_abi_map, options) + + case do_decoded_input_data(data, full_abi, hash) 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}, full_abi_acc} -> + {:error, error} when error in [:could_not_decode, :no_matching_function] -> case decoded_input_data( %__MODULE__{ to_address: %NotLoaded{}, @@ -878,21 +937,21 @@ defmodule Explorer.Chain.Transaction do }, skip_sig_provider?, options, - full_abi_acc, - methods_acc + methods_map, + smart_contract_full_abi_map ) do - {{: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, []} -> + decode_function_call_via_sig_provider_wrapper(input, hash, skip_sig_provider?) - {{:error, :contract_not_verified, candidates}, full_abi_acc, methods_acc} -> - {{:error, :contract_verified, candidates}, full_abi_acc, methods_acc} + {:error, :contract_not_verified, candidates} -> + {:error, :contract_verified, candidates} - {_, full_abi_acc, methods_acc} -> - {{:error, :could_not_decode}, full_abi_acc, methods_acc} + _ -> + {:error, :could_not_decode} end - {output, full_abi_acc} -> - {output, full_abi_acc, methods_acc} + output -> + output end end @@ -906,16 +965,13 @@ defmodule Explorer.Chain.Transaction do end end - 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} - ), full_abi_acc} + defp do_decoded_input_data(data, full_abi, hash) do + 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} + end end defp decode_function_call_via_sig_provider(%{bytes: data} = input, hash, skip_sig_provider?) do @@ -925,8 +981,7 @@ 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, abi, hash) do [candidate] else _ -> @@ -934,28 +989,22 @@ 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 + defp check_methods_cache(method_id, methods_map, options) do + Map.get_lazy(methods_map, method_id, fn -> + method_id + |> ContractMethod.find_contract_method_query(1) + |> Chain.select_repo(options).all() + 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 + defp check_full_abi_cache( + smart_contract, + smart_contract_full_abi_map, + options + ) do + Map.get_lazy(smart_contract_full_abi_map, smart_contract.address_hash, fn -> + Proxy.combine_proxy_implementation_abi(smart_contract, options) + end) end def get_method_name( @@ -975,10 +1024,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) _ -> @@ -1464,7 +1513,7 @@ defmodule Explorer.Chain.Transaction do |> 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?) + |> put_has_token_transfers_to_transaction(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 @@ -1532,6 +1581,18 @@ defmodule Explorer.Chain.Transaction do end end + defp compare_custom_sorting([{block_order, :block_number}, {index_order, :index}]) do + fn a, b -> + case {Helper.compare(a.block_number, b.block_number), Helper.compare(a.index, b.index)} do + {:eq, :eq} -> compare_default_sorting(a, b) + {:eq, :gt} -> index_order == :desc + {:eq, :lt} -> index_order == :asc + {:gt, _} -> block_order == :desc + {:lt, _} -> block_order == :asc + end + end + end + defp compare_custom_sorting([{:dynamic, :fee, order, _dynamic_fee}]) do fn a, b -> nil_case = @@ -1643,7 +1704,7 @@ defmodule Explorer.Chain.Transaction do @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), + def page_transaction(query, %PagingOptions{is_pending_transaction: true} = options), do: page_pending_transaction(query, options) def page_transaction(query, %PagingOptions{key: {0, index}, is_index_in_asc_order: true}) do @@ -1713,16 +1774,16 @@ defmodule Explorer.Chain.Transaction do 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 + @spec put_has_token_transfers_to_transaction(Ecto.Query.t() | atom, boolean) :: Ecto.Query.t() + def put_has_token_transfers_to_transaction(query, true), do: query - def put_has_token_transfers_to_tx(query, false) do - from(tx in query, + def put_has_token_transfers_to_transaction(query, false) do + from(transaction in query, select_merge: %{ has_token_transfers: fragment( "(SELECT transaction_hash FROM token_transfers WHERE transaction_hash = ? LIMIT 1) IS NOT NULL", - tx.hash + transaction.hash ) } ) @@ -1733,7 +1794,7 @@ defmodule Explorer.Chain.Transaction do """ @spec dynamic_fee :: Ecto.Query.dynamic_expr() def dynamic_fee do - dynamic([tx], tx.gas_price * fragment("COALESCE(?, ?)", tx.gas_used, tx.gas)) + dynamic([transaction], transaction.gas_price * fragment("COALESCE(?, ?)", transaction.gas_used, transaction.gas)) end @doc """ @@ -1743,10 +1804,11 @@ defmodule Explorer.Chain.Transaction do 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 + %__MODULE__{block_number: block_number, index: index, inserted_at: inserted_at, hash: hash, value: value} = + transaction ) do %{ - "fee" => tx |> fee(:wei) |> elem(1), + "fee" => transaction |> fee(:wei) |> elem(1), "value" => value, "block_number" => block_number, "index" => index, @@ -1756,7 +1818,7 @@ defmodule Explorer.Chain.Transaction do end @doc """ - The fee a `transaction` paid for the `t:Explorer.Transaction.t/0` `gas` + The fee a `transaction` paid for the `t:Explorer.Chain.Transaction.t/0` `gas`. If the transaction is pending, then the fee will be a range of `unit` @@ -1787,43 +1849,65 @@ defmodule Explorer.Chain.Transaction do @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} = tx, unit) do - {:maximum, fee(tx, gas_price, gas, unit)} + def fee(%Transaction{gas: gas, gas_price: gas_price, gas_used: nil} = transaction, unit) do + {:maximum, fee_calc(transaction, gas_price, gas, unit)} end - def fee(%Transaction{gas_price: nil, gas_used: gas_used} = transaction, unit) do - if Application.get_env(:explorer, :chain_type) == :optimism do + if Application.compile_env(:explorer, :chain_type) == :optimism do + def fee(%Transaction{gas_price: nil, gas_used: _gas_used}, _unit) do {:actual, nil} - else + end + else + def fee(%Transaction{gas_price: nil, gas_used: gas_used} = transaction, unit) do gas_price = effective_gas_price(transaction) - - {:actual, - gas_price && - gas_price - |> Wei.to(unit) - |> Decimal.mult(gas_used)} + {:actual, gas_price && l2_fee_calc(gas_price, gas_used, unit)} end end - def fee(%Transaction{gas_price: gas_price, gas_used: gas_used} = tx, unit) do - {:actual, fee(tx, gas_price, gas_used, unit)} + def fee(%Transaction{gas_price: gas_price, gas_used: gas_used} = transaction, unit) do + {:actual, fee_calc(transaction, gas_price, gas_used, unit)} end - defp fee(tx, gas_price, gas, unit) do + defp fee_calc(transaction, gas_price, gas_used, unit) do l1_fee = - case Map.get(tx, :l1_fee) do + case Map.get(transaction, :l1_fee) do nil -> Wei.from(Decimal.new(0), :wei) value -> value end gas_price - |> Wei.to(unit) - |> Decimal.mult(gas) + |> l2_fee_calc(gas_used, unit) |> Wei.from(unit) |> Wei.sum(l1_fee) |> Wei.to(unit) end + @doc """ + The execution fee a `transaction` paid for the `t:Explorer.Chain.Transaction.t/0` `gas`. + Doesn't include L1 fee. See the description for the `fee` function for parameters and return values. + """ + @spec l2_fee(Transaction.t(), :ether | :gwei | :wei) :: {:maximum, Decimal.t() | nil} | {:actual, Decimal.t() | nil} + def l2_fee(%Transaction{gas: _gas, gas_price: nil, gas_used: nil}, _unit), do: {:maximum, nil} + + def l2_fee(%Transaction{gas: gas, gas_price: gas_price, gas_used: nil}, unit) do + {:maximum, l2_fee_calc(gas_price, gas, unit)} + end + + def l2_fee(%Transaction{gas_price: nil, gas_used: gas_used} = transaction, unit) do + gas_price = effective_gas_price(transaction) + {:actual, gas_price && l2_fee_calc(gas_price, gas_used, unit)} + end + + def l2_fee(%Transaction{gas_price: gas_price, gas_used: gas_used}, unit) do + {:actual, l2_fee_calc(gas_price, gas_used, unit)} + end + + defp l2_fee_calc(gas_price, gas_used, unit) do + gas_price + |> Wei.to(unit) + |> Decimal.mult(gas_used) + end + @doc """ Wrapper around `effective_gas_price/2` """ @@ -1868,12 +1952,13 @@ defmodule Explorer.Chain.Transaction do @doc """ Dynamically adds to/from for `transactions` query based on whether the target address EOA or smart-contract - todo: pay attention to [EIP-5003](https://eips.ethereum.org/EIPS/eip-5003): if it will be included, this logic should be rolled back. + EOAs with code (EIP-7702) are treated as regular EOAs. """ @spec where_transactions_to_from(Hash.Address.t()) :: any() def where_transactions_to_from(address_hash) do with {:ok, address} <- Chain.hash_to_address(address_hash), - true <- Address.smart_contract?(address) do + true <- Address.smart_contract?(address), + false <- Address.eoa_with_code?(address) do dynamic([transaction], transaction.to_address_hash == ^address_hash) else _ -> @@ -1888,8 +1973,8 @@ defmodule Explorer.Chain.Transaction do Returns the number of transactions included into the blocks of the specified block range. Only consensus blocks are taken into account. """ - @spec tx_count_for_block_range(Range.t()) :: non_neg_integer() - def tx_count_for_block_range(from..to) do + @spec transaction_count_for_block_range(Range.t()) :: non_neg_integer() + def transaction_count_for_block_range(from..to//_) do Repo.replica().aggregate( from( t in Transaction, @@ -1903,29 +1988,106 @@ defmodule Explorer.Chain.Transaction do end @doc """ - Receives as input list of transactions and returns tuple {decoded_input_data, abi_acc, methods_acc} + Receives as input list of transactions and returns decoded_input_data Where - `decoded_input_data` is list of results: either `{:ok, _identifier, _text, _mapping}` or `nil` - - `abi_acc` is list of all smart contracts ABIs fetched during decoding - - `methods_acc` is list of all smart contracts methods fetched from `contract_methods` table during decoding """ - @spec decode_transactions([Transaction.t()], boolean(), Keyword.t()) :: {[any()], map(), map()} + @spec decode_transactions([Transaction.t()], boolean(), Keyword.t()) :: [nil | {:ok, String.t(), String.t(), map()}] def decode_transactions(transactions, skip_sig_provider?, opts) do - {results, abi_acc, methods_acc} = - Enum.reduce(transactions, {[], %{}, %{}}, fn transaction, {results, abi_acc, methods_acc} -> - {result, abi_acc, methods_acc} = - decoded_input_data(transaction, skip_sig_provider?, opts, abi_acc, methods_acc) + smart_contract_full_abi_map = combine_smart_contract_full_abi_map(transactions) + + # first we assemble an empty methods map, so that decoded_input_data will skip ContractMethod.t() lookup and decoding + empty_methods_map = + transactions + |> Enum.flat_map(fn + %{input: %{bytes: <>}} -> [method_id] + _ -> [] + end) + |> Enum.into(%{}, &{&1, []}) + + # try to decode transaction using full abi data from smart_contract_full_abi_map + decoded_transactions = + transactions + |> Enum.map(fn transaction -> + transaction + |> decoded_input_data(skip_sig_provider?, opts, empty_methods_map, smart_contract_full_abi_map) + |> format_decoded_input() + end) + |> Enum.zip(transactions) + + # assemble a new methods map from methods in non-decoded transactions + methods_map = + decoded_transactions + |> Enum.flat_map(fn + {nil, %{input: %{bytes: <>}}} -> [method_id] + _ -> [] + end) + |> Enum.uniq() + |> ContractMethod.find_contract_methods(opts) + |> Enum.into(empty_methods_map, &{&1.identifier, [&1]}) + + # decode remaining transaction using methods map + decoded_transactions + |> Enum.map(fn + {nil, transaction} -> + transaction + |> Map.put(:to_address, %NotLoaded{}) + |> decoded_input_data(skip_sig_provider?, opts, methods_map, smart_contract_full_abi_map) + |> format_decoded_input() + + {decoded, _} -> + decoded + end) + end - {[format_decoded_input(result) | results], abi_acc, methods_acc} + defp combine_smart_contract_full_abi_map(transactions) do + # parse unique address hashes of smart-contracts from to_address and created_contract_address properties of the transactions list + unique_to_address_hashes = + transactions + |> Enum.flat_map(fn + %Transaction{to_address: %Address{hash: hash}} -> [hash] + %Transaction{created_contract_address: %Address{hash: hash}} -> [hash] + _ -> [] end) + |> Enum.uniq() + + # query from the DB proxy implementation objects for those address hashes + multiple_proxy_implementations = + Implementation.get_proxy_implementations_for_multiple_proxies(unique_to_address_hashes) + + # query from the DB address objects with smart_contract preload for all found above proxy and implementation addresses + addresses_with_smart_contracts = + multiple_proxy_implementations + |> Enum.flat_map(fn proxy_implementations -> proxy_implementations.address_hashes end) + |> Enum.concat(unique_to_address_hashes) + |> Chain.hashes_to_addresses(necessity_by_association: %{smart_contract: :optional}) + |> Enum.into(%{}, &{&1.hash, &1}) + + # combine map %{proxy_address_hash => implementation address hashes} + proxy_implementations_map = + multiple_proxy_implementations + |> Enum.into(%{}, &{&1.proxy_address_hash, &1.address_hashes}) + + # combine map %{proxy_address_hash => combined proxy abi} + unique_to_address_hashes + |> Enum.into(%{}, fn to_address_hash -> + full_abi = + [to_address_hash | Map.get(proxy_implementations_map, to_address_hash, [])] + |> Enum.map(&Map.get(addresses_with_smart_contracts, &1)) + |> Enum.flat_map(fn + %{smart_contract: %{abi: abi}} when is_list(abi) -> abi + _ -> [] + end) + |> Enum.filter(&(!is_nil(&1))) - {Enum.reverse(results), abi_acc, methods_acc} + {to_address_hash, full_abi} + end) end @doc """ Receives as input result of decoded_input_data/5, returns either nil or decoded input in format: {:ok, _identifier, _text, _mapping} """ - @spec format_decoded_input(any()) :: nil | tuple() + @spec format_decoded_input(any()) :: nil | {:ok, String.t(), String.t(), map()} 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 diff --git a/apps/explorer/lib/explorer/chain/transaction/history/historian.ex b/apps/explorer/lib/explorer/chain/transaction/history/historian.ex index bb6e25a..fe09465 100644 --- a/apps/explorer/lib/explorer/chain/transaction/history/historian.ex +++ b/apps/explorer/lib/explorer/chain/transaction/history/historian.ex @@ -17,7 +17,7 @@ defmodule Explorer.Chain.Transaction.History.Historian do @impl Historian def compile_records(num_days, records \\ []) do - Logger.info("tx/per day chart: collect records for txs per day stats") + Logger.info("tx/per day chart: collect records for transactions per day stats") if num_days == 1 do Logger.info("tx/per day chart: records collected #{inspect(records)}") diff --git a/apps/explorer/lib/explorer/chain/transaction/state_change.ex b/apps/explorer/lib/explorer/chain/transaction/state_change.ex index 9bdf922..3acf6ea 100644 --- a/apps/explorer/lib/explorer/chain/transaction/state_change.ex +++ b/apps/explorer/lib/explorer/chain/transaction/state_change.ex @@ -24,55 +24,59 @@ defmodule Explorer.Chain.Transaction.StateChange do @zero_wei %Wei{value: Decimal.new(0)} @spec coin_balances_before(Transaction.t(), [Transaction.t()], coin_balances_map()) :: coin_balances_map() - def coin_balances_before(tx, block_txs, coin_balances_before_block) do - block = tx.block + def coin_balances_before(transaction, block_transactions, coin_balances_before_block) do + block = transaction.block - block_txs + block_transactions |> Enum.reduce_while( coin_balances_before_block, - fn block_tx, acc -> - if block_tx.index < tx.index do - {:cont, update_coin_balances_from_tx(acc, block_tx, block)} + fn block_transaction, acc -> + if block_transaction.index < transaction.index do + {:cont, update_coin_balances_from_transaction(acc, block_transaction, block)} else - # txs ordered by index ascending, so we can halt after facing index greater or equal than index of our tx + # transactions ordered by index ascending, so we can halt after facing index greater or equal than index of our transaction {:halt, acc} end end ) end - @spec update_coin_balances_from_tx(coin_balances_map(), Transaction.t(), Block.t()) :: coin_balances_map() - def update_coin_balances_from_tx(coin_balances, tx, block) do + @spec update_coin_balances_from_transaction(coin_balances_map(), Transaction.t(), Block.t()) :: coin_balances_map() + def update_coin_balances_from_transaction(coin_balances, transaction, block) do coin_balances = coin_balances - |> update_balance(tx.from_address_hash, &Wei.sub(&1, from_loss(tx))) - |> update_balance(tx.to_address_hash, &Wei.sum(&1, to_profit(tx))) - |> update_balance(block.miner_hash, &Wei.sum(&1, miner_profit(tx, block))) + |> update_balance(transaction.from_address_hash, &Wei.sub(&1, from_loss(transaction))) + |> update_balance(transaction.to_address_hash, &Wei.sum(&1, to_profit(transaction))) + |> update_balance(block.miner_hash, &Wei.sum(&1, miner_profit(transaction, block))) - if error?(tx) do + if error?(transaction) do coin_balances else - tx.internal_transactions |> Enum.reduce(coin_balances, &update_coin_balances_from_internal_tx(&1, &2)) + transaction.internal_transactions + |> Enum.reduce(coin_balances, &update_coin_balances_from_internal_transaction(&1, &2)) end end - defp update_coin_balances_from_internal_tx(%InternalTransaction{index: 0}, coin_balances), do: coin_balances + defp update_coin_balances_from_internal_transaction(%InternalTransaction{call_type: :delegatecall}, coin_balances), + do: coin_balances + + defp update_coin_balances_from_internal_transaction(%InternalTransaction{index: 0}, coin_balances), do: coin_balances - defp update_coin_balances_from_internal_tx(internal_tx, coin_balances) do + defp update_coin_balances_from_internal_transaction(internal_transaction, coin_balances) do coin_balances - |> update_balance(internal_tx.from_address_hash, &Wei.sub(&1, from_loss(internal_tx))) - |> update_balance(internal_tx.to_address_hash, &Wei.sum(&1, to_profit(internal_tx))) + |> update_balance(internal_transaction.from_address_hash, &Wei.sub(&1, from_loss(internal_transaction))) + |> update_balance(internal_transaction.to_address_hash, &Wei.sum(&1, to_profit(internal_transaction))) end - def token_balances_before(balances_before, tx, block_txs) do - block_txs + def token_balances_before(balances_before, transaction, block_transactions) do + block_transactions |> 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)} + fn block_transaction, state -> + if block_transaction.index < transaction.index do + {:cont, do_update_token_balances_from_token_transfers(block_transaction.token_transfers, state)} else - # txs ordered by index ascending, so we can halt after facing index greater or equal than index of our tx + # transactions ordered by index ascending, so we can halt after facing index greater or equal than index of our transaction {:halt, state} end end @@ -158,18 +162,18 @@ defmodule Explorer.Chain.Transaction.StateChange do or an internal transaction. """ @spec from_loss(Transaction.t() | InternalTransaction.t()) :: Wei.t() - def from_loss(%Transaction{} = tx) do - {_, fee} = Transaction.fee(tx, :wei) + def from_loss(%Transaction{} = transaction) do + {_, fee} = Transaction.fee(transaction, :wei) - if error?(tx) do + if error?(transaction) do %Wei{value: fee} else - Wei.sum(tx.value, %Wei{value: fee}) + Wei.sum(transaction.value, %Wei{value: fee}) end end - def from_loss(%InternalTransaction{} = tx) do - tx.value + def from_loss(%InternalTransaction{} = transaction) do + transaction.value end @doc """ @@ -177,33 +181,33 @@ defmodule Explorer.Chain.Transaction.StateChange do or an internal transaction. """ @spec to_profit(Transaction.t() | InternalTransaction.t()) :: Wei.t() - def to_profit(%Transaction{} = tx) do - if error?(tx) do + def to_profit(%Transaction{} = transaction) do + if error?(transaction) do %Wei{value: 0} else - tx.value + transaction.value end end - def to_profit(%InternalTransaction{} = tx) do - tx.value + def to_profit(%InternalTransaction{} = transaction) do + transaction.value end - defp miner_profit(tx, block) do + defp miner_profit(transaction, 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 + max_priority_fee_per_gas = transaction.max_priority_fee_per_gas || transaction.gas_price + max_fee_per_gas = transaction.max_fee_per_gas || transaction.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) + Wei.mult(priority_fee_per_gas, transaction.gas_used) end - defp error?(tx) do - case Chain.transaction_to_status(tx) do + defp error?(transaction) do + case Chain.transaction_to_status(transaction) do {:error, _} -> true _ -> false end @@ -233,14 +237,15 @@ defmodule Explorer.Chain.Transaction.StateChange do taking into account state changes from previous transactions in the same block. """ @spec native_coin_entries(Transaction.t(), coin_balances_map()) :: [t()] - def native_coin_entries(transaction, coin_balances_before_tx) do + def native_coin_entries(transaction, coin_balances_before_transaction) do block = transaction.block - coin_balances_after_tx = update_coin_balances_from_tx(coin_balances_before_tx, transaction, block) + coin_balances_after_transaction = + update_coin_balances_from_transaction(coin_balances_before_transaction, transaction, block) - coin_balances_before_tx + coin_balances_before_transaction |> Enum.reduce([], fn {address_hash, {address, coin_balance_before}}, acc -> - {_, coin_balance_after} = coin_balances_after_tx[address_hash] + {_, coin_balance_after} = coin_balances_after_transaction[address_hash] coin_entry = coin_entry(address, coin_balance_before, coin_balance_after, address_hash == block.miner_hash) if coin_entry do diff --git a/apps/explorer/lib/explorer/chain/transaction_action.ex b/apps/explorer/lib/explorer/chain/transaction_action.ex index a8aba0b..795892a 100644 --- a/apps/explorer/lib/explorer/chain/transaction_action.ex +++ b/apps/explorer/lib/explorer/chain/transaction_action.ex @@ -63,8 +63,8 @@ defmodule Explorer.Chain.TransactionAction do timestamps() end - def changeset(%__MODULE__{} = tx_actions, attrs \\ %{}) do - tx_actions + def changeset(%__MODULE__{} = transaction_actions, attrs \\ %{}) do + transaction_actions |> cast(attrs, @required_attrs) |> validate_required(@required_attrs) |> foreign_key_constraint(:hash) diff --git a/apps/explorer/lib/explorer/chain/zksync/batch_transaction.ex b/apps/explorer/lib/explorer/chain/zksync/batch_transaction.ex index e6fb1fc..d9c95f4 100644 --- a/apps/explorer/lib/explorer/chain/zksync/batch_transaction.ex +++ b/apps/explorer/lib/explorer/chain/zksync/batch_transaction.ex @@ -15,11 +15,11 @@ defmodule Explorer.Chain.ZkSync.BatchTransaction do alias Explorer.Chain.{Hash, Transaction} alias Explorer.Chain.ZkSync.TransactionBatch - @required_attrs ~w(batch_number tx_hash)a + @required_attrs ~w(batch_number transaction_hash)a @typedoc """ - * `tx_hash` - The hash of the rollup transaction. - * `l2_transaction` - An instance of `Explorer.Chain.Transaction` referenced by `tx_hash`. + * `transaction_hash` - The hash of the rollup transaction. + * `l2_transaction` - An instance of `Explorer.Chain.Transaction` referenced by `transaction_hash`. * `batch_number` - The number of the ZkSync batch. * `batch` - An instance of `Explorer.Chain.ZkSync.TransactionBatch` referenced by `batch_number`. """ @@ -28,7 +28,7 @@ defmodule Explorer.Chain.ZkSync.BatchTransaction do belongs_to(:batch, TransactionBatch, foreign_key: :batch_number, references: :number, type: :integer) belongs_to(:l2_transaction, Transaction, - foreign_key: :tx_hash, + foreign_key: :transaction_hash, primary_key: true, references: :hash, type: Hash.Full @@ -46,6 +46,6 @@ defmodule Explorer.Chain.ZkSync.BatchTransaction do |> cast(attrs, @required_attrs) |> validate_required(@required_attrs) |> foreign_key_constraint(:batch_number) - |> unique_constraint(:tx_hash) + |> unique_constraint(:transaction_hash) end end diff --git a/apps/explorer/lib/explorer/chain/zksync/reader.ex b/apps/explorer/lib/explorer/chain/zksync/reader.ex index ca61e25..ed632db 100644 --- a/apps/explorer/lib/explorer/chain/zksync/reader.ex +++ b/apps/explorer/lib/explorer/chain/zksync/reader.ex @@ -297,18 +297,18 @@ defmodule Explorer.Chain.ZkSync.Reader do Reads a list of L1 transactions by their hashes from the `zksync_lifecycle_l1_transactions` table. ## Parameters - - `l1_tx_hashes`: A list of hashes to retrieve L1 transactions for. + - `l1_transaction_hashes`: A list of hashes to retrieve L1 transactions for. ## Returns - A list of `Explorer.Chain.ZkSync.LifecycleTransaction` corresponding to the hashes from the input list. The output list may be smaller than the input list. """ @spec lifecycle_transactions(maybe_improper_list(binary(), [])) :: [Explorer.Chain.ZkSync.LifecycleTransaction] - def lifecycle_transactions(l1_tx_hashes) do + def lifecycle_transactions(l1_transaction_hashes) do query = from( lt in LifecycleTransaction, select: {lt.hash, lt.id}, - where: lt.hash in ^l1_tx_hashes + where: lt.hash in ^l1_transaction_hashes ) Repo.all(query, timeout: :infinity) diff --git a/apps/explorer/lib/explorer/chain/zksync/transaction_batch.ex b/apps/explorer/lib/explorer/chain/zksync/transaction_batch.ex index 3f6ac40..d19f22a 100644 --- a/apps/explorer/lib/explorer/chain/zksync/transaction_batch.ex +++ b/apps/explorer/lib/explorer/chain/zksync/transaction_batch.ex @@ -13,13 +13,13 @@ defmodule Explorer.Chain.ZkSync.TransactionBatch do @optional_attrs ~w(commit_id prove_id execute_id)a - @required_attrs ~w(number timestamp l1_tx_count l2_tx_count root_hash l1_gas_price l2_fair_gas_price start_block end_block)a + @required_attrs ~w(number timestamp l1_transaction_count l2_transaction_count root_hash l1_gas_price l2_fair_gas_price start_block end_block)a @type t :: %__MODULE__{ number: non_neg_integer(), timestamp: DateTime.t(), - l1_tx_count: non_neg_integer(), - l2_tx_count: non_neg_integer(), + l1_transaction_count: non_neg_integer(), + l2_transaction_count: non_neg_integer(), root_hash: Hash.t(), l1_gas_price: Wei.t(), l2_fair_gas_price: Wei.t(), @@ -36,8 +36,8 @@ defmodule Explorer.Chain.ZkSync.TransactionBatch do @primary_key {:number, :integer, autogenerate: false} schema "zksync_transaction_batches" do field(:timestamp, :utc_datetime_usec) - field(:l1_tx_count, :integer) - field(:l2_tx_count, :integer) + field(:l1_transaction_count, :integer) + field(:l2_transaction_count, :integer) field(:root_hash, Hash.Full) field(:l1_gas_price, Wei) field(:l2_fair_gas_price, Wei) diff --git a/apps/explorer/lib/explorer/counters/block_burnt_fee_counter.ex b/apps/explorer/lib/explorer/counters/block_burnt_fee_counter.ex index aa925f3..64c02d5 100644 --- a/apps/explorer/lib/explorer/counters/block_burnt_fee_counter.ex +++ b/apps/explorer/lib/explorer/counters/block_burnt_fee_counter.ex @@ -57,7 +57,7 @@ defmodule Explorer.Counters.BlockBurntFeeCounter do defp update_cache(block_hash) do block_hash_string = get_block_hash_string(block_hash) - new_data = Chain.block_to_gas_used_by_1559_txs(block_hash) + new_data = Chain.block_to_gas_used_by_1559_transactions(block_hash) Helper.put_into_ets_cache(@cache_name, "#{block_hash_string}", new_data) end 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 42a4a55..79cd92f 100644 --- a/apps/explorer/lib/explorer/counters/block_priority_fee_counter.ex +++ b/apps/explorer/lib/explorer/counters/block_priority_fee_counter.ex @@ -57,7 +57,7 @@ defmodule Explorer.Counters.BlockPriorityFeeCounter do defp update_cache(block_hash) do block_hash_string = get_block_hash_string(block_hash) - new_data = Chain.block_to_priority_fee_of_1559_txs(block_hash) + new_data = Chain.block_to_priority_fee_of_1559_transactions(block_hash) Helper.put_into_ets_cache(@cache_name, "#{block_hash_string}", new_data) end diff --git a/apps/explorer/lib/explorer/counters/helper.ex b/apps/explorer/lib/explorer/counters/helper.ex index 6a461df..2ec17a7 100644 --- a/apps/explorer/lib/explorer/counters/helper.ex +++ b/apps/explorer/lib/explorer/counters/helper.ex @@ -10,6 +10,16 @@ defmodule Explorer.Counters.Helper do read_concurrency: true ] + @doc """ + Returns the current time in milliseconds since the Unix epoch. + + This function retrieves the current UTC time and converts it to Unix + timestamp in milliseconds. + + ## Returns + - The number of milliseconds since the Unix epoch. + """ + @spec current_time() :: non_neg_integer() def current_time do utc_now = DateTime.utc_now() diff --git a/apps/explorer/lib/explorer/counters/transactions_24h_stats.ex b/apps/explorer/lib/explorer/counters/transactions_24h_stats.ex index 80bef49..234a6fd 100644 --- a/apps/explorer/lib/explorer/counters/transactions_24h_stats.ex +++ b/apps/explorer/lib/explorer/counters/transactions_24h_stats.ex @@ -10,11 +10,11 @@ defmodule Explorer.Counters.Transactions24hStats do import Ecto.Query alias Explorer.{Chain, Repo} - alias Explorer.Chain.Transaction + alias Explorer.Chain.{DenormalizationHelper, Transaction} - @tx_count_name "transaction_count_24h" - @tx_fee_sum_name "transaction_fee_sum_24h" - @tx_fee_average_name "transaction_fee_average_24h" + @transaction_count_name "transaction_count_24h" + @transaction_fee_sum_name "transaction_fee_sum_24h" + @transaction_fee_average_name "transaction_fee_average_24h" @doc """ Starts a process to periodically update the counters. @@ -55,24 +55,24 @@ defmodule Explorer.Counters.Transactions24hStats do end @doc """ - Fetches the value for a `#{@tx_count_name}` counter type from the `last_fetched_counters` table. + Fetches the value for a `#{@transaction_count_name}` counter type from the `last_fetched_counters` table. """ def fetch_count(options) do - Chain.get_last_fetched_counter(@tx_count_name, options) + Chain.get_last_fetched_counter(@transaction_count_name, options) end @doc """ - Fetches the value for a `#{@tx_fee_sum_name}` counter type from the `last_fetched_counters` table. + Fetches the value for a `#{@transaction_fee_sum_name}` counter type from the `last_fetched_counters` table. """ def fetch_fee_sum(options) do - Chain.get_last_fetched_counter(@tx_fee_sum_name, options) + Chain.get_last_fetched_counter(@transaction_fee_sum_name, options) end @doc """ - Fetches the value for a `#{@tx_fee_average_name}` counter type from the `last_fetched_counters` table. + Fetches the value for a `#{@transaction_fee_average_name}` counter type from the `last_fetched_counters` table. """ def fetch_fee_average(options) do - Chain.get_last_fetched_counter(@tx_fee_average_name, options) + Chain.get_last_fetched_counter(@transaction_fee_average_name, options) end @doc """ @@ -94,15 +94,18 @@ defmodule Explorer.Counters.Transactions24hStats do sum_query = dynamic([_, _], sum(^fee_query)) avg_query = dynamic([_, _], avg(^fee_query)) - query = + base_query = from(transaction in Transaction, join: block in assoc(transaction, :block), - where: block.timestamp >= ago(24, "hour"), select: %{count: count(transaction.hash)}, select_merge: ^%{fee_sum: sum_query}, select_merge: ^%{fee_average: avg_query} ) + query = + base_query + |> where_block_timestamp_in_last_24_hours() + %{ count: count, fee_sum: fee_sum, @@ -110,21 +113,29 @@ defmodule Explorer.Counters.Transactions24hStats do } = Repo.one!(query, timeout: :infinity) Chain.upsert_last_fetched_counter(%{ - counter_type: @tx_count_name, + counter_type: @transaction_count_name, value: count }) Chain.upsert_last_fetched_counter(%{ - counter_type: @tx_fee_sum_name, + counter_type: @transaction_fee_sum_name, value: fee_sum }) Chain.upsert_last_fetched_counter(%{ - counter_type: @tx_fee_average_name, + counter_type: @transaction_fee_average_name, value: fee_average }) end + defp where_block_timestamp_in_last_24_hours(query) do + if DenormalizationHelper.transactions_denormalization_finished?() do + where(query, [transaction, _block], transaction.block_timestamp >= ago(24, "hour")) + else + where(query, [_transaction, block], block.timestamp >= ago(24, "hour")) + end + end + @doc """ Returns a boolean that indicates whether consolidation is enabled diff --git a/apps/explorer/lib/explorer/etherscan.ex b/apps/explorer/lib/explorer/etherscan.ex index f7a44bd..92284c0 100644 --- a/apps/explorer/lib/explorer/etherscan.ex +++ b/apps/explorer/lib/explorer/etherscan.ex @@ -4,7 +4,18 @@ defmodule Explorer.Etherscan do """ import Ecto.Query, - only: [from: 2, where: 3, union: 2, subquery: 1, order_by: 3, limit: 2, offset: 2, preload: 3, select_merge: 3] + only: [ + from: 2, + where: 3, + union: 2, + subquery: 1, + order_by: 3, + limit: 2, + offset: 2, + preload: 2, + preload: 3, + select_merge: 3 + ] import Explorer.Chain.SmartContract, only: [burn_address_hash_string: 0] @@ -331,7 +342,17 @@ defmodule Explorer.Etherscan do |> where_end_block_match_tt(options) |> limit(^options.page_size) |> offset(^offset(options)) - |> preload([block: block], [{:block, block}, :transaction]) + |> maybe_preload_block() + end + + defp maybe_preload_block(query) do + if DenormalizationHelper.tt_denormalization_finished?() do + query + |> preload(:transaction) + else + query + |> preload([block: block], [{:block, block}, :transaction]) + end end @doc """ @@ -787,9 +808,9 @@ defmodule Explorer.Etherscan do {:ok, date} -> query = from( - tx_stats in TransactionStats, - where: tx_stats.date == ^date, - select: tx_stats.total_fee + transaction_stats in TransactionStats, + where: transaction_stats.date == ^date, + select: transaction_stats.total_fee ) total_fees = Repo.replica().one(query) diff --git a/apps/explorer/lib/explorer/etherscan/contracts.ex b/apps/explorer/lib/explorer/etherscan/contracts.ex index 4ecd397..56d4f2e 100644 --- a/apps/explorer/lib/explorer/etherscan/contracts.ex +++ b/apps/explorer/lib/explorer/etherscan/contracts.ex @@ -153,7 +153,7 @@ defmodule Explorer.Etherscan.Contracts do query = from( address in Address, - where: address.contract_code != <<>>, + where: address.contract_code != ^%Explorer.Chain.Data{bytes: <<>>}, where: not is_nil(address.contract_code), where: address.decompiled == true, limit: ^limit, @@ -171,7 +171,7 @@ defmodule Explorer.Etherscan.Contracts do query = from( address in Address, - where: address.contract_code != <<>>, + where: address.contract_code != ^%Explorer.Chain.Data{bytes: <<>>}, where: not is_nil(address.contract_code), where: fragment("? IS NOT TRUE", address.verified), limit: ^limit, @@ -191,7 +191,7 @@ defmodule Explorer.Etherscan.Contracts do address in Address, where: fragment("? IS NOT TRUE", address.verified), where: fragment("? IS NOT TRUE", address.decompiled), - where: address.contract_code != <<>>, + where: address.contract_code != ^%Explorer.Chain.Data{bytes: <<>>}, where: not is_nil(address.contract_code), limit: ^limit, offset: ^offset @@ -207,7 +207,7 @@ defmodule Explorer.Etherscan.Contracts do def list_empty_contracts(limit, offset) do query = from(address in Address, - where: address.contract_code == <<>>, + where: address.contract_code == ^%Explorer.Chain.Data{bytes: <<>>}, preload: [:smart_contract, :decompiled_smart_contracts], order_by: [asc: address.inserted_at], limit: ^limit, diff --git a/apps/explorer/lib/explorer/etherscan/logs.ex b/apps/explorer/lib/explorer/etherscan/logs.ex index b22dbbd..3cce390 100644 --- a/apps/explorer/lib/explorer/etherscan/logs.ex +++ b/apps/explorer/lib/explorer/etherscan/logs.ex @@ -103,6 +103,7 @@ defmodule Explorer.Etherscan.Logs do ) all_transaction_logs_query + |> Chain.wrapped_union_subquery() |> order_by([log], asc: log.block_number, asc: log.index) |> Repo.replica().all() else diff --git a/apps/explorer/lib/explorer/graphql.ex b/apps/explorer/lib/explorer/graphql.ex index feb318f..1492e68 100644 --- a/apps/explorer/lib/explorer/graphql.ex +++ b/apps/explorer/lib/explorer/graphql.ex @@ -11,14 +11,17 @@ defmodule Explorer.GraphQL do where: 3 ] + alias Explorer.{Chain, Repo} + alias Explorer.Chain.{ Hash, InternalTransaction, + Token, TokenTransfer, Transaction } - alias Explorer.Repo + @api_true [api?: true] @doc """ Returns a query to fetch transactions with a matching `to_address_hash`, @@ -83,6 +86,31 @@ defmodule Explorer.GraphQL do end end + @doc """ + Returns a token for a given contract address hash. + """ + @spec get_token(map()) :: {:ok, Token.t()} | {:error, String.t()} + def get_token(%{contract_address_hash: _} = clauses) do + if token = Repo.replica().get_by(Token, clauses) do + {:ok, token} + else + {:error, "Token not found."} + end + end + + @doc """ + Returns a transaction for a given hash. + """ + @spec get_transaction_by_hash(Hash.t()) :: {:ok, Transaction.t()} | {:error, String.t()} + def get_transaction_by_hash(hash) do + hash + |> Chain.hash_to_transaction(@api_true) + |> case do + {:ok, _} = result -> result + {:error, :not_found} -> {:error, "Transaction not found."} + end + end + @doc """ Returns a query to fetch token transfers for a token contract address hash. diff --git a/apps/explorer/lib/explorer/graphql/celo.ex b/apps/explorer/lib/explorer/graphql/celo.ex new file mode 100644 index 0000000..6f82167 --- /dev/null +++ b/apps/explorer/lib/explorer/graphql/celo.ex @@ -0,0 +1,160 @@ +defmodule Explorer.GraphQL.Celo do + @moduledoc """ + Defines Ecto queries to fetch Celo blockchain data for the legacy GraphQL + schema. + + Includes functions to construct queries for token transfers and transactions. + """ + + import Ecto.Query, + only: [from: 2, order_by: 3, where: 3, subquery: 1] + + alias Explorer.Chain.{ + Block, + Hash, + Token, + TokenTransfer, + Transaction + } + + @doc """ + Constructs a paginated query for token transfers involving a specific address. + """ + @spec token_transaction_transfers_query_for_address(Hash.Address.t(), integer(), integer()) :: Ecto.Query.t() + def token_transaction_transfers_query_for_address(address_hash, offset, limit) do + page = floor(offset / limit) + 1 + growing_limit = limit * (page + 1) + + tokens = + from( + tt in TokenTransfer, + where: not is_nil(tt.transaction_hash), + where: tt.to_address_hash == ^address_hash, + or_where: tt.from_address_hash == ^address_hash, + select: %{ + transaction_hash: tt.transaction_hash, + block_number: tt.block_number, + to_address_hash: tt.to_address_hash, + from_address_hash: tt.from_address_hash + }, + distinct: [desc: tt.block_number, desc: tt.transaction_hash], + order_by: [ + desc: tt.block_number, + desc: tt.transaction_hash, + desc: tt.from_address_hash, + desc: tt.to_address_hash + ], + limit: ^growing_limit + ) + + query = + from( + tt in subquery(tokens), + as: :token_transfer, + inner_join: transaction in Transaction, + as: :transaction, + on: transaction.hash == tt.transaction_hash, + inner_join: b in Block, + on: transaction.block_hash == b.hash, + left_join: token in Token, + on: transaction.gas_token_contract_address_hash == token.contract_address_hash, + select: %{ + transaction_hash: tt.transaction_hash, + to_address_hash: tt.to_address_hash, + from_address_hash: tt.from_address_hash, + gas_used: transaction.gas_used, + gas_price: transaction.gas_price, + fee_currency: transaction.gas_token_contract_address_hash, + fee_token: fragment("coalesce(?, 'CELO')", token.symbol), + # gateway_fee: transaction.gateway_fee, + # gateway_fee_recipient: transaction.gas_fee_recipient_hash, + timestamp: b.timestamp, + input: transaction.input, + nonce: transaction.nonce, + block_number: tt.block_number + } + ) + + query + |> order_by( + [transaction: t], + desc: t.block_number, + desc: t.hash, + asc: t.nonce, + desc: t.from_address_hash, + desc: t.to_address_hash + ) + end + + @doc """ + Constructs a query to fetch token transfers within a given transaction. + + ## Parameters + - transaction_hash: the hash of the transaction + + ## Returns + - Ecto query + """ + @spec token_transaction_transfers_query_by_transaction_hash(Hash.Full.t()) :: Ecto.Query.t() + def token_transaction_transfers_query_by_transaction_hash(transaction_hash) do + query = token_transaction_transfers_query() + + from( + t in subquery(query), + where: t.transaction_hash == ^transaction_hash, + order_by: [t.log_index] + ) + end + + @doc """ + Constructs a query for token transfers filtered by a specific address. + """ + @spec token_transaction_transfers_query_by_address(Hash.Address.t()) :: Ecto.Query.t() + def token_transaction_transfers_query_by_address(address_hash) do + token_transaction_transfers_query() + |> where([t], t.from_address_hash == ^address_hash or t.to_address_hash == ^address_hash) + |> order_by([transaction: t], desc: t.block_number, asc: t.nonce) + end + + @doc """ + Constructs a query to fetch detailed token transfer information. + """ + @spec token_transaction_transfers_query() :: Ecto.Query.t() + def token_transaction_transfers_query do + from( + tt in TokenTransfer, + inner_join: transaction in Transaction, + as: :transaction, + on: transaction.hash == tt.transaction_hash, + inner_join: b in Block, + on: tt.block_number == b.number, + # left_join: wf in CeloWalletAccounts, + # on: tt.from_address_hash == wf.wallet_address_hash, + # left_join: wt in CeloWalletAccounts, + # on: tt.to_address_hash == wt.wallet_address_hash, + left_join: token in Token, + on: tt.token_contract_address_hash == token.contract_address_hash, + select: %{ + gas_used: transaction.gas_used, + gas_price: transaction.gas_price, + timestamp: b.timestamp, + input: transaction.input, + transaction_hash: tt.transaction_hash, + from_address_hash: tt.from_address_hash, + to_address_hash: tt.to_address_hash, + # from_account_hash: wf.account_address_hash, + # to_account_hash: wt.account_address_hash, + log_index: tt.log_index, + value: tt.amount, + # comment: tt.comment, + token: token.symbol, + token_address: token.contract_address_hash, + nonce: transaction.nonce, + block_number: tt.block_number, + token_type: token.type, + token_id: fragment("(COALESCE(?, ARRAY[]::Decimal[]))[1]", tt.token_ids) + }, + order_by: [desc: tt.block_number, desc: tt.amount, desc: tt.log_index] + ) + end +end diff --git a/apps/explorer/lib/explorer/helper.ex b/apps/explorer/lib/explorer/helper.ex index e4d2bc6..226379d 100644 --- a/apps/explorer/lib/explorer/helper.ex +++ b/apps/explorer/lib/explorer/helper.ex @@ -7,7 +7,7 @@ defmodule Explorer.Helper do alias Explorer.Chain alias Explorer.Chain.Data - import Ecto.Query, only: [where: 3] + import Ecto.Query, only: [join: 5, where: 3] import Explorer.Chain.SmartContract, only: [burn_address_hash_string: 0] @max_safe_integer round(:math.pow(2, 63)) - 1 @@ -212,4 +212,88 @@ defmodule Explorer.Helper do true -> :eq end end + + @doc """ + Conditionally hides scam addresses in the given query. + + ## Parameters + + - query: The Ecto query to be modified. + - address_hash_key: The key used to identify address hash field in the query to join with base query table on. + + ## Returns + + The modified query with scam addresses hidden, if applicable. + """ + @spec maybe_hide_scam_addresses(nil | Ecto.Query.t(), atom()) :: Ecto.Query.t() + def maybe_hide_scam_addresses(nil, _address_hash_key), do: nil + + def maybe_hide_scam_addresses(query, address_hash_key) do + if Application.get_env(:block_scout_web, :hide_scam_addresses) do + query + |> join( + :inner, + [q], + q2 in fragment(""" + ( + SELECT hash + FROM addresses a + WHERE NOT EXISTS + (SELECT 1 FROM scam_address_badge_mappings sabm WHERE sabm.address_hash=a.hash) + ) + """), + on: field(q, ^address_hash_key) == q2.hash + ) + else + query + end + end + + @doc """ + Checks if a specified time interval has passed since a given datetime. + + This function compares the given datetime plus the interval against the current + time. It returns `true` if the interval has passed, or the number of seconds + remaining if it hasn't. + + ## Parameters + - `sent_at`: The reference datetime, or `nil`. + - `interval`: The time interval in milliseconds. + + ## Returns + - `true` if the interval has passed or if `sent_at` is `nil`. + - An integer representing the number of seconds remaining in the interval if it + hasn't passed yet. + """ + @spec check_time_interval(DateTime.t() | nil, integer()) :: true | integer() + def check_time_interval(nil, _interval), do: true + + def check_time_interval(sent_at, interval) do + now = DateTime.utc_now() + + if sent_at + |> DateTime.add(interval, :millisecond) + |> DateTime.compare(now) != :gt do + true + else + sent_at + |> DateTime.add(interval, :millisecond) + |> DateTime.diff(now, :second) + end + end + + @doc """ + Retrieves the host URL for the BlockScoutWeb application. + + This function fetches the host URL from the application's configuration, + specifically from the `:block_scout_web` application's `BlockScoutWeb.Endpoint` + configuration. + + ## Returns + A string containing the host URL for the BlockScoutWeb application. + """ + @spec get_app_host :: String.t() + def get_app_host do + Application.get_env(:block_scout_web, BlockScoutWeb.Endpoint)[:url][:host] + end end diff --git a/apps/explorer/lib/explorer/microservice_interfaces/metadata.ex b/apps/explorer/lib/explorer/microservice_interfaces/metadata.ex index ad9e692..60c1476 100644 --- a/apps/explorer/lib/explorer/microservice_interfaces/metadata.ex +++ b/apps/explorer/lib/explorer/microservice_interfaces/metadata.ex @@ -22,8 +22,8 @@ defmodule Explorer.MicroserviceInterfaces.Metadata do with :ok <- Microservice.check_enabled(__MODULE__) do body = %{ addresses: Enum.join(addresses, ","), - tagsLimit: @tags_per_address_limit, - chainId: Application.get_env(:block_scout_web, :chain_id) + tags_limit: @tags_per_address_limit, + chain_id: Application.get_env(:block_scout_web, :chain_id) } http_get_request(addresses_metadata_url(), body) @@ -38,8 +38,8 @@ defmodule Explorer.MicroserviceInterfaces.Metadata do with :ok <- Microservice.check_enabled(__MODULE__) do params = params - |> Map.put("pageSize", @page_size) - |> Map.put("chainId", Application.get_env(:block_scout_web, :chain_id)) + |> Map.put("page_size", @page_size) + |> Map.put("chain_id", Application.get_env(:block_scout_web, :chain_id)) http_get_request_for_proxy_method(addresses_url(), params, &prepare_addresses_response/1) end @@ -136,11 +136,11 @@ defmodule Explorer.MicroserviceInterfaces.Metadata do Map.put(tag, "meta", Jason.decode!(meta)) end - defp prepare_addresses_response({:ok, %{"addresses" => addresses} = response}) do + defp prepare_addresses_response({:ok, %{"items" => addresses} = response}) do {:ok, Map.put( response, - "addresses", + "items", addresses |> Chain.hashes_to_addresses( necessity_by_association: %{names: :optional, smart_contract: :optional, proxy_implementations: :optional} diff --git a/apps/explorer/lib/explorer/migrator/filecoin_pending_address_operations.ex b/apps/explorer/lib/explorer/migrator/filecoin_pending_address_operations.ex new file mode 100644 index 0000000..47254d8 --- /dev/null +++ b/apps/explorer/lib/explorer/migrator/filecoin_pending_address_operations.ex @@ -0,0 +1,71 @@ +defmodule Explorer.Migrator.FilecoinPendingAddressOperations do + @moduledoc """ + Creates a pending address operation for each address missing Filecoin address + information, specifically when `filecoin_id`, `filecoin_robust`, and + `filecoin_actor_type` are `nil`. + """ + + use Explorer.Migrator.FillingMigration + + import Ecto.Query + + alias Explorer.Chain.{Address, Filecoin.PendingAddressOperation, Import} + alias Explorer.Migrator.FillingMigration + alias Explorer.Repo + + @migration_name "filecoin_pending_address_operations" + + @impl FillingMigration + def migration_name, do: @migration_name + + @impl FillingMigration + def last_unprocessed_identifiers(state) do + limit = batch_size() * concurrency() + + ids = + unprocessed_data_query() + |> select([address], address.hash) + |> limit(^limit) + |> Repo.all(timeout: :infinity) + + {ids, state} + end + + @impl FillingMigration + def unprocessed_data_query do + from( + address in Address, + left_join: op in PendingAddressOperation, + on: address.hash == op.address_hash, + where: + is_nil(address.filecoin_id) and + is_nil(address.filecoin_robust) and + is_nil(address.filecoin_actor_type) and + is_nil(op.address_hash), + order_by: [asc: address.hash] + ) + end + + @impl FillingMigration + def update_batch(ordered_address_hashes) do + ordered_pending_operations = + Enum.map( + ordered_address_hashes, + &%{address_hash: &1} + ) + + Import.insert_changes_list( + Repo, + ordered_pending_operations, + conflict_target: :address_hash, + on_conflict: :nothing, + for: PendingAddressOperation, + returning: true, + timeout: :infinity, + timestamps: Import.timestamps() + ) + end + + @impl FillingMigration + def update_cache, do: :ok +end diff --git a/apps/explorer/lib/explorer/migrator/filling_migration.ex b/apps/explorer/lib/explorer/migrator/filling_migration.ex index b150dce..5d46272 100644 --- a/apps/explorer/lib/explorer/migrator/filling_migration.ex +++ b/apps/explorer/lib/explorer/migrator/filling_migration.ex @@ -8,6 +8,8 @@ defmodule Explorer.Migrator.FillingMigration do @callback last_unprocessed_identifiers(map()) :: {[any()], map()} @callback update_batch([any()]) :: any() @callback update_cache :: any() + @callback on_finish :: any() + @callback before_start :: any() defmacro __using__(_opts) do quote do @@ -37,15 +39,16 @@ defmodule Explorer.Migrator.FillingMigration do @impl true def handle_continue(:ok, state) do - case MigrationStatus.get_status(migration_name()) do - "completed" -> + case MigrationStatus.fetch(migration_name()) do + %{status: "completed"} -> update_cache() {:stop, :normal, state} - _ -> + migration_status -> MigrationStatus.set_status(migration_name(), "started") + before_start() schedule_batch_migration() - {:noreply, %{}} + {:noreply, (migration_status && migration_status.meta) || %{}} end end @@ -53,6 +56,7 @@ defmodule Explorer.Migrator.FillingMigration do def handle_info(:migrate_batch, state) do case last_unprocessed_identifiers(state) do {[], new_state} -> + on_finish() update_cache() MigrationStatus.set_status(migration_name(), "completed") {:stop, :normal, new_state} @@ -63,6 +67,8 @@ defmodule Explorer.Migrator.FillingMigration do |> Enum.map(&run_task/1) |> Task.await_many(:infinity) + MigrationStatus.update_meta(migration_name(), new_state) + schedule_batch_migration() {:noreply, new_state} @@ -84,6 +90,16 @@ defmodule Explorer.Migrator.FillingMigration do Application.get_env(:explorer, __MODULE__)[:concurrency] || default end + + def on_finish do + :ignore + end + + def before_start do + :ignore + end + + defoverridable on_finish: 0, before_start: 0 end end end diff --git a/apps/explorer/lib/explorer/migrator/migration_status.ex b/apps/explorer/lib/explorer/migrator/migration_status.ex index e59011e..ac1fe8f 100644 --- a/apps/explorer/lib/explorer/migrator/migration_status.ex +++ b/apps/explorer/lib/explorer/migrator/migration_status.ex @@ -8,25 +8,72 @@ defmodule Explorer.Migrator.MigrationStatus do @primary_key false typed_schema "migrations_status" do - field(:migration_name, :string) + field(:migration_name, :string, primary_key: true) # ["started", "completed"] field(:status, :string) + field(:meta, :map) timestamps() end @doc false def changeset(migration_status \\ %__MODULE__{}, params) do - cast(migration_status, params, [:migration_name, :status]) + cast(migration_status, params, [:migration_name, :status, :meta]) end + @doc """ + Get the `MigrationStatus` struct by migration name. + """ + @spec fetch(String.t()) :: __MODULE__.t() | nil + def fetch(migration_name) do + migration_name + |> get_by_migration_name_query() + |> Repo.one() + end + + @doc """ + Get the status of migration by its name. + """ + @spec get_status(String.t()) :: String.t() | nil def get_status(migration_name) do - Repo.one(from(ms in __MODULE__, where: ms.migration_name == ^migration_name, select: ms.status)) + migration_name + |> get_by_migration_name_query() + |> select([ms], ms.status) + |> Repo.one() end + @doc """ + Set the status of migration by its name. + """ + @spec set_status(String.t(), String.t()) :: {:ok, __MODULE__.t()} | {:error, Ecto.Changeset.t()} 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) + |> Repo.insert(on_conflict: {:replace_all_except, [:inserted_at, :meta]}, conflict_target: :migration_name) + end + + @doc """ + Update migration meta by its name. + """ + @spec update_meta(String.t(), map()) :: :ok | {:ok, __MODULE__.t()} | {:error, Ecto.Changeset.t()} + def update_meta(migration_name, new_meta) do + migration_name + |> get_by_migration_name_query() + |> Repo.one() + |> case do + nil -> + :ok + + migration_status -> + updated_meta = Map.merge(migration_status.meta || %{}, new_meta) + + migration_status + |> changeset(%{meta: updated_meta}) + |> Repo.update() + end + end + + defp get_by_migration_name_query(query \\ __MODULE__, migration_name) do + from(ms in query, where: ms.migration_name == ^migration_name) end end diff --git a/apps/explorer/lib/explorer/migrator/refetch_contract_codes.ex b/apps/explorer/lib/explorer/migrator/refetch_contract_codes.ex new file mode 100644 index 0000000..a205744 --- /dev/null +++ b/apps/explorer/lib/explorer/migrator/refetch_contract_codes.ex @@ -0,0 +1,80 @@ +defmodule Explorer.Migrator.RefetchContractCodes do + @moduledoc """ + Refetch contract_code for. Migration created for running on zksync chain type. + It has an issue with created contract code derived from internal transactions. Such codes are not correct. + So, this migration fetches for all current smart contracts actual bytecode from the JSON RPC node. + """ + + use Explorer.Migrator.FillingMigration + + import Ecto.Query + + alias Explorer.Chain.{Address, Data, Import} + alias Explorer.Chain.Hash.Address, as: AddressHash + alias Explorer.Chain.Import.Runner.Addresses + alias Explorer.Migrator.FillingMigration + alias Explorer.Repo + + require Logger + + @migration_name "refetch_contract_codes" + + @impl FillingMigration + def migration_name, do: @migration_name + + @impl FillingMigration + def last_unprocessed_identifiers(state) do + limit = batch_size() * concurrency() + + ids = + unprocessed_data_query() + |> select([address], address.hash) + |> limit(^limit) + |> Repo.all(timeout: :infinity) + + {ids, state} + end + + @impl FillingMigration + def unprocessed_data_query do + Address + |> where([address], not is_nil(address.contract_code) and not address.contract_code_refetched) + end + + @impl FillingMigration + def update_batch(address_hashes) do + json_rpc_named_arguments = Application.get_env(:explorer, :json_rpc_named_arguments) + + address_hashes + |> Enum.map(&address_to_fetch_code_params/1) + |> EthereumJSONRPC.fetch_codes(json_rpc_named_arguments) + |> case do + {:ok, create_address_codes} -> + addresses_params = create_address_codes.params_list |> Enum.map(¶m_to_address/1) |> Enum.sort_by(& &1.hash) + + Addresses.insert(Repo, addresses_params, %{ + timeout: :infinity, + on_conflict: {:replace, [:contract_code, :contract_code_refetched, :updated_at]}, + timestamps: Import.timestamps() + }) + + {:error, reason} -> + Logger.error(fn -> ["failed to fetch contract codes: ", inspect(reason)] end, + error_count: Enum.count(address_hashes) + ) + end + end + + @impl FillingMigration + def update_cache, do: :ok + + defp address_to_fetch_code_params(address_hash) do + %{block_quantity: "latest", address: to_string(address_hash)} + end + + defp param_to_address(%{code: bytecode, address: address_hash}) do + {:ok, address_hash} = AddressHash.cast(address_hash) + {:ok, bytecode} = Data.cast(bytecode) + %{hash: address_hash, contract_code: bytecode, contract_code_refetched: true} + end +end diff --git a/apps/explorer/lib/explorer/migrator/reindex_internal_transactions_with_incompatible_status.ex b/apps/explorer/lib/explorer/migrator/reindex_internal_transactions_with_incompatible_status.ex new file mode 100644 index 0000000..94a6a26 --- /dev/null +++ b/apps/explorer/lib/explorer/migrator/reindex_internal_transactions_with_incompatible_status.ex @@ -0,0 +1,83 @@ +defmodule Explorer.Migrator.ReindexInternalTransactionsWithIncompatibleStatus do + @moduledoc """ + Searches for all failed transactions for which all internal transactions are successful + and adds block numbers of these transactions to pending_block_operations. + """ + + use Explorer.Migrator.FillingMigration + + import Ecto.Query + + alias Explorer.Chain.{Block, InternalTransaction, PendingBlockOperation, Transaction} + alias Explorer.Migrator.FillingMigration + alias Explorer.Repo + + @migration_name "reindex_internal_transactions_with_incompatible_status" + + @impl FillingMigration + def migration_name, do: @migration_name + + @impl FillingMigration + def last_unprocessed_identifiers(state) do + limit = batch_size() * concurrency() + + ids = + unprocessed_data_query() + |> select([t], t.block_number) + |> distinct(true) + |> limit(^limit) + |> Repo.all(timeout: :infinity) + + {ids, state} + end + + @impl FillingMigration + def unprocessed_data_query do + pbo_query = + from( + pbo in PendingBlockOperation, + where: pbo.block_number == parent_as(:transaction).block_number + ) + + it_query = + from( + it in InternalTransaction, + where: parent_as(:transaction).hash == it.transaction_hash and it.index > 0, + select: 1 + ) + + it_error_query = + from( + it in InternalTransaction, + where: parent_as(:transaction).hash == it.transaction_hash and not is_nil(it.error) and it.index > 0, + select: 1 + ) + + from( + t in Transaction, + as: :transaction, + where: t.status == ^:error, + where: not exists(pbo_query), + where: exists(it_query), + where: not exists(it_error_query) + ) + end + + @impl FillingMigration + def update_batch(block_numbers) do + now = DateTime.utc_now() + + params = + Block + |> where([b], b.number in ^block_numbers) + |> select([b], %{block_hash: b.hash, block_number: b.number}) + |> Repo.all() + |> Enum.uniq_by(& &1.block_number) + |> Enum.map(&Map.merge(&1, %{inserted_at: now, updated_at: now})) + + Repo.insert_all(PendingBlockOperation, params, on_conflict: :nothing) + end + + @impl FillingMigration + def update_cache, do: :ok +end diff --git a/apps/explorer/lib/explorer/migrator/restore_omitted_weth_transfers.ex b/apps/explorer/lib/explorer/migrator/restore_omitted_weth_transfers.ex index edc89f2..a635b31 100644 --- a/apps/explorer/lib/explorer/migrator/restore_omitted_weth_transfers.ex +++ b/apps/explorer/lib/explorer/migrator/restore_omitted_weth_transfers.ex @@ -207,7 +207,7 @@ defmodule Explorer.Migrator.RestoreOmittedWETHTransfers do else _ -> Logger.error( - "Failed to decode log: (tx_hash, block_hash, index) = #{to_string(log.transaction_hash)}, #{to_string(log.block_hash)}, #{to_string(log.index)}" + "Failed to decode log: (transaction_hash, block_hash, index) = #{to_string(log.transaction_hash)}, #{to_string(log.block_hash)}, #{to_string(log.index)}" ) nil diff --git a/apps/explorer/lib/explorer/migrator/sanitize_duplicated_log_index_logs.ex b/apps/explorer/lib/explorer/migrator/sanitize_duplicated_log_index_logs.ex new file mode 100644 index 0000000..7ecfadb --- /dev/null +++ b/apps/explorer/lib/explorer/migrator/sanitize_duplicated_log_index_logs.ex @@ -0,0 +1,214 @@ +defmodule Explorer.Migrator.SanitizeDuplicatedLogIndexLogs do + @moduledoc """ + This module is responsible for sanitizing duplicate log index entries in the database. + The migration process includes identifying duplicate log indexes and updating the related token transfers and token instances accordingly. + """ + + use Explorer.Migrator.FillingMigration + + import Ecto.Query + + alias Explorer.Chain.Cache.BackgroundMigrations + alias Explorer.Chain.{Log, TokenTransfer} + alias Explorer.Chain.Token.Instance + alias Explorer.Migrator.FillingMigration + alias Explorer.Repo + + require Logger + + @migration_name "sanitize_duplicated_log_index_logs" + + @impl FillingMigration + def migration_name, do: @migration_name + + @impl FillingMigration + def last_unprocessed_identifiers(state) do + block_number = state[:block_number_to_process] || 0 + + limit = batch_size() * concurrency() + + ids = + block_number + |> unprocessed_data_query(block_number + limit) + |> Repo.all(timeout: :infinity) + |> Enum.group_by(& &1.block_hash) + |> Map.to_list() + + {ids, Map.put(state, :block_number_to_process, block_number + limit)} + end + + @doc """ + Stub implementation to satisfy FillingMigration behaviour + """ + @impl FillingMigration + @spec unprocessed_data_query() :: nil + def unprocessed_data_query do + nil + end + + def unprocessed_data_query(block_number_start, block_number_end) do + Log + |> where([l], l.block_number >= ^block_number_start and l.block_number < ^block_number_end) + end + + @impl FillingMigration + @doc """ + Updates a batch of logs grouped by block. + + ## Parameters + + - logs_by_block: A map where the keys are block identifiers and the values are lists of logs associated with those blocks. + + ## Returns + + :ok + """ + def update_batch(logs_by_block) do + logs_to_update = + logs_by_block + |> Enum.map(&process_block/1) + |> Enum.reject(&(&1 == :ignore)) + |> List.flatten() + + {ids, logs, ids_to_new_index} = + logs_to_update + |> Enum.reduce({[], [], %{}}, fn {log, new_index}, {ids, logs, ids_to_new_index} -> + id = {log.transaction_hash, log.block_hash, log.index} + + {[id | ids], + [ + %Log{log | index: new_index} |> Map.from_struct() |> Map.drop([:block, :address, :transaction, :__meta__]) + | logs + ], Map.put(ids_to_new_index, id, new_index)} + end) + + prepared_ids = + Enum.map(ids, fn {transaction_hash, block_hash, log_index} -> + {transaction_hash.bytes, block_hash.bytes, log_index} + end) + + Repo.transaction(fn -> + Log + |> where( + [log], + fragment( + "(?, ?, ?) = ANY(?::log_id[])", + log.transaction_hash, + log.block_hash, + log.index, + ^prepared_ids + ) + ) + |> Repo.delete_all(timeout: :infinity) + + {_, token_transfers} = + TokenTransfer + |> where( + [token_transfer], + fragment( + "(?, ?, ?) = ANY(?::log_id[])", + token_transfer.transaction_hash, + token_transfer.block_hash, + token_transfer.log_index, + ^prepared_ids + ) + ) + |> select([token_transfer], token_transfer) + |> Repo.delete_all(timeout: :infinity) + + Repo.insert_all(Log, logs, timeout: :infinity) + + token_transfers + |> Enum.map(fn token_transfer -> + id = token_transfer_to_index(token_transfer) + + %TokenTransfer{token_transfer | log_index: ids_to_new_index[id]} + |> Map.from_struct() + |> Map.drop([ + :token_id, + :index_in_batch, + :reverse_index_in_batch, + :token_decimals, + :from_address, + :to_address, + :token_contract_address, + :block, + :instances, + :token, + :transaction, + :__meta__ + ]) + end) + |> (&Repo.insert_all(TokenTransfer, &1, timeout: :infinity)).() + + nft_instances_params = + token_transfers + |> Enum.filter(&(&1.token_type == "ERC-721")) + |> Enum.map(fn token_transfer -> {token_transfer.block_number, token_transfer.log_index} end) + + nft_updates_map = + token_transfers + |> Enum.filter(&(&1.token_type == "ERC-721" && &1.block_consensus)) + |> Enum.reduce(%{}, fn token_transfer, acc -> + id = token_transfer_to_index(token_transfer) + Map.put(acc, {token_transfer.block_number, token_transfer.log_index}, ids_to_new_index[id]) + end) + + Instance + |> where( + [nft], + fragment( + "(?, ?) = ANY(?::nft_id[])", + nft.owner_updated_at_block, + nft.owner_updated_at_log_index, + ^nft_instances_params + ) + ) + |> Repo.all(timeout: :infinity) + |> Enum.map(fn nft -> + %Instance{ + nft + | owner_updated_at_log_index: nft_updates_map[{nft.owner_updated_at_block, nft.owner_updated_at_log_index}] + } + |> Map.from_struct() + |> Map.drop([ + :current_token_balance, + :is_unique, + :owner, + :token, + :__meta__ + ]) + end) + |> (&Repo.insert_all(Instance, &1, + conflict_target: [:token_contract_address_hash, :token_id], + on_conflict: {:replace, [:owner_updated_at_log_index]}, + timeout: :infinity + )).() + end) + + :ok + end + + defp process_block({block_hash, logs}) do + if logs |> Enum.frequencies_by(& &1.index) |> Map.values() |> Enum.max() == 1 do + :ignore + else + Logger.error("Found logs with same index within one block: #{block_hash} in DB") + + logs = Repo.preload(logs, :transaction) + + logs + |> Enum.sort_by(&{&1.transaction.index, &1.index, &1.transaction_hash}) + |> Enum.with_index(&{&1, &2}) + end + end + + @impl FillingMigration + def update_cache do + BackgroundMigrations.set_sanitize_duplicated_log_index_logs_finished(true) + end + + defp token_transfer_to_index(token_transfer) do + {token_transfer.transaction_hash, token_transfer.block_hash, token_transfer.log_index} + end +end diff --git a/apps/explorer/lib/explorer/migrator/sanitize_missing_token_balances.ex b/apps/explorer/lib/explorer/migrator/sanitize_missing_token_balances.ex new file mode 100644 index 0000000..5ec34fa --- /dev/null +++ b/apps/explorer/lib/explorer/migrator/sanitize_missing_token_balances.ex @@ -0,0 +1,73 @@ +defmodule Explorer.Migrator.SanitizeMissingTokenBalances do + @moduledoc """ + Deletes empty current token balances if the related highest block_number historical token balance is filled. + Set value and value_fetched_at to nil for those token balances so the token balance fetcher could re-fetch them. + """ + + use Explorer.Migrator.FillingMigration + + import Ecto.Query + + alias Explorer.Chain.Address.{CurrentTokenBalance, TokenBalance} + alias Explorer.Migrator.FillingMigration + alias Explorer.Repo + + @migration_name "sanitize_missing_token_balances" + + @impl FillingMigration + def migration_name, do: @migration_name + + @impl FillingMigration + def last_unprocessed_identifiers(state) do + limit = batch_size() * concurrency() + + ids = + unprocessed_data_query() + |> select([ctb, tb], {ctb.id, tb.id}) + |> limit(^limit) + |> Repo.all(timeout: :infinity) + + {ids, state} + end + + @impl FillingMigration + def unprocessed_data_query do + from( + ctb in CurrentTokenBalance, + join: tb in TokenBalance, + on: + ctb.address_hash == tb.address_hash and + ctb.token_contract_address_hash == tb.token_contract_address_hash and + ((is_nil(ctb.token_id) and is_nil(tb.token_id)) or ctb.token_id == tb.token_id), + where: is_nil(ctb.value) or is_nil(ctb.value_fetched_at), + where: not is_nil(tb.value) and not is_nil(tb.value_fetched_at), + distinct: ctb.id, + order_by: [asc: ctb.id, desc: tb.block_number] + ) + end + + @impl FillingMigration + def update_batch(identifiers) do + {ctb_ids, tb_ids} = + Enum.reduce(identifiers, {[], []}, fn {ctb_id, tb_id}, {ctb_acc, tb_acc} -> + {[ctb_id | ctb_acc], [tb_id | tb_acc]} + end) + + Repo.transaction(fn -> + ctb_query = from(ctb in CurrentTokenBalance, where: ctb.id in ^ctb_ids) + + Repo.delete_all(ctb_query, timeout: :infinity) + + tb_query = + from(tb in TokenBalance, + where: tb.id in ^tb_ids, + update: [set: [value: nil, value_fetched_at: nil]] + ) + + Repo.update_all(tb_query, [], timeout: :infinity) + end) + end + + @impl FillingMigration + def update_cache, do: :ok +end diff --git a/apps/explorer/lib/explorer/migrator/sanitize_replaced_transactions.ex b/apps/explorer/lib/explorer/migrator/sanitize_replaced_transactions.ex new file mode 100644 index 0000000..5b92f71 --- /dev/null +++ b/apps/explorer/lib/explorer/migrator/sanitize_replaced_transactions.ex @@ -0,0 +1,46 @@ +defmodule Explorer.Migrator.SanitizeReplacedTransactions do + @moduledoc """ + Cleans the transactions that are related to non-consensus blocks. + """ + + use Explorer.Migrator.FillingMigration + + import Ecto.Query + + alias Explorer.Chain.Transaction + alias Explorer.Migrator.FillingMigration + alias Explorer.Repo + + @migration_name "sanitize_replaced_transactions" + + @impl FillingMigration + def migration_name, do: @migration_name + + @impl FillingMigration + def last_unprocessed_identifiers(state) do + limit = batch_size() * concurrency() + + ids = + unprocessed_data_query() + |> select([t], t.hash) + |> limit(^limit) + |> Repo.all(timeout: :infinity) + + {ids, state} + end + + @impl FillingMigration + def unprocessed_data_query do + from(t in Transaction, where: t.block_consensus == false) + end + + @impl FillingMigration + def update_batch(transaction_hashes) do + query = from(t in Transaction, where: t.hash in ^transaction_hashes) + + Repo.delete_all(query, timeout: :infinity) + end + + @impl FillingMigration + def update_cache, do: :ok +end diff --git a/apps/explorer/lib/explorer/migrator/shrink_internal_transactions.ex b/apps/explorer/lib/explorer/migrator/shrink_internal_transactions.ex index 414f330..29d8f65 100644 --- a/apps/explorer/lib/explorer/migrator/shrink_internal_transactions.ex +++ b/apps/explorer/lib/explorer/migrator/shrink_internal_transactions.ex @@ -18,13 +18,13 @@ defmodule Explorer.Migrator.ShrinkInternalTransactions do def migration_name, do: @migration_name @impl FillingMigration - def last_unprocessed_identifiers(%{max_block_number: -1} = state), do: {[], state} + def last_unprocessed_identifiers(%{"max_block_number" => -1} = state), do: {[], state} - def last_unprocessed_identifiers(%{max_block_number: from_block_number} = state) do + def last_unprocessed_identifiers(%{"max_block_number" => from_block_number} = state) do limit = batch_size() * concurrency() to_block_number = max(from_block_number - limit + 1, 0) - {Enum.to_list(from_block_number..to_block_number), %{state | max_block_number: to_block_number - 1}} + {Enum.to_list(from_block_number..to_block_number), %{state | "max_block_number" => to_block_number - 1}} end def last_unprocessed_identifiers(state) do @@ -38,7 +38,7 @@ defmodule Explorer.Migrator.ShrinkInternalTransactions do max_block_number = Repo.one(query, timeout: :infinity) state - |> Map.put(:max_block_number, max_block_number || -1) + |> Map.put("max_block_number", max_block_number || -1) |> last_unprocessed_identifiers() end diff --git a/apps/explorer/lib/explorer/paging_options.ex b/apps/explorer/lib/explorer/paging_options.ex index 081d160..0823975 100644 --- a/apps/explorer/lib/explorer/paging_options.ex +++ b/apps/explorer/lib/explorer/paging_options.ex @@ -8,7 +8,7 @@ defmodule Explorer.PagingOptions do key: key, page_size: page_size, page_number: page_number, - is_pending_tx: is_pending_tx, + is_pending_transaction: is_pending_transaction, is_index_in_asc_order: is_index_in_asc_order, asc_order: asc_order, batch_key: batch_key @@ -17,7 +17,7 @@ defmodule Explorer.PagingOptions do @typep key :: any() @typep page_size :: non_neg_integer() | nil @typep page_number :: pos_integer() - @typep is_pending_tx :: atom() + @typep is_pending_transaction :: atom() @typep is_index_in_asc_order :: atom() @typep asc_order :: atom() @typep batch_key :: any() @@ -26,7 +26,7 @@ defmodule Explorer.PagingOptions do :key, :page_size, page_number: 1, - is_pending_tx: false, + is_pending_transaction: false, is_index_in_asc_order: false, asc_order: false, batch_key: nil diff --git a/apps/explorer/lib/explorer/repo.ex b/apps/explorer/lib/explorer/repo.ex index 08a2a92..5f1ff6e 100644 --- a/apps/explorer/lib/explorer/repo.ex +++ b/apps/explorer/lib/explorer/repo.ex @@ -167,6 +167,16 @@ defmodule Explorer.Repo do end end + defmodule Scroll do + use Ecto.Repo, + otp_app: :explorer, + adapter: Ecto.Adapters.Postgres + + def init(_, opts) do + ConfigHelper.init_repo_module(__MODULE__, opts) + end + end + defmodule ZkSync do use Ecto.Repo, otp_app: :explorer, @@ -286,4 +296,14 @@ defmodule Explorer.Repo do ConfigHelper.init_repo_module(__MODULE__, opts) end end + + defmodule Blackfort do + use Ecto.Repo, + otp_app: :explorer, + adapter: Ecto.Adapters.Postgres + + def init(_, opts) do + ConfigHelper.init_repo_module(__MODULE__, opts) + end + end end diff --git a/apps/explorer/lib/explorer/repo/config_helper.ex b/apps/explorer/lib/explorer/repo/config_helper.ex index 68b18e0..bfed70d 100644 --- a/apps/explorer/lib/explorer/repo/config_helper.ex +++ b/apps/explorer/lib/explorer/repo/config_helper.ex @@ -49,7 +49,7 @@ defmodule Explorer.Repo.ConfigHelper do Application.put_env(:explorer, module, merged) - {:ok, Keyword.put(opts, :url, db_url)} + {:ok, opts |> Keyword.put(:url, remove_search_path(db_url)) |> Keyword.merge(Keyword.take(merged, [:search_path]))} end def ssl_enabled?, do: String.equivalent?(System.get_env("ECTO_USE_SSL") || "true", "true") @@ -58,10 +58,44 @@ defmodule Explorer.Repo.ConfigHelper do # sobelow_skip ["DOS.StringToAtom"] def extract_parameters(database_url) do - ~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_-]*)/ + ~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_\-]*)(\?.*search_path=(?[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) + |> adjust_search_path() + end + + defp adjust_search_path(params) do + case params[:search_path] do + empty when empty in [nil, ""] -> Keyword.delete(params, :search_path) + [_search_path] -> params + search_path -> Keyword.put(params, :search_path, [search_path]) + end + end + + # Workaround for Ecto.Repo init. + # It takes parameters from the url in priority over provided options (as strings) + # while Postgrex expects search_path to be a list + # which means that it will always crash if there is a search_path parameter in DB url. + # That's why we need to remove this parameter from DB url before passing it to Ecto. + defp remove_search_path(nil), do: nil + + defp remove_search_path(db_url) do + case URI.parse(db_url) do + %{query: nil} -> + db_url + + %{query: query} = uri -> + query_without_search_path = + query + |> URI.decode_query() + |> Map.delete("search_path") + |> URI.encode_query() + + uri + |> Map.put(:query, query_without_search_path) + |> URI.to_string() + end end defp get_env_vars(vars, env_function) do diff --git a/apps/explorer/lib/explorer/smart_contract/helper.ex b/apps/explorer/lib/explorer/smart_contract/helper.ex index 51e7d86..3196373 100644 --- a/apps/explorer/lib/explorer/smart_contract/helper.ex +++ b/apps/explorer/lib/explorer/smart_contract/helper.ex @@ -112,9 +112,23 @@ defmodule Explorer.SmartContract.Helper do end end + @doc """ + Prepares the bytecode for a microservice by processing the given body, creation input, and deployed bytecode. + + ## Parameters + + - body: The body of the request or data to be processed. + - creation_input: The input data used during the creation of the smart contract. + - deployed_bytecode: The bytecode of the deployed smart contract. + + ## Returns + + The processed bytecode ready for the microservice. + """ + @spec prepare_bytecode_for_microservice(map(), binary() | nil, binary() | nil) :: map() def prepare_bytecode_for_microservice(body, creation_input, deployed_bytecode) - def prepare_bytecode_for_microservice(body, empty, deployed_bytecode) when is_nil(empty) do + def prepare_bytecode_for_microservice(body, creation_input, deployed_bytecode) when is_nil(creation_input) do if Application.get_env(:explorer, :chain_type) == :zksync do body |> Map.put("code", deployed_bytecode) @@ -144,7 +158,7 @@ defmodule Explorer.SmartContract.Helper do def cast_libraries(_value, map), do: map def contract_creation_input(address_hash) do - case Chain.smart_contract_creation_tx_bytecode(address_hash) do + case Chain.smart_contract_creation_transaction_bytecode(address_hash) do %{init: init, created_contract_code: _created_contract_code} -> init @@ -181,12 +195,13 @@ defmodule Explorer.SmartContract.Helper do if Application.get_env(:explorer, :chain_type) == :zksync do {nil, deployed_bytecode, metadata} else - case SmartContract.creation_tx_with_bytecode(address_hash) do - %{init: init, tx: tx} -> - {init, deployed_bytecode, tx |> tx_to_metadata(init) |> Map.merge(metadata)} + case SmartContract.creation_transaction_with_bytecode(address_hash) do + %{init: init, transaction: transaction} -> + {init, deployed_bytecode, transaction |> transaction_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)} + %{init: init, internal_transaction: internal_transaction} -> + {init, deployed_bytecode, + internal_transaction |> internal_transaction_to_metadata(init) |> Map.merge(metadata)} _ -> {nil, deployed_bytecode, metadata} @@ -194,22 +209,22 @@ defmodule Explorer.SmartContract.Helper do end end - defp tx_to_metadata(tx, init) do + defp transaction_to_metadata(transaction, 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), + "blockNumber" => to_string(transaction.block_number), + "transactionHash" => to_string(transaction.hash), + "transactionIndex" => to_string(transaction.index), + "deployer" => to_string(transaction.from_address_hash), "creationCode" => to_string(init) } end - defp internal_tx_to_metadata(internal_tx, init) do + defp internal_transaction_to_metadata(internal_transaction, 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), + "blockNumber" => to_string(internal_transaction.block_number), + "transactionHash" => to_string(internal_transaction.transaction_hash), + "transactionIndex" => to_string(internal_transaction.transaction_index), + "deployer" => to_string(internal_transaction.from_address_hash), "creationCode" => to_string(init) } 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 b97f9bc..0cc621a 100644 --- a/apps/explorer/lib/explorer/smart_contract/sig_provider_interface.ex +++ b/apps/explorer/lib/explorer/smart_contract/sig_provider_interface.ex @@ -10,7 +10,7 @@ defmodule Explorer.SmartContract.SigProviderInterface do @request_error_msg "Error while sending request to sig-provider" def decode_function_call(input) do - base_url = tx_input_decode_url() + base_url = transaction_input_decode_url() url = base_url @@ -74,7 +74,7 @@ defmodule Explorer.SmartContract.SigProviderInterface do def process_sig_provider_response(other_responses), do: {:error, other_responses} - def tx_input_decode_url, do: "#{base_api_url()}" <> "/function" + def transaction_input_decode_url, do: "#{base_api_url()}" <> "/function" def event_decode_url, do: "#{base_api_url()}" <> "/event" diff --git a/apps/explorer/lib/explorer/smart_contract/solidity/publisher.ex b/apps/explorer/lib/explorer/smart_contract/solidity/publisher.ex index c59f46b..cf24dca 100644 --- a/apps/explorer/lib/explorer/smart_contract/solidity/publisher.ex +++ b/apps/explorer/lib/explorer/smart_contract/solidity/publisher.ex @@ -72,6 +72,7 @@ defmodule Explorer.SmartContract.Solidity.Publisher do def publish_with_standard_json_input(%{"address_hash" => address_hash} = params, json_input) do Logger.info(@sc_verification_via_standard_json_input_started) + params = maybe_add_zksync_specific_data(params) case Verifier.evaluate_authenticity_via_standard_json_input(address_hash, params, json_input) do {:ok, @@ -452,4 +453,12 @@ defmodule Explorer.SmartContract.Solidity.Publisher do Map.put(params, "external_libraries", clean_external_libraries) end + + defp maybe_add_zksync_specific_data(params) do + if Application.get_env(:explorer, :chain_type) == :zksync do + Map.put(params, "constructor_arguments", SmartContract.zksync_get_constructor_arguments(params["address_hash"])) + else + params + end + end end diff --git a/apps/explorer/lib/explorer/smart_contract/solidity/verifier.ex b/apps/explorer/lib/explorer/smart_contract/solidity/verifier.ex index 79b4f34..6777ef0 100644 --- a/apps/explorer/lib/explorer/smart_contract/solidity/verifier.ex +++ b/apps/explorer/lib/explorer/smart_contract/solidity/verifier.ex @@ -45,10 +45,10 @@ defmodule Explorer.SmartContract.Solidity.Verifier do end defp evaluate_authenticity_inner(true, address_hash, params) do - {creation_tx_input, deployed_bytecode, verifier_metadata} = fetch_data_for_verification(address_hash) + {creation_transaction_input, deployed_bytecode, verifier_metadata} = fetch_data_for_verification(address_hash) %{} - |> prepare_bytecode_for_microservice(creation_tx_input, deployed_bytecode) + |> prepare_bytecode_for_microservice(creation_transaction_input, deployed_bytecode) |> Map.put("sourceFiles", %{ "#{params["name"]}.#{smart_contract_source_file_extension(parse_boolean(params["is_yul"]))}" => params["contract_source_code"] @@ -127,20 +127,21 @@ defmodule Explorer.SmartContract.Solidity.Verifier do end def evaluate_authenticity_via_standard_json_input_inner(true, address_hash, params, json_input) do - {creation_tx_input, deployed_bytecode, verifier_metadata} = fetch_data_for_verification(address_hash) + {creation_transaction_input, deployed_bytecode, verifier_metadata} = fetch_data_for_verification(address_hash) - compiler_version_map = + verification_params = if Application.get_env(:explorer, :chain_type) == :zksync do %{ "solcCompiler" => params["compiler_version"], - "zkCompiler" => params["zk_compiler_version"] + "zkCompiler" => params["zk_compiler_version"], + "constructorArguments" => params["constructor_arguments"] } else %{"compilerVersion" => params["compiler_version"]} end - compiler_version_map - |> prepare_bytecode_for_microservice(creation_tx_input, deployed_bytecode) + verification_params + |> prepare_bytecode_for_microservice(creation_transaction_input, deployed_bytecode) |> Map.put("input", json_input) |> (&if(Application.get_env(:explorer, :chain_type) == :zksync, do: RustVerifierInterface.zksync_verify_standard_json_input(&1, verifier_metadata), @@ -153,10 +154,10 @@ defmodule Explorer.SmartContract.Solidity.Verifier do end def evaluate_authenticity_via_multi_part_files(address_hash, params, files) do - {creation_tx_input, deployed_bytecode, verifier_metadata} = fetch_data_for_verification(address_hash) + {creation_transaction_input, deployed_bytecode, verifier_metadata} = fetch_data_for_verification(address_hash) %{} - |> prepare_bytecode_for_microservice(creation_tx_input, deployed_bytecode) + |> prepare_bytecode_for_microservice(creation_transaction_input, deployed_bytecode) |> Map.put("sourceFiles", files) |> Map.put("libraries", params["external_libraries"]) |> Map.put("optimizationRuns", prepare_optimization_runs(params["optimization"], params["optimization_runs"])) @@ -359,8 +360,8 @@ defmodule Explorer.SmartContract.Solidity.Verifier do bc_deployed_bytecode = Chain.smart_contract_bytecode(address_hash) - bc_creation_tx_input = - case Chain.smart_contract_creation_tx_bytecode(address_hash) do + bc_creation_transaction_input = + case Chain.smart_contract_creation_transaction_bytecode(address_hash) do %{init: init, created_contract_code: _created_contract_code} -> "0x" <> init_without_0x = init init_without_0x @@ -371,12 +372,12 @@ defmodule Explorer.SmartContract.Solidity.Verifier do %{ "metadata_hash_with_length" => bc_meta, - "trimmed_bytecode" => bc_creation_tx_input_without_meta, + "trimmed_bytecode" => bc_creation_transaction_input_without_meta, "compiler_version" => solc_bc - } = extract_bytecode_and_metadata_hash(bc_creation_tx_input, bc_deployed_bytecode) + } = extract_bytecode_and_metadata_hash(bc_creation_transaction_input, bc_deployed_bytecode) bc_replaced_local = - String.replace(bc_creation_tx_input_without_meta, local_bytecode_without_meta, "", global: false) + String.replace(bc_creation_transaction_input_without_meta, local_bytecode_without_meta, "", global: false) has_constructor_with_params? = has_constructor_with_params?(abi) @@ -386,16 +387,16 @@ defmodule Explorer.SmartContract.Solidity.Verifier do empty_constructor_arguments = arguments_data == "" or arguments_data == nil cond do - bc_creation_tx_input == "" -> + bc_creation_transaction_input == "" -> {:error, :no_creation_data} - !String.contains?(bc_creation_tx_input, bc_meta) || bc_deployed_bytecode in ["", "0x"] -> + !String.contains?(bc_creation_transaction_input, bc_meta) || bc_deployed_bytecode in ["", "0x"] -> {:error, :deployed_bytecode} solc_local != solc_bc -> {:error, :compiler_version} - !String.contains?(bc_creation_tx_input_without_meta, local_bytecode_without_meta) -> + !String.contains?(bc_creation_transaction_input_without_meta, local_bytecode_without_meta) -> {:error, :generated_bytecode} bc_replaced_local == "" && !has_constructor_with_params? -> @@ -410,15 +411,21 @@ defmodule Explorer.SmartContract.Solidity.Verifier do {:error, :autodetect_constructor_arguments_failed} has_constructor_with_params? && - (empty_constructor_arguments || !String.contains?(bc_creation_tx_input, arguments_data)) -> + (empty_constructor_arguments || !String.contains?(bc_creation_transaction_input, arguments_data)) -> {:error, :constructor_arguments} has_constructor_with_params? && is_constructor_args_valid?.(arguments_data) && (bc_replaced_local == arguments_data || - check_users_constructor_args_validity(bc_creation_tx_input, bytecode, bc_meta, local_meta, arguments_data)) -> + check_users_constructor_args_validity( + bc_creation_transaction_input, + bytecode, + bc_meta, + local_meta, + arguments_data + )) -> {:ok, %{abi: abi, constructor_arguments: arguments_data}} - try_library_verification(local_bytecode_without_meta, bc_creation_tx_input_without_meta) -> + try_library_verification(local_bytecode_without_meta, bc_creation_transaction_input_without_meta) -> {:ok, %{abi: abi}} true -> @@ -556,7 +563,7 @@ defmodule Explorer.SmartContract.Solidity.Verifier do @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. using metadata we split creation_transaction_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 """ @@ -575,8 +582,8 @@ defmodule Explorer.SmartContract.Solidity.Verifier do ) :: 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 + creation_transaction_input = + case Chain.smart_contract_creation_transaction_bytecode(address_hash) do %{init: init, created_contract_code: _created_contract_code} -> "0x" <> init_without_0x = init init_without_0x @@ -587,9 +594,9 @@ defmodule Explorer.SmartContract.Solidity.Verifier do 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", + false <- is_nil(creation_transaction_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), + [_bytecode, constructor_args] <- String.split(creation_transaction_input, meta <> meta_length), ^constructor_args <- check_function.(constructor_args) do constructor_args else diff --git a/apps/explorer/lib/explorer/smart_contract/vyper/verifier.ex b/apps/explorer/lib/explorer/smart_contract/vyper/verifier.ex index 3ca5ac9..049c9f5 100644 --- a/apps/explorer/lib/explorer/smart_contract/vyper/verifier.ex +++ b/apps/explorer/lib/explorer/smart_contract/vyper/verifier.ex @@ -117,10 +117,10 @@ defmodule Explorer.SmartContract.Vyper.Verifier do end defp vyper_verify_multipart(params, evm_version, files, address_hash) do - {creation_tx_input, deployed_bytecode, verifier_metadata} = fetch_data_for_verification(address_hash) + {creation_transaction_input, deployed_bytecode, verifier_metadata} = fetch_data_for_verification(address_hash) %{} - |> prepare_bytecode_for_microservice(creation_tx_input, deployed_bytecode) + |> prepare_bytecode_for_microservice(creation_transaction_input, deployed_bytecode) |> Map.put("evmVersion", evm_version) |> Map.put("sourceFiles", files) |> Map.put("compilerVersion", params["compiler_version"]) @@ -129,10 +129,10 @@ defmodule Explorer.SmartContract.Vyper.Verifier do end defp vyper_verify_standard_json(params, address_hash) do - {creation_tx_input, deployed_bytecode, verifier_metadata} = fetch_data_for_verification(address_hash) + {creation_transaction_input, deployed_bytecode, verifier_metadata} = fetch_data_for_verification(address_hash) %{} - |> prepare_bytecode_for_microservice(creation_tx_input, deployed_bytecode) + |> prepare_bytecode_for_microservice(creation_transaction_input, deployed_bytecode) |> Map.put("compilerVersion", params["compiler_version"]) |> Map.put("input", params["input"]) |> RustVerifierInterface.vyper_verify_standard_json(verifier_metadata) diff --git a/apps/explorer/lib/explorer/tags/address_tag.ex b/apps/explorer/lib/explorer/tags/address_tag.ex index 65d7cc0..d6c20c2 100644 --- a/apps/explorer/lib/explorer/tags/address_tag.ex +++ b/apps/explorer/lib/explorer/tags/address_tag.ex @@ -1,6 +1,6 @@ defmodule Explorer.Tags.AddressTag do @moduledoc """ - Represents a Tag object. + Represents an address Tag object. """ use Explorer.Schema @@ -9,11 +9,12 @@ defmodule Explorer.Tags.AddressTag do import Ecto.Query, only: [ - from: 2 + from: 2, + select: 3 ] alias Explorer.Repo - alias Explorer.Tags.{AddressTag, AddressToTag} + alias Explorer.Tags.AddressToTag @typedoc """ * `:id` - id of Tag @@ -21,7 +22,7 @@ defmodule Explorer.Tags.AddressTag do * `:display_name` - Label's display name """ typed_schema "address_tags" do - field(:label, :string, null: false) + field(:label, :string, primary_key: true, null: false) field(:display_name, :string, null: false) has_many(:tag_id, AddressToTag, foreign_key: :id) @@ -38,55 +39,61 @@ defmodule Explorer.Tags.AddressTag do |> unique_constraint(:label, name: :address_tags_label_index) end - def set_tag(name, display_name) do - tag = get_tag(name) + @spec set(String.t() | nil, String.t() | nil) :: {:ok, Ecto.Schema.t()} | {:error, Ecto.Changeset.t()} | :invalid + def set(name, display_name) + + def set(nil, _), do: :invalid + + def set(_, nil), do: :invalid + + def set(name, display_name) do + tag = get_by_label(name) if tag do tag - |> AddressTag.changeset(%{display_name: display_name}) + |> __MODULE__.changeset(%{display_name: display_name}) |> Repo.update() else - %AddressTag{} - |> AddressTag.changeset(%{label: name, display_name: display_name}) + %__MODULE__{} + |> __MODULE__.changeset(%{label: name, display_name: display_name}) |> Repo.insert() end end - def get_tag_id(nil), do: nil - - def get_tag_id(label) do - query = - from( - tag in AddressTag, - where: tag.label == ^label, - select: tag.id - ) + @doc """ + Fetches AddressTag.t() by label name from the DB + """ + @spec get_id_by_label(String.t()) :: non_neg_integer() + def get_id_by_label(nil), do: nil - query + def get_id_by_label(label) do + label + |> get_by_label_query() + |> select([tag], tag.id) |> Repo.one() end - def get_tag(nil), do: nil + @doc """ + Fetches all AddressTag.t() from the DB + """ + @spec get_all() :: __MODULE__.t() + def get_all do + __MODULE__ + |> Repo.all() + end - def get_tag(label) do - query = - from( - tag in AddressTag, - where: tag.label == ^label - ) + defp get_by_label(nil), do: nil - query + defp get_by_label(label) do + label + |> get_by_label_query() |> Repo.one() end - def get_all_tags do - query = - from( - tag in AddressTag, - select: tag - ) - - query - |> Repo.all() + defp get_by_label_query(label) do + from( + tag in __MODULE__, + where: tag.label == ^label + ) end end diff --git a/apps/explorer/lib/explorer/tags/address_tag_cataloger.ex b/apps/explorer/lib/explorer/tags/address_tag_cataloger.ex index da54d81..5295a29 100644 --- a/apps/explorer/lib/explorer/tags/address_tag_cataloger.ex +++ b/apps/explorer/lib/explorer/tags/address_tag_cataloger.ex @@ -9,7 +9,6 @@ defmodule Explorer.Tags.AddressTag.Cataloger do alias Explorer.EnvVarTranslator alias Explorer.Tags.{AddressTag, AddressToTag} alias Explorer.Validator.MetadataRetriever - alias Poison.Parser def start_link(_) do GenServer.start_link(__MODULE__, :ok, name: __MODULE__) @@ -24,9 +23,6 @@ defmodule Explorer.Tags.AddressTag.Cataloger do @impl GenServer def handle_info(:fetch_tags, state) do - # set tag for every chainlink oracle - create_chainlink_oracle_tag() - create_new_tags() send(self(), :bind_addresses) @@ -47,7 +43,7 @@ defmodule Explorer.Tags.AddressTag.Cataloger do # set L2 tag set_l2_tag() - all_tags = AddressTag.get_all_tags() + all_tags = AddressTag.get_all() all_tags |> Enum.each(fn %{label: tag_name} -> @@ -68,21 +64,6 @@ defmodule Explorer.Tags.AddressTag.Cataloger do |> String.replace(".", "_") end - def create_chainlink_oracle_tag do - chainlink_oracles_config = Application.get_env(:block_scout_web, :chainlink_oracles) - - if chainlink_oracles_config do - chainlink_oracles_config - |> Parser.parse!(%{keys: :atoms!}) - |> Enum.each(fn %{:name => name, :address => address} -> - chainlink_tag_name = "chainlink oracle #{String.downcase(name)}" - AddressTag.set_tag(chainlink_tag_name, chainlink_tag_name) - tag_id = AddressTag.get_tag_id(chainlink_tag_name) - AddressToTag.set_tag_to_addresses(tag_id, [address]) - end) - end - end - defp set_tag_for_multiple_env_var_addresses(env_vars, tag) do addresses = env_vars @@ -92,7 +73,7 @@ defmodule Explorer.Tags.AddressTag.Cataloger do |> String.downcase() end) - tag_id = AddressTag.get_tag_id(tag) + tag_id = AddressTag.get_id_by_label(tag) AddressToTag.set_tag_to_addresses(tag_id, addresses) end @@ -112,23 +93,23 @@ defmodule Explorer.Tags.AddressTag.Cataloger do end) end) - tag_id = AddressTag.get_tag_id(tag) + tag_id = AddressTag.get_id_by_label(tag) AddressToTag.set_tag_to_addresses(tag_id, addresses) end - def create_new_tags do + defp create_new_tags do tags = EnvVarTranslator.map_array_env_var_to_list(:new_tags) tags |> Enum.each(fn %{tag: tag_name, title: tag_display_name} -> - AddressTag.set_tag(tag_name, tag_display_name) + AddressTag.set(tag_name, tag_display_name) end) end defp set_tag_for_env_var_multiple_addresses(env_var, tag) do addresses = env_var_string_array_to_list(env_var) - tag_id = AddressTag.get_tag_id(tag) + tag_id = AddressTag.get_id_by_label(tag) AddressToTag.set_tag_to_addresses(tag_id, addresses) end @@ -152,7 +133,7 @@ defmodule Explorer.Tags.AddressTag.Cataloger do defp set_validator_tag do validators = MetadataRetriever.fetch_validators_list() FetchValidatorInfoOnDemand.trigger_fetch(validators) - tag_id = AddressTag.get_tag_id("validator") + tag_id = AddressTag.get_id_by_label("validator") AddressToTag.set_tag_to_addresses(tag_id, validators) end @@ -177,28 +158,4 @@ defmodule Explorer.Tags.AddressTag.Cataloger do defp set_l2_tag do set_tag_for_multiple_env_var_addresses(["CUSTOM_CONTRACT_ADDRESSES_AOX"], "l2") end - - def set_chainlink_oracle_tag do - chainlink_oracles = chainlink_oracles_list() - - tag_id = AddressTag.get_tag_id("chainlink oracle") - AddressToTag.set_tag_to_addresses(tag_id, chainlink_oracles) - end - - defp chainlink_oracles_list do - chainlink_oracles_config = Application.get_env(:block_scout_web, :chainlink_oracles) - - if chainlink_oracles_config do - try do - chainlink_oracles_config - |> Parser.parse!(%{keys: :atoms!}) - |> Enum.map(fn %{:name => _name, :address => address} -> address end) - rescue - _ -> - [] - end - else - [] - end - end end diff --git a/apps/explorer/lib/explorer/tags/address_to_tag.ex b/apps/explorer/lib/explorer/tags/address_to_tag.ex index 729b5eb..6f83a0c 100644 --- a/apps/explorer/lib/explorer/tags/address_to_tag.ex +++ b/apps/explorer/lib/explorer/tags/address_to_tag.ex @@ -1,6 +1,6 @@ defmodule Explorer.Tags.AddressToTag do @moduledoc """ - Represents ann Address to Tag relation. + Represents Address to Tag relation. """ use Explorer.Schema @@ -9,7 +9,7 @@ defmodule Explorer.Tags.AddressToTag do alias Explorer.{Chain, Repo} alias Explorer.Chain.{Address, Hash} - alias Explorer.Tags.{AddressTag, AddressToTag} + alias Explorer.Tags.AddressTag # Notation.import_types(BlockScoutWeb.GraphQL.Schema.Types) @@ -53,7 +53,7 @@ defmodule Explorer.Tags.AddressToTag do defp get_address_hashes_mapped_to_tag(tag_id) do query = from( - att in AddressToTag, + att in __MODULE__, where: att.tag_id == ^tag_id, select: att.address_hash ) @@ -62,6 +62,7 @@ defmodule Explorer.Tags.AddressToTag do |> Repo.all() end + @spec set_tag_to_addresses(non_neg_integer(), list()) :: any() def set_tag_to_addresses(tag_id, address_hash_string_list) do current_address_hashes = get_address_hashes_mapped_to_tag(tag_id) @@ -105,10 +106,10 @@ defmodule Explorer.Tags.AddressToTag do end) |> Enum.filter(&(!is_nil(&1))) - if not Enum.empty?(addresses_to_delete) do + unless Enum.empty?(addresses_to_delete) do delete_query_base = from( - att in AddressToTag, + att in __MODULE__, where: att.tag_id == ^tag_id ) @@ -119,7 +120,7 @@ defmodule Explorer.Tags.AddressToTag do Repo.delete_all(delete_query) end - Repo.insert_all(AddressToTag, changeset_to_add_list, + Repo.insert_all(__MODULE__, changeset_to_add_list, on_conflict: :nothing, conflict_target: [:address_hash, :tag_id] ) diff --git a/apps/explorer/lib/explorer/third_party_integrations/auth0.ex b/apps/explorer/lib/explorer/third_party_integrations/auth0.ex index 2b5c6c6..51c5980 100644 --- a/apps/explorer/lib/explorer/third_party_integrations/auth0.ex +++ b/apps/explorer/lib/explorer/third_party_integrations/auth0.ex @@ -2,11 +2,33 @@ defmodule Explorer.ThirdPartyIntegrations.Auth0 do @moduledoc """ Module for fetching jwt Auth0 Management API (https://auth0.com/docs/api/management/v2) jwt """ + require Logger + + alias Explorer.Account.Identity + alias Explorer.{Account, Helper, Repo} + alias OAuth2.{AccessToken, Client} + alias Ueberauth.Auth + alias Ueberauth.Strategy.Auth0 + alias Ueberauth.Strategy.Auth0.OAuth + @redis_key "auth0" + @request_siwe_message "Request Sign in with Ethereum message via /api/account/v2/siwe_message" + @wrong_nonce "Wrong nonce in message" + @misconfiguration_detected "Misconfiguration detected, please contact support." + @disabled_otp_error_description "Grant type 'http://auth0.com/oauth/grant-type/passwordless/otp' not allowed for the client." + @users_path "/api/v2/users" + @json_content_type [{"Content-type", "application/json"}] + @doc """ - Function responsible for retrieving machine to machine JWT for interacting with Auth0 Management API. - Firstly it tries to access cached token and if there is no cached one, token will be requested from Auth0 + Retrieves a machine-to-machine JWT for interacting with the Auth0 Management API. + + This function first attempts to access a cached token. If no cached token is + found, it requests a new token from Auth0 and caches it for future use. + + ## Returns + - `nil` if token retrieval fails + - `String.t()` containing the JWT if successful """ @spec get_m2m_jwt() :: nil | String.t() def get_m2m_jwt do @@ -16,7 +38,7 @@ defmodule Explorer.ThirdPartyIntegrations.Auth0 do def get_m2m_jwt_inner({:ok, token}) when not is_nil(token), do: token def get_m2m_jwt_inner(_) do - config = Application.get_env(:ueberauth, Ueberauth.Strategy.Auth0.OAuth) + config = Application.get_env(:ueberauth, OAuth) body = %{ "client_id" => config[:client_id], @@ -25,9 +47,7 @@ defmodule Explorer.ThirdPartyIntegrations.Auth0 do "grant_type" => "client_credentials" } - headers = [{"Content-type", "application/json"}] - - case HTTPoison.post("https://#{config[:domain]}/oauth/token", Jason.encode!(body), headers, []) do + case HTTPoison.post("https://#{config[:domain]}/oauth/token", Jason.encode!(body), @json_content_type, []) do {:ok, %HTTPoison.Response{status_code: 200, body: body}} -> case Jason.decode!(body) do %{"access_token" => token, "expires_in" => ttl} -> @@ -43,7 +63,16 @@ defmodule Explorer.ThirdPartyIntegrations.Auth0 do end @doc """ - Generates key from chain_id and cookie hash for storing in Redis + Generates a key from chain_id and cookie hash for storing in Redis. + + This function combines the chain_id (if available) with the provided hash to + create a unique key for Redis storage. + + ## Parameters + - `hash`: The hash to be combined with the chain_id + + ## Returns + - `String.t()` representing the generated key """ @spec cookie_key(binary) :: String.t() def cookie_key(hash) do @@ -60,4 +89,724 @@ defmodule Explorer.ThirdPartyIntegrations.Auth0 do Redix.command(:redix, ["SET", cookie_key(@redis_key), token, "EX", ttl]) token end + + @doc """ + Sends a one-time password (OTP) for linking an email to an existing account. + + This function checks if the email is already associated with an account before + sending the OTP. If the email is already in use, it returns an error. + + ## Parameters + - `email`: The email address to send the OTP to + - `ip`: The IP address of the requester + + ## Returns + - `:ok` if the OTP was sent successfully + - `{:error, String.t()}` error with the description + - `:error` if there was an unexpected error + """ + @spec send_otp_for_linking(String.t(), String.t()) :: :error | :ok | {:error, String.t()} + def send_otp_for_linking(email, ip) do + case find_users_by_email(email) do + {:ok, []} -> + do_send_otp(email, ip) + + {:ok, users} when is_list(users) and length(users) > 0 -> + {:error, "Account with this email already exists"} + + error -> + error + end + end + + @doc """ + Sends a one-time password (OTP) to the specified email address. + + This function checks if the email is associated with an existing user before + sending the OTP. If the user exists, it checks time interval and sends the OTP + or reports when the user can request a new OTP. + + ## Parameters + - `email`: The email address to send the OTP to + - `ip`: The IP address of the requester + + ## Returns + - `:ok` if the OTP was sent successfully + - `:error` if there was an unexpected error + - `{:interval, integer()}` if the user need to wait before sending the OTP + """ + @spec send_otp(String.t(), String.t()) :: :error | :ok | {:interval, integer()} + def send_otp(email, ip) do + case find_users_by_email(email) do + {:ok, []} -> + do_send_otp(email, ip) + + {:ok, [user | _]} -> + handle_existing_user(user, email, ip) + + error -> + error + end + end + + @doc """ + Links an email to an existing user account using a one-time password (OTP). + + This function verifies the OTP, creates a new identity for the email, and links + it to the existing user account. + + ## Parameters + - `primary_user_id`: The ID of the existing user account + - `email`: The email address to be linked + - `otp`: The one-time password for verification + - `ip`: The IP address of the requester + + ## Returns + - `{:ok, Auth.t()}` if the email was successfully linked + - `{:error, String.t()}` error with the description + - `:error` if there was an unexpected error + """ + @spec link_email(Identity.session(), String.t(), String.t(), String.t()) :: + :error | {:ok, Auth.t()} | {:error, String.t()} + def link_email(%{uid: primary_user_id, email: nil}, email, otp, ip) do + case find_users_by_email(email) do + {:ok, []} -> + with {:ok, token} <- confirm_otp(email, otp, ip), + {:ok, %{"sub" => "email|" <> identity_id}} <- get_user_from_token(token), + :ok <- link_users(primary_user_id, identity_id, "email"), + {:ok, user} <- update_user_email(primary_user_id, email) do + {:ok, create_auth(user)} + end + + {:ok, users} when is_list(users) -> + {:error, "Account with this email already exists"} + + error -> + error + end + end + + def link_email(_account_with_email, _, _, _), do: {:error, "This account already has an email"} + + @doc """ + Confirms a one-time password (OTP) and retrieves authentication information. + + This function verifies the OTP for the given email and returns the + authentication information if successful. + + ## Parameters + - `email`: The email address associated with the OTP + - `otp`: The one-time password to be confirmed + - `ip`: The IP address of the requester + + ## Returns + - `{:ok, Auth.t()}` if the OTP is confirmed and authentication is successful + - `{:error, String.t()}` error with the description + - `:error` if there was an unexpected error + """ + @spec confirm_otp_and_get_auth(String.t(), String.t(), String.t()) :: :error | {:error, String.t()} | {:ok, Auth.t()} + def confirm_otp_and_get_auth(email, otp, ip) do + with {:ok, token} <- confirm_otp(email, otp, ip), + {:ok, %{"sub" => user_id} = user} <- get_user_from_token(token), + {:search, _user_from_token, {:ok, user}} <- {:search, user, get_user_by_id(user_id)} do + maybe_link_email_and_get_auth(user) + else + # newly created user, sometimes just created user with otp does not appear in the search + {:search, %{"sub" => user_id} = user_from_token, {:error, "User not found"}} -> + {:ok, user_from_token |> Map.put("user_id", user_id) |> create_auth()} + + err -> + err + end + end + + @doc """ + Updates the session with the user's address hash. + + This function checks if the session already has an address hash. If not, it + retrieves the user's information and adds the address hash to the session. + + ## Parameters + - `session`: The current session map (Identity.session()) + + ## Returns + - `{:old, Identity.session()}` if the session already has an address hash + - `{:new, Identity.session()}` if the address hash was added to the session + """ + @spec update_session_with_address_hash(Identity.session()) :: {:old, Identity.session()} | {:new, Identity.session()} + def update_session_with_address_hash(%{address_hash: _} = session), do: {:old, session} + + def update_session_with_address_hash(%{uid: user_id} = session) do + case get_user_by_id(user_id) do + {:ok, user} -> + {:new, Map.put(session, :address_hash, user |> create_auth() |> Identity.address_hash_from_auth())} + + error -> + Logger.error("Error when updating session with address hash: #{inspect(error)}") + {:old, session} + end + end + + @doc """ + Generates a Sign-In with Ethereum (SIWE) message for the given address. + + This function creates a SIWE message with a unique nonce, caches the nonce, + and returns the formatted message string. + + ## Parameters + - `address`: The Ethereum address for which to generate the SIWE message + + ## Returns + - `{:ok, String.t()}` containing the generated SIWE message + - `{:error, String.t()}` error with the description + """ + @spec generate_siwe_message(String.t()) :: {:ok, String.t()} | {:error, String.t()} + def generate_siwe_message(address) do + nonce = Siwe.generate_nonce() + {int_chain_id, _} = Integer.parse(Application.get_env(:block_scout_web, :chain_id)) + + message = %Siwe.Message{ + domain: Helper.get_app_host(), + address: address, + statement: Application.get_env(:explorer, Account)[:siwe_message], + uri: + Application.get_env(:block_scout_web, BlockScoutWeb.Endpoint)[:url][:scheme] <> + "://" <> Helper.get_app_host(), + version: "1", + chain_id: int_chain_id, + nonce: nonce, + issued_at: DateTime.utc_now() |> DateTime.to_iso8601(), + expiration_time: DateTime.utc_now() |> DateTime.add(300, :second) |> DateTime.to_iso8601() + } + + with {:cache, {:ok, _nonce}} <- {:cache, cache_nonce_for_address(nonce, address)}, + {:message, {:ok, message}} <- {:message, Siwe.to_str(message)} do + {:ok, message} + else + {:cache, {:error, error}} -> + Logger.error("Error while caching nonce: #{inspect(error)}") + {:error, @misconfiguration_detected} + + {:message, {:error, error}} -> + Logger.error("Error while generating Sign in with Ethereum Message: #{inspect(error)}") + {:error, error} + end + end + + @doc """ + Links an Ethereum address to an existing user account. + + This function verifies the SIWE message and signature, checks for existing + users with the same address, and updates the user's account with the new + address. + + ## Parameters + - `user_id`: The ID of the existing user account + - `message`: The SIWE message + - `signature`: The signature of the SIWE message + + ## Returns + - `{:ok, Auth.t()}` if the address was successfully linked + - `{:error, String.t()}` error with the description + - `:error` if there was an unexpected error + """ + @spec link_address(String.t(), String.t(), String.t()) :: :error | {:error, String.t()} | {:ok, Auth.t()} + def link_address(user_id, message, signature) do + with {:signature, {:ok, %{nonce: nonce, address: address}}} <- + {:signature, message |> String.trim() |> Siwe.parse_if_valid(signature)}, + {:nonce, {:ok, ^nonce}} <- + {:nonce, Redix.command(:redix, ["GET", cookie_key(address <> "siwe_nonce")])}, + Redix.command(:redix, ["DEL", cookie_key(address <> "siwe_nonce")]), + {:user, {:ok, []}} <- {:user, find_users_by_web3_address(address)}, + {:ok, user} <- update_user_with_web3_address(user_id, address) do + {:ok, create_auth(user)} + else + {:nonce, {:ok, _}} -> + {:error, @wrong_nonce} + + {:nonce, _} -> + {:error, @request_siwe_message} + + {:signature, error} -> + error + + {:user, {:ok, _users}} -> + {:error, "Account with this address already exists"} + + {:user, error} -> + error + + other -> + other + end + end + + @doc """ + Authenticates a user using a Sign-In with Ethereum (SIWE) message and signature. + + This function verifies the SIWE message and signature, finds or creates a user + associated with the Ethereum address, and returns the authentication information. + + ## Parameters + - `message`: The SIWE message + - `signature`: The signature of the SIWE message + + ## Returns + - `{:ok, Auth.t()}` if authentication is successful + - `{:error, String.t()}` error with the description + - `:error` if there was an unexpected error + """ + @spec get_auth_with_web3(String.t(), String.t()) :: :error | {:error, String.t()} | {:ok, Auth.t()} + def get_auth_with_web3(message, signature) do + with {:signature, {:ok, %{nonce: nonce, address: address}}} <- + {:signature, message |> String.trim() |> Siwe.parse_if_valid(signature)}, + {:nonce, {:ok, ^nonce}} <- + {:nonce, Redix.command(:redix, ["GET", cookie_key(address <> "siwe_nonce")])}, + {:user, {:ok, user}} <- {:user, find_or_create_web3_user(address, signature)} do + Redix.command(:redix, ["DEL", cookie_key(address <> "siwe_nonce")]) + {:ok, create_auth(user)} + else + {:nonce, {:ok, _}} -> + {:error, @wrong_nonce} + + {:nonce, _} -> + {:error, @request_siwe_message} + + {_step, error} -> + error + end + end + + defp handle_existing_user(user, email, ip) do + user + |> create_auth() + |> Identity.find_identity() + |> handle_identity(email, ip) + end + + defp handle_identity(nil, email, ip), do: do_send_otp(email, ip) + + defp handle_identity(%Identity{otp_sent_at: otp_sent_at} = identity, email, ip) do + otp_resend_interval = Application.get_env(:explorer, Account, :otp_resend_interval) + + case Helper.check_time_interval(otp_sent_at, otp_resend_interval) do + true -> + identity + |> Identity.changeset(%{otp_sent_at: DateTime.utc_now()}) + |> Repo.account_repo().update() + + do_send_otp(email, ip) + + interval -> + {:interval, interval} + end + end + + defp do_send_otp(email, ip) do + client = OAuth.client() + + body = + %{ + email: email, + connection: :email, + send: :code + } + |> put_client_id_and_secret() + + headers = [{"auth0-forwarded-for", ip} | @json_content_type] + + case Client.post(client, "/passwordless/start", body, headers) do + {:ok, %OAuth2.Response{status_code: 200}} -> + :ok + + other -> + Logger.error("Error while sending otp: ", inspect(other)) + + :error + end + end + + defp get_user_from_token(%AccessToken{other_params: %{"id_token" => token}}) do + case Joken.peek_claims(token) do + {:ok, %{"sub" => _} = user} -> + {:ok, user} + + error -> + Logger.error("Error while peeking claims from token: #{inspect(error)}") + :error + end + end + + defp get_user_from_token(token) do + Logger.error("No id_token in token: #{inspect(Map.update(token, :access_token, "xxx", fn _ -> "xxx" end))}") + + {:error, @misconfiguration_detected} + end + + defp confirm_otp(email, otp, ip) do + client = OAuth.client() + + body = + %{ + username: email, + otp: otp, + realm: :email, + grant_type: :"http://auth0.com/oauth/grant-type/passwordless/otp" + } + |> put_client_id_and_secret() + + headers = [{"auth0-forwarded-for", ip} | @json_content_type] + + case Client.post(client, "/oauth/token", body, headers) do + {:ok, %OAuth2.Response{status_code: 200, body: body}} -> + {:ok, AccessToken.new(body)} + + {:error, + %OAuth2.Response{ + status_code: 403, + body: + %{ + "error" => "unauthorized_client", + "error_description" => @disabled_otp_error_description, + "error_uri" => "https://auth0.com/docs/clients/client-grant-types" + } = body + }} -> + Logger.error("Need to enable OTP: #{inspect(body)}") + {:error, @misconfiguration_detected} + + {:error, + %OAuth2.Response{ + status_code: 403, + body: %{"error" => "invalid_grant", "error_description" => "Wrong email or verification code."} + }} -> + {:error, "Wrong verification code."} + + other -> + Logger.error("Error while confirming otp: #{inspect(other)}") + + :error + end + end + + defp get_user_by_id(id) do + case get_m2m_jwt() do + token when is_binary(token) -> + client = OAuth.client(token: token) + + case Client.get(client, "#{@users_path}/#{URI.encode(id)}") do + {:ok, %OAuth2.Response{status_code: 200, body: %{"user_id" => ^id} = user}} -> + {:ok, user} + + {:error, %OAuth2.Response{status_code: 404}} -> + {:error, "User not found"} + + other -> + Logger.error(["Error while getting user by id: ", inspect(other)]) + :error + end + end + end + + defp find_users_by_email(email) do + case get_m2m_jwt() do + token when is_binary(token) -> + client = OAuth.client(token: token) + email = URI.encode(email) + + case Client.get(client, @users_path, [], + params: %{"q" => ~s(email:"#{email}" OR user_metadata.email:"#{email}")} + ) do + {:ok, %OAuth2.Response{status_code: 200, body: users}} when is_list(users) -> + {:ok, users} + + {:error, %OAuth2.Response{status_code: 403, body: %{"errorCode" => "insufficient_scope"} = body}} -> + Logger.error(["Failed to get web3 user. Insufficient scope: ", inspect(body)]) + {:error, @misconfiguration_detected} + + other -> + Logger.error(["Error while getting web3 user: ", inspect(other)]) + :error + end + + nil -> + Logger.error("Failed to get M2M JWT") + {:error, @misconfiguration_detected} + end + end + + defp maybe_link_email_and_get_auth(%{"email" => email, "user_id" => "email|" <> identity_id = user_id} = user) do + case get_m2m_jwt() do + token when is_binary(token) -> + client = OAuth.client(token: token) + + case Client.get(client, @users_path, [], + params: %{"q" => ~s(email:"#{URI.encode(email)}" AND NOT user_id:"#{URI.encode(user_id)}")} + ) do + {:ok, %OAuth2.Response{status_code: 200, body: []}} -> + {:ok, create_auth(user)} + + {:ok, %OAuth2.Response{status_code: 200, body: [%{"user_id" => primary_user_id} = user]}} -> + link_users(primary_user_id, identity_id, "email") + maybe_verify_email(user) + {:ok, create_auth(user)} + + {:ok, %OAuth2.Response{status_code: 200, body: users}} when is_list(users) and length(users) > 1 -> + merge_email_users(users, identity_id, "email") + + {:error, %OAuth2.Response{status_code: 403, body: %{"errorCode" => "insufficient_scope"} = body}} -> + Logger.error(["Failed to get web3 user. Insufficient scope: ", inspect(body)]) + {:error, @misconfiguration_detected} + + other -> + Logger.error(["Error while getting web3 user: ", inspect(other)]) + :error + end + + nil -> + Logger.error("Failed to get M2M JWT") + {:error, @misconfiguration_detected} + end + end + + defp maybe_link_email_and_get_auth(user) do + {:ok, create_auth(user)} + end + + defp merge_web3_users([primary_user | _] = users) do + identity_map = + users + |> Enum.map(& &1["user_id"]) + |> Identity.find_identities() + |> Map.new(&{&1.uid, &1}) + + users_map = users |> Enum.map(&{&1["user_id"], &1}) |> Map.new() + + case users |> Enum.map(&identity_map[&1["user_id"]]) |> Enum.reject(&is_nil(&1)) |> Account.merge() do + {{:ok, 0}, nil} -> + unless match?(%{"user_metadata" => %{"web3_address_hash" => _}}, primary_user) do + update_user_with_web3_address( + primary_user["user_id"], + primary_user |> create_auth() |> Identity.address_hash_from_auth() + ) + end + + {:ok, primary_user} + + {{:ok, _}, primary_identity} -> + primary_user_from_db = users_map[primary_identity.uid] + + unless match?(%{"user_metadata" => %{"web3_address_hash" => _}}, primary_user_from_db) do + update_user_with_web3_address( + primary_user_from_db["user_id"], + primary_user_from_db |> create_auth() |> Identity.address_hash_from_auth() + ) + end + + {:ok, primary_user_from_db} + + error -> + Logger.error(["Error while merging users with the same address: ", inspect(error)]) + :error + end + end + + defp merge_email_users([primary_user | _] = users, identity_id_to_link, provider_for_linking) do + identity_map = + users + |> Enum.map(& &1["user_id"]) + |> Identity.find_identities() + |> Map.new(&{&1.uid, &1}) + + users_map = users |> Enum.map(&{&1["user_id"], &1}) |> Map.new() + + case users |> Enum.map(&identity_map[&1["user_id"]]) |> Enum.reject(&is_nil(&1)) |> Account.merge() do + {{:ok, 0}, nil} -> + link_users(primary_user["user_id"], identity_id_to_link, provider_for_linking) + maybe_verify_email(primary_user) + {:ok, create_auth(primary_user)} + + {{:ok, _}, primary_identity} -> + link_users(primary_identity.uid, identity_id_to_link, provider_for_linking) + maybe_verify_email(users_map[primary_identity.uid]) + {:ok, create_auth(users_map[primary_identity.uid])} + + error -> + Logger.error(["Error while merging users with the same email: ", inspect(error)]) + :error + end + end + + defp maybe_verify_email(%{"email_verified" => false, "user_id" => user_id}) do + with token when is_binary(token) <- get_m2m_jwt(), + client = OAuth.client(token: token), + body = %{"email_verified" => true}, + {:ok, %OAuth2.Response{status_code: 200, body: _user}} <- + Client.patch(client, "#{@users_path}/#{URI.encode(user_id)}", body, @json_content_type) do + :ok + else + error -> handle_common_errors(error, "Failed to patch email_verified to true") + end + end + + defp maybe_verify_email(_), do: :ok + + defp cache_nonce_for_address(nonce, address) do + case Redix.command(:redix, ["SET", cookie_key(address <> "siwe_nonce"), nonce, "EX", 300]) do + {:ok, _} -> {:ok, nonce} + err -> err + end + end + + defp find_or_create_web3_user(address, signature) do + case find_users_by_web3_address(address) do + {:ok, [%{"user_metadata" => %{"web3_address_hash" => ^address}} = user]} -> + {:ok, user} + + {:ok, [%{"user_id" => user_id}]} -> + update_user_with_web3_address(user_id, address) + + {:ok, []} -> + create_web3_user(address, signature) + + {:ok, users} when is_list(users) and length(users) > 1 -> + merge_web3_users(users) + + other -> + other + end + end + + defp find_users_by_web3_address(address) do + with token when is_binary(token) <- get_m2m_jwt(), + client = OAuth.client(token: token), + {:ok, %OAuth2.Response{status_code: 200, body: users}} when is_list(users) <- + Client.get( + client, + @users_path, + [], + params: %{ + "q" => + ~s|user_id:*siwe*#{address} OR user_id:*Passkey*#{address} OR user_metadata.web3_address_hash:"#{address}" OR (user_id:*Passkey* AND nickname:"#{address}")| + } + ) do + {:ok, users} + else + error -> handle_common_errors(error, "Failed to search user by address") + end + end + + defp update_user_email(user_id, email) do + with token when is_binary(token) <- get_m2m_jwt(), + client = OAuth.client(token: token), + body = %{"user_metadata" => %{"email" => email}}, + {:ok, %OAuth2.Response{status_code: 200, body: user}} <- + Client.patch(client, "#{@users_path}/#{URI.encode(user_id)}", body, @json_content_type) do + {:ok, user} + else + error -> handle_common_errors(error, "Failed to update user email") + end + end + + defp update_user_with_web3_address(user_id, address) do + with token when is_binary(token) <- get_m2m_jwt(), + client = OAuth.client(token: token), + body = %{"user_metadata" => %{"web3_address_hash" => address}}, + {:ok, %OAuth2.Response{status_code: 200, body: user}} <- + Client.patch(client, "#{@users_path}/#{URI.encode(user_id)}", body, @json_content_type) do + {:ok, user} + else + error -> handle_common_errors(error, "Failed to update user address") + end + end + + defp create_web3_user(address, signature) do + with token when is_binary(token) <- get_m2m_jwt(), + client = OAuth.client(token: token), + body = %{ + username: address, + password: signature, + email_verified: true, + connection: "Username-Password-Authentication", + user_metadata: %{web3_address_hash: address} + }, + {:ok, %OAuth2.Response{status_code: 201, body: user}} <- + Client.post(client, @users_path, body, @json_content_type) do + {:ok, user} + else + {:error, + %OAuth2.Response{ + status_code: 400, + body: + %{ + "errorCode" => "invalid_body", + "message" => "Payload validation error: 'Missing required property: email'." + } = body + }} -> + Logger.error([ + "Failed to create web3 user. Need to allow users without email in Username-Password-Authentication connection: ", + inspect(body) + ]) + + {:error, @misconfiguration_detected} + + error -> + handle_common_errors(error, "Failed to create web3 user") + end + end + + defp link_users(primary_user_id, secondary_identity_id, provider) do + with token when is_binary(token) <- get_m2m_jwt(), + client = OAuth.client(token: token), + body = %{ + provider: provider, + user_id: secondary_identity_id + }, + {:ok, %OAuth2.Response{status_code: 201}} <- + Client.post(client, "#{@users_path}/#{URI.encode(primary_user_id)}/identities", body, @json_content_type) do + :ok + else + error -> handle_common_errors(error, "Failed to link accounts") + end + end + + defp create_auth(user) do + conn_stub = %{private: %{auth0_user: user, auth0_token: nil}} + + %Auth{ + uid: user["user_id"], + provider: :auth0, + strategy: Auth0, + info: Auth0.info(conn_stub), + credentials: %Auth.Credentials{}, + extra: Auth0.extra(conn_stub) + } + end + + defp put_client_id_and_secret(map) do + auth0_config = Application.get_env(:ueberauth, OAuth) + + Map.merge( + map, + %{ + client_id: auth0_config[:client_id], + client_secret: auth0_config[:client_secret] + } + ) + end + + defp handle_common_errors(error, error_msg) do + case error do + nil -> + Logger.error("Failed to get M2M JWT") + {:error, @misconfiguration_detected} + + {:error, %OAuth2.Response{status_code: 403, body: %{"errorCode" => "insufficient_scope"} = body}} -> + Logger.error(["#{error_msg}. Insufficient scope: ", inspect(body)]) + {:error, @misconfiguration_detected} + + other -> + Logger.error(["#{error_msg}: ", inspect(other)]) + :error + end + 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 index 8d0a9fd..c51f590 100644 --- a/apps/explorer/lib/explorer/third_party_integrations/noves_fi.ex +++ b/apps/explorer/lib/explorer/third_party_integrations/noves_fi.ex @@ -3,6 +3,8 @@ defmodule Explorer.ThirdPartyIntegrations.NovesFi do Module for Noves.Fi API integration https://blockscout.noves.fi/swagger/index.html """ + require Logger + alias Explorer.Helper alias Explorer.Utility.Microservice @@ -33,7 +35,11 @@ defmodule Explorer.ThirdPartyIntegrations.NovesFi do {:ok, %HTTPoison.Response{status_code: status, body: body}} -> {Helper.decode_json(body), status} - _ -> + {:error, reason} -> + Logger.error(fn -> + ["Error while requesting Noves.Fi API endpoint #{url}. The reason is: ", inspect(reason)] + end) + {nil, 500} end end @@ -47,33 +53,37 @@ defmodule Explorer.ThirdPartyIntegrations.NovesFi do {:ok, %HTTPoison.Response{status_code: status, body: body}} -> {Helper.decode_json(body), status} - _ -> + {:error, reason} -> + Logger.error(fn -> + ["Error while requesting Noves.Fi API endpoint #{url}. The reason is: ", inspect(reason)] + end) + {nil, 500} end end @doc """ - Noves.fi /evm/{chain}/tx/{txHash} endpoint + Noves.fi /evm/:chain/tx/:transaction_hash endpoint """ - @spec tx_url(String.t()) :: String.t() - def tx_url(transaction_hash_string) do + @spec transaction_url(String.t()) :: String.t() + def transaction_url(transaction_hash_string) do "#{base_url()}/evm/#{chain_name()}/tx/#{transaction_hash_string}" end @doc """ - Noves.fi /evm/{chain}/describeTxs endpoint + Noves.fi /evm/:chain/describeTxs endpoint """ - @spec describe_txs_url() :: String.t() - def describe_txs_url do + @spec describe_transactions_url() :: String.t() + def describe_transactions_url do "#{base_url()}/evm/#{chain_name()}/describeTxs" end @doc """ - Noves.fi /evm/{chain}/txs/{accountAddress} endpoint + Noves.fi /evm/:chain/transactions/:address_hash 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}" + @spec address_transactions_url(String.t()) :: String.t() + def address_transactions_url(address_hash_string) do + "#{base_url()}/evm/#{chain_name()}/transactions/#{address_hash_string}" end defp base_url do diff --git a/apps/explorer/lib/explorer/third_party_integrations/sourcify.ex b/apps/explorer/lib/explorer/third_party_integrations/sourcify.ex index 5604324..d23491a 100644 --- a/apps/explorer/lib/explorer/third_party_integrations/sourcify.ex +++ b/apps/explorer/lib/explorer/third_party_integrations/sourcify.ex @@ -399,7 +399,7 @@ defmodule Explorer.ThirdPartyIntegrations.Sourcify do trimmed_path = path |> String.split("/") - |> Enum.slice(9..-1) + |> Enum.slice(9..-1//-1) |> Enum.join("/") %{ diff --git a/apps/explorer/lib/explorer/third_party_integrations/xname.ex b/apps/explorer/lib/explorer/third_party_integrations/xname.ex new file mode 100644 index 0000000..aa86995 --- /dev/null +++ b/apps/explorer/lib/explorer/third_party_integrations/xname.ex @@ -0,0 +1,50 @@ +defmodule Explorer.ThirdPartyIntegrations.Xname do + @moduledoc """ + Module for proxying xname https://xname.app/ API endpoints + """ + + require Logger + + alias Explorer.Helper + alias Explorer.Utility.Microservice + + @recv_timeout 60_000 + + @doc """ + Proxy request to XName API endpoints + """ + @spec api_request(String.t(), Plug.Conn.t(), atom()) :: {any(), integer()} + def api_request(url, conn, method \\ :get) + + def api_request(url, _conn, :get) do + headers = [{"x-api-key", api_key()}] + + case HTTPoison.get(url, headers, recv_timeout: @recv_timeout) do + {:ok, %HTTPoison.Response{status_code: status, body: body}} -> + {Helper.decode_json(body), status} + + {:error, reason} -> + Logger.error(fn -> + ["Error while requesting XName app API endpoint #{url}. The reason is: ", inspect(reason)] + end) + + {nil, 500} + end + end + + @doc """ + https://gateway.xname.app/xhs/level/:address_hash endpoint + """ + @spec address_url(String.t()) :: String.t() + def address_url(address_hash_string) do + "#{base_url()}/xhs/level/#{address_hash_string}" + end + + defp base_url do + Microservice.base_url(__MODULE__) + end + + defp api_key do + Application.get_env(:explorer, __MODULE__)[:api_key] + end +end diff --git a/apps/explorer/lib/explorer/third_party_integrations/zerion.ex b/apps/explorer/lib/explorer/third_party_integrations/zerion.ex index 2822e16..7d129c3 100644 --- a/apps/explorer/lib/explorer/third_party_integrations/zerion.ex +++ b/apps/explorer/lib/explorer/third_party_integrations/zerion.ex @@ -3,6 +3,8 @@ defmodule Explorer.ThirdPartyIntegrations.Zerion do Module for Zerion API integration https://developers.zerion.io/reference """ + require Logger + alias Explorer.Helper alias Explorer.Utility.Microservice @@ -11,7 +13,7 @@ defmodule Explorer.ThirdPartyIntegrations.Zerion do @doc """ Proxy request to Zerion API endpoints """ - @spec api_request(String.t(), Plug.Conn.t(), :get | :post_transactions) :: {any(), integer()} + @spec api_request(String.t(), Plug.Conn.t(), atom()) :: {any(), integer()} def api_request(url, conn, method \\ :get) def api_request(url, _conn, :get) do @@ -22,13 +24,17 @@ defmodule Explorer.ThirdPartyIntegrations.Zerion do {:ok, %HTTPoison.Response{status_code: status, body: body}} -> {Helper.decode_json(body), status} - _ -> + {:error, reason} -> + Logger.error(fn -> + ["Error while requesting Zerion API endpoint #{url}. The reason is: ", inspect(reason)] + end) + {nil, 500} end end @doc """ - Zerion /wallets/{accountAddress}/portfolio endpoint + Zerion /wallets/:address_hash/portfolio endpoint """ @spec wallet_portfolio_url(String.t()) :: String.t() def wallet_portfolio_url(address_hash_string) do diff --git a/apps/explorer/lib/explorer/token/metadata_retriever.ex b/apps/explorer/lib/explorer/token/metadata_retriever.ex index 959add1..9381fe9 100644 --- a/apps/explorer/lib/explorer/token/metadata_retriever.ex +++ b/apps/explorer/lib/explorer/token/metadata_retriever.ex @@ -13,8 +13,8 @@ defmodule Explorer.Token.MetadataRetriever do @no_uri_error "no uri" @vm_execution_error "VM execution error" - @ipfs_protocol "ipfs://" @invalid_base64_data "invalid data:application/json;base64" + @default_headers [{"User-Agent", "blockscout-6.9.2"}] # https://eips.ethereum.org/EIPS/eip-1155#metadata @erc1155_token_id_placeholder "{id}" @@ -476,12 +476,12 @@ defmodule Explorer.Token.MetadataRetriever do gateway_url_param_value = ipfs_params[:gateway_url_param_value] if gateway_url_param_key && gateway_url_param_value do - [{gateway_url_param_key, gateway_url_param_value}] + [{gateway_url_param_key, gateway_url_param_value} | @default_headers] else - [] + @default_headers end else - [] + @default_headers end end @@ -515,31 +515,11 @@ defmodule Explorer.Token.MetadataRetriever do end end - # CIDv0 IPFS links # https://docs.ipfs.tech/concepts/content-addressing/#version-0-v0 - defp fetch_json_from_uri({:ok, ["Qm" <> _ = result]}, _, token_id, hex_token_id, from_base_uri?) do - if String.length(result) == 46 do - ipfs? = true - fetch_json_from_uri({:ok, [ipfs_link(result)]}, ipfs?, token_id, hex_token_id, from_base_uri?) - else - Logger.warning(["Unknown metadata format result #{inspect(result)}."], fetcher: :token_instances) - - {:error, truncate_error(result)} - end - end - defp fetch_json_from_uri({:ok, ["'" <> token_uri]}, ipfs?, token_id, hex_token_id, from_base_uri?) do token_uri = token_uri |> String.split("'") |> List.first() fetch_metadata_inner(token_uri, ipfs?, token_id, hex_token_id, from_base_uri?) end - defp fetch_json_from_uri({:ok, ["http://" <> _ = token_uri]}, ipfs?, token_id, hex_token_id, from_base_uri?) do - fetch_metadata_inner(token_uri, ipfs?, token_id, hex_token_id, from_base_uri?) - end - - defp fetch_json_from_uri({:ok, ["https://" <> _ = token_uri]}, ipfs?, token_id, hex_token_id, from_base_uri?) do - fetch_metadata_inner(token_uri, ipfs?, token_id, hex_token_id, from_base_uri?) - end - defp fetch_json_from_uri( {:ok, [type = "data:application/json;utf8," <> json]}, ipfs?, @@ -587,35 +567,63 @@ defmodule Explorer.Token.MetadataRetriever do {:error, @invalid_base64_data} end - defp fetch_json_from_uri({:ok, ["#{@ipfs_protocol}ipfs/" <> right]}, _ipfs?, _token_id, hex_token_id, _from_base_uri?) do - fetch_from_ipfs(right, hex_token_id) + defp fetch_json_from_uri({:ok, [token_uri_string]}, ipfs?, token_id, hex_token_id, from_base_uri?) do + fetch_from_ipfs?(token_uri_string, ipfs?, token_id, hex_token_id, from_base_uri?) end - defp fetch_json_from_uri({:ok, ["ipfs/" <> right]}, _ipfs?, _token_id, hex_token_id, _from_base_uri?) do - fetch_from_ipfs(right, hex_token_id) - end + defp fetch_json_from_uri(uri, _ipfs?, _token_id, _hex_token_id, _from_base_uri?) do + Logger.warning(["Unknown metadata uri format #{inspect(uri)}."], fetcher: :token_instances) - defp fetch_json_from_uri({:ok, [@ipfs_protocol <> right]}, _ipfs?, _token_id, hex_token_id, _from_base_uri?) do - fetch_from_ipfs(right, hex_token_id) + {:error, "unknown metadata uri format"} end - defp fetch_json_from_uri({:ok, [json]}, _ipfs?, _token_id, hex_token_id, _from_base_uri?) do - json = ExplorerHelper.decode_json(json, true) + # credo:disable-for-next-line Credo.Check.Refactor.CyclomaticComplexity + defp fetch_from_ipfs?(token_uri_string, ipfs?, token_id, hex_token_id, from_base_uri?) do + case URI.parse(token_uri_string) do + %URI{scheme: "ipfs", host: host, path: path} -> + resource_id = + if host == "ipfs" do + "/" <> resource_id = path + resource_id + else + # credo:disable-for-next-line + if is_nil(path), do: host, else: host <> path + end + + fetch_from_ipfs(resource_id, hex_token_id) + + %URI{scheme: _, path: "/ipfs/" <> resource_id} -> + fetch_from_ipfs(resource_id, hex_token_id) + + %URI{scheme: _, path: "ipfs/" <> resource_id} -> + fetch_from_ipfs(resource_id, hex_token_id) + + %URI{scheme: scheme} when not is_nil(scheme) -> + fetch_metadata_inner(token_uri_string, ipfs?, token_id, hex_token_id, from_base_uri?) + + %URI{path: path} -> + case path do + "Qm" <> <<_::binary-size(44)>> = resource_id -> + fetch_from_ipfs(resource_id, hex_token_id) - check_type(json, hex_token_id) + # todo: rewrite for strict CID v1 support + "bafybe" <> _ = resource_id -> + fetch_from_ipfs(resource_id, hex_token_id) + + _ -> + json = ExplorerHelper.decode_json(token_uri_string, true) + + check_type(json, hex_token_id) + end + end rescue e -> - Logger.warning(["Unknown metadata format #{inspect(json)}.", Exception.format(:error, e, __STACKTRACE__)], + Logger.warning( + ["Unknown metadata format #{inspect(token_uri_string)}.", Exception.format(:error, e, __STACKTRACE__)], fetcher: :token_instances ) - {:error, "invalid json"} - end - - defp fetch_json_from_uri(uri, _ipfs?, _token_id, _hex_token_id, _from_base_uri?) do - Logger.warning(["Unknown metadata uri format #{inspect(uri)}."], fetcher: :token_instances) - - {:error, "unknown metadata uri format"} + {:error, "invalid token_uri_string"} end defp fetch_json_from_json_string(json, ipfs?, token_id, hex_token_id, from_base_uri?, type) do @@ -663,7 +671,7 @@ defmodule Explorer.Token.MetadataRetriever do end defp fetch_metadata_from_uri_request(uri, hex_token_id, ipfs?) do - headers = if ipfs?, do: ipfs_headers(), else: [] + headers = if ipfs?, do: ipfs_headers(), else: @default_headers case Application.get_env(:explorer, :http_adapter).get(uri, headers, recv_timeout: 30_000, diff --git a/apps/explorer/lib/explorer/utility/address_contract_code_fetch_attempt.ex b/apps/explorer/lib/explorer/utility/address_contract_code_fetch_attempt.ex index 89f5672..167584a 100644 --- a/apps/explorer/lib/explorer/utility/address_contract_code_fetch_attempt.ex +++ b/apps/explorer/lib/explorer/utility/address_contract_code_fetch_attempt.ex @@ -23,9 +23,17 @@ defmodule Explorer.Utility.AddressContractCodeFetchAttempt do end @doc """ - Gets retries number and updated_at for the Explorer.Chain.Address + Retrieves the number of retries and the last update timestamp for a given address. + + ## Parameters + - `address_hash`: The address to query. + + ## Returns + - `{retries_number, updated_at}`: A tuple containing the number of retries and + the last update timestamp. + - `nil`: If no record is found for the given address. """ - @spec get_retries_number(Hash.Address.t()) :: {non_neg_integer(), DateTime.t()} + @spec get_retries_number(Hash.Address.t()) :: {non_neg_integer(), DateTime.t()} | nil def get_retries_number(address_hash) do __MODULE__ |> where([address_contract_code_fetch_attempt], address_contract_code_fetch_attempt.address_hash == ^address_hash) @@ -37,7 +45,15 @@ defmodule Explorer.Utility.AddressContractCodeFetchAttempt do end @doc """ - Deletes row from address_contract_code_fetch_attempts table for the given address + Deletes the entry from the `address_contract_code_fetch_attempts` table that corresponds to the provided address hash. + + ## Parameters + - `address_hash`: The `t:Explorer.Chain.Hash.Address.t/0` of the address + whose fetch attempt record should be deleted. + + ## Returns + A tuple `{count, nil}`, where `count` is the number of records deleted + (typically 1 if the record existed, 0 otherwise). """ @spec delete(Hash.Address.t()) :: {non_neg_integer(), nil} def delete(address_hash) do @@ -47,14 +63,13 @@ defmodule Explorer.Utility.AddressContractCodeFetchAttempt do end @doc """ - Inserts the number of retries for fetching contract code for a given address. + Inserts the number of retries for fetching contract code for a given address. - ## Parameters + ## Parameters - `address_hash` - The hash of the address for which the retries number is to be inserted. - ## Returns + ## Returns The result of the insertion operation. - """ @spec insert_retries_number(Hash.Address.t()) :: {non_neg_integer(), nil | [term()]} def insert_retries_number(address_hash) do diff --git a/apps/explorer/lib/explorer/utility/missing_block_range.ex b/apps/explorer/lib/explorer/utility/missing_block_range.ex index 9bcc6c9..16b37b2 100644 --- a/apps/explorer/lib/explorer/utility/missing_block_range.ex +++ b/apps/explorer/lib/explorer/utility/missing_block_range.ex @@ -55,7 +55,7 @@ defmodule Explorer.Utility.MissingBlockRange do |> save_batch() end - def save_range(from..to) do + def save_range(from..to//_) do min_number = min(from, to) max_number = max(from, to) @@ -84,7 +84,7 @@ defmodule Explorer.Utility.MissingBlockRange do end end - def delete_range(from..to) do + def delete_range(from..to//_) do min_number = min(from, to) max_number = max(from, to) @@ -294,7 +294,7 @@ defmodule Explorer.Utility.MissingBlockRange do number, nil -> {:cont, number..number} - number, first..last when number == last + 1 -> + number, first..last//_ when number == last + 1 -> {:cont, first..number} number, range -> diff --git a/apps/explorer/lib/fetch_celo_core_contracts.ex b/apps/explorer/lib/fetch_celo_core_contracts.ex index a1a1288..6d72035 100644 --- a/apps/explorer/lib/fetch_celo_core_contracts.ex +++ b/apps/explorer/lib/fetch_celo_core_contracts.ex @@ -156,10 +156,10 @@ defmodule Mix.Tasks.FetchCeloCoreContracts do IO.puts("CELO_CORE_CONTRACTS=#{core_contracts_json}") end - defp fetch_logs_by_chunks(from_block..to_block, requests_func, json_rpc_named_arguments) do + defp fetch_logs_by_chunks(from_block..to_block//_, requests_func, json_rpc_named_arguments) do from_block..to_block |> IndexerHelper.range_chunk_every(@chunk_size) - |> Enum.reduce([], fn chunk_start..chunk_end, acc -> + |> Enum.reduce([], fn chunk_start..chunk_end//_, acc -> IndexerHelper.log_blocks_chunk_handling(chunk_start, chunk_end, 0, to_block, nil, :L1) requests = requests_func.(chunk_start, chunk_end) diff --git a/apps/explorer/lib/test_helper.ex b/apps/explorer/lib/test_helper.ex index e82a742..bc67463 100644 --- a/apps/explorer/lib/test_helper.ex +++ b/apps/explorer/lib/test_helper.ex @@ -89,6 +89,43 @@ defmodule Explorer.TestHelper do end) end + def mock_eip_2535_storage_pointer_request( + mox, + error?, + resp \\ "0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000" + ) do + response = + if error?, + do: {:error, "error"}, + else: + {:ok, + [ + %{ + id: 0, + jsonrpc: "2.0", + result: resp + } + ]} + + expect(mox, :json_rpc, fn [ + %{ + id: 0, + jsonrpc: "2.0", + method: "eth_call", + params: [ + %{ + data: "0x52ef6b2c", + to: _ + }, + "latest" + ] + } + ], + _options -> + response + end) + end + def get_eip1967_implementation_non_zero_address(address_hash_string) do EthereumJSONRPC.Mox |> mock_logic_storage_pointer_request(false) @@ -102,6 +139,7 @@ defmodule Explorer.TestHelper do |> mock_beacon_storage_pointer_request(false) |> mock_oz_storage_pointer_request(false) |> mock_eip_1822_storage_pointer_request(false) + |> mock_eip_2535_storage_pointer_request(false) end def get_eip1967_implementation_error_response do @@ -110,6 +148,7 @@ defmodule Explorer.TestHelper do |> mock_beacon_storage_pointer_request(true) |> mock_oz_storage_pointer_request(true) |> mock_eip_1822_storage_pointer_request(true) + |> mock_eip_2535_storage_pointer_request(true) end def fetch_token_uri_mock(url, token_contract_address_hash_string) do diff --git a/apps/explorer/mix.exs b/apps/explorer/mix.exs index 1dde22e..d7110e8 100644 --- a/apps/explorer/mix.exs +++ b/apps/explorer/mix.exs @@ -24,7 +24,7 @@ defmodule Explorer.Mixfile do dialyzer: :test ], start_permanent: Mix.env() == :prod, - version: "6.8.0", + version: "6.9.2", xref: [exclude: [BlockScoutWeb.Routers.WebRouter.Helpers, Indexer.Helper]] ] end @@ -110,7 +110,7 @@ defmodule Explorer.Mixfile do # `:spandex` tracing of `:ecto` {:spandex_ecto, "~> 0.7.0"}, # Attach `:prometheus_ecto` to `:ecto` - {:telemetry, "~> 1.2.1"}, + {:telemetry, "~> 1.3.0"}, # `Timex.Duration` for `Explorer.Counters.AverageBlockTime.average_block_time/0` {:timex, "~> 3.7.1"}, {:con_cache, "~> 1.0"}, @@ -122,7 +122,13 @@ defmodule Explorer.Mixfile do {:logger_json, "~> 5.1"}, {:typed_ecto_schema, "~> 0.4.1", runtime: false}, {:ueberauth, "~> 0.7"}, - {:recon, "~> 2.5"} + {:recon, "~> 2.5"}, + {:varint, "~> 1.4"}, + {:blake2, "~> 1.0"}, + {:ueberauth_auth0, "~> 2.0"}, + {:oauth2, "~> 2.0"}, + {:siwe, github: "royal-markets/siwe-ex", ref: "51c9c08240eb7eea3c35693011f8d260cd9bb3be"}, + {:joken, "~> 2.6"} ] end diff --git a/apps/explorer/package-lock.json b/apps/explorer/package-lock.json index e62ea29..64c4a8c 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.26" + "solc": "0.8.27" }, "engines": { "node": "18.x", @@ -76,9 +76,9 @@ } }, "node_modules/solc": { - "version": "0.8.26", - "resolved": "https://registry.npmjs.org/solc/-/solc-0.8.26.tgz", - "integrity": "sha512-yiPQNVf5rBFHwN6SIf3TUUvVAFKcQqmSUFeq+fb6pNRCo0ZCgpYOZDi3BVoezCPIAcKrVYd/qXlBLUP9wVrZ9g==", + "version": "0.8.27", + "resolved": "https://registry.npmjs.org/solc/-/solc-0.8.27.tgz", + "integrity": "sha512-BNxMol2tUAbkH7HKlXBcBqrGi2aqgv+uMHz26mJyTtlVgWmBA4ktiw0qVKHfkjf2oaHbwtbtaSeE2dhn/gTAKw==", "dependencies": { "command-exists": "^1.2.8", "commander": "^8.1.0", @@ -144,9 +144,9 @@ "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==" }, "solc": { - "version": "0.8.26", - "resolved": "https://registry.npmjs.org/solc/-/solc-0.8.26.tgz", - "integrity": "sha512-yiPQNVf5rBFHwN6SIf3TUUvVAFKcQqmSUFeq+fb6pNRCo0ZCgpYOZDi3BVoezCPIAcKrVYd/qXlBLUP9wVrZ9g==", + "version": "0.8.27", + "resolved": "https://registry.npmjs.org/solc/-/solc-0.8.27.tgz", + "integrity": "sha512-BNxMol2tUAbkH7HKlXBcBqrGi2aqgv+uMHz26mJyTtlVgWmBA4ktiw0qVKHfkjf2oaHbwtbtaSeE2dhn/gTAKw==", "requires": { "command-exists": "^1.2.8", "commander": "^8.1.0", diff --git a/apps/explorer/package.json b/apps/explorer/package.json index effecd6..9389c1f 100644 --- a/apps/explorer/package.json +++ b/apps/explorer/package.json @@ -13,6 +13,6 @@ }, "scripts": {}, "dependencies": { - "solc": "0.8.26" + "solc": "0.8.27" } } 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 index 346c9ec..176b65c 100644 --- a/apps/explorer/priv/account/migrations/20231207201701_add_watchlist_id_column.exs +++ b/apps/explorer/priv/account/migrations/20231207201701_add_watchlist_id_column.exs @@ -3,7 +3,7 @@ defmodule Explorer.Repo.Account.Migrations.AddWatchlistIdColumn do def change do execute(""" - ALTER TABLE public.account_watchlist_notifications + ALTER TABLE account_watchlist_notifications DROP CONSTRAINT account_watchlist_notifications_watchlist_address_id_fkey; """) diff --git a/apps/explorer/priv/account/migrations/20240913194307_account_v2.exs b/apps/explorer/priv/account/migrations/20240913194307_account_v2.exs new file mode 100644 index 0000000..824f7f6 --- /dev/null +++ b/apps/explorer/priv/account/migrations/20240913194307_account_v2.exs @@ -0,0 +1,50 @@ +defmodule Explorer.Repo.Account.Migrations.AccountV2 do + use Ecto.Migration + + def change do + alter table(:account_identities) do + remove(:name) + remove(:nickname) + add(:otp_sent_at, :"timestamp without time zone", null: true) + end + + alter table(:account_custom_abis) do + add(:user_created, :boolean, default: true) + end + + alter table(:account_tag_addresses) do + add(:user_created, :boolean, default: true) + end + + alter table(:account_tag_transactions) do + add(:user_created, :boolean, default: true) + end + + alter table(:account_watchlist_addresses) do + add(:user_created, :boolean, default: true) + end + + drop_if_exists(unique_index(:account_custom_abis, [:identity_id, :address_hash_hash])) + drop_if_exists(unique_index(:account_tag_addresses, [:identity_id, :address_hash_hash])) + drop_if_exists(unique_index(:account_tag_transactions, [:identity_id, :tx_hash_hash])) + + drop_if_exists( + unique_index(:account_watchlist_addresses, [:watchlist_id, :address_hash_hash], + name: "unique_watchlist_id_address_hash_hash_index" + ) + ) + + create(unique_index(:account_custom_abis, [:identity_id, :address_hash_hash], where: "user_created = true")) + + create(unique_index(:account_tag_addresses, [:identity_id, :address_hash_hash], where: "user_created = true")) + + create(unique_index(:account_tag_transactions, [:identity_id, :tx_hash_hash], where: "user_created = true")) + + create( + unique_index(:account_watchlist_addresses, [:watchlist_id, :address_hash_hash], + name: "unique_watchlist_id_address_hash_hash_index", + where: "user_created = true" + ) + ) + end +end diff --git a/apps/explorer/priv/account/migrations/20241015091450_rename_tx_hash_field.exs b/apps/explorer/priv/account/migrations/20241015091450_rename_tx_hash_field.exs new file mode 100644 index 0000000..61e1b43 --- /dev/null +++ b/apps/explorer/priv/account/migrations/20241015091450_rename_tx_hash_field.exs @@ -0,0 +1,9 @@ +defmodule Explorer.Repo.Account.Migrations.RenameTxHashField do + use Ecto.Migration + + def change do + rename(table(:account_tag_transactions), :tx_hash, to: :transaction_hash) + rename(table(:account_tag_transactions), :tx_hash_hash, to: :transaction_hash_hash) + rename(table(:account_watchlist_notifications), :tx_fee, to: :transaction_fee) + end +end diff --git a/apps/explorer/priv/arbitrum/migrations/20241015093220_rename_tx_hash_field_arbitrum.exs b/apps/explorer/priv/arbitrum/migrations/20241015093220_rename_tx_hash_field_arbitrum.exs new file mode 100644 index 0000000..2e6aa50 --- /dev/null +++ b/apps/explorer/priv/arbitrum/migrations/20241015093220_rename_tx_hash_field_arbitrum.exs @@ -0,0 +1,7 @@ +defmodule Explorer.Repo.Arbitrum.Migrations.RenameTxHashFieldArbitrum do + use Ecto.Migration + + def change do + rename(table(:arbitrum_batch_l2_transactions), :tx_hash, to: :transaction_hash) + end +end diff --git a/apps/explorer/priv/blackfort/migrations/20240910112251_add_blackfort_validators.exs b/apps/explorer/priv/blackfort/migrations/20240910112251_add_blackfort_validators.exs new file mode 100644 index 0000000..d1d2b55 --- /dev/null +++ b/apps/explorer/priv/blackfort/migrations/20240910112251_add_blackfort_validators.exs @@ -0,0 +1,20 @@ +defmodule Explorer.Repo.Blackfort.Migrations.AddBlackfortValidators do + use Ecto.Migration + + def change do + create table(:validators_blackfort, primary_key: false) do + add(:address_hash, :bytea, null: false, primary_key: true) + add(:name, :string) + add(:commission, :smallint) + add(:self_bonded_amount, :numeric, precision: 100) + add(:delegated_amount, :numeric, precision: 100) + add(:slashing_status_is_slashed, :boolean, default: false) + add(:slashing_status_by_block, :bigint) + add(:slashing_status_multiplier, :integer) + + timestamps() + end + + create_if_not_exists(index(:validators_blackfort, ["address_hash ASC"])) + end +end diff --git a/apps/explorer/priv/celo/migrations/20240830094610_add_account_address_and_group_address_to_primary_key.exs b/apps/explorer/priv/celo/migrations/20240830094610_add_account_address_and_group_address_to_primary_key.exs new file mode 100644 index 0000000..d03857b --- /dev/null +++ b/apps/explorer/priv/celo/migrations/20240830094610_add_account_address_and_group_address_to_primary_key.exs @@ -0,0 +1,33 @@ +defmodule Explorer.Repo.Celo.Migrations.AddAccountAddressAndGroupAddressToPrimaryKey do + use Ecto.Migration + + def up do + execute(""" + ALTER TABLE celo_validator_group_votes + DROP CONSTRAINT celo_validator_group_votes_pkey + """) + + execute(""" + ALTER TABLE celo_validator_group_votes + ADD CONSTRAINT celo_validator_group_votes_pkey + PRIMARY KEY ( + transaction_hash, + account_address_hash, + group_address_hash + ) + """) + end + + def down do + execute(""" + ALTER TABLE celo_validator_group_votes + DROP CONSTRAINT celo_validator_group_votes_pkey + """) + + execute(""" + ALTER TABLE celo_validator_group_votes + ADD CONSTRAINT celo_validator_group_votes_pkey + PRIMARY KEY transaction_hash + """) + end +end diff --git a/apps/explorer/priv/celo/migrations/20241029131554_modify_collated_gas_price_constraint.exs b/apps/explorer/priv/celo/migrations/20241029131554_modify_collated_gas_price_constraint.exs new file mode 100644 index 0000000..3162375 --- /dev/null +++ b/apps/explorer/priv/celo/migrations/20241029131554_modify_collated_gas_price_constraint.exs @@ -0,0 +1,27 @@ +defmodule Explorer.Repo.Celo.Migrations.ModifyCollatedGasPriceConstraint do + use Ecto.Migration + + def up 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 + + def down 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" + ) + ) + end +end diff --git a/apps/explorer/priv/filecoin/migrations/20240801134142_create_pending_address_operations.exs b/apps/explorer/priv/filecoin/migrations/20240801134142_create_pending_address_operations.exs new file mode 100644 index 0000000..7da8999 --- /dev/null +++ b/apps/explorer/priv/filecoin/migrations/20240801134142_create_pending_address_operations.exs @@ -0,0 +1,23 @@ +defmodule Explorer.Repo.Filecoin.Migrations.CreatePendingAddressOperations do + use Ecto.Migration + + def change do + create table(:filecoin_pending_address_operations, primary_key: false) do + add( + :address_hash, + references( + :addresses, + column: :hash, + type: :bytea, + on_delete: :delete_all + ), + null: false, + primary_key: true + ) + + add(:http_status_code, :smallint) + + timestamps() + end + end +end diff --git a/apps/explorer/priv/filecoin/migrations/20240807134138_add_chain_type_fields_to_address.exs b/apps/explorer/priv/filecoin/migrations/20240807134138_add_chain_type_fields_to_address.exs new file mode 100644 index 0000000..378e3b2 --- /dev/null +++ b/apps/explorer/priv/filecoin/migrations/20240807134138_add_chain_type_fields_to_address.exs @@ -0,0 +1,11 @@ +defmodule Explorer.Repo.Filecoin.Migrations.AddChainTypeFieldsToAddress do + use Ecto.Migration + + def change do + alter table(:addresses) do + add(:filecoin_id, :bytea) + add(:filecoin_robust, :bytea) + add(:filecoin_actor_type, :smallint) + end + end +end diff --git a/apps/explorer/priv/optimism/migrations/20241015140121_rename_tx_related_field_optimism.exs b/apps/explorer/priv/optimism/migrations/20241015140121_rename_tx_related_field_optimism.exs new file mode 100644 index 0000000..6f2b4a4 --- /dev/null +++ b/apps/explorer/priv/optimism/migrations/20241015140121_rename_tx_related_field_optimism.exs @@ -0,0 +1,7 @@ +defmodule Explorer.Repo.Optimism.Migrations.RenameTxRelatedFieldOptimism do + use Ecto.Migration + + def change do + rename(table(:transactions), :l1_tx_origin, to: :l1_transaction_origin) + 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 index 559799e..9a532bd 100644 --- 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 @@ -1,4 +1,4 @@ -defmodule Explorer.Repo.Migrations.ModifyCollatedGasPriceConstraint do +defmodule Explorer.Repo.PolygonEdge.Migrations.ModifyCollatedGasPriceConstraint do use Ecto.Migration def change do diff --git a/apps/explorer/priv/repo/migrations/20190613065856_add_tx_hash_inserted_at_index.exs b/apps/explorer/priv/repo/migrations/20190613065856_add_transaction_hash_inserted_at_index.exs similarity index 59% rename from apps/explorer/priv/repo/migrations/20190613065856_add_tx_hash_inserted_at_index.exs rename to apps/explorer/priv/repo/migrations/20190613065856_add_transaction_hash_inserted_at_index.exs index 4596b1e..6402ee9 100644 --- a/apps/explorer/priv/repo/migrations/20190613065856_add_tx_hash_inserted_at_index.exs +++ b/apps/explorer/priv/repo/migrations/20190613065856_add_transaction_hash_inserted_at_index.exs @@ -1,4 +1,4 @@ -defmodule Explorer.Repo.Migrations.AddTxHashInsertedAtIndex do +defmodule Explorer.Repo.Migrations.AddTransactionHashInsertedAtIndex do use Ecto.Migration def change do diff --git a/apps/explorer/priv/repo/migrations/20191018140054_add_pending_internal_txs_operation.exs b/apps/explorer/priv/repo/migrations/20191018140054_add_pending_internal_transactions_operation.exs similarity index 96% rename from apps/explorer/priv/repo/migrations/20191018140054_add_pending_internal_txs_operation.exs rename to apps/explorer/priv/repo/migrations/20191018140054_add_pending_internal_transactions_operation.exs index 13d46ab..d7a7234 100644 --- a/apps/explorer/priv/repo/migrations/20191018140054_add_pending_internal_txs_operation.exs +++ b/apps/explorer/priv/repo/migrations/20191018140054_add_pending_internal_transactions_operation.exs @@ -1,4 +1,4 @@ -defmodule Explorer.Repo.Migrations.AddPendingInternalTxsOperation do +defmodule Explorer.Repo.Migrations.AddPendingInternalTransactionsOperation do use Ecto.Migration def change do diff --git a/apps/explorer/priv/repo/migrations/20191203112646_internal_transactions_add_to_address_hash_index.exs b/apps/explorer/priv/repo/migrations/20191203112646_internal_transactions_add_to_address_hash_index.exs index f0ab878..bf9bc5c 100644 --- a/apps/explorer/priv/repo/migrations/20191203112646_internal_transactions_add_to_address_hash_index.exs +++ b/apps/explorer/priv/repo/migrations/20191203112646_internal_transactions_add_to_address_hash_index.exs @@ -3,15 +3,15 @@ defmodule Explorer.Repo.Migrations.InternalTransactionsAddToAddressHashIndex do def change do execute( - "CREATE INDEX IF NOT EXISTS internal_transactions_from_address_hash_partial_index on public.internal_transactions(from_address_hash, block_number DESC, transaction_index DESC, index DESC) WHERE (((type = 'call') AND (index > 0)) OR (type != 'call'));" + "CREATE INDEX IF NOT EXISTS internal_transactions_from_address_hash_partial_index on internal_transactions(from_address_hash, block_number DESC, transaction_index DESC, index DESC) WHERE (((type = 'call') AND (index > 0)) OR (type != 'call'));" ) execute( - "CREATE INDEX IF NOT EXISTS internal_transactions_to_address_hash_partial_index on public.internal_transactions(to_address_hash, block_number DESC, transaction_index DESC, index DESC) WHERE (((type = 'call') AND (index > 0)) OR (type != 'call'));" + "CREATE INDEX IF NOT EXISTS internal_transactions_to_address_hash_partial_index on internal_transactions(to_address_hash, block_number DESC, transaction_index DESC, index DESC) WHERE (((type = 'call') AND (index > 0)) OR (type != 'call'));" ) execute( - "CREATE INDEX IF NOT EXISTS internal_transactions_created_contract_address_hash_partial_index on public.internal_transactions(created_contract_address_hash, block_number DESC, transaction_index DESC, index DESC) WHERE (((type = 'call') AND (index > 0)) OR (type != 'call'));" + "CREATE INDEX IF NOT EXISTS internal_transactions_created_contract_address_hash_partial_index on internal_transactions(created_contract_address_hash, block_number DESC, transaction_index DESC, index DESC) WHERE (((type = 'call') AND (index > 0)) OR (type != 'call'));" ) drop_if_exists( diff --git a/apps/explorer/priv/repo/migrations/20200421102450_pending_txs.exs b/apps/explorer/priv/repo/migrations/20200421102450_pending_transactions.exs similarity index 89% rename from apps/explorer/priv/repo/migrations/20200421102450_pending_txs.exs rename to apps/explorer/priv/repo/migrations/20200421102450_pending_transactions.exs index 4f69981..9361999 100644 --- a/apps/explorer/priv/repo/migrations/20200421102450_pending_txs.exs +++ b/apps/explorer/priv/repo/migrations/20200421102450_pending_transactions.exs @@ -1,4 +1,4 @@ -defmodule Explorer.Repo.Migrations.PendingTxs do +defmodule Explorer.Repo.Migrations.PendingTransactions do use Ecto.Migration def up do diff --git a/apps/explorer/priv/repo/migrations/20211217201759_add_has_error_in_iternal_txs_field_to_transaction.exs b/apps/explorer/priv/repo/migrations/20211217201759_add_has_error_in_internal_txs_field_to_transaction.exs similarity index 100% rename from apps/explorer/priv/repo/migrations/20211217201759_add_has_error_in_iternal_txs_field_to_transaction.exs rename to apps/explorer/priv/repo/migrations/20211217201759_add_has_error_in_internal_txs_field_to_transaction.exs diff --git a/apps/explorer/priv/repo/migrations/20220919105140_add_method_id_index.exs b/apps/explorer/priv/repo/migrations/20220919105140_add_method_id_index.exs index 95ad385..daa9672 100644 --- a/apps/explorer/priv/repo/migrations/20220919105140_add_method_id_index.exs +++ b/apps/explorer/priv/repo/migrations/20220919105140_add_method_id_index.exs @@ -5,7 +5,7 @@ defmodule Explorer.Repo.Migrations.AddMethodIdIndex do def up do execute(""" - CREATE INDEX CONCURRENTLY IF NOT EXISTS method_id ON public.transactions USING btree (substring(input for 4)); + CREATE INDEX CONCURRENTLY IF NOT EXISTS method_id ON transactions USING btree (substring(input for 4)); """) end diff --git a/apps/explorer/priv/repo/migrations/20221126103223_add_txs_indexes.exs b/apps/explorer/priv/repo/migrations/20221126103223_add_transactions_indexes.exs similarity index 96% rename from apps/explorer/priv/repo/migrations/20221126103223_add_txs_indexes.exs rename to apps/explorer/priv/repo/migrations/20221126103223_add_transactions_indexes.exs index 241ed05..9cc38fb 100644 --- a/apps/explorer/priv/repo/migrations/20221126103223_add_txs_indexes.exs +++ b/apps/explorer/priv/repo/migrations/20221126103223_add_transactions_indexes.exs @@ -1,4 +1,4 @@ -defmodule Explorer.Repo.Migrations.AddTxsIndexes do +defmodule Explorer.Repo.Migrations.AddTransactionsIndexes do use Ecto.Migration @disable_ddl_transaction true @disable_migration_lock true diff --git a/apps/explorer/priv/repo/migrations/20230417093914_allow_nil_tx_gas_price.exs b/apps/explorer/priv/repo/migrations/20230417093914_allow_nil_transaction_gas_price.exs similarity index 82% rename from apps/explorer/priv/repo/migrations/20230417093914_allow_nil_tx_gas_price.exs rename to apps/explorer/priv/repo/migrations/20230417093914_allow_nil_transaction_gas_price.exs index 01a984c..14dbd67 100644 --- a/apps/explorer/priv/repo/migrations/20230417093914_allow_nil_tx_gas_price.exs +++ b/apps/explorer/priv/repo/migrations/20230417093914_allow_nil_transaction_gas_price.exs @@ -1,4 +1,4 @@ -defmodule Explorer.Repo.Migrations.AllowNilTxGasPrice do +defmodule Explorer.Repo.Migrations.AllowNilTransactionGasPrice do use Ecto.Migration def change do diff --git a/apps/explorer/priv/repo/migrations/20240828140638_add_token_balance_retry_fields.exs b/apps/explorer/priv/repo/migrations/20240828140638_add_token_balance_retry_fields.exs new file mode 100644 index 0000000..3d1fddf --- /dev/null +++ b/apps/explorer/priv/repo/migrations/20240828140638_add_token_balance_retry_fields.exs @@ -0,0 +1,10 @@ +defmodule Explorer.Repo.Migrations.AddTokenBalanceRetryFields do + use Ecto.Migration + + def change do + alter table(:address_token_balances) do + add(:refetch_after, :utc_datetime_usec) + add(:retries_count, :smallint) + end + end +end diff --git a/apps/explorer/priv/repo/migrations/20240830142652_add_meta_to_migrations_status.exs b/apps/explorer/priv/repo/migrations/20240830142652_add_meta_to_migrations_status.exs new file mode 100644 index 0000000..f174862 --- /dev/null +++ b/apps/explorer/priv/repo/migrations/20240830142652_add_meta_to_migrations_status.exs @@ -0,0 +1,9 @@ +defmodule Explorer.Repo.Migrations.AddMetaToMigrationsStatus do + use Ecto.Migration + + def change do + alter table(:migrations_status) do + add(:meta, :map) + end + end +end diff --git a/apps/explorer/priv/repo/migrations/20240904161254_create_signed_authorizations.exs b/apps/explorer/priv/repo/migrations/20240904161254_create_signed_authorizations.exs new file mode 100644 index 0000000..e37cd42 --- /dev/null +++ b/apps/explorer/priv/repo/migrations/20240904161254_create_signed_authorizations.exs @@ -0,0 +1,25 @@ +defmodule Explorer.Repo.Migrations.CreateSignedAuthorizations do + use Ecto.Migration + + def change do + create table(:signed_authorizations, primary_key: false) do + add(:transaction_hash, references(:transactions, column: :hash, on_delete: :delete_all, type: :bytea), + null: false, + primary_key: true + ) + + add(:index, :integer, null: false, primary_key: true) + add(:chain_id, :bigint, null: false) + add(:address, :bytea, null: false) + add(:nonce, :integer, null: false) + add(:v, :integer, null: false) + add(:r, :numeric, precision: 100, null: false) + add(:s, :numeric, precision: 100, null: false) + add(:authority, :bytea, null: true) + + timestamps(null: false, type: :utc_datetime_usec) + end + + create(index(:signed_authorizations, [:authority, :nonce])) + end +end diff --git a/apps/explorer/priv/repo/migrations/20240910095635_add_address_badges_tables.exs b/apps/explorer/priv/repo/migrations/20240910095635_add_address_badges_tables.exs new file mode 100644 index 0000000..ba7bbc7 --- /dev/null +++ b/apps/explorer/priv/repo/migrations/20240910095635_add_address_badges_tables.exs @@ -0,0 +1,14 @@ +defmodule Explorer.Repo.Migrations.AddAddressBadgesTables do + use Ecto.Migration + + def change do + create table(:scam_address_badge_mappings, primary_key: false) do + add(:address_hash, references(:addresses, column: :hash, type: :bytea, on_delete: :delete_all), + null: false, + primary_key: true + ) + + timestamps(null: false, type: :utc_datetime_usec) + end + end +end diff --git a/apps/explorer/priv/repo/migrations/20240918104231_new_proxy_type_eip7702.exs b/apps/explorer/priv/repo/migrations/20240918104231_new_proxy_type_eip7702.exs new file mode 100644 index 0000000..85c1ef8 --- /dev/null +++ b/apps/explorer/priv/repo/migrations/20240918104231_new_proxy_type_eip7702.exs @@ -0,0 +1,7 @@ +defmodule Explorer.Repo.Migrations.NewProxyTypeEip7702 do + use Ecto.Migration + + def change do + execute("ALTER TYPE proxy_type ADD VALUE 'eip7702' BEFORE 'unknown'") + end +end diff --git a/apps/explorer/priv/repo/migrations/20240923135258_reset_sanitize_missing_token_balances_migration_status.exs b/apps/explorer/priv/repo/migrations/20240923135258_reset_sanitize_missing_token_balances_migration_status.exs new file mode 100644 index 0000000..d96bab5 --- /dev/null +++ b/apps/explorer/priv/repo/migrations/20240923135258_reset_sanitize_missing_token_balances_migration_status.exs @@ -0,0 +1,7 @@ +defmodule Explorer.Repo.Migrations.ResetSanitizeMissingTokenBalancesMigrationStatus do + use Ecto.Migration + + def change do + execute("DELETE FROM migrations_status WHERE migration_name = 'sanitize_missing_token_balances'") + end +end diff --git a/apps/explorer/priv/repo/migrations/20240923173516_address_tags_add_primary_key.exs b/apps/explorer/priv/repo/migrations/20240923173516_address_tags_add_primary_key.exs new file mode 100644 index 0000000..97a9314 --- /dev/null +++ b/apps/explorer/priv/repo/migrations/20240923173516_address_tags_add_primary_key.exs @@ -0,0 +1,9 @@ +defmodule Explorer.Repo.Migrations.AddressTagsAddPrimaryKey do + use Ecto.Migration + + def change do + alter table(:address_tags) do + modify(:label, :string, null: false, primary_key: true) + end + end +end diff --git a/apps/explorer/priv/repo/migrations/20241015140214_rename_tx_related_field.exs b/apps/explorer/priv/repo/migrations/20241015140214_rename_tx_related_field.exs new file mode 100644 index 0000000..948ae16 --- /dev/null +++ b/apps/explorer/priv/repo/migrations/20241015140214_rename_tx_related_field.exs @@ -0,0 +1,7 @@ +defmodule Explorer.Repo.Migrations.RenameTxRelatedField do + use Ecto.Migration + + def change do + rename(table(:transactions), :has_error_in_internal_txs, to: :has_error_in_internal_transactions) + end +end diff --git a/apps/explorer/priv/repo/migrations/20241022133006_add_aux_types_for_duplicated_log_index_logs_migration.exs b/apps/explorer/priv/repo/migrations/20241022133006_add_aux_types_for_duplicated_log_index_logs_migration.exs new file mode 100644 index 0000000..f5684a9 --- /dev/null +++ b/apps/explorer/priv/repo/migrations/20241022133006_add_aux_types_for_duplicated_log_index_logs_migration.exs @@ -0,0 +1,30 @@ +defmodule Explorer.Repo.Migrations.AddAuxTypesForDuplicatedLogIndexLogsMigration do + use Ecto.Migration + + def up do + execute(""" + CREATE TYPE log_id AS ( + transaction_hash bytea, + block_hash bytea, + log_index integer + ); + """) + + execute(""" + CREATE TYPE nft_id AS ( + block_number bigint, + log_index integer + ); + """) + end + + def down do + execute(""" + DROP TYPE log_id; + """) + + execute(""" + DROP TYPE nft_id; + """) + end +end diff --git a/apps/explorer/priv/scroll/migrations/20240710101931_add_fee_fields.exs b/apps/explorer/priv/scroll/migrations/20240710101931_add_fee_fields.exs new file mode 100644 index 0000000..e3cbaa6 --- /dev/null +++ b/apps/explorer/priv/scroll/migrations/20240710101931_add_fee_fields.exs @@ -0,0 +1,23 @@ +defmodule Explorer.Repo.Scroll.Migrations.AddFeeFields do + use Ecto.Migration + + def change do + alter table(:transactions) do + add(:l1_fee, :numeric, precision: 100, null: true) + end + + execute( + "CREATE TYPE scroll_l1_fee_param_names AS ENUM ('overhead', 'scalar', 'commit_scalar', 'blob_scalar', 'l1_base_fee', 'l1_blob_base_fee')", + "DROP TYPE scroll_l1_fee_param_names" + ) + + create table(:scroll_l1_fee_params, primary_key: false) do + add(:block_number, :bigint, null: false, primary_key: true) + add(:tx_index, :integer, null: false, primary_key: true) + add(:name, :scroll_l1_fee_param_names, null: false, primary_key: true) + add(:value, :bigint, null: false) + + timestamps(null: false, type: :utc_datetime_usec) + end + end +end diff --git a/apps/explorer/priv/scroll/migrations/20240807114346_add_queue_index_field.exs b/apps/explorer/priv/scroll/migrations/20240807114346_add_queue_index_field.exs new file mode 100644 index 0000000..f49c6d9 --- /dev/null +++ b/apps/explorer/priv/scroll/migrations/20240807114346_add_queue_index_field.exs @@ -0,0 +1,9 @@ +defmodule Explorer.Repo.Scroll.Migrations.AddQueueIndexField do + use Ecto.Migration + + def change do + alter table(:transactions) do + add(:queue_index, :bigint, null: true) + end + end +end diff --git a/apps/explorer/priv/scroll/migrations/20240815102318_add_bridge_table.exs b/apps/explorer/priv/scroll/migrations/20240815102318_add_bridge_table.exs new file mode 100644 index 0000000..44bcd09 --- /dev/null +++ b/apps/explorer/priv/scroll/migrations/20240815102318_add_bridge_table.exs @@ -0,0 +1,24 @@ +defmodule Explorer.Repo.Scroll.Migrations.AddBridgeTable do + use Ecto.Migration + + def change do + execute( + "CREATE TYPE scroll_bridge_op_type AS ENUM ('deposit', 'withdrawal')", + "DROP TYPE scroll_bridge_op_type" + ) + + create table(:scroll_bridge, primary_key: false) do + add(:type, :scroll_bridge_op_type, null: false, primary_key: true) + add(:index, :integer, null: true) + add(:l1_transaction_hash, :bytea, null: true) + add(:l2_transaction_hash, :bytea, null: true) + add(:amount, :numeric, precision: 100, null: true) + add(:block_number, :bigint, null: true) + add(:block_timestamp, :"timestamp without time zone", null: true) + add(:message_hash, :bytea, null: false, primary_key: true) + timestamps(null: false, type: :utc_datetime_usec) + end + + create(index(:scroll_bridge, [:type, :index])) + end +end diff --git a/apps/explorer/priv/scroll/migrations/20240913114630_add_batches_tables.exs b/apps/explorer/priv/scroll/migrations/20240913114630_add_batches_tables.exs new file mode 100644 index 0000000..1391369 --- /dev/null +++ b/apps/explorer/priv/scroll/migrations/20240913114630_add_batches_tables.exs @@ -0,0 +1,40 @@ +defmodule Explorer.Repo.Scroll.Migrations.AddBatchesTables do + use Ecto.Migration + + def change do + execute( + "CREATE TYPE scroll_da_containers_types AS ENUM ('in_blob4844', 'in_calldata')", + "DROP TYPE scroll_da_containers_types" + ) + + create table(:scroll_batch_bundles, primary_key: true) do + add(:final_batch_number, :bigint, null: false) + add(:finalize_transaction_hash, :bytea, null: false) + add(:finalize_block_number, :bigint, null: false) + add(:finalize_timestamp, :"timestamp without time zone", null: false) + timestamps(null: false, type: :utc_datetime_usec) + end + + create table(:scroll_batches, primary_key: false) do + add(:number, :bigint, primary_key: true) + add(:commit_transaction_hash, :bytea, null: false) + add(:commit_block_number, :bigint, null: false) + add(:commit_timestamp, :"timestamp without time zone", null: false) + + add( + :bundle_id, + references(:scroll_batch_bundles, on_delete: :restrict, on_update: :update_all, type: :bigint), + null: true, + default: nil + ) + + add(:l2_block_range, :int8range) + add(:container, :scroll_da_containers_types, null: false) + timestamps(null: false, type: :utc_datetime_usec) + end + + create(index(:scroll_batch_bundles, :finalize_block_number)) + create(index(:scroll_batches, :commit_block_number)) + create(index(:scroll_batches, :l2_block_range)) + end +end diff --git a/apps/explorer/priv/scroll/migrations/20241016105249_rename_fields.exs b/apps/explorer/priv/scroll/migrations/20241016105249_rename_fields.exs new file mode 100644 index 0000000..304d409 --- /dev/null +++ b/apps/explorer/priv/scroll/migrations/20241016105249_rename_fields.exs @@ -0,0 +1,7 @@ +defmodule Explorer.Repo.Scroll.Migrations.RenameFields do + use Ecto.Migration + + def change do + rename(table(:scroll_l1_fee_params), :tx_index, to: :transaction_index) + end +end diff --git a/apps/explorer/priv/zk_sync/migrations/20241015093336_rename_tx_hash_field_zksync.exs b/apps/explorer/priv/zk_sync/migrations/20241015093336_rename_tx_hash_field_zksync.exs new file mode 100644 index 0000000..e62c628 --- /dev/null +++ b/apps/explorer/priv/zk_sync/migrations/20241015093336_rename_tx_hash_field_zksync.exs @@ -0,0 +1,7 @@ +defmodule Explorer.Repo.ZkSync.Migrations.RenameTxHashFieldArbitrum do + use Ecto.Migration + + def change do + rename(table(:zksync_batch_l2_transactions), :tx_hash, to: :transaction_hash) + end +end diff --git a/apps/explorer/priv/zk_sync/migrations/20241028102407_rename_tx_count_fields_zksync.exs b/apps/explorer/priv/zk_sync/migrations/20241028102407_rename_tx_count_fields_zksync.exs new file mode 100644 index 0000000..dd89c64 --- /dev/null +++ b/apps/explorer/priv/zk_sync/migrations/20241028102407_rename_tx_count_fields_zksync.exs @@ -0,0 +1,8 @@ +defmodule Explorer.Repo.ZkSync.Migrations.RenameTxCountFieldsZksync do + use Ecto.Migration + + def change do + rename(table(:zksync_transaction_batches), :l1_tx_count, to: :l1_transaction_count) + rename(table(:zksync_transaction_batches), :l2_tx_count, to: :l2_transaction_count) + end +end diff --git a/apps/explorer/priv/zk_sync/migrations/20241028102853_add_contract_code_refetched.exs b/apps/explorer/priv/zk_sync/migrations/20241028102853_add_contract_code_refetched.exs new file mode 100644 index 0000000..7f24a30 --- /dev/null +++ b/apps/explorer/priv/zk_sync/migrations/20241028102853_add_contract_code_refetched.exs @@ -0,0 +1,13 @@ +defmodule Explorer.Repo.ZkSync.Migrations.AddContractCodeRefetched do + use Ecto.Migration + + def change do + alter table(:addresses) do + add(:contract_code_refetched, :boolean, default: false) + end + + execute(""" + ALTER TABLE addresses ALTER COLUMN contract_code_refetched SET DEFAULT true; + """) + end +end diff --git a/apps/block_scout_web/test/block_scout_web/models/user_from_auth_test.exs b/apps/explorer/test/explorer/account/identity_test.exs similarity index 89% rename from apps/block_scout_web/test/block_scout_web/models/user_from_auth_test.exs rename to apps/explorer/test/explorer/account/identity_test.exs index 2d13f11..1acde2e 100644 --- a/apps/block_scout_web/test/block_scout_web/models/user_from_auth_test.exs +++ b/apps/explorer/test/explorer/account/identity_test.exs @@ -1,9 +1,7 @@ -defmodule UserFromAuthTest do +defmodule Explorer.Account.IdentityTest do use Explorer.DataCase - alias BlockScoutWeb.Models.UserFromAuth - alias Explorer.Account.Identity - alias Explorer.Account.Watchlist + alias Explorer.Account.{Identity, Watchlist} alias Explorer.Repo alias Ueberauth.Auth alias Ueberauth.Auth.Info @@ -30,12 +28,11 @@ defmodule UserFromAuthTest do uid: "github|666666" } - user_data = UserFromAuth.find_or_create(auth) + user_data = Identity.find_or_create(auth) %{ id: identity_id, email: "john@blockscout.com", - name: "John Snow", uid: "github|666666" } = Identity |> first |> Repo.account_repo().one() @@ -77,12 +74,11 @@ defmodule UserFromAuthTest do uid: "google-oauth2|666666" } - user_data = UserFromAuth.find_or_create(auth) + user_data = Identity.find_or_create(auth) %{ id: identity_id, email: "john@blockscout.com", - name: "John Snow", uid: "google-oauth2|666666" } = Identity |> first |> Repo.account_repo().one() diff --git a/apps/explorer/test/explorer/account/notifier/email_test.exs b/apps/explorer/test/explorer/account/notifier/email_test.exs index 184e7d3..7d7b8d5 100644 --- a/apps/explorer/test/explorer/account/notifier/email_test.exs +++ b/apps/explorer/test/explorer/account/notifier/email_test.exs @@ -42,7 +42,8 @@ defmodule Explorer.Account.Notifier.EmailTest do describe "composing email" do test "compose_email" do - {:ok, tx_hash} = string_to_transaction_hash("0x5d5ff210261f1b2d6e4af22ea494f428f9997d4ab614a629d4f1390004b3e80d") + {:ok, transaction_hash} = + string_to_transaction_hash("0x5d5ff210261f1b2d6e4af22ea494f428f9997d4ab614a629d4f1390004b3e80d") {:ok, from_hash} = string_to_address_hash("0x092D537737E767Dae48c28aE509f34094496f030") @@ -67,14 +68,14 @@ defmodule Explorer.Account.Notifier.EmailTest do watchlist_notification = %WatchlistNotification{ watchlist_address: watchlist_address, - transaction_hash: tx_hash, + transaction_hash: transaction_hash, from_address_hash: from_hash, to_address_hash: to_hash, direction: "incoming", method: "transfer", block_number: 24_121_177, amount: Decimal.new(1), - tx_fee: Decimal.new(210_000), + transaction_fee: Decimal.new(210_000), name: "wallet", type: "COIN" } @@ -108,6 +109,8 @@ defmodule Explorer.Account.Notifier.EmailTest do "transaction_hash" => "0x5d5ff210261f1b2d6e4af22ea494f428f9997d4ab614a629d4f1390004b3e80d", "transaction_url" => "https://eth.blockscout.com/tx/0x5d5ff210261f1b2d6e4af22ea494f428f9997d4ab614a629d4f1390004b3e80d", + "transaction_fee" => Decimal.new(210_000), + # todo: keep next line for compatibility with old version of SendGrid template. Remove it when the changes released and Sendgrid template updated. "tx_fee" => Decimal.new(210_000), "username" => "John Snow" }, diff --git a/apps/explorer/test/explorer/account/notifier/notify_test.exs b/apps/explorer/test/explorer/account/notifier/notify_test.exs index 25a7fd3..1187837 100644 --- a/apps/explorer/test/explorer/account/notifier/notify_test.exs +++ b/apps/explorer/test/explorer/account/notifier/notify_test.exs @@ -38,9 +38,9 @@ defmodule Explorer.Account.Notifier.NotifyTest do describe "notify" do test "when address not in any watchlist" do - tx = with_block(insert(:transaction)) + transaction = with_block(insert(:transaction)) - notify = Notify.call([tx]) + notify = Notify.call([transaction]) wn = WatchlistNotification @@ -60,17 +60,17 @@ defmodule Explorer.Account.Notifier.NotifyTest do _watchlist_address = Repo.preload(wa, watchlist: :identity) - tx = + transaction = %Transaction{ from_address: _from_address, to_address: _to_address, block_number: _block_number, - hash: _tx_hash + hash: _transaction_hash } = with_block(insert(:transaction, to_address: %Chain.Address{hash: address_hash})) - {_, fee} = Transaction.fee(tx, :gwei) - amount = Wei.to(tx.value, :ether) - notify = Notify.call([tx]) + {_, fee} = Transaction.fee(transaction, :gwei) + amount = Wei.to(transaction.value, :ether) + notify = Notify.call([transaction]) wn = WatchlistNotification @@ -83,7 +83,7 @@ defmodule Explorer.Account.Notifier.NotifyTest do assert wn.direction == "incoming" assert wn.method == "transfer" assert wn.subject == "Coin transaction" - assert wn.tx_fee == fee + assert wn.transaction_fee == fee assert wn.type == "COIN" end @@ -99,17 +99,17 @@ defmodule Explorer.Account.Notifier.NotifyTest do _watchlist_address = Repo.preload(wa, watchlist: :identity) - tx = + transaction = %Transaction{ from_address: _from_address, to_address: _to_address, block_number: _block_number, - hash: _tx_hash + hash: _transaction_hash } = with_block(insert(:transaction, to_address: %Chain.Address{hash: address_hash})) - {_, fee} = Transaction.fee(tx, :gwei) - amount = Wei.to(tx.value, :ether) - notify = Notify.call([tx]) + {_, fee} = Transaction.fee(transaction, :gwei) + amount = Wei.to(transaction.value, :ether) + notify = Notify.call([transaction]) wn = WatchlistNotification @@ -122,19 +122,19 @@ defmodule Explorer.Account.Notifier.NotifyTest do assert wn.direction == "incoming" assert wn.method == "transfer" assert wn.subject == "Coin transaction" - assert wn.tx_fee == fee + assert wn.transaction_fee == fee assert wn.type == "COIN" address = Repo.get(Chain.Address, address_hash) - tx = + transaction = %Transaction{ from_address: _from_address, to_address: _to_address, block_number: _block_number, - hash: _tx_hash + hash: _transaction_hash } = with_block(insert(:transaction, to_address: address)) - Notify.call([tx]) + Notify.call([transaction]) WatchlistNotification |> first diff --git a/apps/explorer/test/explorer/account/notifier/summary_test.exs b/apps/explorer/test/explorer/account/notifier/summary_test.exs index ebc82d5..9cb52e0 100644 --- a/apps/explorer/test/explorer/account/notifier/summary_test.exs +++ b/apps/explorer/test/explorer/account/notifier/summary_test.exs @@ -9,18 +9,18 @@ defmodule Explorer.Account.Notifier.SummaryTest do describe "call" do test "Coin transaction" do - tx = + transaction = %Transaction{ from_address: from_address, to_address: to_address, block_number: block_number, - hash: tx_hash + hash: transaction_hash } = with_block(insert(:transaction)) - {_, fee} = Transaction.fee(tx, :gwei) - amount = Wei.to(tx.value, :ether) + {_, fee} = Transaction.fee(transaction, :gwei) + amount = Wei.to(transaction.value, :ether) - assert Summary.process(tx) == [ + assert Summary.process(transaction) == [ %Summary{ amount: amount, block_number: block_number, @@ -29,22 +29,22 @@ defmodule Explorer.Account.Notifier.SummaryTest do name: "ETH", subject: "Coin transaction", to_address_hash: to_address.hash, - transaction_hash: tx_hash, - tx_fee: fee, + transaction_hash: transaction_hash, + transaction_fee: fee, type: "COIN" } ] end test "Pending Coin transaction (w/o block)" do - tx = + transaction = %Transaction{ from_address: _from_address, to_address: _to_address, - hash: _tx_hash + hash: _transaction_hash } = insert(:transaction) - assert Summary.process(tx) == [] + assert Summary.process(transaction) == [] end test "Contract creation transaction" do @@ -53,21 +53,21 @@ defmodule Explorer.Account.Notifier.SummaryTest do block = insert(:block) - tx = + transaction = %Transaction{ from_address: _from_address, block_number: _block_number, - hash: tx_hash + hash: transaction_hash } = :transaction |> insert(from_address: address, to_address: nil) |> with_contract_creation(contract_address) |> with_block(block) - {_, fee} = Transaction.fee(tx, :gwei) - amount = Wei.to(tx.value, :ether) + {_, fee} = Transaction.fee(transaction, :gwei) + amount = Wei.to(transaction.value, :ether) - assert Summary.process(tx) == [ + assert Summary.process(transaction) == [ %Summary{ amount: amount, block_number: block.number, @@ -76,20 +76,20 @@ defmodule Explorer.Account.Notifier.SummaryTest do name: "ETH", subject: "Contract creation", to_address_hash: contract_address.hash, - transaction_hash: tx_hash, - tx_fee: fee, + transaction_hash: transaction_hash, + transaction_fee: fee, type: "COIN" } ] end test "ERC-20 Token transfer" do - tx = + transaction = %Transaction{ from_address: _from_address, to_address: _to_address, block_number: _block_number, - hash: _tx_hash + hash: _transaction_hash } = with_block(insert(:transaction)) transfer = @@ -101,12 +101,12 @@ defmodule Explorer.Account.Notifier.SummaryTest do token: token } = :token_transfer - |> insert(transaction: tx) + |> insert(transaction: transaction) |> Repo.preload([ :token ]) - {_, fee} = Transaction.fee(tx, :gwei) + {_, fee} = Transaction.fee(transaction, :gwei) token_decimals = Decimal.to_integer(token.decimals) @@ -123,8 +123,8 @@ defmodule Explorer.Account.Notifier.SummaryTest do name: "Infinite Token", subject: "ERC-20", to_address_hash: to_address.hash, - transaction_hash: tx.hash, - tx_fee: fee, + transaction_hash: transaction.hash, + transaction_fee: fee, type: "ERC-20" } ] @@ -133,12 +133,12 @@ defmodule Explorer.Account.Notifier.SummaryTest do test "ERC-721 Token transfer" do token = insert(:token, type: "ERC-721") - tx = + transaction = %Transaction{ from_address: _from_address, to_address: _to_address, block_number: _block_number, - hash: _tx_hash + hash: _transaction_hash } = with_block(insert(:transaction)) transfer = @@ -150,7 +150,7 @@ defmodule Explorer.Account.Notifier.SummaryTest do } = :token_transfer |> insert( - transaction: tx, + transaction: transaction, token_ids: [42], token_contract_address: token.contract_address ) @@ -158,7 +158,7 @@ defmodule Explorer.Account.Notifier.SummaryTest do :token ]) - {_, fee} = Transaction.fee(tx, :gwei) + {_, fee} = Transaction.fee(transaction, :gwei) assert Summary.process(transfer) == [ %Summary{ @@ -169,8 +169,8 @@ defmodule Explorer.Account.Notifier.SummaryTest do name: "Infinite Token", subject: "42", to_address_hash: to_address.hash, - transaction_hash: tx.hash, - tx_fee: fee, + transaction_hash: transaction.hash, + transaction_fee: fee, type: "ERC-721" } ] @@ -179,12 +179,12 @@ defmodule Explorer.Account.Notifier.SummaryTest do test "ERC-1155 single Token transfer" do token = insert(:token, type: "ERC-1155") - tx = + transaction = %Transaction{ from_address: _from_address, to_address: _to_address, block_number: _block_number, - hash: _tx_hash + hash: _transaction_hash } = with_block(insert(:transaction)) transfer = @@ -196,7 +196,7 @@ defmodule Explorer.Account.Notifier.SummaryTest do } = :token_transfer |> insert( - transaction: tx, + transaction: transaction, token_ids: [42], token_contract_address: token.contract_address ) @@ -204,7 +204,7 @@ defmodule Explorer.Account.Notifier.SummaryTest do :token ]) - {_, fee} = Transaction.fee(tx, :gwei) + {_, fee} = Transaction.fee(transaction, :gwei) assert Summary.process(transfer) == [ %Summary{ @@ -215,8 +215,8 @@ defmodule Explorer.Account.Notifier.SummaryTest do name: "Infinite Token", subject: "42", to_address_hash: to_address.hash, - transaction_hash: tx.hash, - tx_fee: fee, + transaction_hash: transaction.hash, + transaction_fee: fee, type: "ERC-1155" } ] @@ -225,12 +225,12 @@ defmodule Explorer.Account.Notifier.SummaryTest do test "ERC-1155 multiple Token transfer" do token = insert(:token, type: "ERC-1155") - tx = + transaction = %Transaction{ from_address: _from_address, to_address: _to_address, block_number: _block_number, - hash: _tx_hash + hash: _transaction_hash } = with_block(insert(:transaction)) transfer = @@ -242,7 +242,7 @@ defmodule Explorer.Account.Notifier.SummaryTest do } = :token_transfer |> insert( - transaction: tx, + transaction: transaction, token_ids: [23, 42], token_contract_address: token.contract_address ) @@ -250,7 +250,7 @@ defmodule Explorer.Account.Notifier.SummaryTest do :token ]) - {_, fee} = Transaction.fee(tx, :gwei) + {_, fee} = Transaction.fee(transaction, :gwei) assert Summary.process(transfer) == [ %Summary{ @@ -261,8 +261,8 @@ defmodule Explorer.Account.Notifier.SummaryTest do name: "Infinite Token", subject: "23, 42", to_address_hash: to_address.hash, - transaction_hash: tx.hash, - tx_fee: fee, + transaction_hash: transaction.hash, + transaction_fee: fee, type: "ERC-1155" } ] @@ -271,12 +271,12 @@ defmodule Explorer.Account.Notifier.SummaryTest do test "ERC-404 Token transfer with token id" do token = insert(:token, type: "ERC-404") - tx = + transaction = %Transaction{ from_address: _from_address, to_address: _to_address, block_number: _block_number, - hash: _tx_hash + hash: _transaction_hash } = with_block(insert(:transaction)) transfer = @@ -288,7 +288,7 @@ defmodule Explorer.Account.Notifier.SummaryTest do } = :token_transfer |> insert( - transaction: tx, + transaction: transaction, token_ids: [42], token_contract_address: token.contract_address ) @@ -296,7 +296,7 @@ defmodule Explorer.Account.Notifier.SummaryTest do :token ]) - {_, fee} = Transaction.fee(tx, :gwei) + {_, fee} = Transaction.fee(transaction, :gwei) token_decimals = Decimal.to_integer(token.decimals) @@ -313,8 +313,8 @@ defmodule Explorer.Account.Notifier.SummaryTest do name: "Infinite Token", subject: "42", to_address_hash: to_address.hash, - transaction_hash: tx.hash, - tx_fee: fee, + transaction_hash: transaction.hash, + transaction_fee: fee, type: "ERC-404" } ] @@ -323,12 +323,12 @@ defmodule Explorer.Account.Notifier.SummaryTest do test "ERC-404 Token transfer without token id" do token = insert(:token, type: "ERC-404") - tx = + transaction = %Transaction{ from_address: _from_address, to_address: _to_address, block_number: _block_number, - hash: _tx_hash + hash: _transaction_hash } = with_block(insert(:transaction)) transfer = @@ -340,7 +340,7 @@ defmodule Explorer.Account.Notifier.SummaryTest do } = :token_transfer |> insert( - transaction: tx, + transaction: transaction, token_ids: [], token_contract_address: token.contract_address ) @@ -348,7 +348,7 @@ defmodule Explorer.Account.Notifier.SummaryTest do :token ]) - {_, fee} = Transaction.fee(tx, :gwei) + {_, fee} = Transaction.fee(transaction, :gwei) token_decimals = Decimal.to_integer(token.decimals) @@ -365,8 +365,8 @@ defmodule Explorer.Account.Notifier.SummaryTest do name: "Infinite Token", subject: "ERC-404", to_address_hash: to_address.hash, - transaction_hash: tx.hash, - tx_fee: fee, + transaction_hash: transaction.hash, + transaction_fee: fee, type: "ERC-404" } ] diff --git a/apps/explorer/test/explorer/chain/address_test.exs b/apps/explorer/test/explorer/chain/address_test.exs index c6ca324..ca3f13f 100644 --- a/apps/explorer/test/explorer/chain/address_test.exs +++ b/apps/explorer/test/explorer/chain/address_test.exs @@ -72,7 +72,7 @@ defmodule Explorer.Chain.AddressTest do test "with top addresses in order" do address_hashes = - 4..1 + 4..1//-1 |> Enum.map(&insert(:address, fetched_coin_balance: &1)) |> Enum.map(& &1.hash) 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 669dfde..3fc1493 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 @@ -28,7 +28,7 @@ defmodule Explorer.Chain.Cache.GasPriceOracleTest do }}, []} = GasPriceOracle.get_average_gas_price(3, 35, 60, 90) end - test "returns gas prices for blocks with failed txs in the DB" do + test "returns gas prices for blocks with failed transactions in the DB" do block = insert(:block, number: 100, hash: "0x3e51328bccedee581e8ba35190216a61a5d67fd91ca528f3553142c0c7d18391") :transaction 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 87bc3e3..c559b39 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 @@ -26,7 +26,7 @@ defmodule Explorer.Chain.AddressTokenTransferCsvExporterTest do |> Enum.to_list() |> Enum.drop(1) |> Enum.map(fn [ - [[], tx_hash], + [[], transaction_hash], _, [[], block_number], _, @@ -54,7 +54,7 @@ defmodule Explorer.Chain.AddressTokenTransferCsvExporterTest do _ ] -> %{ - tx_hash: tx_hash, + transaction_hash: transaction_hash, block_number: block_number, timestamp: timestamp, from_address: from_address, @@ -71,7 +71,7 @@ defmodule Explorer.Chain.AddressTokenTransferCsvExporterTest do end) assert result.block_number == to_string(transaction.block_number) - assert result.tx_hash == to_string(transaction.hash) + assert result.transaction_hash == to_string(transaction.hash) 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) diff --git a/apps/explorer/test/explorer/chain/filecoin/native_address_test.exs b/apps/explorer/test/explorer/chain/filecoin/native_address_test.exs new file mode 100644 index 0000000..2836826 --- /dev/null +++ b/apps/explorer/test/explorer/chain/filecoin/native_address_test.exs @@ -0,0 +1,175 @@ +defmodule Explorer.Chain.Filecoin.NativeAddressTest do + use ExUnit.Case, async: true + + alias Explorer.Chain.Hash + alias Explorer.Chain.Hash.Address + alias Explorer.Chain.Filecoin.{NativeAddress, IDAddress} + + doctest NativeAddress + doctest IDAddress + + @doc """ + The following test cases are taken from the filecoin spec: + https://spec.filecoin.io/appendix/address/#section-appendix.address.test-vectors + + The key is the address and the value is the hex-encoded binary representation + of the address in the database. + """ + # cspell:disable + @test_cases %{ + "f00" => "0000", + "f0150" => "009601", + "f01024" => "008008", + "f01729" => "00c10d", + "f018446744073709551615" => "00ffffffffffffffffff01", + "f17uoq6tp427uzv7fztkbsnn64iwotfrristwpryy" => "01fd1d0f4dfcd7e99afcb99a8326b7dc459d32c628", + "f1xcbgdhkgkwht3hrrnui3jdopeejsoatkzmoltqy" => "01b882619d46558f3d9e316d11b48dcf211327026a", + "f1xtwapqc6nh4si2hcwpr3656iotzmlwumogqbuaa" => "01bcec07c05e69f92468e2b3e3bf77c874f2c5da8c", + "f1wbxhu3ypkuo6eyp6hjx6davuelxaxrvwb2kuwva" => "01b06e7a6f0f551de261fe3a6fe182b422ee0bc6b6", + "f12fiakbhe2gwd5cnmrenekasyn6v5tnaxaqizq6a" => "01d1500504e4d1ac3e89ac891a4502586fabd9b417", + "f24vg6ut43yw2h2jqydgbg2xq7x6f4kub3bg6as6i" => "02e54dea4f9bc5b47d261819826d5e1fbf8bc5503b", + "f25nml2cfbljvn4goqtclhifepvfnicv6g7mfmmvq" => "02eb58bd08a15a6ade19d0989674148fa95a8157c6", + "f2nuqrg7vuysaue2pistjjnt3fadsdzvyuatqtfei" => "026d21137eb4c4814269e894d296cf6500e43cd714", + "f24dd4ox4c2vpf5vk5wkadgyyn6qtuvgcpxxon64a" => "02e0c7c75f82d55e5ed55db28033630df4274a984f", + "f2gfvuyh7v2sx3patm5k23wdzmhyhtmqctasbr23y" => "02316b4c1ff5d4afb7826ceab5bb0f2c3e0f364053", + "f3vvmn62lofvhjd2ugzca6sof2j2ubwok6cj4xxbfzz4yuxfkgobpihhd2thlanmsh3w2ptld2gqkn2jvlss4a" => + "03ad58df696e2d4e91ea86c881e938ba4ea81b395e12797b84b9cf314b9546705e839c7a99d606b247ddb4f9ac7a3414dd", + "f3wmuu6crofhqmm3v4enos73okk2l366ck6yc4owxwbdtkmpk42ohkqxfitcpa57pjdcftql4tojda2poeruwa" => + "03b3294f0a2e29e0c66ebc235d2fedca5697bf784af605c75af608e6a63d5cd38ea85ca8989e0efde9188b382f9372460d", + "f3s2q2hzhkpiknjgmf4zq3ejab2rh62qbndueslmsdzervrhapxr7dftie4kpnpdiv2n6tvkr743ndhrsw6d3a" => + "0396a1a3e4ea7a14d49985e661b22401d44fed402d1d0925b243c923589c0fbc7e32cd04e29ed78d15d37d3aaa3fe6da33", + "f3q22fijmmlckhl56rn5nkyamkph3mcfu5ed6dheq53c244hfmnq2i7efdma3cj5voxenwiummf2ajlsbxc65a" => + "0386b454258c589475f7d16f5aac018a79f6c1169d20fc33921dd8b5ce1cac6c348f90a3603624f6aeb91b64518c2e8095", + "f3u5zgwa4ael3vuocgc5mfgygo4yuqocrntuuhcklf4xzg5tcaqwbyfabxetwtj4tsam3pbhnwghyhijr5mixa" => + "03a7726b038022f75a384617585360cee629070a2d9d28712965e5f26ecc40858382803724ed34f2720336f09db631f074" + } + + # cspell:enable + + describe "cast/1" do + test "parses f0, f1, f2, f3 addresses from spec test vectors" do + for {address, hex_string} <- @test_cases do + {protocol_indicator_hex, payload} = String.split_at(hex_string, 2) + protocol_indicator = String.to_integer(protocol_indicator_hex, 16) + payload = Base.decode16!(payload, case: :lower) + + assert {:ok, + %NativeAddress{ + protocol_indicator: ^protocol_indicator, + actor_id: nil, + payload: ^payload + }} = NativeAddress.cast(address) + end + end + + test "parses f4 addresses" do + address = "f410fabpafjfjgqkc3douo3yzfug5tq4bwfvuhsewxji" + {:ok, evm_address} = Address.cast("0x005E02A4A934142D8DD476F192D0DD9C381B16B4") + evm_address_bytes = evm_address.bytes + + assert {:ok, + %NativeAddress{ + protocol_indicator: 4, + actor_id: 10, + payload: ^evm_address_bytes + }} = NativeAddress.cast(address) + end + end + + describe "dump/1" do + test "encodes f0, f1, f2, f3 addresses to bytes" do + for {address, hex_string} <- @test_cases do + bytes = Base.decode16!(hex_string, case: :lower) + + assert {:ok, ^bytes} = + address + |> NativeAddress.cast() + |> elem(1) + |> NativeAddress.dump() + end + end + + test "converts f4 addresses" do + address = "f410fabpafjfjgqkc3douo3yzfug5tq4bwfvuhsewxji" + {:ok, evm_address} = Address.cast("0x005E02A4A934142D8DD476F192D0DD9C381B16B4") + bytes = <<4, 10, evm_address.bytes::binary>> + + assert {:ok, ^bytes} = + address + |> NativeAddress.cast() + |> elem(1) + |> NativeAddress.dump() + end + end + + describe "load/1" do + test "decodes f0, f1, f2, f3 addresses from bytes" do + for {address, hex_string} <- Map.values(@test_cases) do + {protocol_indicator_hex, payload_hex} = String.split_at(hex_string, 2) + protocol_indicator = String.to_integer(protocol_indicator_hex, 16) + payload = Base.decode16!(payload_hex, case: :lower) + + assert {:ok, + %NativeAddress{ + protocol_indicator: ^protocol_indicator, + actor_id: nil, + payload: ^payload + }} = + address + |> NativeAddress.cast() + |> elem(1) + |> NativeAddress.dump() + |> elem(1) + |> NativeAddress.load() + end + end + + test "decodes f4 addresses" do + address = "f410fabpafjfjgqkc3douo3yzfug5tq4bwfvuhsewxji" + {:ok, %Hash{bytes: payload}} = Address.cast("0x005E02A4A934142D8DD476F192D0DD9C381B16B4") + + assert {:ok, + %NativeAddress{ + protocol_indicator: 4, + actor_id: 10, + payload: ^payload + }} = + address + |> NativeAddress.cast() + |> elem(1) + |> NativeAddress.dump() + |> elem(1) + |> NativeAddress.load() + end + end + + describe "to_string/1" do + test "converts f0, f1, f2, f3 addresses to string" do + for {address, _} <- @test_cases do + assert ^address = + address + |> NativeAddress.cast() + |> elem(1) + |> NativeAddress.dump() + |> elem(1) + |> NativeAddress.load() + |> elem(1) + |> NativeAddress.to_string() + end + end + + test "converts f4 addresses to string" do + address = "f410fabpafjfjgqkc3douo3yzfug5tq4bwfvuhsewxji" + + assert ^address = + address + |> NativeAddress.cast() + |> elem(1) + |> NativeAddress.dump() + |> elem(1) + |> NativeAddress.load() + |> elem(1) + |> NativeAddress.to_string() + end + end +end 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 2ba66c3..61ce2aa 100644 --- a/apps/explorer/test/explorer/chain/import/runner/blocks_test.exs +++ b/apps/explorer/test/explorer/chain/import/runner/blocks_test.exs @@ -86,121 +86,32 @@ defmodule Explorer.Chain.Import.Runner.BlocksTest do "Tuple was written even though it is not distinct" end - test "update_token_instances_owner inserts correct token instances in cases when log_index is not unique within block", - %{ - consensus_block: %{hash: previous_block_hash, miner_hash: miner_hash, number: previous_block_number}, - options: options - } do - old_env = Application.get_env(:explorer, :chain_type) - - Application.put_env(:explorer, :chain_type, :polygon_zkevm) - - previous_consensus_block = insert(:block, hash: previous_block_hash, number: previous_block_number) - %{hash: block_hash, number: block_number} = consensus_block = insert(:block) - - transaction = - :transaction - |> insert() - |> with_block(consensus_block) - - transaction_with_previous_transfer = - :transaction - |> insert() - |> with_block(previous_consensus_block, index: 1) - - older_transaction_with_previous_transfer = - :transaction - |> insert() - |> with_block(previous_consensus_block, index: 0) - - transaction_of_other_instance = - :transaction - |> insert() - |> with_block(previous_consensus_block) - - token = insert(:token, type: "ERC-721") - correct_token_id = Decimal.new(1) - - forked_token_transfer = - insert(:token_transfer, - token_type: "ERC-721", - token_contract_address: token.contract_address, - transaction: transaction, - token_ids: [correct_token_id], - block_number: block_number - ) - - _token_instance = - insert(:token_instance, - token_id: correct_token_id, - token_contract_address_hash: token.contract_address_hash, - owner_updated_at_block: block_number, - owner_updated_at_log_index: forked_token_transfer.log_index - ) - - _previous_token_transfer = - insert(:token_transfer, - token_type: "ERC-721", - token_contract_address: token.contract_address, - transaction: transaction_with_previous_transfer, - token_ids: [correct_token_id], - block_number: previous_block_number, - log_index: 10 - ) - - _older_previous_token_transfer = - insert(:token_transfer, - token_type: "ERC-721", - token_contract_address: token.contract_address, - transaction: older_transaction_with_previous_transfer, - token_ids: [correct_token_id], - block_number: previous_block_number, - log_index: 11 - ) - - _unsuitable_token_instance = - insert(:token_instance, - token_id: 2, - token_contract_address_hash: token.contract_address_hash, - owner_updated_at_block: previous_block_number, - owner_updated_at_log_index: forked_token_transfer.log_index - ) + test "coin balances are deleted and new balances are derived if some blocks lost consensus", + %{consensus_block: %{number: block_number} = block, options: options} do + %{hash: address_hash} = address = insert(:address) - _unsuitable_token_transfer = - insert(:token_transfer, - token_type: "ERC-721", - token_contract_address: token.contract_address, - transaction: transaction_of_other_instance, - token_ids: [2], - block_number: previous_block_number, - log_index: forked_token_transfer.log_index - ) + prev_block_number = block_number - 1 - block_params = - params_for(:block, hash: block_hash, miner_hash: miner_hash, number: block_number, consensus: false) + insert(:address_coin_balance, address: address, block_number: block_number) + %{value: prev_value} = insert(:address_coin_balance, address: address, block_number: prev_block_number) - %Ecto.Changeset{valid?: true, changes: block_changes} = Block.changeset(%Block{}, block_params) - changes_list = [block_changes] + assert count(Address.CoinBalance) == 2 - assert {:ok, %{}} + insert(:block, number: block_number, consensus: true) assert {:ok, %{ - update_token_instances_owner: [ + delete_address_coin_balances: [^address_hash], + derive_address_fetched_coin_balances: [ %{ - token_id: ^correct_token_id, - owner_updated_at_block: ^previous_block_number, - owner_updated_at_log_index: 10 + hash: ^address_hash, + fetched_coin_balance: ^prev_value, + fetched_coin_balance_block_number: ^prev_block_number } ] - }} = - Multi.new() - |> Blocks.run(changes_list, options) - |> Repo.transaction() + }} = run_block_consensus_change(block, true, options) - on_exit(fn -> - Application.put_env(:explorer, :chain_type, old_env) - end) + assert %{value: ^prev_value, block_number: ^prev_block_number} = Repo.one(Address.CoinBalance) end test "delete_address_current_token_balances deletes rows with matching block number when consensus is true", @@ -219,7 +130,7 @@ defmodule Explorer.Chain.Import.Runner.BlocksTest do ] }} = run_block_consensus_change(block, true, options) - assert %{value: nil} = Repo.one(Address.CurrentTokenBalance) + assert count(Address.CurrentTokenBalance) == 0 end test "delete_address_current_token_balances does not delete rows with matching block number when consensus is false", @@ -238,6 +149,100 @@ defmodule Explorer.Chain.Import.Runner.BlocksTest do assert count(Address.CurrentTokenBalance) == count end + test "derive_address_current_token_balances inserts rows if there is an address_token_balance left for the rows deleted by delete_address_current_token_balances", + %{consensus_block: %{number: block_number} = block, options: options} do + token = insert(:token) + token_contract_address_hash = token.contract_address_hash + + %Address{hash: address_hash} = + insert_address_with_token_balances(%{ + previous: %{value: 1}, + current: %{block_number: block_number, value: 2}, + token_contract_address_hash: token_contract_address_hash + }) + + # Token must exist with non-`nil` `holder_count` for `blocks_update_token_holder_counts` to update + update_holder_count!(token_contract_address_hash, 1) + + assert count(Address.TokenBalance) == 2 + assert count(Address.CurrentTokenBalance) == 1 + + previous_block_number = block_number - 1 + + insert(:block, number: block_number, consensus: true) + + assert {:ok, + %{ + delete_address_current_token_balances: [ + %{ + address_hash: ^address_hash, + token_contract_address_hash: ^token_contract_address_hash + } + ], + delete_address_token_balances: [ + %{ + address_hash: ^address_hash, + token_contract_address_hash: ^token_contract_address_hash, + block_number: ^block_number + } + ], + derive_address_current_token_balances: [ + %{ + address_hash: ^address_hash, + token_contract_address_hash: ^token_contract_address_hash, + block_number: ^previous_block_number + } + ], + # no updates because it both deletes and derives a holder + blocks_update_token_holder_counts: [] + }} = run_block_consensus_change(block, true, options) + + assert count(Address.TokenBalance) == 1 + assert count(Address.CurrentTokenBalance) == 1 + + previous_value = Decimal.new(1) + + assert %Address.CurrentTokenBalance{block_number: ^previous_block_number, value: ^previous_value} = + Repo.get_by(Address.CurrentTokenBalance, + address_hash: address_hash, + token_contract_address_hash: token_contract_address_hash + ) + end + + test "a non-holder reverting to a holder increases the holder_count", + %{consensus_block: %{hash: block_hash, miner_hash: miner_hash, number: block_number}, options: options} do + token = insert(:token) + token_contract_address_hash = token.contract_address_hash + + non_holder_reverts_to_holder(%{ + current: %{block_number: block_number}, + token_contract_address_hash: token_contract_address_hash + }) + + # 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) + changes_list = [block_changes] + + assert {:ok, + %{ + blocks_update_token_holder_counts: [ + %{ + contract_address_hash: ^token_contract_address_hash, + holder_count: 1 + } + ] + }} = + Multi.new() + |> Blocks.run(changes_list, options) + |> Repo.transaction() + end + test "a holder reverting to a non-holder decreases the holder_count", %{consensus_block: %{hash: block_hash, miner_hash: miner_hash, number: block_number}, options: options} do token = insert(:token) @@ -526,7 +531,7 @@ defmodule Explorer.Chain.Import.Runner.BlocksTest do consensus_block_2 = insert(:block, %{hash: hash_2, number: block_number - 2}) for _ <- 0..10 do - tx = + transaction = :transaction |> insert() |> with_block(consensus_block_2) @@ -534,7 +539,7 @@ defmodule Explorer.Chain.Import.Runner.BlocksTest do insert(:token_transfer, token_ids: [id], token_type: "ERC-721", - transaction: tx, + transaction: transaction, token_contract_address: tt.token_contract_address, block_number: consensus_block_2.number, block: consensus_block_2 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 17144cc..2b04d60 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 @@ -22,12 +22,12 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactionsTest do 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 + test "transaction's has_error_in_internal_transactions become true when its internal_transaction (where index != 0) 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) assert :ok == transaction.status - assert nil == transaction.has_error_in_internal_txs + assert nil == transaction.has_error_in_internal_transactions index = 0 error = nil @@ -40,18 +40,18 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactionsTest do internal_transaction_changes_1 = make_internal_transaction_changes(transaction, index, error) assert {:ok, _} = run_internal_transactions([internal_transaction_changes, internal_transaction_changes_1]) - tx = Repo.get(Transaction, transaction.hash) + transaction = Repo.get(Transaction, transaction.hash) - assert :ok == tx.status - assert true == tx.has_error_in_internal_txs + assert :ok == transaction.status + assert true == transaction.has_error_in_internal_transactions end - test "transaction's has_error_in_internal_txs become false when its internal_transaction (where index == 0) has an error" do + test "transaction's has_error_in_internal_transactions become false when its internal_transaction (where index == 0) 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) assert :ok == transaction.status - assert nil == transaction.has_error_in_internal_txs + assert nil == transaction.has_error_in_internal_transactions index = 0 error = "Reverted" @@ -59,18 +59,18 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactionsTest do internal_transaction_changes = make_internal_transaction_changes(transaction, index, error) assert {:ok, _} = run_internal_transactions([internal_transaction_changes]) - tx = Repo.get(Transaction, transaction.hash) + transaction = Repo.get(Transaction, transaction.hash) - assert :ok == tx.status - assert false == tx.has_error_in_internal_txs + assert :ok == transaction.status + assert false == transaction.has_error_in_internal_transactions end - test "transaction's has_error_in_internal_txs become false when its internal_transaction has no error" do + test "transaction's has_error_in_internal_transactions become false when its internal_transaction has no error" do transaction = insert(:transaction) |> with_block(status: :ok) insert(:pending_block_operation, block_hash: transaction.block_hash, block_number: transaction.block_number) assert :ok == transaction.status - assert nil == transaction.has_error_in_internal_txs + assert nil == transaction.has_error_in_internal_transactions index = 0 error = nil @@ -84,10 +84,10 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactionsTest do assert {:ok, _} = run_internal_transactions([internal_transaction_changes, internal_transaction_changes_1]) - tx = Repo.get(Transaction, transaction.hash) + transaction = Repo.get(Transaction, transaction.hash) - assert :ok == tx.status - assert false == tx.has_error_in_internal_txs + assert :ok == transaction.status + assert false == transaction.has_error_in_internal_transactions end test "simple coin transfer's status doesn't become :error when its internal_transaction has an error" do @@ -132,7 +132,7 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactionsTest do assert :ok == Repo.get(Transaction, transaction2.hash).status end - test "for block with simple coin transfer and method calls, method calls internal txs have correct block_index" do + test "for block with simple coin transfer and method calls, method calls internal transactions have correct block_index" do a_block = insert(:block, number: 1000) transaction0 = insert(:transaction) |> with_block(a_block, status: :ok) transaction1 = insert(:transaction) |> with_block(a_block, status: :ok) diff --git a/apps/explorer/test/explorer/chain/import_test.exs b/apps/explorer/test/explorer/chain/import_test.exs index d08b759..098d116 100644 --- a/apps/explorer/test/explorer/chain/import_test.exs +++ b/apps/explorer/test/explorer/chain/import_test.exs @@ -655,7 +655,7 @@ defmodule Explorer.Chain.ImportTest do } } - internal_txs_options = %{ + internal_transactions_options = %{ internal_transactions: %{ params: [ %{ @@ -685,7 +685,7 @@ defmodule Explorer.Chain.ImportTest do {:ok, block_hash_casted} = Explorer.Chain.Hash.Full.cast(block_hash) assert [^block_hash_casted] = Explorer.Repo.all(PendingBlockOperation.block_hashes()) - assert {:ok, _} = Import.all(internal_txs_options) + assert {:ok, _} = Import.all(internal_transactions_options) assert [] == Explorer.Repo.all(PendingBlockOperation.block_hashes()) end @@ -746,7 +746,7 @@ defmodule Explorer.Chain.ImportTest do } } - internal_txs_options = %{ + internal_transactions_options = %{ internal_transactions: %{ params: [ %{ @@ -775,7 +775,7 @@ defmodule Explorer.Chain.ImportTest do {:ok, block_hash_casted} = Explorer.Chain.Hash.Full.cast(block_hash) assert [^block_hash_casted] = Explorer.Repo.all(PendingBlockOperation.block_hashes()) - assert {:ok, _} = Import.all(internal_txs_options) + assert {:ok, _} = Import.all(internal_transactions_options) assert [] == Explorer.Repo.all(PendingBlockOperation.block_hashes()) end @@ -2145,11 +2145,12 @@ defmodule Explorer.Chain.ImportTest do } }) - assert %{value: nil} = + assert is_nil( Repo.get_by(Address.CurrentTokenBalance, address_hash: address_hash, token_contract_address_hash: token_contract_address_hash ) + ) assert is_nil( Repo.get_by(Address.TokenBalance, @@ -2159,5 +2160,186 @@ defmodule Explorer.Chain.ImportTest do ) ) end + + test "address_current_token_balances is derived during reorgs" do + %Block{number: block_number} = insert(:block, consensus: true) + previous_block_number = block_number - 1 + + %Address.TokenBalance{ + address_hash: address_hash, + token_contract_address_hash: token_contract_address_hash, + value: previous_value, + block_number: previous_block_number + } = insert(:token_balance, block_number: previous_block_number) + + address = Repo.get(Address, address_hash) + + %Address.TokenBalance{ + address_hash: ^address_hash, + token_contract_address_hash: token_contract_address_hash, + value: current_value, + block_number: ^block_number + } = + insert(:token_balance, + address: address, + token_contract_address_hash: token_contract_address_hash, + block_number: block_number + ) + + refute current_value == previous_value + + %Address.CurrentTokenBalance{ + address_hash: ^address_hash, + token_contract_address_hash: ^token_contract_address_hash, + block_number: ^block_number + } = + insert(:address_current_token_balance, + address: address, + token_contract_address_hash: token_contract_address_hash, + block_number: block_number, + value: current_value + ) + + miner_hash_after = address_hash() + from_address_hash_after = address_hash() + block_hash_after = block_hash() + + assert {:ok, _} = + Import.all(%{ + addresses: %{ + params: [ + %{hash: miner_hash_after}, + %{hash: from_address_hash_after} + ] + }, + blocks: %{ + params: [ + %{ + consensus: true, + difficulty: 1, + gas_limit: 1, + gas_used: 1, + hash: block_hash_after, + miner_hash: miner_hash_after, + nonce: 1, + number: block_number, + parent_hash: block_hash(), + size: 1, + timestamp: Timex.parse!("2019-01-01T02:00:00Z", "{ISO:Extended:Z}"), + total_difficulty: 1 + } + ] + } + }) + + assert %Address.CurrentTokenBalance{block_number: ^previous_block_number, value: ^previous_value} = + Repo.get_by(Address.CurrentTokenBalance, + address_hash: address_hash, + token_contract_address_hash: token_contract_address_hash + ) + + assert is_nil( + Repo.get_by(Address.TokenBalance, + address_hash: address_hash, + token_contract_address_hash: token_contract_address_hash, + block_number: block_number + ) + ) + end + + test "address_token_balances and address_current_token_balances can be replaced during reorgs" do + %Block{number: block_number} = insert(:block, consensus: true) + value_before = Decimal.new(1) + + %Address{hash: address_hash} = address = insert(:address) + + %Address.TokenBalance{ + address_hash: ^address_hash, + token_contract_address_hash: token_contract_address_hash, + block_number: ^block_number + } = insert(:token_balance, address: address, block_number: block_number, value: value_before) + + %Address.CurrentTokenBalance{ + address_hash: ^address_hash, + token_contract_address_hash: ^token_contract_address_hash, + block_number: ^block_number + } = + insert(:address_current_token_balance, + address: address, + token_contract_address_hash: token_contract_address_hash, + block_number: block_number, + value: value_before + ) + + miner_hash_after = address_hash() + from_address_hash_after = address_hash() + block_hash_after = block_hash() + value_after = Decimal.add(value_before, 1) + + assert {:ok, _} = + Import.all(%{ + addresses: %{ + params: [ + %{hash: address_hash}, + %{hash: token_contract_address_hash}, + %{hash: miner_hash_after}, + %{hash: from_address_hash_after} + ] + }, + address_token_balances: %{ + params: [ + %{ + address_hash: address_hash, + token_contract_address_hash: token_contract_address_hash, + block_number: block_number, + value: value_after, + token_type: "ERC-20" + } + ] + }, + address_current_token_balances: %{ + params: [ + %{ + address_hash: address_hash, + token_contract_address_hash: token_contract_address_hash, + block_number: block_number, + value: value_after, + token_type: "ERC-20" + } + ] + }, + blocks: %{ + params: [ + %{ + consensus: true, + difficulty: 1, + gas_limit: 1, + gas_used: 1, + hash: block_hash_after, + miner_hash: miner_hash_after, + nonce: 1, + number: block_number, + parent_hash: block_hash(), + size: 1, + timestamp: Timex.parse!("2019-01-01T02:00:00Z", "{ISO:Extended:Z}"), + total_difficulty: 1 + } + ] + } + }) + + assert %Address.CurrentTokenBalance{value: ^value_after} = + Repo.get_by(Address.CurrentTokenBalance, + address_hash: address_hash, + token_contract_address_hash: token_contract_address_hash + ) + + assert %Address.TokenBalance{value: ^value_after} = + Repo.get_by(Address.TokenBalance, + address_hash: address_hash, + token_contract_address_hash: token_contract_address_hash, + block_number: block_number + ) + end end end diff --git a/apps/explorer/test/explorer/chain/smart_contract/proxy/models/implementation_test.exs b/apps/explorer/test/explorer/chain/smart_contract/proxy/models/implementation_test.exs index 7893139..506ba65 100644 --- a/apps/explorer/test/explorer/chain/smart_contract/proxy/models/implementation_test.exs +++ b/apps/explorer/test/explorer/chain/smart_contract/proxy/models/implementation_test.exs @@ -102,6 +102,13 @@ defmodule Explorer.Chain.SmartContract.Proxy.Models.Implementation.Test do assert implementation_1.updated_at == implementation_2.updated_at && contract_1.updated_at == contract_2.updated_at + + proxy = + :explorer + |> Application.get_env(:proxy) + |> Keyword.replace(:fallback_cached_implementation_data_ttl, :timer.seconds(20)) + + Application.put_env(:explorer, :proxy, proxy) end test "get_implementation/1 for twins contract" do diff --git a/apps/explorer/test/explorer/chain/smart_contract/proxy_test.exs b/apps/explorer/test/explorer/chain/smart_contract/proxy_test.exs index 4bfda97..b38977f 100644 --- a/apps/explorer/test/explorer/chain/smart_contract/proxy_test.exs +++ b/apps/explorer/test/explorer/chain/smart_contract/proxy_test.exs @@ -139,62 +139,51 @@ defmodule Explorer.Chain.SmartContract.ProxyTest do end test "combine_proxy_implementation_abi/2 returns [] abi for unverified proxy" do + TestHelper.get_eip1967_implementation_zero_addresses() + proxy_contract_address = insert(:contract_address) smart_contract = insert(:smart_contract, address_hash: proxy_contract_address.hash, abi: [], contract_code_md5: "123") - TestHelper.get_eip1967_implementation_zero_addresses() - assert Proxy.combine_proxy_implementation_abi(smart_contract) == [] end test "combine_proxy_implementation_abi/2 returns proxy abi if implementation is not verified" do + TestHelper.get_eip1967_implementation_zero_addresses() + proxy_contract_address = insert(:contract_address) smart_contract = insert(:smart_contract, address_hash: proxy_contract_address.hash, abi: @proxy_abi, contract_code_md5: "123") - TestHelper.get_eip1967_implementation_zero_addresses() - 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 = + proxy_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) - - TestHelper.get_eip1967_implementation_zero_addresses() - - 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_smart_contract = + insert(:smart_contract, + address_hash: implementation_contract_address.hash, + abi: @implementation_abi, + contract_code_md5: "123", + name: "impl" + ) + + insert(:proxy_implementation, + proxy_address_hash: proxy_contract_address.hash, + proxy_type: "eip1167", + address_hashes: [implementation_contract_address.hash], + names: [implementation_smart_contract.name] ) - combined_abi = Proxy.combine_proxy_implementation_abi(smart_contract) + combined_abi = Proxy.combine_proxy_implementation_abi(proxy_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 @@ -212,17 +201,6 @@ defmodule Explorer.Chain.SmartContract.ProxyTest do [] 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") - - TestHelper.get_eip1967_implementation_zero_addresses() - - 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) @@ -489,6 +467,13 @@ defmodule Explorer.Chain.SmartContract.ProxyTest do assert Proxy.proxy_contract?(smart_contract) verify!(EthereumJSONRPC.Mox) + + proxy = + :explorer + |> Application.get_env(:proxy) + |> Keyword.replace(:fallback_cached_implementation_data_ttl, :timer.seconds(20)) + + Application.put_env(:explorer, :proxy, proxy) end defp eip_1967_beacon_proxy_mock_requests( diff --git a/apps/explorer/test/explorer/chain/transaction_test.exs b/apps/explorer/test/explorer/chain/transaction_test.exs index 85726e7..69a4564 100644 --- a/apps/explorer/test/explorer/chain/transaction_test.exs +++ b/apps/explorer/test/explorer/chain/transaction_test.exs @@ -252,31 +252,33 @@ defmodule Explorer.Chain.TransactionTest do test "that a transaction that is not a contract call returns a commensurate error" do transaction = insert(:transaction) - assert {{:error, :not_a_contract_call}, _, _} = Transaction.decoded_input_data(transaction, []) + 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 transaction = :transaction - |> insert(to_address: insert(:contract_address)) + |> insert(to_address: insert(:contract_address), input: "0x1234567891") |> Repo.preload(to_address: :smart_contract) - assert {{:error, :contract_not_verified, []}, _, _} = Transaction.decoded_input_data(transaction, []) + 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 + TestHelper.get_eip1967_implementation_zero_addresses() + transaction = :transaction_to_verified_contract |> insert() |> Repo.preload(to_address: :smart_contract) - TestHelper.get_eip1967_implementation_zero_addresses() - - assert {{: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 + TestHelper.get_eip1967_implementation_zero_addresses() + :transaction_to_verified_contract |> insert() |> Repo.preload(to_address: :smart_contract) @@ -293,13 +295,13 @@ defmodule Explorer.Chain.TransactionTest do |> insert(to_address: contract.address, input: "0x" <> input_data) |> Repo.preload(to_address: :smart_contract) - TestHelper.get_eip1967_implementation_zero_addresses() - - assert {{:ok, "60fe47b1", "set(uint256 x)", [{"x", "uint256", 10}]}, _, _} = + assert {:ok, "60fe47b1", "set(uint256 x)", [{"x", "uint256", 10}]} = Transaction.decoded_input_data(transaction, []) end test "arguments name in function call replaced with argN if it's empty string" do + TestHelper.get_eip1967_implementation_zero_addresses() + contract = insert(:smart_contract, contract_code_md5: "123", @@ -327,9 +329,7 @@ defmodule Explorer.Chain.TransactionTest do |> insert(to_address: contract.address, input: "0x" <> input_data) |> Repo.preload(to_address: :smart_contract) - TestHelper.get_eip1967_implementation_zero_addresses() - - assert {{:ok, "60fe47b1", "set(uint256 arg0)", [{"arg0", "uint256", 10}]}, _, _} = + assert {:ok, "60fe47b1", "set(uint256 arg0)", [{"arg0", "uint256", 10}]} = Transaction.decoded_input_data(transaction, []) 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 e610fd6..24ce1da 100644 --- a/apps/explorer/test/explorer/chain_spec/parity/importer_test.exs +++ b/apps/explorer/test/explorer/chain_spec/parity/importer_test.exs @@ -108,9 +108,9 @@ defmodule Explorer.ChainSpec.Parity.ImporterTest do assert %{ address_hash: %Hash{ byte_count: 20, - bytes: <<167, 105, 41, 137, 10, 123, 71, 251, 133, 145, 150, 1, 108, 111, 221, 130, 137, 206, 183, 85>> + bytes: <<25, 104, 125, 170, 57, 195, 104, 19, 155, 110, 123, 230, 13, 193, 117, 58, 159, 12, 190, 163>> }, - value: 5_000_000_000_000_000_000_000, + value: 8_000_000_000_000_000_000_000, contract_code: nil, nonce: 0 } == diff --git a/apps/explorer/test/explorer/chain_test.exs b/apps/explorer/test/explorer/chain_test.exs index a57dac0..ffd74e6 100644 --- a/apps/explorer/test/explorer/chain_test.exs +++ b/apps/explorer/test/explorer/chain_test.exs @@ -280,9 +280,7 @@ defmodule Explorer.ChainTest do |> insert(to_address: address) |> with_block() - _log1 = insert(:log, transaction: transaction, index: 1, address: address, block_number: transaction.block_number) - - 2..51 + 1..51 |> Enum.map(fn index -> insert(:log, block: transaction.block, @@ -292,7 +290,6 @@ defmodule Explorer.ChainTest do block_number: transaction.block_number ) end) - |> Enum.map(& &1.index) paging_options1 = %PagingOptions{page_size: 1} @@ -485,7 +482,7 @@ defmodule Explorer.ChainTest do end end - describe "block_to_gas_used_by_1559_txs/1" do + describe "block_to_gas_used_by_1559_transactions/1" do test "sum of gas_usd from all transactions including legacy" do block = insert(:block, base_fee_per_gas: 4) @@ -507,12 +504,12 @@ defmodule Explorer.ChainTest do index: 2 ) - assert Decimal.new(10) == Chain.block_to_gas_used_by_1559_txs(block.hash) + assert Decimal.new(10) == Chain.block_to_gas_used_by_1559_transactions(block.hash) end end - describe "block_to_priority_fee_of_1559_txs/1" do - test "with transactions: tx.max_fee_per_gas = 0" do + describe "block_to_priority_fee_of_1559_transactions/1" do + test "with transactions: transaction.max_fee_per_gas = 0" do block = insert(:block, base_fee_per_gas: 4) insert(:transaction, @@ -525,10 +522,10 @@ defmodule Explorer.ChainTest do max_priority_fee_per_gas: 3 ) - assert Decimal.new(0) == Chain.block_to_priority_fee_of_1559_txs(block.hash) + assert Decimal.new(0) == Chain.block_to_priority_fee_of_1559_transactions(block.hash) end - test "with transactions: tx.max_fee_per_gas - block.base_fee_per_gas >= tx.max_priority_fee_per_gas" do + test "with transactions: transaction.max_fee_per_gas - block.base_fee_per_gas >= transaction.max_priority_fee_per_gas" do block = insert(:block, base_fee_per_gas: 1) insert(:transaction, @@ -541,10 +538,10 @@ defmodule Explorer.ChainTest do max_priority_fee_per_gas: 1 ) - assert Decimal.new(3) == Chain.block_to_priority_fee_of_1559_txs(block.hash) + assert Decimal.new(3) == Chain.block_to_priority_fee_of_1559_transactions(block.hash) end - test "with transactions: tx.max_fee_per_gas - block.base_fee_per_gas < tx.max_priority_fee_per_gas" do + test "with transactions: transaction.max_fee_per_gas - block.base_fee_per_gas < transaction.max_priority_fee_per_gas" do block = insert(:block, base_fee_per_gas: 4) insert(:transaction, @@ -557,7 +554,7 @@ defmodule Explorer.ChainTest do max_priority_fee_per_gas: 3 ) - assert Decimal.new(4) == Chain.block_to_priority_fee_of_1559_txs(block.hash) + assert Decimal.new(4) == Chain.block_to_priority_fee_of_1559_transactions(block.hash) end test "with legacy transactions" do @@ -572,7 +569,7 @@ defmodule Explorer.ChainTest do index: 1 ) - assert Decimal.new(24) == Chain.block_to_priority_fee_of_1559_txs(block.hash) + assert Decimal.new(24) == Chain.block_to_priority_fee_of_1559_transactions(block.hash) end test "0 in blockchain with no EIP-1559 implemented" do @@ -587,7 +584,7 @@ defmodule Explorer.ChainTest do index: 1 ) - assert 0 == Chain.block_to_priority_fee_of_1559_txs(block.hash) + assert 0 == Chain.block_to_priority_fee_of_1559_transactions(block.hash) end end @@ -746,7 +743,7 @@ defmodule Explorer.ChainTest do assert Chain.finished_indexing_internal_transactions?() end - test "finished indexing (no txs)" do + test "finished indexing (no transactions)" do assert Chain.finished_indexing_internal_transactions?() end @@ -3039,7 +3036,7 @@ defmodule Explorer.ChainTest do |> insert() |> with_block(block) - insert(:log, address: address, transaction: transaction) + insert(:log, address: address, transaction: transaction, block: block, block_number: block.number) balance = insert(:unfetched_balance, address_hash: address.hash, block_number: block.number) @@ -3229,7 +3226,7 @@ defmodule Explorer.ChainTest do |> insert() |> with_block(log_block) - insert(:log, address: miner, transaction: log_transaction) + insert(:log, address: miner, transaction: log_transaction, block: log_block, block_number: log_block.number) insert(:unfetched_balance, address_hash: miner.hash, block_number: log_block.number) from_internal_transaction_block = insert(:block) @@ -3310,7 +3307,7 @@ defmodule Explorer.ChainTest do |> insert() |> with_block(block) - insert(:log, address: miner, transaction: log_transaction) + insert(:log, address: miner, transaction: log_transaction, block: block, block_number: block.number) from_internal_transaction_transaction = :transaction @@ -4324,6 +4321,9 @@ defmodule Explorer.ChainTest do end ) + init_config = Application.get_env(:ethereum_jsonrpc, EthereumJSONRPC.Geth) + Application.put_env(:ethereum_jsonrpc, EthereumJSONRPC.Geth, tracer: "call_tracer", debug_trace_timeout: "5s") + assert Chain.transaction_to_revert_reason(transaction) == hex_reason assert Transaction.decoded_revert_reason(transaction, hex_reason) == { @@ -4332,6 +4332,8 @@ defmodule Explorer.ChainTest do "Error(string reason)", [{"reason", "string", "No credit of that type"}] } + + Application.put_env(:ethereum_jsonrpc, EthereumJSONRPC.Geth, init_config) end end diff --git a/apps/explorer/test/explorer/etherscan/logs_test.exs b/apps/explorer/test/explorer/etherscan/logs_test.exs index b595340..02cc8f3 100644 --- a/apps/explorer/test/explorer/etherscan/logs_test.exs +++ b/apps/explorer/test/explorer/etherscan/logs_test.exs @@ -65,7 +65,7 @@ defmodule Explorer.Etherscan.LogsTest do |> insert(to_address: contract_address, block_timestamp: block.timestamp) |> with_block(block) - log = insert(:log, address: contract_address, block_number: block.number, transaction: transaction) + log = insert(:log, address: contract_address, block: block, block_number: block.number, transaction: transaction) filter = %{ from_block: block.number, @@ -99,7 +99,12 @@ defmodule Explorer.Etherscan.LogsTest do |> insert(to_address: contract_address) |> with_block() - insert_list(2, :log, address: contract_address, transaction: transaction, block_number: block.number) + insert_list(2, :log, + address: contract_address, + transaction: transaction, + block_number: block.number, + block: block + ) filter = %{ from_block: block.number, @@ -130,8 +135,19 @@ defmodule Explorer.Etherscan.LogsTest do |> insert(to_address: contract_address) |> with_block(second_block) - 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) + insert(:log, + address: contract_address, + transaction: transaction_block1, + block: first_block, + block_number: first_block.number + ) + + insert(:log, + address: contract_address, + transaction: transaction_block2, + block: second_block, + block_number: second_block.number + ) filter = %{ from_block: second_block.number, @@ -163,8 +179,19 @@ defmodule Explorer.Etherscan.LogsTest do |> insert(to_address: contract_address) |> with_block(second_block) - 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) + insert(:log, + address: contract_address, + transaction: transaction_block1, + block: first_block, + block_number: first_block.number + ) + + insert(:log, + address: contract_address, + transaction: transaction_block2, + block: second_block, + block_number: second_block.number + ) filter = %{ from_block: first_block.number, @@ -188,7 +215,12 @@ defmodule Explorer.Etherscan.LogsTest do |> with_block() inserted_records = - insert_list(2000, :log, address: contract_address, transaction: transaction, block_number: block.number) + insert_list(2000, :log, + address: contract_address, + transaction: transaction, + block_number: block.number, + block: block + ) filter = %{ from_block: block.number, @@ -230,12 +262,16 @@ defmodule Explorer.Etherscan.LogsTest do log1_details = [ address: contract_address, transaction: transaction, + block: block, + block_number: block.number, first_topic: topic(@first_topic_hex_string_1) ] log2_details = [ address: contract_address, transaction: transaction, + block: block, + block_number: block.number, first_topic: topic(@first_topic_hex_string_2) ] @@ -266,6 +302,8 @@ defmodule Explorer.Etherscan.LogsTest do log1_details = [ address: contract_address, transaction: transaction, + block: block, + block_number: block.number, first_topic: topic(@first_topic_hex_string_1), second_topic: topic(@second_topic_hex_string_1) ] @@ -273,6 +311,8 @@ defmodule Explorer.Etherscan.LogsTest do log2_details = [ address: contract_address, transaction: transaction, + block: block, + block_number: block.number, first_topic: topic(@first_topic_hex_string_1), second_topic: topic(@second_topic_hex_string_2) ] @@ -307,6 +347,8 @@ defmodule Explorer.Etherscan.LogsTest do log1_details = [ address: contract_address, transaction: transaction, + block: block, + block_number: block.number, first_topic: topic(@first_topic_hex_string_1), second_topic: topic(@second_topic_hex_string_1) ] @@ -314,6 +356,8 @@ defmodule Explorer.Etherscan.LogsTest do log2_details = [ address: contract_address, transaction: transaction, + block: block, + block_number: block.number, first_topic: topic(@first_topic_hex_string_2), second_topic: topic(@second_topic_hex_string_2) ] @@ -346,6 +390,7 @@ defmodule Explorer.Etherscan.LogsTest do log1_details = [ address: contract_address, transaction: transaction, + block: block, first_topic: topic(@first_topic_hex_string_1), block_number: block.number ] @@ -353,6 +398,7 @@ defmodule Explorer.Etherscan.LogsTest do log2_details = [ address: contract_address, transaction: transaction, + block: block, first_topic: topic(@first_topic_hex_string_2), block_number: block.number ] @@ -385,6 +431,7 @@ defmodule Explorer.Etherscan.LogsTest do log1_details = [ address: contract_address, transaction: transaction, + block: block, first_topic: topic(@first_topic_hex_string_1), second_topic: topic(@second_topic_hex_string_1), block_number: block.number @@ -393,6 +440,7 @@ defmodule Explorer.Etherscan.LogsTest do log2_details = [ address: contract_address, transaction: transaction, + block: block, first_topic: topic(@first_topic_hex_string_2), second_topic: topic(@second_topic_hex_string_2), block_number: block.number @@ -428,6 +476,7 @@ defmodule Explorer.Etherscan.LogsTest do log1_details = [ address: contract_address, transaction: transaction, + block: block, first_topic: topic(@first_topic_hex_string_1), second_topic: topic(@second_topic_hex_string_1), third_topic: topic(@third_topic_hex_string_1), @@ -437,6 +486,7 @@ defmodule Explorer.Etherscan.LogsTest do log2_details = [ address: contract_address, transaction: transaction, + block: block, first_topic: topic(@first_topic_hex_string_2), second_topic: topic(@second_topic_hex_string_2), third_topic: topic(@third_topic_hex_string_2), @@ -446,6 +496,7 @@ defmodule Explorer.Etherscan.LogsTest do log3_details = [ address: contract_address, transaction: transaction, + block: block, first_topic: topic(@first_topic_hex_string_3), second_topic: topic(@second_topic_hex_string_3), third_topic: topic(@third_topic_hex_string_3), @@ -488,6 +539,7 @@ defmodule Explorer.Etherscan.LogsTest do log1_details = [ address: contract_address, transaction: transaction, + block: block, first_topic: topic(@first_topic_hex_string_1), second_topic: topic(@second_topic_hex_string_1), third_topic: topic(@third_topic_hex_string_1), @@ -497,6 +549,7 @@ defmodule Explorer.Etherscan.LogsTest do log2_details = [ address: contract_address, transaction: transaction, + block: block, first_topic: topic(@first_topic_hex_string_2), second_topic: topic(@second_topic_hex_string_2), third_topic: topic(@third_topic_hex_string_2), @@ -506,6 +559,7 @@ defmodule Explorer.Etherscan.LogsTest do log3_details = [ address: contract_address, transaction: transaction, + block: block, first_topic: topic(@first_topic_hex_string_3), second_topic: topic(@second_topic_hex_string_3), third_topic: topic(@third_topic_hex_string_3), @@ -548,6 +602,7 @@ defmodule Explorer.Etherscan.LogsTest do log1_details = [ address: contract_address, transaction: transaction, + block: block, first_topic: topic(@first_topic_hex_string_1), second_topic: topic(@second_topic_hex_string_1), third_topic: topic(@third_topic_hex_string_1), @@ -557,6 +612,7 @@ defmodule Explorer.Etherscan.LogsTest do log2_details = [ address: contract_address, transaction: transaction, + block: block, first_topic: topic(@first_topic_hex_string_1), second_topic: topic(@second_topic_hex_string_2), third_topic: topic(@third_topic_hex_string_1), @@ -566,6 +622,7 @@ defmodule Explorer.Etherscan.LogsTest do log3_details = [ address: contract_address, transaction: transaction, + block: block, first_topic: topic(@first_topic_hex_string_1), second_topic: topic(@second_topic_hex_string_1), third_topic: topic(@third_topic_hex_string_1), @@ -608,6 +665,7 @@ defmodule Explorer.Etherscan.LogsTest do log1_details = [ address: contract_address, transaction: transaction, + block: block, first_topic: topic(@first_topic_hex_string_1), second_topic: topic(@second_topic_hex_string_1), block_number: block.number @@ -616,6 +674,7 @@ defmodule Explorer.Etherscan.LogsTest do log2_details = [ address: contract_address, transaction: transaction, + block: block, first_topic: topic(@first_topic_hex_string_2), second_topic: topic(@second_topic_hex_string_2), third_topic: topic(@third_topic_hex_string_2), @@ -626,6 +685,7 @@ defmodule Explorer.Etherscan.LogsTest do log3_details = [ address: contract_address, transaction: transaction, + block: block, first_topic: topic(@first_topic_hex_string_1), second_topic: topic(@second_topic_hex_string_1), third_topic: topic(@third_topic_hex_string_1), @@ -686,9 +746,26 @@ defmodule Explorer.Etherscan.LogsTest do |> insert(to_address: contract_address) |> with_block(third_block) - insert(:log, address: contract_address, transaction: transaction_block3) - insert(:log, address: contract_address, transaction: transaction_block1) - insert(:log, address: contract_address, transaction: transaction_block2) + insert(:log, + address: contract_address, + transaction: transaction_block3, + block: third_block, + block_number: third_block.number + ) + + insert(:log, + address: contract_address, + transaction: transaction_block1, + block: first_block, + block_number: first_block.number + ) + + insert(:log, + address: contract_address, + transaction: transaction_block2, + block: second_block, + block_number: second_block.number + ) filter = %{ from_block: first_block.number, diff --git a/apps/explorer/test/explorer/migrator/reindex_internal_transactions_with_incompatible_status_test.exs b/apps/explorer/test/explorer/migrator/reindex_internal_transactions_with_incompatible_status_test.exs new file mode 100644 index 0000000..4ca3037 --- /dev/null +++ b/apps/explorer/test/explorer/migrator/reindex_internal_transactions_with_incompatible_status_test.exs @@ -0,0 +1,73 @@ +defmodule Explorer.Migrator.ReindexInternalTransactionsWithIncompatibleStatusTest do + use Explorer.DataCase, async: false + + alias Explorer.Chain.PendingBlockOperation + alias Explorer.Migrator.{ReindexInternalTransactionsWithIncompatibleStatus, MigrationStatus} + alias Explorer.Repo + + describe "Migrate incorrect internal transactions" do + test "Adds new pbo for incorrect internal transactions" do + incorrect_block_numbers = + Enum.map(1..5, fn i -> + block = insert(:block) + transaction = :transaction |> insert() |> with_block(block, status: :error) + + insert(:internal_transaction, + index: 10, + transaction: transaction, + block: block, + block_number: block.number, + block_index: i, + error: nil + ) + + block.number + end) + + Enum.each(1..5, fn i -> + block = insert(:block) + transaction = :transaction |> insert() |> with_block(block, status: :error) + + insert(:internal_transaction, + index: 10, + transaction: transaction, + block: block, + block_number: block.number, + block_index: i, + error: "error", + output: nil + ) + end) + + Enum.each(1..5, fn i -> + block = insert(:block) + transaction = :transaction |> insert() |> with_block(block, status: :ok) + + insert(:internal_transaction, + index: 10, + transaction: transaction, + block: block, + block_number: block.number, + block_index: i, + error: nil + ) + end) + + assert MigrationStatus.get_status("reindex_internal_transactions_with_incompatible_status") == nil + assert Repo.all(PendingBlockOperation) == [] + + ReindexInternalTransactionsWithIncompatibleStatus.start_link([]) + Process.sleep(100) + + pbo_block_numbers = + PendingBlockOperation + |> Repo.all() + |> Enum.map(& &1.block_number) + |> Enum.sort() + + assert incorrect_block_numbers == pbo_block_numbers + + assert MigrationStatus.get_status("reindex_internal_transactions_with_incompatible_status") == "completed" + end + end +end diff --git a/apps/explorer/test/explorer/migrator/sanitize_duplicated_log_index_logs_test.exs b/apps/explorer/test/explorer/migrator/sanitize_duplicated_log_index_logs_test.exs new file mode 100644 index 0000000..7d0c5cf --- /dev/null +++ b/apps/explorer/test/explorer/migrator/sanitize_duplicated_log_index_logs_test.exs @@ -0,0 +1,148 @@ +defmodule Explorer.Migrator.SanitizeDuplicatedLogIndexLogsTest do + use Explorer.DataCase, async: false + + alias Explorer.Chain.Cache.BackgroundMigrations + alias Explorer.Chain.Log + alias Explorer.Chain.TokenTransfer + alias Explorer.Chain.Token.Instance + alias Explorer.Migrator.{SanitizeDuplicatedLogIndexLogs, MigrationStatus} + + if Application.compile_env(:explorer, :chain_type) in [:polygon_zkevm, :rsk, :filecoin] do + describe "Sanitize duplicated log index logs" do + test "correctly identifies and updates duplicated log index logs" do + block = insert(:block) + + tx1 = :transaction |> insert() |> with_block(block, index: 0) + tx2 = :transaction |> insert() |> with_block(block, index: 1) + + _log1 = insert(:log, transaction: tx1, index: 3, data: "0x01", block: block, block_number: block.number) + _log2 = insert(:log, transaction: tx1, index: 0, data: "0x02", block: block, block_number: block.number) + _log3 = insert(:log, transaction: tx2, index: 3, data: "0x03", block: block, block_number: block.number) + + log4 = insert(:log) + + assert MigrationStatus.get_status("sanitize_duplicated_log_index_logs") == nil + + SanitizeDuplicatedLogIndexLogs.start_link([]) + :timer.sleep(500) + + assert MigrationStatus.get_status("sanitize_duplicated_log_index_logs") == "completed" + assert BackgroundMigrations.get_sanitize_duplicated_log_index_logs_finished() == true + + updated_logs = + Repo.all(Log |> where([log], log.block_number == ^block.number) |> order_by([log], asc: log.index)) + + assert match?( + [ + %{index: 0, data: %Explorer.Chain.Data{bytes: <<2>>}}, + %{index: 1, data: %Explorer.Chain.Data{bytes: <<1>>}}, + %{index: 2, data: %Explorer.Chain.Data{bytes: <<3>>}} + ], + updated_logs + ) + + assert %Log{log4 | address: nil, block: nil, transaction: nil} == %Log{ + Repo.one(Log |> where([log], log.block_number != ^block.number)) + | address: nil, + block: nil, + transaction: nil + } + end + + test "correctly identifies and updates duplicated log index logs & updates corresponding token transfers and token instances" do + block = insert(:block) + 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) + + tx1 = :transaction |> insert() |> with_block(block, index: 0) + tx2 = :transaction |> insert() |> with_block(block, index: 1) + + log1 = insert(:log, transaction: tx1, index: 3, data: "0x01", block: block, block_number: block.number) + log2 = insert(:log, transaction: tx1, index: 0, data: "0x02", block: block, block_number: block.number) + log3 = insert(:log, transaction: tx2, index: 3, data: "0x03", block: block, block_number: block.number) + + log4 = insert(:log) + + _tt1 = + insert(:token_transfer, + token_type: "ERC-721", + block: block, + block_number: block.number, + log_index: log1.index, + token_ids: [instance.token_id], + token_contract_address: token_address, + token_contract_address_hash: token_address.hash, + transaction: tx1, + transaction_hash: tx1.hash, + block_hash: block.hash + ) + + _tt2 = + insert(:token_transfer, + block: block, + block_number: block.number, + log_index: log2.index, + transaction: tx1, + transaction_hash: tx1.hash + ) + + _tt3 = + insert(:token_transfer, + block: block, + block_number: block.number, + log_index: log3.index, + transaction: tx2, + transaction_hash: tx2.hash + ) + + Instance.changeset(instance, %{owner_updated_at_block: block.number, owner_updated_at_log_index: log1.index}) + |> Repo.update!() + + assert MigrationStatus.get_status("sanitize_duplicated_log_index_logs") == nil + + SanitizeDuplicatedLogIndexLogs.start_link([]) + :timer.sleep(500) + + assert MigrationStatus.get_status("sanitize_duplicated_log_index_logs") == "completed" + assert BackgroundMigrations.get_sanitize_duplicated_log_index_logs_finished() == true + + updated_logs = + Repo.all(Log |> where([log], log.block_number == ^block.number) |> order_by([log], asc: log.index)) + + assert match?( + [ + %{index: 0, data: %Explorer.Chain.Data{bytes: <<2>>}}, + %{index: 1, data: %Explorer.Chain.Data{bytes: <<1>>}}, + %{index: 2, data: %Explorer.Chain.Data{bytes: <<3>>}} + ], + updated_logs + ) + + block_number = block.number + assert [%{owner_updated_at_block: ^block_number, owner_updated_at_log_index: 1}] = Repo.all(Instance) + + assert [%{log_index: 1, block_number: ^block_number}] = + Repo.all(TokenTransfer |> where([tt], tt.token_type == "ERC-721")) + + assert %Log{log4 | address: nil, block: nil, transaction: nil} == %Log{ + Repo.one(Log |> where([log], log.block_number != ^block.number)) + | address: nil, + block: nil, + transaction: nil + } + end + + test "correctly handles cases where there are no duplicated log index logs" do + assert MigrationStatus.get_status("sanitize_duplicated_log_index_logs") == nil + + SanitizeDuplicatedLogIndexLogs.start_link([]) + :timer.sleep(100) + + assert MigrationStatus.get_status("sanitize_duplicated_log_index_logs") == "completed" + assert BackgroundMigrations.get_sanitize_duplicated_log_index_logs_finished() == true + end + end + end +end diff --git a/apps/explorer/test/explorer/migrator/sanitize_incorrect_weth_token_transfers_test.exs b/apps/explorer/test/explorer/migrator/sanitize_incorrect_weth_token_transfers_test.exs index 834c3d8..b2f4be3 100644 --- a/apps/explorer/test/explorer/migrator/sanitize_incorrect_weth_token_transfers_test.exs +++ b/apps/explorer/test/explorer/migrator/sanitize_incorrect_weth_token_transfers_test.exs @@ -132,7 +132,7 @@ defmodule Explorer.Migrator.SanitizeIncorrectWETHTokenTransfersTest do %{token_contract_address_hash: ^whitelisted_token_address_hash}, %{token_contract_address_hash: ^whitelisted_token_address_hash}, %{token_contract_address_hash: ^whitelisted_token_address_hash} - ] = transfers = Repo.all(TokenTransfer) + ] = transfers = Repo.all(TokenTransfer, order_by: [asc: :block_number, asc: :log_index]) withdrawal = Enum.at(transfers, 1) deposit = Enum.at(transfers, 2) diff --git a/apps/explorer/test/explorer/migrator/sanitize_missing_token_balances_test.exs b/apps/explorer/test/explorer/migrator/sanitize_missing_token_balances_test.exs new file mode 100644 index 0000000..e8c990e --- /dev/null +++ b/apps/explorer/test/explorer/migrator/sanitize_missing_token_balances_test.exs @@ -0,0 +1,52 @@ +defmodule Explorer.Migrator.SanitizeMissingTokenBalancesTest do + use Explorer.DataCase, async: false + + alias Explorer.Chain.Address.{CurrentTokenBalance, TokenBalance} + alias Explorer.Migrator.{SanitizeMissingTokenBalances, MigrationStatus} + alias Explorer.Repo + + describe "Migrate token balances" do + test "Unset value and value_fetched_at for token balances related to not processed current token balances" do + Enum.each(0..10, fn _x -> + token_balance = insert(:token_balance) + + insert(:token_balance, + address: token_balance.address, + token_contract_address_hash: token_balance.token_contract_address_hash, + token_id: token_balance.token_id + ) + + insert(:address_current_token_balance, + address: token_balance.address, + token_contract_address_hash: token_balance.token_contract_address_hash, + token_id: token_balance.token_id, + value: nil, + value_fetched_at: nil + ) + + refute is_nil(token_balance.value) + refute is_nil(token_balance.value_fetched_at) + end) + + assert MigrationStatus.get_status("sanitize_missing_token_balances") == nil + + SanitizeMissingTokenBalances.start_link([]) + Process.sleep(100) + + TokenBalance + |> Repo.all() + |> Enum.group_by(&{&1.address_hash, &1.token_contract_address_hash, &1.token_id}) + |> Enum.each(fn {_, tbs} -> + assert [%{value: nil, value_fetched_at: nil}, %{value: old_value, value_fetched_at: old_value_fetched_at}] = + Enum.sort_by(tbs, & &1.block_number, &>=/2) + + refute is_nil(old_value) + refute is_nil(old_value_fetched_at) + end) + + assert Repo.all(CurrentTokenBalance) == [] + + assert MigrationStatus.get_status("sanitize_missing_token_balances") == "completed" + end + end +end diff --git a/apps/explorer/test/explorer/smart_contract/solidity/verifier_test.exs b/apps/explorer/test/explorer/smart_contract/solidity/verifier_test.exs index a5500ec..89dbd97 100644 --- a/apps/explorer/test/explorer/smart_contract/solidity/verifier_test.exs +++ b/apps/explorer/test/explorer/smart_contract/solidity/verifier_test.exs @@ -529,7 +529,7 @@ if Application.compile_env(:explorer, :chain_type) !== :zksync do assert abi != nil end - test "verifies smart-contract created from another contract using successful tx" do + test "verifies smart-contract created from another contract using successful transaction" do path = File.cwd!() <> "/test/support/fixture/smart_contract/contract_from_factory.sol" contract = File.read!(path) diff --git a/apps/explorer/test/explorer/token/metadata_retriever_test.exs b/apps/explorer/test/explorer/token/metadata_retriever_test.exs index 97d59bd..b45c3b2 100644 --- a/apps/explorer/test/explorer/token/metadata_retriever_test.exs +++ b/apps/explorer/test/explorer/token/metadata_retriever_test.exs @@ -763,7 +763,7 @@ defmodule Explorer.Token.MetadataRetrieverTest do Explorer.Mox.HTTPoison |> expect(:get, fn "https://ipfs.io/ipfs/QmT1Yz43R1PLn2RVovAnEM5dHQEvpTcnwgX8zftvY1FcjP", - [{"x-apikey", "mykey"}], + [{"x-apikey", "mykey"}, {"User-Agent", _}], _options -> {:ok, %HTTPoison.Response{status_code: 200, body: Jason.encode!(result)}} end) @@ -885,7 +885,7 @@ defmodule Explorer.Token.MetadataRetrieverTest do }} end - test "fetches image from ipfs link directly", %{bypass: bypass} do + test "fetches image from ipfs link directly" do path = "/ipfs/bafybeig6nlmyzui7llhauc52j2xo5hoy4lzp6442lkve5wysdvjkizxonu" json = """ @@ -894,14 +894,19 @@ defmodule Explorer.Token.MetadataRetrieverTest do } """ - Bypass.expect(bypass, "GET", path, fn conn -> - Conn.resp(conn, 200, json) + Application.put_env(:explorer, :http_adapter, Explorer.Mox.HTTPoison) + + Explorer.Mox.HTTPoison + |> expect(:get, fn "https://ipfs.io/ipfs/bafybeig6nlmyzui7llhauc52j2xo5hoy4lzp6442lkve5wysdvjkizxonu", + _headers, + _options -> + {:ok, %HTTPoison.Response{status_code: 200, body: json}} end) data = {:ok, [ - "http://localhost:#{bypass.port}#{path}" + path ]} assert {:ok, @@ -910,9 +915,11 @@ defmodule Explorer.Token.MetadataRetrieverTest do "image" => "https://ipfs.io/ipfs/bafybeig6nlmyzui7llhauc52j2xo5hoy4lzp6442lkve5wysdvjkizxonu" } }} == MetadataRetriever.fetch_json(data) + + Application.put_env(:explorer, :http_adapter, HTTPoison) end - test "Fetches metadata from ipfs", %{bypass: bypass} do + test "Fetches metadata from ipfs" do path = "/ipfs/bafybeid4ed2ua7fwupv4nx2ziczr3edhygl7ws3yx6y2juon7xakgj6cfm/51.json" json = """ @@ -921,14 +928,19 @@ defmodule Explorer.Token.MetadataRetrieverTest do } """ - Bypass.expect(bypass, "GET", path, fn conn -> - Conn.resp(conn, 200, json) + Application.put_env(:explorer, :http_adapter, Explorer.Mox.HTTPoison) + + Explorer.Mox.HTTPoison + |> expect(:get, fn "https://ipfs.io/ipfs/bafybeid4ed2ua7fwupv4nx2ziczr3edhygl7ws3yx6y2juon7xakgj6cfm/51.json", + _headers, + _options -> + {:ok, %HTTPoison.Response{status_code: 200, body: json}} end) data = {:ok, [ - "http://localhost:#{bypass.port}#{path}" + path ]} {:ok, @@ -937,6 +949,7 @@ defmodule Explorer.Token.MetadataRetrieverTest do }} = MetadataRetriever.fetch_json(data) assert "ipfs://bafybeihxuj3gxk7x5p36amzootyukbugmx3pw7dyntsrohg3se64efkuga/51.png" == Map.get(metadata, "image") + Application.put_env(:explorer, :http_adapter, HTTPoison) end test "Fetches metadata from '${url}'", %{bypass: bypass} do diff --git a/apps/explorer/test/support/factory.ex b/apps/explorer/test/support/factory.ex index 7d823a8..3a78abc 100644 --- a/apps/explorer/test/support/factory.ex +++ b/apps/explorer/test/support/factory.ex @@ -60,7 +60,7 @@ defmodule Explorer.Factory do alias Explorer.Utility.{MissingBalanceOfToken, MissingBlockRange} alias Ueberauth.Strategy.Auth0 - alias Ueberauth.Auth.Info + alias Ueberauth.Auth.{Extra, Info} alias Ueberauth.Auth if Application.compile_env(:explorer, :chain_type) == :zksync do @@ -78,13 +78,20 @@ defmodule Explorer.Factory do end def auth_factory do + email = sequence(:email, &"test_user-#{&1}@blockscout.com") + image = sequence("https://example.com/avatar/test_user") + name = sequence("User Test") + nickname = sequence("test_user") + uid = sequence("blockscout|000") + address_hash = to_string(build(:contract_address).hash) + %Auth{ info: %Info{ birthday: nil, description: nil, - email: sequence(:email, &"test_user-#{&1}@blockscout.com"), + email: email, first_name: nil, - image: sequence("https://example.com/avatar/test_user"), + image: image, last_name: nil, location: nil, name: sequence("User Test"), @@ -94,7 +101,36 @@ defmodule Explorer.Factory do }, provider: :auth0, strategy: Auth0, - uid: sequence("blockscout|000") + uid: uid, + extra: %Extra{ + raw_info: %{ + user: %{ + "created_at" => "2024-09-06T13:49:20.481Z", + "email" => email, + "email_verified" => true, + "identities" => [ + %{ + "connection" => "email", + "isSocial" => false, + "provider" => "email", + "user_id" => "66db0852af53e2c0ae80ddb2" + } + ], + "last_ip" => "42.42.42.42", + "last_login" => "2024-09-14T12:14:26.965Z", + "logins_count" => 11, + "name" => name, + "nickname" => nickname, + "picture" => image, + "updated_at" => "2024-09-14T12:14:26.966Z", + "user_id" => uid, + "user_metadata" => %{ + "web3_address_hash" => address_hash + } + }, + token: nil + } + } } end @@ -186,7 +222,7 @@ defmodule Explorer.Factory do end def tag_transaction_db_factory(%{user: user}) do - %TagTransaction{name: sequence("name"), tx_hash: insert(:transaction).hash, identity_id: user.id} + %TagTransaction{name: sequence("name"), transaction_hash: insert(:transaction).hash, identity_id: user.id} end def address_to_tag_factory do @@ -229,6 +265,19 @@ defmodule Explorer.Factory do %Address{ hash: address_hash() } + |> Map.merge(address_factory_chain_type_fields()) + end + + case Application.compile_env(:explorer, :chain_type) do + :zksync -> + defp address_factory_chain_type_fields() do + %{ + contract_code_refetched: true + } + end + + _ -> + defp address_factory_chain_type_fields(), do: %{} end def address_name_factory do @@ -825,11 +874,12 @@ defmodule Explorer.Factory do token_address = insert(:contract_address, contract_code: contract_code) token = insert(:token, contract_address: token_address) + block = build(:block) %TokenTransfer{ - block: build(:block), + block: block, amount: Decimal.new(1), - block_number: block_number(), + block_number: block.number, from_address: from_address, to_address: to_address, token_contract_address: token_address, diff --git a/apps/explorer/test/support/fixture/vcr_cassettes/transaction_importer_txn_without_block.json b/apps/explorer/test/support/fixture/vcr_cassettes/transaction_importer_transaction_without_block.json similarity index 100% rename from apps/explorer/test/support/fixture/vcr_cassettes/transaction_importer_txn_without_block.json rename to apps/explorer/test/support/fixture/vcr_cassettes/transaction_importer_transaction_without_block.json diff --git a/apps/indexer/config/config.exs b/apps/indexer/config/config.exs index 5ce9ab2..808c367 100644 --- a/apps/indexer/config/config.exs +++ b/apps/indexer/config/config.exs @@ -20,6 +20,11 @@ config :logger, :indexer, block_number step count error_count shrunk import_id transaction_id)a, metadata_filter: [application: :indexer] +config :os_mon, + start_cpu_sup: false, + start_disksup: false, + start_memsup: true + # Import environment specific config. This must remain at the bottom # of this file so it overrides the configuration defined above. import_config "#{config_env()}.exs" diff --git a/apps/indexer/lib/indexer/application.ex b/apps/indexer/lib/indexer/application.ex index 2a688a1..5851e73 100644 --- a/apps/indexer/lib/indexer/application.ex +++ b/apps/indexer/lib/indexer/application.ex @@ -12,19 +12,9 @@ defmodule Indexer.Application do alias Indexer.Fetcher.OnDemand.TokenTotalSupply, as: TokenTotalSupplyOnDemand alias Indexer.Memory - alias Indexer.Prometheus.PendingBlockOperationsCollector - alias Prometheus.Registry @impl Application def start(_type, _args) do - Registry.register_collector(PendingBlockOperationsCollector) - - memory_monitor_options = - case Application.get_env(:indexer, :memory_limit) do - nil -> %{} - integer when is_integer(integer) -> %{limit: integer} - end - memory_monitor_name = Memory.Monitor json_rpc_named_arguments = Application.fetch_env!(:indexer, :json_rpc_named_arguments) @@ -50,7 +40,7 @@ defmodule Indexer.Application do base_children = [ :hackney_pool.child_spec(:token_instance_fetcher, max_connections: pool_size), - {Memory.Monitor, [memory_monitor_options, [name: memory_monitor_name]]}, + {Memory.Monitor, [%{}, [name: memory_monitor_name]]}, {CoinBalanceOnDemand.Supervisor, [json_rpc_named_arguments]}, {ContractCodeOnDemand.Supervisor, [json_rpc_named_arguments]}, {TokenInstanceMetadataRefetchOnDemand.Supervisor, [json_rpc_named_arguments]}, diff --git a/apps/indexer/lib/indexer/block/catchup/fetcher.ex b/apps/indexer/lib/indexer/block/catchup/fetcher.ex index 180ddf3..11a7de7 100644 --- a/apps/indexer/lib/indexer/block/catchup/fetcher.ex +++ b/apps/indexer/lib/indexer/block/catchup/fetcher.ex @@ -14,6 +14,7 @@ defmodule Indexer.Block.Catchup.Fetcher do async_import_celo_epoch_block_operations: 2, async_import_coin_balances: 2, async_import_created_contract_codes: 2, + async_import_filecoin_addresses_info: 2, async_import_internal_transactions: 2, async_import_replaced_transactions: 2, async_import_token_balances: 2, @@ -57,8 +58,8 @@ defmodule Indexer.Block.Catchup.Fetcher do } missing_ranges -> - first.._ = List.first(missing_ranges) - _..last = List.last(missing_ranges) + first.._//_ = List.first(missing_ranges) + _..last//_ = List.last(missing_ranges) Logger.metadata(first_block_number: first, last_block_number: last) @@ -141,6 +142,7 @@ defmodule Indexer.Block.Catchup.Fetcher do async_import_token_instances(imported) async_import_blobs(imported, realtime?) async_import_celo_epoch_block_operations(imported, realtime?) + async_import_filecoin_addresses_info(imported, realtime?) end defp stream_fetch_and_import(state, ranges) do @@ -163,7 +165,7 @@ defmodule Indexer.Block.Catchup.Fetcher do ) defp fetch_and_import_missing_range( %__MODULE__{block_fetcher: %Block.Fetcher{} = block_fetcher}, - first..last = range + first..last//_ = range ) do Logger.metadata(fetcher: :block_catchup, first_block_number: first, last_block_number: last) Process.flag(:trap_exit, true) @@ -174,8 +176,8 @@ defmodule Indexer.Block.Catchup.Fetcher do case result do {:ok, %{inserted: inserted, errors: errors}} -> - handle_null_rounds(errors) - clear_missing_ranges(range, errors) + valid_errors = handle_null_rounds(errors) + clear_missing_ranges(range, valid_errors) {:ok, inserted: inserted} @@ -268,7 +270,7 @@ defmodule Indexer.Block.Catchup.Fetcher do number, nil -> {:cont, number..number} - number, first..last when number == last - 1 -> + number, first..last//_ when number == last - 1 -> {:cont, first..number} number, range -> diff --git a/apps/indexer/lib/indexer/block/catchup/massive_blocks_fetcher.ex b/apps/indexer/lib/indexer/block/catchup/massive_blocks_fetcher.ex index fa1c91e..ea3de2e 100644 --- a/apps/indexer/lib/indexer/block/catchup/massive_blocks_fetcher.ex +++ b/apps/indexer/lib/indexer/block/catchup/massive_blocks_fetcher.ex @@ -58,7 +58,7 @@ defmodule Indexer.Block.Catchup.MassiveBlocksFetcher do defp process_block(block_fetcher, number) do case Fetcher.fetch_and_import_range(block_fetcher, number..number, %{timeout: :infinity}) do {:ok, _result} -> - Logger.info("MassiveBlockFetcher successfully proceed block #{inspect(number)}") + Logger.info("MassiveBlockFetcher successfully processed block #{inspect(number)}") MassiveBlock.delete_block_number(number) [] @@ -66,6 +66,10 @@ defmodule Indexer.Block.Catchup.MassiveBlocksFetcher do Logger.error("MassiveBlockFetcher failed: #{inspect(error)}") [number] end + rescue + error -> + Logger.error("MassiveBlockFetcher failed: #{inspect(error)}") + [number] end defp generate_block_fetcher do 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 43b79e7..ebd10dc 100644 --- a/apps/indexer/lib/indexer/block/catchup/missing_ranges_collector.ex +++ b/apps/indexer/lib/indexer/block/catchup/missing_ranges_collector.ex @@ -60,7 +60,7 @@ defmodule Indexer.Block.Catchup.MissingRangesCollector do ranges |> Enum.reverse() - |> Enum.flat_map(fn f..l -> Chain.missing_block_number_ranges(l..f) end) + |> Enum.flat_map(fn f..l//_ -> Chain.missing_block_number_ranges(l..f) end) |> MissingRangesManipulator.save_batch() if not is_nil(max_fetched_block_number) do @@ -246,7 +246,7 @@ defmodule Indexer.Block.Catchup.MissingRangesCollector do |> RangesHelper.sanitize_ranges() case List.last(ranges) do - _from.._to -> + _from.._to//_ -> {:finite_ranges, ranges} nil -> diff --git a/apps/indexer/lib/indexer/block/fetcher.ex b/apps/indexer/lib/indexer/block/fetcher.ex index 0308203..7672de6 100644 --- a/apps/indexer/lib/indexer/block/fetcher.ex +++ b/apps/indexer/lib/indexer/block/fetcher.ex @@ -11,15 +11,18 @@ defmodule Indexer.Block.Fetcher do alias EthereumJSONRPC.{Blocks, FetchedBeneficiaries} alias Explorer.Chain - alias Explorer.Chain.{Address, Block, Hash, Import, Transaction, Wei} alias Explorer.Chain.Block.Reward alias Explorer.Chain.Cache.Blocks, as: BlocksCache alias Explorer.Chain.Cache.{Accounts, BlockNumber, Transactions, Uncles} + alias Explorer.Chain.Filecoin.PendingAddressOperation, as: FilecoinPendingAddressOperation + alias Explorer.Chain.{Address, Block, Hash, Import, Transaction, Wei} alias Indexer.Block.Fetcher.Receipts + alias Indexer.Fetcher.Arbitrum.MessagesToL2Matcher, as: ArbitrumMessagesToL2Matcher alias Indexer.Fetcher.Celo.EpochBlockOperations, as: CeloEpochBlockOperations alias Indexer.Fetcher.Celo.EpochLogs, as: CeloEpochLogs alias Indexer.Fetcher.CoinBalance.Catchup, as: CoinBalanceCatchup alias Indexer.Fetcher.CoinBalance.Realtime, as: CoinBalanceRealtime + alias Indexer.Fetcher.Filecoin.AddressInfo, as: FilecoinAddressInfo alias Indexer.Fetcher.PolygonZkevm.BridgeL1Tokens, as: PolygonZkevmBridgeL1Tokens alias Indexer.Fetcher.TokenInstance.Realtime, as: TokenInstanceRealtime @@ -41,6 +44,7 @@ defmodule Indexer.Block.Fetcher do Addresses, AddressTokenBalances, MintTransfers, + SignedAuthorizations, TokenInstances, TokenTransfers, TransactionActions @@ -50,6 +54,8 @@ defmodule Indexer.Block.Fetcher do alias Indexer.Transform.PolygonEdge.{DepositExecutes, Withdrawals} + alias Indexer.Transform.Scroll.L1FeeParams, as: ScrollL1FeeParams + alias Indexer.Transform.Arbitrum.Messaging, as: ArbitrumMessaging alias Indexer.Transform.Shibarium.Bridge, as: ShibariumBridge @@ -135,7 +141,7 @@ defmodule Indexer.Block.Fetcher do callback_module: callback_module, json_rpc_named_arguments: json_rpc_named_arguments } = state, - _.._ = range, + _.._//_ = range, additional_options \\ %{} ) when callback_module != nil do @@ -156,7 +162,7 @@ defmodule Indexer.Block.Fetcher do %{logs: receipt_logs, receipts: receipts} = receipt_params, transactions_with_receipts = Receipts.put(transactions_params_without_receipts, receipts), celo_epoch_logs = CeloEpochLogs.fetch(blocks, json_rpc_named_arguments), - logs = receipt_logs ++ celo_epoch_logs, + logs = maybe_set_new_log_index(receipt_logs) ++ celo_epoch_logs, %{token_transfers: token_transfers, tokens: tokens} = TokenTransfers.parse(logs), %{token_transfers: celo_native_token_transfers, tokens: celo_tokens} = CeloTransactionTokenTransfers.parse_transactions(transactions_with_receipts), @@ -174,6 +180,11 @@ defmodule Indexer.Block.Fetcher do do: DepositExecutes.parse(logs), else: [] ), + scroll_l1_fee_params = + if(callback_module == Indexer.Block.Realtime.Fetcher, + do: ScrollL1FeeParams.parse(logs), + else: [] + ), shibarium_bridge_operations = if(callback_module == Indexer.Block.Realtime.Fetcher, do: ShibariumBridge.parse(blocks, transactions_with_receipts, logs), @@ -184,7 +195,8 @@ defmodule Indexer.Block.Fetcher do do: PolygonZkevmBridge.parse(blocks, logs), else: [] ), - arbitrum_xlevel_messages = ArbitrumMessaging.parse(transactions_with_receipts, logs), + {arbitrum_xlevel_messages, arbitrum_transactions_for_further_handling} = + ArbitrumMessaging.parse(transactions_with_receipts, logs), %FetchedBeneficiaries{params_set: beneficiary_params_set, errors: beneficiaries_errors} = fetch_beneficiaries(blocks, transactions_with_receipts, json_rpc_named_arguments), addresses = @@ -232,7 +244,8 @@ defmodule Indexer.Block.Fetcher do tokens: %{params: tokens}, transactions: %{params: transactions_with_receipts}, withdrawals: %{params: withdrawals_params}, - token_instances: %{params: token_instances} + token_instances: %{params: token_instances}, + signed_authorizations: %{params: SignedAuthorizations.parse(transactions_with_receipts)} }, chain_type_import_options = %{ transactions_with_receipts: transactions_with_receipts, @@ -240,6 +253,7 @@ defmodule Indexer.Block.Fetcher do polygon_edge_withdrawals: polygon_edge_withdrawals, polygon_edge_deposit_executes: polygon_edge_deposit_executes, polygon_zkevm_bridge_operations: polygon_zkevm_bridge_operations, + scroll_l1_fee_params: scroll_l1_fee_params, shibarium_bridge_operations: shibarium_bridge_operations, celo_gas_tokens: celo_gas_tokens, arbitrum_messages: arbitrum_xlevel_messages @@ -249,13 +263,13 @@ defmodule Indexer.Block.Fetcher do state, basic_import_options |> Map.merge(additional_options) |> import_options(chain_type_import_options) ), - {:tx_actions, {:ok, inserted_tx_actions}} <- - {:tx_actions, + {:transaction_actions, {:ok, inserted_transaction_actions}} <- + {:transaction_actions, Chain.import(%{ transaction_actions: %{params: transaction_actions}, timeout: :infinity })} do - inserted = Map.merge(inserted, inserted_tx_actions) + inserted = Map.merge(inserted, inserted_transaction_actions) Prometheus.Instrumenter.block_batch_fetch(fetch_time, callback_module) result = {:ok, %{inserted: inserted, errors: blocks_errors}} update_block_cache(inserted[:blocks]) @@ -263,6 +277,9 @@ defmodule Indexer.Block.Fetcher do update_addresses_cache(inserted[:addresses]) update_uncles_cache(inserted[:block_second_degree_relations]) update_withdrawals_cache(inserted[:withdrawals]) + + async_match_arbitrum_messages_to_l2(arbitrum_transactions_for_further_handling) + result else {step, {:error, reason}} -> {:error, {step, reason}} @@ -270,41 +287,51 @@ defmodule Indexer.Block.Fetcher do end end - defp import_options(basic_import_options, %{ - transactions_with_receipts: transactions_with_receipts, - optimism_withdrawals: optimism_withdrawals, - polygon_edge_withdrawals: polygon_edge_withdrawals, - polygon_edge_deposit_executes: polygon_edge_deposit_executes, - polygon_zkevm_bridge_operations: polygon_zkevm_bridge_operations, - shibarium_bridge_operations: shibarium_bridge_operations, - celo_gas_tokens: celo_gas_tokens, - arbitrum_messages: arbitrum_xlevel_messages - }) do - case Application.get_env(:explorer, :chain_type) do - :ethereum -> + case Application.compile_env(:explorer, :chain_type) do + :ethereum -> + defp import_options(basic_import_options, %{transactions_with_receipts: transactions_with_receipts}) do basic_import_options |> Map.put_new(:beacon_blob_transactions, %{ params: transactions_with_receipts |> Enum.filter(&Map.has_key?(&1, :max_fee_per_blob_gas)) }) + end - :optimism -> + :optimism -> + defp import_options(basic_import_options, %{optimism_withdrawals: optimism_withdrawals}) do basic_import_options |> Map.put_new(:optimism_withdrawals, %{params: optimism_withdrawals}) + end - :polygon_edge -> + :polygon_edge -> + defp import_options(basic_import_options, %{ + polygon_edge_withdrawals: polygon_edge_withdrawals, + polygon_edge_deposit_executes: polygon_edge_deposit_executes + }) 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}) + end - :polygon_zkevm -> + :polygon_zkevm -> + defp import_options(basic_import_options, %{polygon_zkevm_bridge_operations: polygon_zkevm_bridge_operations}) do basic_import_options |> Map.put_new(:polygon_zkevm_bridge_operations, %{params: polygon_zkevm_bridge_operations}) + end + + :scroll -> + defp import_options(basic_import_options, %{scroll_l1_fee_params: scroll_l1_fee_params}) do + basic_import_options + |> Map.put_new(:scroll_l1_fee_params, %{params: scroll_l1_fee_params}) + end - :shibarium -> + :shibarium -> + defp import_options(basic_import_options, %{shibarium_bridge_operations: shibarium_bridge_operations}) do basic_import_options |> Map.put_new(:shibarium_bridge_operations, %{params: shibarium_bridge_operations}) + end - :celo -> + :celo -> + defp import_options(basic_import_options, %{celo_gas_tokens: celo_gas_tokens}) do tokens = basic_import_options |> Map.get(:tokens, %{}) @@ -315,14 +342,18 @@ defmodule Indexer.Block.Fetcher do :tokens, %{params: (tokens ++ celo_gas_tokens) |> Enum.uniq()} ) + end - :arbitrum -> + :arbitrum -> + defp import_options(basic_import_options, %{arbitrum_messages: arbitrum_xlevel_messages}) do basic_import_options |> Map.put_new(:arbitrum_messages, %{params: arbitrum_xlevel_messages}) + end - _ -> + _ -> + defp import_options(basic_import_options, _) do basic_import_options - end + end end defp update_block_cache([]), do: :ok @@ -511,6 +542,14 @@ defmodule Indexer.Block.Fetcher do def async_import_celo_epoch_block_operations(_, _), do: :ok + def async_import_filecoin_addresses_info(%{addresses: addresses}, realtime?) do + addresses + |> Enum.map(&%FilecoinPendingAddressOperation{address_hash: &1.hash}) + |> FilecoinAddressInfo.async_fetch(realtime?) + end + + def async_import_filecoin_addresses_info(_, _), do: :ok + defp block_reward_errors_to_block_numbers(block_reward_errors) when is_list(block_reward_errors) do Enum.map(block_reward_errors, &block_reward_error_to_block_number/1) end @@ -729,4 +768,30 @@ defmodule Indexer.Block.Fetcher do Map.put(token_transfer, :token, token) end) end + + # Asynchronously schedules matching of Arbitrum L1-to-L2 messages where the message ID is hashed. + @spec async_match_arbitrum_messages_to_l2([map()]) :: :ok + defp async_match_arbitrum_messages_to_l2([]), do: :ok + + defp async_match_arbitrum_messages_to_l2(transactions_with_messages_from_l1) do + ArbitrumMessagesToL2Matcher.async_discover_match(transactions_with_messages_from_l1) + end + + # workaround for cases when RPC send logs with same index within one block + defp maybe_set_new_log_index(logs) do + logs + |> Enum.group_by(& &1.block_hash) + |> Enum.map(fn {block_hash, logs_per_block} -> + if logs_per_block |> Enum.frequencies_by(& &1.index) |> Map.values() |> Enum.max() == 1 do + logs_per_block + else + Logger.error("Found logs with same index within one block: #{block_hash}") + + logs_per_block + |> Enum.sort_by(&{&1.transaction_index, &1.index, &1.transaction_hash}) + |> Enum.with_index(&%{&1 | index: &2}) + end + end) + |> List.flatten() + end end diff --git a/apps/indexer/lib/indexer/block/realtime/fetcher.ex b/apps/indexer/lib/indexer/block/realtime/fetcher.ex index 10d6fba..c0d4650 100644 --- a/apps/indexer/lib/indexer/block/realtime/fetcher.ex +++ b/apps/indexer/lib/indexer/block/realtime/fetcher.ex @@ -17,6 +17,7 @@ defmodule Indexer.Block.Realtime.Fetcher do async_import_block_rewards: 2, async_import_celo_epoch_block_operations: 2, async_import_created_contract_codes: 2, + async_import_filecoin_addresses_info: 2, async_import_internal_transactions: 2, async_import_polygon_zkevm_bridge_l1_tokens: 1, async_import_realtime_coin_balances: 1, @@ -36,11 +37,6 @@ defmodule Indexer.Block.Realtime.Fetcher do alias Explorer.Utility.MissingRangesManipulator alias Indexer.{Block, Tracer} alias Indexer.Block.Realtime.TaskSupervisor - alias Indexer.Fetcher.Optimism.TxnBatch, as: OptimismTxnBatch - alias Indexer.Fetcher.Optimism.Withdrawal, as: OptimismWithdrawal - alias Indexer.Fetcher.PolygonEdge.{DepositExecute, Withdrawal} - alias Indexer.Fetcher.PolygonZkevm.BridgeL2, as: PolygonZkevmBridgeL2 - alias Indexer.Fetcher.Shibarium.L2, as: ShibariumBridgeL2 alias Indexer.Prometheus alias Timex.Duration @@ -169,14 +165,21 @@ defmodule Indexer.Block.Realtime.Fetcher do Process.cancel_timer(timer) end - if Application.compile_env(:explorer, :chain_type) == :stability do - defp fetch_validators_async do - GenServer.cast(Indexer.Fetcher.Stability.Validator, :update_validators_list) - end - else - defp fetch_validators_async do - :ignore - end + case Application.compile_env(:explorer, :chain_type) do + :stability -> + defp fetch_validators_async do + GenServer.cast(Indexer.Fetcher.Stability.Validator, :update_validators_list) + end + + :blackfort -> + defp fetch_validators_async do + GenServer.cast(Indexer.Fetcher.Blackfort.Validator, :update_validators_list) + end + + _ -> + defp fetch_validators_async do + :ignore + end end defp subscribe_to_new_heads(%__MODULE__{subscription: nil} = state, subscribe_named_arguments) @@ -288,17 +291,7 @@ defmodule Indexer.Block.Realtime.Fetcher do Indexer.Logger.metadata( fn -> if reorg? do - # we need to remove all rows from `op_transaction_batches` and `op_withdrawals` tables previously written starting from reorg block number - remove_optimism_assets_by_number(block_number_to_fetch) - - # 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) - - # we need to remove all rows from `shibarium_bridge` table previously written starting from reorg block number - remove_shibarium_assets_by_number(block_number_to_fetch) - - # we need to remove all rows from `polygon_zkevm_bridge` table previously written starting from reorg block number - remove_polygon_zkevm_assets_by_number(block_number_to_fetch) + remove_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 @@ -312,30 +305,54 @@ defmodule Indexer.Block.Realtime.Fetcher do ) end - defp remove_optimism_assets_by_number(block_number_to_fetch) do - if Application.get_env(:explorer, :chain_type) == :optimism do - OptimismTxnBatch.handle_l2_reorg(block_number_to_fetch) - OptimismWithdrawal.remove(block_number_to_fetch) - end - end + @spec remove_assets_by_number(non_neg_integer()) :: any() - 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 + case Application.compile_env(:explorer, :chain_type) do + :optimism -> + # Removes all rows from `op_transaction_batches` and `op_withdrawals` tables + # previously written starting from the reorg block number + defp remove_assets_by_number(reorg_block) do + # credo:disable-for-lines:2 Credo.Check.Design.AliasUsage + Indexer.Fetcher.Optimism.TransactionBatch.handle_l2_reorg(reorg_block) + Indexer.Fetcher.Optimism.Withdrawal.remove(reorg_block) + end - defp remove_polygon_zkevm_assets_by_number(block_number_to_fetch) do - if Application.get_env(:explorer, :chain_type) == :polygon_zkevm do - PolygonZkevmBridgeL2.reorg_handle(block_number_to_fetch) - end - end + :polygon_edge -> + # Removes all rows from `polygon_edge_withdrawals` and `polygon_edge_deposit_executes` tables + # previously written starting from the reorg block number + defp remove_assets_by_number(reorg_block) do + # credo:disable-for-lines:2 Credo.Check.Design.AliasUsage + Indexer.Fetcher.PolygonEdge.Withdrawal.remove(reorg_block) + Indexer.Fetcher.PolygonEdge.DepositExecute.remove(reorg_block) + end - defp remove_shibarium_assets_by_number(block_number_to_fetch) do - if Application.get_env(:explorer, :chain_type) == :shibarium do - ShibariumBridgeL2.reorg_handle(block_number_to_fetch) - end + :polygon_zkevm -> + # Removes all rows from `polygon_zkevm_bridge` table + # previously written starting from the reorg block number + defp remove_assets_by_number(reorg_block) do + # credo:disable-for-next-line Credo.Check.Design.AliasUsage + Indexer.Fetcher.PolygonZkevm.BridgeL2.reorg_handle(reorg_block) + end + + :shibarium -> + # Removes all rows from `shibarium_bridge` table + # previously written starting from the reorg block number + defp remove_assets_by_number(reorg_block) do + # credo:disable-for-next-line Credo.Check.Design.AliasUsage + Indexer.Fetcher.Shibarium.L2.reorg_handle(reorg_block) + end + + :scroll -> + # Removes all rows from `scroll_bridge` and `scroll_l1_fee_params` tables + # previously written starting from the reorg block number + defp remove_assets_by_number(reorg_block) do + # credo:disable-for-lines:2 Credo.Check.Design.AliasUsage + Indexer.Fetcher.Scroll.BridgeL2.reorg_handle(reorg_block) + Indexer.Fetcher.Scroll.L1FeeParam.handle_l2_reorg(reorg_block) + end + + _ -> + defp remove_assets_by_number(_), do: :ok end @decorate span(tracer: Tracer) @@ -467,5 +484,6 @@ defmodule Indexer.Block.Realtime.Fetcher do async_import_blobs(imported, realtime?) async_import_polygon_zkevm_bridge_l1_tokens(imported) async_import_celo_epoch_block_operations(imported, realtime?) + async_import_filecoin_addresses_info(imported, realtime?) end end diff --git a/apps/indexer/lib/indexer/bound_interval.ex b/apps/indexer/lib/indexer/bound_interval.ex index 2b09b5c..27c8b55 100644 --- a/apps/indexer/lib/indexer/bound_interval.ex +++ b/apps/indexer/lib/indexer/bound_interval.ex @@ -8,7 +8,7 @@ defmodule Indexer.BoundInterval do current: 1, maximum: nil - def within(minimum..maximum) when is_integer(minimum) and is_integer(maximum) and minimum <= maximum do + def within(minimum..maximum//_) when is_integer(minimum) and is_integer(maximum) and minimum <= maximum do %__MODULE__{minimum: minimum, current: minimum, maximum: maximum} end diff --git a/apps/indexer/lib/indexer/buffered_task.ex b/apps/indexer/lib/indexer/buffered_task.ex index 3c9095d..a8d055d 100644 --- a/apps/indexer/lib/indexer/buffered_task.ex +++ b/apps/indexer/lib/indexer/buffered_task.ex @@ -1,51 +1,301 @@ defmodule Indexer.BufferedTask do @moduledoc """ - Provides a behaviour for batched task running with retries. + Provides a behaviour for batched task running with retries and memory-aware buffering. + + This module implements a generic buffered task system that allows for efficient + processing of data in batches. It manages a queue of entries, processes them + concurrently up to a specified limit, and provides mechanisms for retrying + failed tasks. The module is designed to be memory-aware, with the ability to + shrink its memory usage when requested by a memory monitor. + + The `BufferedTask` operates as follows: + + 1. Initialization: + When a module implementing the `BufferedTask` behaviour is started as part of + a supervision tree, it must use `BufferedTask.child_spec/1` to define its + child specification. This ensures that `BufferedTask.start_link/2` is called + correctly, initializing the `BufferedTask` process with the implementing + module's options. During this initialization, the periodic flushing of + buffers to the processing queue is also set up, establishing an automated + cycle of data processing. + + 2. Initial Data Streaming: + Upon initialization, the `BufferedTask` sends itself an `:initial_stream` + message. This triggers a call to the `init/3` callback of the implementing + module, populating the queue with initial data. + + 3. Data Processing Flow: + a. Data Entry: + - External processes can add entries to the buffer using the `buffer/4` + function. This function allows for both synchronous and asynchronous + buffering of entries, with an option to prioritize entries. + b. Flushing: + - Periodically, based on the `flush_interval`, or when manually triggered, + the buffered entries are moved to the main processing queue. This + flushing process automatically triggers the processing of the next + batch of queue data, ensuring continuous data handling without manual + intervention. + c. Batch Processing: + - As concurrency becomes available (controlled by `max_concurrency`), + batches of entries (size determined by `max_batch_size`) are taken from + the queue and processed by spawning tasks that call the `run/2` callback + of the implementing module. + d. Retry Mechanism: + - If a task fails or explicitly requests a retry, the entries are + re-added to the queue for another processing attempt. + + The entire process of periodic flushing, batch processing, and retry handling + is automated, requiring no manual management once the `BufferedTask` is + initialized. This design ensures efficient and continuous data processing with + minimal overhead. + + The `BufferedTask` is designed to be memory-aware and can interact with a + memory monitor to adjust its memory usage when system resources are constrained. + It also provides debugging capabilities to monitor its internal state and + performance. + + ## Initialization Parameters + + The following parameters are passed to `BufferedTask.init/1` through + `BufferedTask.child_spec/1` and `BufferedTask.start_link/2`: + + - `:callback_module`: Required. The module implementing the `BufferedTask` + behaviour. + - `:task_supervisor`: Required. The name of the `Task.Supervisor` to spawn + tasks under. + - `:flush_interval`: Required. The interval in milliseconds between automatic + queue flushes. Set to `:infinity` to disable automatic flushing. + - `:max_batch_size`: Required. The maximum number of entries to be processed + in a single batch. + - `:max_concurrency`: Required. The maximum number of concurrent processing + tasks allowed. + - `:memory_monitor`: Optional. The `Indexer.Memory.Monitor` server to register + with for memory-aware operations. + - `:poll`: Optional. Boolean flag to enable/disable polling for new data after + processing all current entries. Defaults to `true`. + - `:state`: Required. Initial state for the callback module. This can include + any parameters or initial values required for proper functioning of `init` + and `run` callbacks. + - `:metadata`: Optional. Logger metadata to set in the `BufferedTask` process + and its child processes. - ## Named Arguments + ## Callbacks - Named arguments are required and are passed in the list that is the second element of the tuple. + ### `c:init/3` - * `:flush_interval` - The interval in milliseconds to flush the buffer. - * `:max_concurrency` - The maximum number of tasks to run concurrently at any given time. - * `:poll` - poll for new records when all records are processed - * `:max_batch_size` - The maximum batch passed to `c:run/2`. - * `:memory_monitor` - The `Indexer.Memory.Monitor` `t:GenServer.server/0` to register as - `Indexer.Memory.Monitor.shrinkable/0` with. - * `:task_supervisor` - The `Task.Supervisor` name to spawn tasks under. + @callback init(initial, reducer, state) :: {final, state} - ## Options + This callback is invoked during the initial streaming process to populate the + queue with initial data. It runs in a separate task, allowing for long-running + operations without blocking the main `BufferedTask` process. - Options are optional and are passed in the list that is second element of the tuple. + - `initial`: An opaque value representing the initial accumulator. Its structure + and content are fully controlled by the `reducer` function, so there's no need + to handle this parameter specifically within the `init/3` callback. + - `reducer`: A function that accumulates entries into the `BufferedTask`'s + internal buffers. + - `state`: The initial state provided during initialization. - * `:name` - The registered name for the new process. + The `init/3` callback should use the `reducer` function to add entries to the + `BufferedTask`'s buffers. The `BufferedTask` will automatically manage these + entries, flushing them to the main processing queue and initiating batch + processing as needed. - ## Callbacks + ### `c:run/2` + + @callback run(entries, state) :: :ok | {:ok, state} | :retry | {:retry, new_entries :: list} | {:retry, new_entries :: list, state} + + This callback is invoked as concurrency becomes available to process batches + of entries from the queue. It is called within a task spawned by the + `Task.Supervisor` specified during initialization. + + - `entries`: A list of entries to be processed, with a maximum length of + `:max_batch_size`. + - `state`: The current state of the callback module. + + The `run/2` callback processes the given entries and returns one of the following + possible results: + + - `:ok`: Indicates successful processing. + - `{:ok, state}`: Indicates successful processing and requests an update to + the callback module state. + - `:retry`: Signals that the entire batch should be retried. + - `{:retry, new_entries}`: Specifies a new list of entries to be retried. This + can be a completely new list of entries or a subset of entries which were not + successfully handled by `run/2` in this iteration. + - `{:retry, new_entries, state}`: Specifies a new list of entries to be retried + and requests an update to the callback module state. + + If the callback crashes, the `BufferedTask` will automatically retry the batch. + The retry mechanism ensures resilience in data processing, allowing for + temporary failures or resource unavailability to be handled gracefully. + + The `BufferedTask` manages concurrency, ensuring that no more than + `:max_concurrency` `run/2` callbacks are executing simultaneously. This + provides controlled parallelism while preventing system overload. + + ## Examples - `c:init/2` is used for a task to populate its buffer on boot with an initial set of entries. + ### Typical Usage - For example, the following callback would buffer all unfetched account balances on startup: + Here's a comprehensive example of a module implementing the BufferedTask behaviour + for processing random integers: - def init(initial, reducer) do - Chain.stream_unfetched_balances([:hash], initial, fn %{hash: hash}, acc -> - reducer.(Hash.to_string(hash), acc) - end) + defmodule NumberProcessor do + # Will generate the Supervisor and TaskSupervisor for NumberProcessor + use Indexer.Fetcher, restart: :permanent + + alias Indexer.BufferedTask + + def child_spec([init_options, gen_server_options]) do + state = %{initial_count: 50} + + buffered_task_init_options = + [ + poll: false, + flush_interval: 5000, + max_batch_size: 10, + max_concurrency: 2, + task_supervisor: NumberProcessor.TaskSupervisor, + metadata: [task: :number_processor] + ] + |> Keyword.merge(init_options) + |> Keyword.put(:state, state) + + Supervisor.child_spec( + {BufferedTask, [{__MODULE__, buffered_task_init_options}, gen_server_options]}, + id: __MODULE__ + ) + end + + @impl BufferedTask + def init(initial, reducer, %{initial_count: count}) do + Enum.reduce(1..count, initial, fn _, acc -> + number = :rand.uniform(1000) + reducer.(number, acc) + end) + end + + @impl BufferedTask + def run(numbers, _state) do + Enum.each(numbers, fn number -> + result = if rem(number, 2) == 0, do: "even", else: "odd" + IO.puts("Number \#{number} is \#{result}") + end) + :ok + end + + def add_numbers(count, high_priority?) do + numbers = Enum.map(1..count, fn _ -> :rand.uniform(1000) end) + BufferedTask.buffer(__MODULE__, numbers, high_priority?) + end end - `c:init/2` may be long-running and allows concurrent calls to `Explorer.BufferedTask.buffer/2` for on-demand entries. - As concurrency becomes available, `c:run/2` of the task is invoked, with a list of batched entries to be processed. + To use this module in your application's supervision tree: + + children = [ + {NumberProcessor.Supervisor, [[memory_monitor: memory_monitor]]} + ] + + Supervisor.init(children, strategy: :one_for_one) + + This setup assumes you have a memory_monitor defined elsewhere in your application. + + To add more numbers to the buffer after initialization: + + NumberProcessor.add_numbers(20, false) # Add 20 numbers with normal priority + NumberProcessor.add_numbers(5, true) # Add 5 numbers with high priority + + ### Minimal Init and Retry Example + + This example demonstrates two key features of BufferedTask: a minimal `init/3` + callback that simply passes through the initial state and use of `{:retry, new_entries}` + in the `run/2` callback for task rescheduling. + + defmodule TaskScheduler do + use Indexer.Fetcher, restart: :permanent + + alias Indexer.BufferedTask - For example, the `c:run/2` for above `c:init/2` could be written: + def child_spec([init_options, gen_server_options]) do + state = %{max_retries: 3} - def run(string_hashes, _state) do - case EthereumJSONRPC.fetch_balances_by_hash(string_hashes) do - {:ok, results} -> :ok = update_balances(results) - {:error, _reason} -> :retry + buffered_task_init_options = + [ + poll: false, + flush_interval: 1000, + max_batch_size: 10, + max_concurrency: 5, + task_supervisor: TaskScheduler.TaskSupervisor, + metadata: [fetcher: :task_scheduler] + ] + |> Keyword.merge(init_options) + |> Keyword.put(:state, state) + + Supervisor.child_spec( + {BufferedTask, [{__MODULE__, buffered_task_init_options}, gen_server_options]}, + id: __MODULE__ + ) + end + + @impl BufferedTask + def init(initial, _reducer, _state) do + initial + end + + @impl BufferedTask + def run(tasks, %{max_retries: max_retries} = state) do + now = System.system_time(:millisecond) + {due_tasks, future_tasks} = Enum.split_with(tasks, &(&1.at <= now)) + + new_future_tasks = Enum.map(due_tasks, fn task -> + case execute_task(task) do + :ok -> nil + :error -> + retry_count = Map.get(task, :retry_count, 0) + if retry_count < max_retries do + %{task | retry_count: retry_count + 1} + end + end + end) + |> Enum.reject(&is_nil/1) + + case future_tasks ++ new_future_tasks do + [] -> :ok + remaining_tasks -> {:retry, remaining_tasks} + end + end + + defp execute_task(%{fun: fun, args: args}) do + try do + apply(fun, args) + :ok + rescue + _ -> :error + end + end + + def schedule_task(at, fun, args) do + task = %{at: at, fun: fun, args: args} + BufferedTask.buffer(__MODULE__, [task], false) end end - If a task crashes, it will be retried automatically. Tasks may also be programmatically retried by returning `:retry` - from `c:run/2`. + To use this module in your application's supervision tree: + + children = [ + {TaskScheduler.Supervisor, [[memory_monitor: memory_monitor]]} + ] + + Supervisor.init(children, strategy: :one_for_one) + + To schedule a new task: + + TaskScheduler.schedule_task( + System.system_time(:millisecond) + 5000, + fn -> IO.puts("Hello from the future!") end, + [] + ) """ use GenServer @@ -54,7 +304,8 @@ defmodule Indexer.BufferedTask do import Indexer.Logger, only: [process: 1] - alias Indexer.{BoundQueue, BufferedTask, Memory} + alias Explorer.BoundQueue + alias Indexer.Memory @enforce_keys [ :callback_module, @@ -78,6 +329,78 @@ defmodule Indexer.BufferedTask do bound_queue: %BoundQueue{}, task_ref_to_batch: %{} + @typedoc """ + BufferedTask struct: + * `init_task` - reference to the initial streaming task. This field holds the + `Task.t()` for the initial data population process. It's used to track the + completion of the initial data streaming. + * `flush_timer` - reference to the timer for periodic buffer flushing. This + field stores the timer reference returned by `Process.send_after/3`, which + is scheduled using the `flush_interval`. When the timer triggers, it sends + a `:flush` message to the process, initiating a buffer flush operation. + This field is managed internally and doesn't need to be set by the user. + * `callback_module` - module implementing the `BufferedTask` behaviour. This + field must be set when initializing the `BufferedTask`. It specifies the + module that defines the `init/3` and `run/2` callbacks, which are crucial + for the operation of the buffered task. + * `callback_module_state` - state maintained by the callback module. This + field holds any state that the callback module needs to persist between + calls to its callbacks. It's passed to and potentially updated by the + `run/2` callback. + * `task_supervisor` - name of the `Task.Supervisor` for spawning tasks. This + field must be set during initialization. It's used to spawn concurrent + tasks for processing batches of data, allowing for controlled + concurrency. + * `flush_interval` - interval in milliseconds between buffer flushes. This + field must be set during initialization. It determines how often the + buffer is automatically flushed, ensuring that data is processed even + if the buffer doesn't fill up quickly. If set to `:infinity`, no automatic + flushing occurs - it can be used for when manual flushing is preferred by + sending a `:flush` message to the process. + * `max_batch_size` - maximum number of entries in a batch for processing. + This field must be set during initialization. It controls the size of + batches sent to the `run/2` callback, allowing for optimized processing + of data. + * `max_concurrency` - maximum number of concurrent processing tasks. This + field must be set during initialization. It limits the number of + simultaneous `run/2` callback executions, preventing system overload. + * `poll` - boolean flag to enable/disable polling for new records. This field + has a default value of true. When true, the module will continue to call + `init/3` to fetch new data after processing all current entries. + * `metadata` - list of metadata for logging purposes. This field can be set + during initialization. It's used to add context to log messages, + improving the traceability of the buffered task's operations. + * `current_buffer` - list of entries waiting to be processed. This field is + used internally to store incoming entries before they're moved to the + bound_queue for processing. Acts as a temporary holding area for + incoming data. + * `current_front_buffer` - list of high-priority entries for processing. These + entries are moved to the front of the bound_queue during flushing. + * `bound_queue` - queue with a maximum size limit for storing entries. This + field uses a `BoundQueue` struct to efficiently manage entries while + respecting memory constraints. + * `task_ref_to_batch` - map of task references to their corresponding batches. + This field is used internally to keep track of which batch is being + processed by each spawned task, facilitating proper handling of task + results and retries. + """ + @type t :: %__MODULE__{ + init_task: Task.t() | :complete | nil, + flush_timer: reference() | nil, + callback_module: module(), + callback_module_state: term(), + task_supervisor: GenServer.name(), + flush_interval: timeout() | :infinity, + max_batch_size: non_neg_integer(), + max_concurrency: non_neg_integer(), + poll: boolean(), + metadata: Logger.metadata(), + current_buffer: [term(), ...], + current_front_buffer: [term(), ...], + bound_queue: BoundQueue.t(term()), + task_ref_to_batch: %{reference() => [term(), ...]} + } + @typedoc """ Entry passed to `t:reducer/2` in `c:init/2` and grouped together into a list as `t:entries/0` passed to `c:run/2`. """ @@ -112,55 +435,106 @@ defmodule Indexer.BufferedTask do @type state :: term() @doc """ - Populates a task's buffer on boot with an initial set of entries. - - For example, the following callback would buffer all unfetched account balances on startup: - - def init(initial, reducer, state) do - final = Chain.stream_unfetched_balances([:hash], initial, fn %{hash: hash}, acc -> - reducer.(Hash.to_string(hash), acc) - end) - - {final, state} - end - - The `init/2` operation may be long-running as it is run in a separate process and allows concurrent calls to - `Explorer.BufferedTask.buffer/2` for on-demand entries. + This callback is invoked during the initial streaming process to populate the + queue with initial data. It runs in a separate task, allowing for long-running + operations without blocking the main `BufferedTask` process. + + - `initial`: An opaque value representing the initial accumulator. Its structure + and content are fully controlled by the `reducer` function, so there's no need + to handle this parameter specifically within the `init/3` callback. + - `reducer`: A function that accumulates entries into the `BufferedTask`'s + internal buffers. + - `state`: The initial state provided during initialization. + + The `init/3` callback should use the `reducer` function to add entries to the + `BufferedTask`'s buffers. The `BufferedTask` will automatically manage these + entries, flushing them to the main processing queue and initiating batch + processing as needed. """ @callback init(initial, reducer, state) :: accumulator @doc """ - Invoked as concurrency becomes available with a list of batched entries to be processed. - - For example, the `c:run/2` callback for the example `c:init/2` callback could be written: - - def run(string_hashes, _state) do - case EthereumJSONRPC.fetch_balances_by_hash(string_hashes) do - {:ok, results} -> :ok = update_balances(results) - {:error, _reason} -> :retry - end - end - - If a task crashes, it will be retried automatically. Tasks may also be programmatically retried by returning `:retry` - from `c:run/2`. - - ## Returns - - * `:ok` - run was successful - * `:retry` - run should be retried after it failed - * `{:retry, new_entries :: list}` - run should be retried with `new_entries` - + This callback is invoked as concurrency becomes available to process batches + of entries from the queue. It is called within a task spawned by the + `Task.Supervisor` specified during initialization. + + - `entries`: A list of entries to be processed, with a maximum length of + `:max_batch_size`. + - `state`: The current state of the callback module. + + The `run/2` callback processes the given entries and returns one of the following + possible results: + + - `:ok`: Indicates successful processing. + - `{:ok, state}`: Indicates successful processing and requests an update to + the callback module state. + - `:retry`: Signals that the entire batch should be retried. + - `{:retry, new_entries}`: Specifies a new list of entries to be retried. This + can be a completely new list of entries or a subset of entries which were not + successfully handled by `run/2` in this iteration. + - `{:retry, new_entries, state}`: Specifies a new list of entries to be retried + and requests an update to the callback module state. + + If the callback crashes, the `BufferedTask` will automatically retry the batch. + The retry mechanism ensures resilience in data processing, allowing for + temporary failures or resource unavailability to be handled gracefully. + + The `BufferedTask` manages concurrency, ensuring that no more than + `:max_concurrency` `run/2` callbacks are executing simultaneously. This + provides controlled parallelism while preventing system overload. """ - @callback run(entries, state) :: :ok | :retry | {:retry, new_entries :: list} + @callback run(entries, state) :: + :ok | {:ok, state} | :retry | {:retry, new_entries :: list} | {:retry, new_entries :: list, state} @doc """ - Buffers list of entries for future async execution. + Buffers a list of entries for future execution. + + This function sends a message to the specified BufferedTask process to add the + given entries to one of two internal buffers: + 1. The regular buffer, if `front?` is `false`. + 2. The front buffer, if `front?` is `true`. + + When the buffers are later flushed asynchronously: + - Entries from the regular buffer are added to the end of the processing queue. + - Entries from the front buffer are added to the beginning of the processing queue. + + Note: If the total number of elements in the buffers exceeds the maximum queue + size (which is undefined by default) when flushed, excess elements will be + dropped without notification to the calling process. + + ## Parameters + - `server`: The name or PID of the BufferedTask process. + - `entries`: A list of entries to be buffered. + - `front?`: If `true`, entries are added to the front buffer; if `false`, + they are added to the regular buffer. + - `timeout`: The maximum time to wait for a reply, in milliseconds. Defaults to + 5000 ms. + + ## Returns + - `:ok` if the entries were successfully added to the appropriate buffer. """ + @spec buffer(GenServer.name(), entries(), boolean()) :: :ok @spec buffer(GenServer.name(), entries(), boolean(), timeout()) :: :ok def buffer(server, entries, front?, timeout \\ 5000) when is_list(entries) do GenServer.call(server, {:buffer, entries, front?}, timeout) end + @doc """ + Defines a child specification for a BufferedTask to be used in a supervision tree. + + ## Parameters + - `[init_arguments]` or `[init_arguments, gen_server_options]`: + - `init_arguments`: A list of initialization arguments for the BufferedTask. + - `gen_server_options`: An optional list of GenServer options. + + ## Returns + A child specification map suitable for use in a supervision tree. + + ## Usage + This function is typically called indirectly as part of the `child_spec/1` + function of a module implementing the BufferedTask behavior. It's not intended + to be called directly in application code. + """ def child_spec([init_arguments]) do child_spec([init_arguments, []]) end @@ -174,33 +548,64 @@ defmodule Indexer.BufferedTask do Supervisor.child_spec(default, []) end - @doc false + @doc """ + Retrieves debug information about the current state of the BufferedTask. + + Returns a map containing the total number of entries in buffers and queue, + and the number of active tasks. This function is useful for monitoring and + debugging the BufferedTask's internal state. + + ## Parameters + - `server`: The name or PID of the BufferedTask process. + + ## Returns + A map with keys `:buffer` (total entries count) and `:tasks` (active tasks count). + """ + @spec debug_count(GenServer.name()) :: %{buffer: non_neg_integer(), tasks: non_neg_integer()} def debug_count(server) do GenServer.call(server, :debug_count) end @doc """ - Starts `callback_module` as a buffered task. + Starts a `BufferedTask` process for the given callback module. - Takes a tuple of the callback module and list of named arguments and options, much like the format accepted for - `Supervisor.start_link/2`, `Supervisor.init/2` and `Supervisor.child_spec/2`. + This function is generally not called directly in application code. Instead, + it's used in the context of a supervision tree, typically invoked through + the `child_spec/1` function of a module implementing the BufferedTask behavior. + It initializes a BufferedTask process and ultimately leads to calling + `BufferedTask.init/1`. - ## Named Arguments + ## Parameters + The function takes a tuple with two elements: + 1. `callback_module`: The module implementing the `BufferedTask` behavior. + 2. A keyword list of options, which is a merge of application-wide defaults + and task-specific options. - Named arguments are required and are passed in the list that is the second element of the tuple. + ### Named Arguments + These are required and should be included in the options list: * `:flush_interval` - The interval in milliseconds to flush the buffer. - * `:max_concurrency` - The maximum number of tasks to run concurrently at any given time. - * `:max_batch_size` - The maximum batch passed to `c:run/2`. + * `:max_concurrency` - The maximum number of tasks to run concurrently. + * `:max_batch_size` - The maximum batch size passed to `c:run/2`. * `:task_supervisor` - The `Task.Supervisor` name to spawn tasks under. + * `:state` - Initial state for the callback module. - ## Options - - Options are optional and are passed in the list that is second element of the tuple. + ### Options + These are optional and can be included in the options list: * `:name` - The registered name for the new process. - * `:metadata` - `Logger.metadata/1` to det in teh `Indexer.BufferedTask` process and any child processes. - + * `:metadata` - `Logger.metadata/1` to set in the `BufferedTask` process + and any child processes. + * `:memory_monitor` - The memory monitor process name, if applicable. + + ## Returns + * `{:ok, pid()}` if the process starts successfully. + * `{:error, {:already_started, pid()}}` if the process is already started. + + ## Note + The options passed to this function are a merge of application-wide defaults + (retrieved from `Application.get_all_env(:indexer)`) and the task-specific + options provided when setting up the fetcher. """ @spec start_link( {callback_module :: module, @@ -220,15 +625,47 @@ defmodule Indexer.BufferedTask do GenServer.start_link(__MODULE__, {module, init_opts}, genserver_opts) end + @doc """ + Initializes the BufferedTask process. + + This function accepts parameters passed from `start_link/2`, sends an + `:initial_stream` message to self to start the initial streaming process, + sets up the process as shrinkable if a memory monitor is provided, sets + Logger metadata, and configures the initial state of `BufferedTask` + including the state of a module implementing the BufferedTask behavior. + + ## Parameters + - `{callback_module, opts}`: A tuple containing: + - `callback_module`: The module implementing the BufferedTask behavior. + - `opts`: A keyword list of options for initializing the BufferedTask. + + ## Options + - `:state`: Required. The initial state for the callback module. + - `:task_supervisor`: Required. The name of the Task.Supervisor. + - `:flush_interval`: Required. The interval for flushing the buffer to the + processing queue. + - `:max_batch_size`: Required. The maximum size of the queue's data batch + to be processed at once. + - `:max_concurrency`: Required. The maximum number of concurrent tasks to + process the queue's data. + - `:poll`: Optional. Whether to poll for new data from the stream after + processing all current data. Defaults to `true`. + - `:metadata`: Optional. Logger metadata. Defaults to an empty list. + + ## Returns + `{:ok, state}` where `state` is the initialized BufferedTask struct. + """ def init({callback_module, opts}) do send(self(), :initial_stream) + # Allows the memory monitor to shrink the amount of elements in the queue + # when extensive memory usage is detected. shrinkable(opts) metadata = Keyword.get(opts, :metadata, []) Logger.metadata(metadata) - state = %BufferedTask{ + state = %__MODULE__{ callback_module: callback_module, callback_module_state: Keyword.fetch!(opts, :state), poll: Keyword.get(opts, :poll, true), @@ -242,54 +679,102 @@ defmodule Indexer.BufferedTask do {:ok, state} end + # Initiates the initial data streaming process in response to the :initial_stream + # message. This message is self-sent during initialization to start populating + # the queue with initial data using the `init/3` function of the callback module. def handle_info(:initial_stream, state) do {:noreply, do_initial_stream(state)} end + # Handles the periodic flush message to process buffered entries. + # This message is scheduled by the flush_interval to ensure regular processing of + # accumulated data, pushing it to the queue and triggering batch processing. def handle_info(:flush, state) do {:noreply, flush(state)} end - def handle_info({ref, :ok}, %{init_task: ref} = state) do + # Handles the successful completion of the initial streaming task. + def handle_info({ref, :ok}, %__MODULE__{init_task: ref} = state) do {:noreply, state} end + # Handles the successful completion of a task processing queue data, removes the + # reference to the task, and triggers processing of the next batch if queue + # contains data. def handle_info({ref, :ok}, state) do {:noreply, drop_task(state, ref)} end + # Handles the successful completion of a task processing queue data, updated the + # callback module state, removes the reference to the task, and triggers processing + # of the next batch if queue contains data. + def handle_info({ref, {:ok, new_callback_module_state}}, state) do + {:noreply, drop_task(%__MODULE__{state | callback_module_state: new_callback_module_state}, ref)} + end + + # Handles a retry request for a task processing queue data. The original batch + # is added back to the queue and processing of the next batch is triggered. + # Useful when all data from the batch needs to be reprocessed. def handle_info({ref, :retry}, state) do {:noreply, drop_task_and_retry(state, ref)} end + # Handles a retry request for a task processing queue data with specified + # retryable entries. These entries are added to the queue and processing of + # the next batch is triggered. Useful when only part of the original batch + # needs to be reprocessed. def handle_info({ref, {:retry, retryable_entries}}, state) do {:noreply, drop_task_and_retry(state, ref, retryable_entries)} end - def handle_info({:DOWN, ref, :process, _pid, :normal}, %BufferedTask{init_task: ref} = state) do - {:noreply, %{state | init_task: :complete}} + # Handles a retry request for a task processing queue data with specified + # retryable entries. If the task modified the state, the call back module + # state is updated. These entries are added to the queue and processing of + # the next batch is triggered. + # If all entries are needed to be retried, the `retryable_entries` should + # be `nil`. + def handle_info({ref, {:retry, retryable_entries, new_callback_module_state}}, state) do + {:noreply, + drop_task_and_retry(%__MODULE__{state | callback_module_state: new_callback_module_state}, ref, retryable_entries)} + end + + # Handles the normal termination of the initial streaming task, marking it as complete. + def handle_info({:DOWN, ref, :process, _pid, :normal}, %__MODULE__{init_task: ref} = state) do + {:noreply, %__MODULE__{state | init_task: :complete}} end + # Handles the normal termination of a non-initial task, no action needed. def handle_info({:DOWN, _ref, :process, _pid, :normal}, state) do {:noreply, state} end + # Handles abnormal termination of a task processing queue data. The task's batch + # is re-added to the queue and processing of the next batch is triggered. def handle_info({:DOWN, ref, :process, _pid, _reason}, state) do {:noreply, drop_task_and_retry(state, ref)} end + # Handles asynchronous buffering of entries. + # Adds the provided entries to either the front or back buffer without waiting for a response. + # This is used for non-blocking buffering operations. def handle_info({:buffer, entries, front?}, state) do {:noreply, buffer_entries(state, entries, front?)} end + # Handles synchronous buffering of entries. + # Adds the provided entries to either the front or back buffer and waits for the operation to complete. + # This is used when the caller needs confirmation that the entries have been buffered. def handle_call({:buffer, entries, front?}, _from, state) do {:reply, :ok, buffer_entries(state, entries, front?)} end + # Provides debug information about the current state of the BufferedTask. + # Returns a count of entries in buffers and queue, and the number of active tasks. + # This is useful for monitoring and debugging the BufferedTask's internal state. def handle_call( :debug_count, _from, - %BufferedTask{ + %__MODULE__{ current_buffer: current_buffer, current_front_buffer: current_front_buffer, bound_queue: bound_queue, @@ -302,6 +787,10 @@ defmodule Indexer.BufferedTask do {:reply, %{buffer: count, tasks: Enum.count(task_ref_to_batch)}, state} end + # Retrieves the full internal state of the BufferedTask. + # This handler provides complete access to the BufferedTask's state, + # primarily for advanced debugging, testing, or custom runtime introspection. + # Use with caution as it exposes internal implementation details. def handle_call( :state, _from, @@ -310,6 +799,8 @@ defmodule Indexer.BufferedTask do {:reply, state, state} end + # Adds entries to the back of the queue and initiates processing of the next + # batch of queue's data by the callback module. def handle_call({:push_back, entries}, _from, state) when is_list(entries) do new_state = state @@ -319,6 +810,8 @@ defmodule Indexer.BufferedTask do {:reply, :ok, new_state} end + # Adds entries to the front of the queue and initiates processing of the next + # batch of queue's data by the callback module. def handle_call({:push_front, entries}, _from, state) when is_list(entries) do new_state = state @@ -328,6 +821,8 @@ defmodule Indexer.BufferedTask do {:reply, :ok, new_state} end + # Attempts to shrink the bound queue in response to high memory usage detection. + # Called by the Memory Monitor when this process is identified as a high memory consumer. def handle_call(:shrink, _from, %__MODULE__{bound_queue: bound_queue} = state) do {reply, shrunk_state} = case BoundQueue.shrink(bound_queue) do @@ -341,42 +836,133 @@ defmodule Indexer.BufferedTask do {:reply, reply, shrunk_state, :hibernate} end + # Checks if the bound queue has been previously shrunk. + # Used by the Memory Monitor to track which processes have been shrunk and may be eligible for expansion. def handle_call(:shrunk?, _from, %__MODULE__{bound_queue: bound_queue} = state) do {:reply, BoundQueue.shrunk?(bound_queue), state} end + # Expands the previously shrunk bound queue to its original size. + # Called by the Memory Monitor when overall system memory usage has decreased sufficiently. def handle_call(:expand, _from, %__MODULE__{bound_queue: bound_queue} = state) do - {:reply, :ok, %{state | bound_queue: BoundQueue.expand(bound_queue)}} + {:reply, :ok, %__MODULE__{state | bound_queue: BoundQueue.expand(bound_queue)}} end + # Removes a task from the state and initiates the next batch processing. + # + # This function is called to remove a task reference from the state, regardless + # of whether the task completed successfully, failed, or needs to be retried. + # After removing the task, it attempts to spawn the next batch of queue's data + # for processing. + # + # ## Parameters + # - `state`: The current state of the BufferedTask. + # - `ref`: The reference of the task to be removed. + # + # ## Returns + # Updated state after removing the task and potentially spawning a new data + # portion for processing. + @spec drop_task(t(), reference()) :: t() defp drop_task(state, ref) do - spawn_next_batch(%BufferedTask{state | task_ref_to_batch: Map.delete(state.task_ref_to_batch, ref)}) + spawn_next_batch(%__MODULE__{state | task_ref_to_batch: Map.delete(state.task_ref_to_batch, ref)}) end - defp drop_task_and_retry(%BufferedTask{task_ref_to_batch: task_ref_to_batch} = state, ref, new_batch \\ nil) do + # Removes a task from the state and schedules it (or another chunk of data) for retry. + # + # This function is called when a task needs to be retried, either due to failure + # or explicit retry request. It removes the task reference from the state and + # pushes either a new batch of entries (if provided) or the original batch back + # to the queue for reprocessing. + # + # ## Parameters + # - `state`: The current state of the BufferedTask. + # - `ref`: The reference of the task to be removed and retried. + # - `new_batch`: Optional. A new batch of entries to be processed instead of + # the original batch. Defaults to nil. + # + # ## Returns + # Updated state after removing the task and pushing entries for retry. + @spec drop_task_and_retry( + %__MODULE__{task_ref_to_batch: %{reference() => [term(), ...]}}, + reference(), + entries() | nil + ) :: t() + defp drop_task_and_retry(%__MODULE__{task_ref_to_batch: task_ref_to_batch} = state, ref, new_batch \\ nil) do batch = Map.fetch!(task_ref_to_batch, ref) + # Question: should we push the data to the queue first and then spawn the next batch? state |> drop_task(ref) |> push_back(new_batch || batch) end + # Adds new entries to the appropriate buffer in the current state. + # + # This function has the following behaviors depending on the input: + # 1. If `front?` is true, it prepends the entries to the current front buffer. + # 2. If `front?` is false, it prepends the entries to the current buffer. + # 3. If entries is empty, it returns the original state unchanged. + # + # ## Parameters + # - `state`: The current state of the BufferedTask. + # - `entries`: A list of new entries to be added to a buffer. + # - `front?`: A boolean indicating whether to add to the front buffer (true) or + # the regular buffer (false). + # + # ## Returns + # An updated state with the new entries added to the appropriate buffer, or + # the original state if entries is empty. + # + # ## Notes + # When entries are added, they are prepended as a single list element to + # the existing buffer, maintaining the order of batches. + @spec buffer_entries(t(), [], boolean()) :: t() defp buffer_entries(state, [], _front?), do: state + @spec buffer_entries(t(), nonempty_list(), true) :: t() defp buffer_entries(state, entries, true) do - %{state | current_front_buffer: [entries | state.current_front_buffer]} + %__MODULE__{state | current_front_buffer: [entries | state.current_front_buffer]} end + @spec buffer_entries(t(), nonempty_list(), false) :: t() defp buffer_entries(state, entries, false) do - %{state | current_buffer: [entries | state.current_buffer]} + %__MODULE__{state | current_buffer: [entries | state.current_buffer]} end - defp do_initial_stream(%BufferedTask{init_task: init_task} = state) when is_reference(init_task) do + # Initiates the initial streaming process for the BufferedTask. + # + # This function has two clauses: + # 1. When an init_task is already in progress, it schedules the next buffer flush. + # 2. When no init_task is in progress, it starts a new async task to initialize + # the stream. + # + # The initialization process: + # - Calls the `init/3` function of the callback module. + # - Accumulates entries into chunks up to the maximum batch size. + # - Pushes full chunks to the queue, triggering processing of queue data. + # - Processes any remaining entries after initialization. These are entries that + # were accumulated but didn't form a complete chunk. They are pushed to the + # queue, ensuring no data is lost, and trigger processing of queue data (which + # may include these entries). + # + # ## Parameters + # - `state`: The current state of the BufferedTask. + # + # ## Returns + # - Updated `state` with the new `init_task` reference and scheduled buffer flush. + @spec do_initial_stream(%__MODULE__{ + init_task: Task.t() | :complete | nil, + callback_module: module(), + max_batch_size: pos_integer(), + task_supervisor: GenServer.name(), + metadata: Logger.metadata() + }) :: t() + defp do_initial_stream(%__MODULE__{init_task: init_task} = state) when is_reference(init_task) do schedule_next_buffer_flush(state) end defp do_initial_stream( - %BufferedTask{ + %__MODULE__{ callback_module: callback_module, callback_module_state: callback_module_state, max_batch_size: max_batch_size, @@ -392,6 +978,12 @@ defmodule Indexer.BufferedTask do {0, []} |> callback_module.init( + # It accumulates entries into chunks until the maximum chunk size is + # reached, then pushes the chunk to the queue and trigger processing + # of the next batch of the queue's data. The chunk size is set to + # ensure it doesn't exceed the batch size used in `spawn_next_batch`, + # guaranteeing that all data in the queue can be included in + # processing batches. fn entry, {len, acc} when len + 1 >= max_batch_size -> entries = Enum.reverse([entry | acc]) @@ -407,9 +999,15 @@ defmodule Indexer.BufferedTask do |> catchup_remaining(max_batch_size, parent) end) - schedule_next_buffer_flush(%BufferedTask{state | init_task: task.ref}) + schedule_next_buffer_flush(%__MODULE__{state | init_task: task.ref}) end + # Processes any remaining entries after the initial streaming operation by + # pushing them to the back of the queue and triggering processing of a batch of + # the queue's data. + @spec catchup_remaining({non_neg_integer(), list()}, pos_integer(), pid()) :: :ok + defp catchup_remaining(chunk_with_length, max_batch_size, pid) + defp catchup_remaining({0, []}, _max_batch_size, _pid), do: :ok defp catchup_remaining({len, batch}, max_batch_size, pid) @@ -419,19 +1017,68 @@ defmodule Indexer.BufferedTask do :ok end + # Pushes entries to the back of the queue. + # + # This function has two behaviors depending on the input: + # 1. If given a PID, it sends a :push_back message to the specified process. + # 2. If given the current state, it adds entries to the back of the bound queue. + # + # ## Parameters + # - `pid`: A PID of a BufferedTask process. + # - `state`: The current state of the BufferedTask. + # - `entries`: A list of entries to be pushed to the back of the queue. + # + # ## Returns + # - When given a PID: The result of the GenServer call (any term). + # - When given the current state: The updated state with a potentially + # modified bound queue. + @spec push_back(pid(), list()) :: term() defp push_back(pid, entries) when is_pid(pid) and is_list(entries) do GenServer.call(pid, {:push_back, entries}) end - defp push_back(%BufferedTask{} = state, entries), do: push(state, entries, false) - + @spec push_back(t(), list()) :: t() + defp push_back(%__MODULE__{} = state, entries), do: push(state, entries, false) + + # Pushes entries to the front of the queue. + # + # This function has two behaviors depending on the input: + # 1. If given a PID, it sends a :push_front message to the specified process. + # 2. If given the current state, it adds entries to the front of the bound queue. + # + # ## Parameters + # - `pid`: A PID of a BufferedTask process. + # - `state`: The current state of the BufferedTask. + # - `entries`: A list of entries to be pushed to the front of the queue. + # + # ## Returns + # - When given a PID: The result of the GenServer call (any term). + # - When given the current state: The updated state with a potentially + # modified bound queue. + @spec push_front(pid(), list()) :: term() defp push_front(pid, entries) when is_pid(pid) and is_list(entries) do GenServer.call(pid, {:push_front, entries}) end - defp push_front(%BufferedTask{} = state, entries), do: push(state, entries, true) - - defp push(%BufferedTask{bound_queue: bound_queue} = state, entries, front?) when is_list(entries) do + @spec push_front(t(), list()) :: t() + defp push_front(%__MODULE__{} = state, entries), do: push(state, entries, true) + + # Pushes a list of entries into the bound queue of the state. + # + # If all entries are successfully added, the function simply updates the state + # with the new bound queue. If the bound queue reaches its maximum size before + # all entries can be added, the function discards the remaining entries and + # logs a warning. + # + # ## Parameters + # - `state`: The current state of the BufferedTask. + # - `entries`: A list of entries to be pushed into the bound queue. + # - `front?`: A boolean flag. If true, pushes to the front; if false, pushes to the back. + # + # ## Returns + # The updated state with the new entries added to the bound queue. + @spec push(%__MODULE__{bound_queue: BoundQueue.t(term())}, list(), boolean()) :: t() + defp push(%__MODULE__{bound_queue: bound_queue} = state, entries, front?) when is_list(entries) do new_bound_queue = case push_until_maximum_size(bound_queue, entries, front?) do {new_bound_queue, []} -> @@ -453,24 +1100,88 @@ defmodule Indexer.BufferedTask do new_bound_queue end - %BufferedTask{state | bound_queue: new_bound_queue} + %__MODULE__{state | bound_queue: new_bound_queue} end + # Pushes entries into a BoundQueue until its maximum size is reached. + # + # This function attempts to add entries to either the front or back of the + # BoundQueue, depending on the boolean flag. It continues adding entries + # until the queue reaches its maximum size or all entries have been added. + # + # The order of entries is preserved during the push operation. When pushing + # to the front, the first entry in the list becomes the first in the queue. + # Conversely, when pushing to the back, the last entry in the list becomes + # the last in the queue. + # + # ## Parameters + # - `bound_queue`: The BoundQueue to push entries into. + # - `entries`: A list of entries to be pushed into the queue. + # - `front?`: A boolean flag. If true, pushes to the front; if false, pushes to the back. + # + # ## Returns + # A tuple containing: + # - The updated BoundQueue with new entries added. + # - A list of remaining entries that couldn't be pushed due to size limitations. + @spec push_until_maximum_size(BoundQueue.t(term()), list(), boolean()) :: {BoundQueue.t(term()), list()} defp push_until_maximum_size(bound_queue, entries, true), do: BoundQueue.push_front_until_maximum_size(bound_queue, entries) defp push_until_maximum_size(bound_queue, entries, false), do: BoundQueue.push_back_until_maximum_size(bound_queue, entries) - defp take_batch(%BufferedTask{bound_queue: bound_queue, max_batch_size: max_batch_size} = state) do + # Takes a batch of entries from the current state or BoundQueue. + # + # This function has three implementations to handle different input types + # and use cases: + # + # 1. When given the current state of the BufferedTask: + # - Takes a batch of entries based on the `max_batch_size`. + # - Returns the batch and an updated state. + # + # 2. When given a BoundQueue and `max_batch_size`: + # - Initializes the recursive batch-taking process. + # - Returns the batch and the updated BoundQueue. + # + # 3. Recursive implementation (private): + # - Recursively takes entries from the BoundQueue. + # - Accumulates entries until the requested number is reached or the queue is empty. + # + # ## Parameters + # - `state`: The current state of the BufferedTask. + # - `bound_queue`: The BoundQueue to take entries from. + # - `max_batch_size`: The maximum number of entries to take. + # - `remaining`: The number of entries still to be taken (in recursive calls). + # - `acc`: The accumulator for entries already taken (in recursive calls). + # + # ## Returns + # Depending on the implementation: + # 1. `{batch, updated_state}` + # 2. `{batch, updated_bound_queue}` + # 3. `{reversed_batch, updated_bound_queue}` + # + # Where: + # - `batch`: A list of taken entries in their original order. + # - `updated_state`: The state with an updated BoundQueue. + # - `updated_bound_queue`: The BoundQueue with taken entries removed. + # + # ## Notes + # - The function ensures that no more than `max_batch_size` entries are taken. + # - If the queue becomes empty before taking all requested entries, the function + # returns with whatever entries it has accumulated so far. + @spec take_batch(%__MODULE__{bound_queue: BoundQueue.t(term()), max_batch_size: non_neg_integer()}) :: + {entries(), t()} + defp take_batch(%__MODULE__{bound_queue: bound_queue, max_batch_size: max_batch_size} = state) do {batch, new_bound_queue} = take_batch(bound_queue, max_batch_size) - {batch, %BufferedTask{state | bound_queue: new_bound_queue}} + {batch, %__MODULE__{state | bound_queue: new_bound_queue}} end + @spec take_batch(BoundQueue.t(term()), non_neg_integer()) :: {entries(), BoundQueue.t(term())} defp take_batch(%BoundQueue{} = bound_queue, max_batch_size) do take_batch(bound_queue, max_batch_size, []) end + @spec take_batch(BoundQueue.t(term()), non_neg_integer(), list()) :: {entries(), BoundQueue.t(term())} defp take_batch(%BoundQueue{} = bound_queue, 0, acc) do {Enum.reverse(acc), bound_queue} end @@ -485,26 +1196,73 @@ defmodule Indexer.BufferedTask do end end - # get more work from `init/2` - defp schedule_next(%BufferedTask{poll: true, bound_queue: %BoundQueue{size: 0}, task_ref_to_batch: tasks} = state) + # Schedules the next operation based on the current state of the BufferedTask. + # + # This function is called after triggering processing a batch of queue's data + # to determine the next action, helping maintain a continuous flow of work in + # the BufferedTask. + # + # This function has two clauses: + # 1. When the queue is empty, there are no ongoing tasks, and polling is enabled, + # it re-initializes the stream to fetch more work. + # 2. In all other cases, it schedules the next buffer flush. + # + # ## Parameters + # - `state`: The current state of the BufferedTask. + # + # ## Returns + # - Updated `state` after scheduling the next operation. + @spec schedule_next(%__MODULE__{ + poll: boolean(), + bound_queue: BoundQueue.t(term()), + task_ref_to_batch: %{reference() => [term(), ...]} + }) :: t() + defp schedule_next(%__MODULE__{poll: true, bound_queue: %BoundQueue{size: 0}, task_ref_to_batch: tasks} = state) when tasks == %{} do do_initial_stream(state) end - # was not shrunk or not out of work - defp schedule_next(%BufferedTask{} = state) do + defp schedule_next(%__MODULE__{} = state) do schedule_next_buffer_flush(state) end + # Schedules the next buffer flush based on the flush_interval. + # + # If the flush_interval is set to `:infinity`, no flush is scheduled and the + # state is returned unchanged. Otherwise, a timer is set to send a `:flush` + # message after the specified interval. + # + # ## Parameters + # - `state`: The current state of the BufferedTask. Must include `:flush_interval`. + # + # ## Returns + # - The updated state with the new flush_timer if a flush was scheduled, + # or the unchanged state if flush_interval is :infinity. + @spec schedule_next_buffer_flush(%__MODULE__{flush_interval: timeout() | :infinity}) :: t() defp schedule_next_buffer_flush(state) do if state.flush_interval == :infinity do state else timer = Process.send_after(self(), :flush, state.flush_interval) - %{state | flush_timer: timer} + %__MODULE__{state | flush_timer: timer} end end + # Registers the current process as shrinkable with the memory monitor if one is provided. + # + # This function checks the options for a `:memory_monitor`. If present, it registers + # the current process with the monitor as a shrinkable process. This allows the + # memory monitor to request the process to reduce its memory usage if needed. + # + # ## Parameters + # - `options`: A keyword list of options that may include :memory_monitor. + # + # ## Returns + # - `:ok` if no memory monitor is provided or after successful registration. + # + # ## Side Effects + # - If a memory monitor is provided, the current process is registered as + # shrinkable with that monitor. defp shrinkable(options) do case Keyword.get(options, :memory_monitor) do nil -> :ok @@ -512,8 +1270,31 @@ defmodule Indexer.BufferedTask do end end + # Spawns the next batch processing task. + # + # This function checks if a new task can be spawned based on the current + # number of running tasks and the availability of entries in the bound queue. + # If conditions are met, it takes a batch from the bound queue and spawns + # a new task to process it. As soon as the task is spawned, the + # `task_ref_to_batch` map is updated to enable retrying the task if needed. + # + # ## Parameters + # - `state`: The current state of the BufferedTask. + # + # ## Returns + # - An updated state if a new task was spawned. Otherwise, the original state + # is returned. + @spec spawn_next_batch(%__MODULE__{ + bound_queue: BoundQueue.t(term()), + callback_module: module(), + callback_module_state: term(), + max_concurrency: non_neg_integer(), + task_ref_to_batch: %{reference() => [term(), ...]}, + task_supervisor: GenServer.name(), + metadata: Logger.metadata() + }) :: t() defp spawn_next_batch( - %BufferedTask{ + %__MODULE__{ bound_queue: bound_queue, callback_module: callback_module, callback_module_state: callback_module_state, @@ -536,14 +1317,39 @@ defmodule Indexer.BufferedTask do } ]) - %BufferedTask{new_state | task_ref_to_batch: Map.put(task_ref_to_batch, ref, batch)} + %__MODULE__{new_state | task_ref_to_batch: Map.put(task_ref_to_batch, ref, batch)} else state end end - # only public so that `Task.Supervisor.async_nolink` can call it - @doc false + @doc """ + Executes the run callback of the specified module. + + This function is designed to be called asynchronously by `Task.Supervisor`. It + invokes the `run/2` callback of the specified callback module. + + ## Parameters + - params: A map containing the following keys: + - `:metadata`: Keyword list of logging metadata to be set. + - `:callback_module`: The module that implements the `run/2` callback. + - `:batch`: A list of items to be processed by the callback. + - `:callback_module_state`: The current state of the callback module. + + ## Returns + Returns the result of calling `run/2` on the callback module. + + ## Notes + This function is public to allow it to be called by `Task.Supervisor`, but it's + not intended for direct use outside of the BufferedTask context. + """ + @spec log_run(%{ + metadata: Logger.metadata(), + callback_module: module(), + batch: entries(), + callback_module_state: term() + }) :: + any() def log_run(%{ metadata: metadata, callback_module: callback_module, @@ -554,17 +1360,31 @@ defmodule Indexer.BufferedTask do callback_module.run(batch, callback_module_state) end - defp flush(%BufferedTask{current_buffer: [], current_front_buffer: []} = state) do + # Initiates processing of the next batch of the queue's data after flushing the current buffers. + # + # This function ensures that all buffered entries are scheduled for + # processing by pushing both the regular and front buffers to the queue if they + # are not empty. Then, it initiates processing of the next batch of the queue's + # data by spawning a task that will call the `run/2` callback of the callback + # module, and schedules the next operation. + # + # ## Parameters + # - `state`: The current state of the BufferedTask. + # + # ## Returns + # - Updated `state` after flushing buffers and scheduling next operations. + @spec flush(%__MODULE__{current_buffer: list(), current_front_buffer: list}) :: t() + defp flush(%__MODULE__{current_buffer: [], current_front_buffer: []} = state) do state |> spawn_next_batch() |> schedule_next() end - defp flush(%BufferedTask{current_buffer: buffer, current_front_buffer: front_buffer} = state) do + defp flush(%__MODULE__{current_buffer: buffer, current_front_buffer: front_buffer} = state) do back_entries = List.flatten(buffer) front_entries = List.flatten(front_buffer) - %BufferedTask{state | current_buffer: [], current_front_buffer: []} + %__MODULE__{state | current_buffer: [], current_front_buffer: []} |> push_back(back_entries) |> push_front(front_entries) |> flush() diff --git a/apps/indexer/lib/indexer/fetcher/arbitrum/da/anytrust.ex b/apps/indexer/lib/indexer/fetcher/arbitrum/da/anytrust.ex index 59c401c..40459db 100644 --- a/apps/indexer/lib/indexer/fetcher/arbitrum/da/anytrust.ex +++ b/apps/indexer/lib/indexer/fetcher/arbitrum/da/anytrust.ex @@ -124,6 +124,25 @@ defmodule Indexer.Fetcher.Arbitrum.DA.Anytrust do }} end + def parse_batch_accompanying_data(batch_number, << + keyset_hash::binary-size(32), + data_hash::binary-size(32), + timeout::big-unsigned-integer-size(64), + signers_mask::big-unsigned-integer-size(64), + bls_signature::binary-size(96) + >>) do + # https://github.com/OffchainLabs/nitro/blob/ad9ab00723e13cf98307b9b65774ad455594ef7b/arbstate/das_reader.go#L95-L151 + {:ok, :in_anytrust, + %__MODULE__{ + batch_number: batch_number, + keyset_hash: keyset_hash, + data_hash: data_hash, + timeout: IndexerHelper.timestamp_to_datetime(timeout), + signers_mask: signers_mask, + bls_signature: bls_signature + }} + end + def parse_batch_accompanying_data(_, _) do log_error("Can not parse Anytrust DA message.") {:error, nil, nil} diff --git a/apps/indexer/lib/indexer/fetcher/arbitrum/da/celestia.ex b/apps/indexer/lib/indexer/fetcher/arbitrum/da/celestia.ex index 57c6c52..e20db6e 100644 --- a/apps/indexer/lib/indexer/fetcher/arbitrum/da/celestia.ex +++ b/apps/indexer/lib/indexer/fetcher/arbitrum/da/celestia.ex @@ -11,7 +11,7 @@ defmodule Indexer.Fetcher.Arbitrum.DA.Celestia do alias Explorer.Chain.Arbitrum - @enforce_keys [:batch_number, :height, :tx_commitment, :raw] + @enforce_keys [:batch_number, :height, :transaction_commitment, :raw] defstruct @enforce_keys @typedoc """ @@ -19,25 +19,25 @@ defmodule Indexer.Fetcher.Arbitrum.DA.Celestia do * `batch_number` - The batch number in Arbitrum rollup associated with the Celestia data. * `height` - The height of the block in Celestia. - * `tx_commitment` - Data commitment in Celestia. + * `transaction_commitment` - Data commitment in Celestia. * `raw` - Unparsed blob pointer data containing data root, proof, etc. """ @type t :: %__MODULE__{ batch_number: non_neg_integer(), height: non_neg_integer(), - tx_commitment: binary(), + transaction_commitment: binary(), raw: binary() } @typedoc """ Celestia Blob Descriptor struct: * `height` - The height of the block in Celestia. - * `tx_commitment` - Data commitment in Celestia. + * `transaction_commitment` - Data commitment in Celestia. * `raw` - Unparsed blob pointer data containing data root, proof, etc. """ @type blob_descriptor :: %{ :height => non_neg_integer(), - :tx_commitment => String.t(), + :transaction_commitment => String.t(), :raw => String.t() } @@ -67,14 +67,15 @@ defmodule Indexer.Fetcher.Arbitrum.DA.Celestia do _key::big-unsigned-integer-size(64), _num_leaves::big-unsigned-integer-size(64), _tuple_root_nonce::big-unsigned-integer-size(64), - tx_commitment::binary-size(32), + transaction_commitment::binary-size(32), _data_root::binary-size(32), _side_nodes_length::big-unsigned-integer-size(64), _rest::binary >> = raw ) do # https://github.com/celestiaorg/nitro-contracts/blob/celestia/blobstream/src/bridge/SequencerInbox.sol#L334-L360 - {:ok, :in_celestia, %__MODULE__{batch_number: batch_number, height: height, tx_commitment: tx_commitment, raw: raw}} + {:ok, :in_celestia, + %__MODULE__{batch_number: batch_number, height: height, transaction_commitment: transaction_commitment, raw: raw}} end def parse_batch_accompanying_data(_, _) do @@ -96,14 +97,14 @@ defmodule Indexer.Fetcher.Arbitrum.DA.Celestia do def prepare_for_import(source, %__MODULE__{} = da_info) do data = %{ height: da_info.height, - tx_commitment: ArbitrumHelper.bytes_to_hex_str(da_info.tx_commitment), + transaction_commitment: ArbitrumHelper.bytes_to_hex_str(da_info.transaction_commitment), raw: ArbitrumHelper.bytes_to_hex_str(da_info.raw) } [ %{ data_type: 0, - data_key: calculate_celestia_data_key(da_info.height, da_info.tx_commitment), + data_key: calculate_celestia_data_key(da_info.height, da_info.transaction_commitment), data: data, batch_number: da_info.batch_number } diff --git a/apps/indexer/lib/indexer/fetcher/arbitrum/da/common.ex b/apps/indexer/lib/indexer/fetcher/arbitrum/da/common.ex index 493ea49..80acc1a 100644 --- a/apps/indexer/lib/indexer/fetcher/arbitrum/da/common.ex +++ b/apps/indexer/lib/indexer/fetcher/arbitrum/da/common.ex @@ -124,8 +124,7 @@ defmodule Indexer.Fetcher.Arbitrum.DA.Common do {:error, nil, nil} 128 -> - log_error("DAS messages are not supported.") - {:error, nil, nil} + Anytrust.parse_batch_accompanying_data(batch_number, rest) 136 -> Anytrust.parse_batch_accompanying_data(batch_number, rest) diff --git a/apps/indexer/lib/indexer/fetcher/arbitrum/messages_to_l2_matcher.ex b/apps/indexer/lib/indexer/fetcher/arbitrum/messages_to_l2_matcher.ex new file mode 100644 index 0000000..8b85578 --- /dev/null +++ b/apps/indexer/lib/indexer/fetcher/arbitrum/messages_to_l2_matcher.ex @@ -0,0 +1,357 @@ +defmodule Indexer.Fetcher.Arbitrum.MessagesToL2Matcher do + @moduledoc """ + Matches and processes L1-to-L2 messages in the Arbitrum protocol. + + This module implements a buffered task system to handle the matching of + L1-to-L2 messages with hashed message IDs. It periodically attempts to match + unmatched messages, imports matched messages to the database, and reschedules + unmatched messages for future processing. + + The matcher operates asynchronously, allowing for efficient handling of + messages even when corresponding L1 transactions are not yet indexed. This + approach prevents blocking the discovery process and ensures eventual + consistency in message matching. + + Key features: + - Implements the `BufferedTask` behavior for efficient batch processing. + - Maintains a cache of uncompleted message IDs to optimize matching. + - Provides functionality to asynchronously schedule message matching. + - Automatically retries unmatched messages based on a configurable interval. + """ + + use Indexer.Fetcher, restart: :permanent + use Spandex.Decorators + + import Indexer.Fetcher.Arbitrum.Utils.Logging, only: [log_info: 1] + + require Logger + + alias Indexer.BufferedTask + alias Indexer.Fetcher.Arbitrum.MessagesToL2Matcher.Supervisor, as: MessagesToL2MatcherSupervisor + alias Indexer.Fetcher.Arbitrum.Messaging, as: MessagingUtils + alias Indexer.Fetcher.Arbitrum.Utils.Db + alias Indexer.Fetcher.Arbitrum.Utils.Helper, as: ArbitrumHelper + + @behaviour BufferedTask + + # Since the cache for DB responses is used, it is efficient to get rid of concurrent handling of the tasks. + @default_max_batch_size 10 + @default_max_concurrency 1 + + @flush_interval :timer.seconds(1) + + @typep min_transaction :: %{ + :hash => binary(), + :type => non_neg_integer(), + optional(:request_id) => non_neg_integer(), + optional(any()) => any() + } + + @doc """ + Defines the child specification for the MessagesToL2Matcher. + + This function creates a child specification for use in a supervision tree, + configuring a `BufferedTask` process for the MessagesToL2Matcher. It sets up + the initial state and options for the task, including the recheck interval + for matching L1-to-L2 messages. + + Using the same value for discovering new L1 messages interval and for the + unmatched L2 messages recheck interval ensures that message matching attempts + are synchronized with the rate of new L1 message discovery, optimizing the + process by avoiding unnecessary rechecks when no new L1 messages have been + added to the database. + + ## Parameters + - `init_options`: A keyword list of initial options for the BufferedTask. + - `gen_server_options`: A keyword list of options for the underlying GenServer. + + ## Returns + A child specification map suitable for use in a supervision tree, with the + following key properties: + - Uses `BufferedTask` as the module to start. + - Configures the MessagesToL2Matcher as the callback module for the BufferedTask. + - Sets the initial state with an empty cache of IDs of uncompleted messages and + the recheck interval from the Arbitrum.TrackingMessagesOnL1 configuration. + - Merges provided options with default options for the BufferedTask. + - Uses this module's name as the child's id in the supervision tree. + """ + def child_spec([init_options, gen_server_options]) do + messages_on_l1_interval = + Application.get_all_env(:indexer)[Indexer.Fetcher.Arbitrum.TrackingMessagesOnL1][:recheck_interval] + + buffered_task_init_options = + defaults() + |> Keyword.merge(init_options) + |> Keyword.merge( + state: %{ + uncompleted_messages: %{}, + recheck_interval: messages_on_l1_interval + } + ) + + Supervisor.child_spec({BufferedTask, [{__MODULE__, buffered_task_init_options}, gen_server_options]}, + id: __MODULE__ + ) + end + + @impl BufferedTask + def init(initial, _, _) do + initial + end + + @doc """ + Processes a batch of transactions with hashed message IDs for L1-to-L2 messages. + + This function, implementing the `BufferedTask` behavior, handles a list of + transactions with associated timeouts. It attempts to match hashed request IDs + with uncompleted L1-to-L2 messages, updates the transactions accordingly, and + imports any successfully matched messages to the database. + + The function performs the following steps: + 1. Separates transactions with expired timeouts from those still delayed. + 2. Attempts to update expired transactions by matching their hashed request IDs. + 3. Processes updated transactions to filter and import L1-to-L2 messages. + 4. Reschedules unmatched or delayed transactions for future processing. + + For unmatched transactions, new timeouts are set to the current time increased + by the value of the recheck interval. + + ## Parameters + - `transactions_with_timeouts`: A list of tuples, each containing a timeout and a + transaction with a potentially hashed request ID. + - `state`: The current state of the task, including cached IDs of uncompleted + messages and the recheck interval. + + ## Returns + - `{:ok, updated_state}` if all transactions were processed successfully and + no retries are needed. + - `{:retry, transactions_to_retry, updated_state}` if some transactions need to be + retried, either due to unmatched request IDs or unexpired timeouts. + + The returned state always includes an updated cache of IDs of uncompleted + messages. + """ + @impl BufferedTask + @spec run([{non_neg_integer(), min_transaction()}], %{ + :recheck_interval => non_neg_integer(), + :uncompleted_messages => %{binary() => binary()}, + optional(any()) => any() + }) :: + {:ok, %{:uncompleted_messages => %{binary() => binary()}, optional(any()) => any()}} + | {:retry, [{non_neg_integer(), min_transaction()}], + %{:uncompleted_messages => %{binary() => binary()}, optional(any()) => any()}} + def run( + transactions_with_timeouts, + %{uncompleted_messages: cached_uncompleted_messages_ids, recheck_interval: _} = state + ) + when is_list(transactions_with_timeouts) do + # For next handling only the transactions with expired timeouts are needed. + now = DateTime.to_unix(DateTime.utc_now(), :millisecond) + + {transactions, delayed_transactions} = + transactions_with_timeouts + |> Enum.reduce({[], []}, fn {timeout, transaction}, {transactions, delayed_transactions} -> + if timeout > now do + {transactions, [{timeout, transaction} | delayed_transactions]} + else + {[transaction | transactions], delayed_transactions} + end + end) + + # Check if the request Id of transactions with expired timeouts matches hashed + # ids of the uncompleted messages and update the transactions with the decoded + # request ids. If it required, the cache is updated. + # Possible outcomes: + # - no transactions were updated, because the transactions list is empty, the cache is updated + # - no transactions were updated, because no matches in both cache and DB were found, the cache is updated + # - all matches were found in the cache, the cache is not updated + # - all matches were found in the DB, the cache is updated + # - some matches were found in the cache, but not all, the cache is not updated + {updated?, handled_transactions, updated_cache} = + update_transactions_with_hashed_ids(transactions, cached_uncompleted_messages_ids) + + updated_state = %{state | uncompleted_messages: updated_cache} + + case {updated?, transactions == []} do + {false, true} -> + # There were no transactions with expired timeouts, so counters of the transactions + # updated and the transactions are scheduled for retry. + {:retry, delayed_transactions, updated_state} + + {false, false} -> + # Some of the transactions were with expired timeouts, but no matches were found + # for these transaction in the cache or the DB. Timeouts for such transactions + # are re-initialized and they are added to the list with transactions with + # updated counters. + transactions_to_retry = + delayed_transactions ++ initialize_timeouts(handled_transactions, now + state.recheck_interval) + + {:retry, transactions_to_retry, updated_state} + + {true, _} -> + {messages, transactions_to_retry_wo_timeouts} = MessagingUtils.filter_l1_to_l2_messages(handled_transactions) + + MessagingUtils.import_to_db(messages) + + if transactions_to_retry_wo_timeouts == [] and delayed_transactions == [] do + {:ok, updated_state} + else + # Either some of the transactions with expired timeouts don't have a matching + # request id in the cache or the DB, or there are transactions with non-expired + # timeouts. All these transactions are needed to be scheduled for retry. + transactions_to_retry = + delayed_transactions ++ initialize_timeouts(transactions_to_retry_wo_timeouts, now + state.recheck_interval) + + {:retry, transactions_to_retry, updated_state} + end + end + end + + @doc """ + Asynchronously schedules the discovery of matches for L1-to-L2 messages. + + This function schedules the processing of transactions with hashed message IDs that + require further matching. + + ## Parameters + - `transactions_with_messages_from_l1`: A list of transactions containing L1-to-L2 + messages with hashed message IDs. + + ## Returns + - `:ok` + """ + @spec async_discover_match([min_transaction()]) :: :ok + def async_discover_match(transactions_with_messages_from_l1) do + # Do nothing in case if the indexing chain is not Arbitrum or the feature is disabled. + if MessagesToL2MatcherSupervisor.disabled?() do + :ok + else + BufferedTask.buffer(__MODULE__, Enum.map(transactions_with_messages_from_l1, &{0, &1}), false) + end + end + + # Retrieves and transforms uncompleted L1-to-L2 message IDs into a map of hashed IDs. + # + # This function fetches the IDs of uncompleted L1-to-L2 messages and creates a map + # where each key is the hashed hexadecimal string representation of a message ID, + # and the corresponding value is the original ID converted to a hexadecimal string. + # + # ## Returns + # A map where: + # - Keys are hashed message IDs as hexadecimal strings. + # - Values are original message IDs as 256-bit hexadecimal strings. + @spec get_hashed_ids_for_uncompleted_messages() :: %{binary() => binary()} + defp get_hashed_ids_for_uncompleted_messages do + Db.get_uncompleted_l1_to_l2_messages_ids() + |> Enum.reduce(%{}, fn id, acc -> + Map.put( + acc, + ArbitrumHelper.get_hashed_message_id_as_hex_str(id), + ArbitrumHelper.bytes_to_hex_str(<>) + ) + end) + end + + # Updates transactions with hashed request IDs, using cached or fresh data. + # + # This function attempts to replace hashed request IDs in transactions with their + # original IDs. It first tries using a cached set of uncompleted message IDs. If + # no matches are found in the cache, it fetches fresh data from the database. + # + # ## Parameters + # - `transactions`: A list of transactions with potentially hashed request IDs. + # - `cached_uncompleted_messages_ids`: A map of cached hashed message IDs to their + # original forms. + # + # ## Returns + # A tuple containing: + # - A boolean indicating whether any transactions were updated. + # - An updated list of transactions, with some request IDs potentially replaced. + # - The map of uncompleted message IDs used for the update (either the cache or + # freshly fetched data). + # + # ## Notes + # - If the cache is used successfully, it's returned as-is, even if potentially + # outdated. + # - If the cache fails, fresh data is fetched and returned, updating the cache. + @spec update_transactions_with_hashed_ids([min_transaction()], %{binary() => binary()}) :: + {boolean(), [min_transaction()], %{binary() => binary()}} + defp update_transactions_with_hashed_ids([], cache), do: {false, [], cache} + + defp update_transactions_with_hashed_ids(transactions, cached_uncompleted_messages_ids) do + # Try to use the cached DB response first. That makes sense if historical + # messages are being processed (by catchup block fetcher or by the missing + # messages handler). Since amount of transactions provided to this function is limited + # it OK to inspect the cache before making a DB request. + case revise_transactions_with_hashed_ids(transactions, cached_uncompleted_messages_ids, true) do + {_, false} -> + # If no matches were found in the cache, try to fetch uncompleted messages from the DB. + uncompleted_messages = get_hashed_ids_for_uncompleted_messages() + + {updated_transactions, updated?} = + revise_transactions_with_hashed_ids(transactions, uncompleted_messages, false) + + {updated?, updated_transactions, uncompleted_messages} + + {updated_transactions, _} -> + # There could be a case when some hashed ids were not found since the cache is outdated + # such transactions will be scheduled for retry and the cache will be updated then. + {true, updated_transactions, cached_uncompleted_messages_ids} + end + end + + # Attempts to replace hashed request IDs in transactions with their original IDs. + # + # This function iterates through a list of transactions, trying to match their + # hashed request IDs with entries in the provided map of uncompleted messages. + # If a match is found, the transaction's request ID is updated to its original + # (non-hashed) form. + # + # ## Parameters + # - `transactions`: A list of transactions with potentially hashed request IDs. + # - `uncompleted_messages`: A map of hashed message IDs to their original forms. + # - `report?`: A boolean flag indicating whether to log decoding attempts. + # + # ## Returns + # A tuple containing: + # - An updated list of transactions, with some request IDs potentially replaced. + # - A boolean indicating whether any transactions were updated. + @spec revise_transactions_with_hashed_ids([min_transaction()], %{binary() => binary()}, boolean()) :: + {[min_transaction()], boolean()} + defp revise_transactions_with_hashed_ids(transactions, uncompleted_messages, report?) do + transactions + |> Enum.reduce({[], false}, fn transaction, {updated_transactions, updated?} -> + if report?, + do: + log_info( + "Attempting to decode the request id #{transaction.request_id} in the transaction #{transaction.hash}" + ) + + case Map.get(uncompleted_messages, transaction.request_id) do + nil -> + {[transaction | updated_transactions], updated?} + + id -> + {[%{transaction | request_id: id} | updated_transactions], true} + end + end) + end + + # Assigns a uniform timeout to each transaction in the given list. + @spec initialize_timeouts([min_transaction()], non_neg_integer()) :: [{non_neg_integer(), min_transaction()}] + defp initialize_timeouts(transactions_to_retry, timeout) do + transactions_to_retry + |> Enum.map(&{timeout, &1}) + end + + defp defaults do + [ + flush_interval: @flush_interval, + 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: false, + task_supervisor: __MODULE__.TaskSupervisor, + metadata: [fetcher: :messages_to_l2_matcher] + ] + end +end diff --git a/apps/indexer/lib/indexer/fetcher/arbitrum/messaging.ex b/apps/indexer/lib/indexer/fetcher/arbitrum/messaging.ex index 29704aa..04f4268 100644 --- a/apps/indexer/lib/indexer/fetcher/arbitrum/messaging.ex +++ b/apps/indexer/lib/indexer/fetcher/arbitrum/messaging.ex @@ -14,10 +14,14 @@ defmodule Indexer.Fetcher.Arbitrum.Messaging do import Indexer.Fetcher.Arbitrum.Utils.Logging, only: [log_info: 1, log_debug: 1] + alias Explorer.Chain + alias Explorer.Chain.Arbitrum.Message alias Indexer.Fetcher.Arbitrum.Utils.Db require Logger + @zero_hex_prefix "0x" <> String.duplicate("0", 56) + @l2_to_l1_event_unindexed_params [ :address, {:uint, 256}, @@ -27,17 +31,6 @@ defmodule Indexer.Fetcher.Arbitrum.Messaging do :bytes ] - @type arbitrum_message :: %{ - direction: :to_l2 | :from_l2, - message_id: non_neg_integer(), - originator_address: binary(), - originating_transaction_hash: binary(), - origination_timestamp: DateTime.t(), - originating_transaction_block_number: non_neg_integer(), - completion_transaction_hash: binary(), - status: :initiated | :sent | :confirmed | :relayed - } - @typep min_transaction :: %{ :hash => binary(), :type => non_neg_integer(), @@ -60,40 +53,57 @@ defmodule Indexer.Fetcher.Arbitrum.Messaging do } @doc """ - Filters a list of rollup transactions to identify L1-to-L2 messages and composes a map for each with the related message information. + Filters rollup transactions to identify L1-to-L2 messages and categorizes them. - This function filters a list of rollup transactions, selecting those where - `request_id` is not nil and is below 2^31, indicating they are L1-to-L2 - message completions. These filtered transactions are then processed to - construct a detailed message structure for each. + This function processes a list of rollup transactions, identifying those with + non-nil `request_id` fields. It then separates these into two categories: + messages with plain message IDs and transactions with hashed message IDs. ## Parameters - `transactions`: A list of rollup transaction entries. - `report`: An optional boolean flag (default `true`) that, when `true`, logs - the number of processed L1-to-L2 messages if any are found. + the number of identified L1-to-L2 messages and transactions requiring + further processing. ## Returns - - A list of L1-to-L2 messages with detailed information and current status. Every - map in the list compatible with the database import operation. All messages in - this context are considered `:relayed` as they represent completed actions from - L1 to L2. + A tuple containing: + - A list of L1-to-L2 messages with detailed information, ready for database + import. All messages in this context are considered `:relayed` as they + represent completed actions from L1 to L2. + - A list of transactions with hashed message IDs that require further + processing for message ID matching. """ - @spec filter_l1_to_l2_messages([min_transaction()]) :: [arbitrum_message] - @spec filter_l1_to_l2_messages([min_transaction()], boolean()) :: [arbitrum_message] + @spec filter_l1_to_l2_messages([min_transaction()]) :: {[Message.to_import()], [min_transaction()]} + @spec filter_l1_to_l2_messages([min_transaction()], boolean()) :: {[Message.to_import()], [min_transaction()]} def filter_l1_to_l2_messages(transactions, report \\ true) when is_list(transactions) and is_boolean(report) do - messages = + {transactions_with_proper_message_id, transactions_with_hashed_message_id} = transactions - |> Enum.filter(fn tx -> - tx[:request_id] != nil and Bitwise.bsr(tx[:request_id], 31) == 0 + |> Enum.filter(fn transaction -> + transaction[:request_id] != nil end) + |> Enum.split_with(fn transaction -> + plain_message_id?(transaction[:request_id]) + end) + + # Transform transactions with the plain message ID into messages + messages = + transactions_with_proper_message_id |> handle_filtered_l1_to_l2_messages() - if report && not (messages == []) do - log_info("#{length(messages)} completions of L1-to-L2 messages will be imported") + if report do + if not (messages == []) do + log_info("#{length(messages)} completions of L1-to-L2 messages will be imported") + end + + if not (transactions_with_hashed_message_id == []) do + log_info( + "#{length(transactions_with_hashed_message_id)} completions of L1-to-L2 messages require message ID matching discovery" + ) + end end - messages + {messages, transactions_with_hashed_message_id} end @doc """ @@ -110,7 +120,7 @@ defmodule Indexer.Fetcher.Arbitrum.Messaging do - A list of L2-to-L1 messages with detailed information and current status. Each map in the list is compatible with the database import operation. """ - @spec filter_l2_to_l1_messages(maybe_improper_list(min_log, [])) :: [arbitrum_message] + @spec filter_l2_to_l1_messages(maybe_improper_list(min_log, [])) :: [Message.to_import()] def filter_l2_to_l1_messages(logs) when is_list(logs) do arbsys_contract = Application.get_env(:indexer, __MODULE__)[:arbsys_contract] @@ -127,7 +137,7 @@ defmodule Indexer.Fetcher.Arbitrum.Messaging do Processes a list of filtered rollup transactions representing L1-to-L2 messages, constructing a detailed message structure for each. ## Parameters - - `filtered_txs`: A list of rollup transaction entries, each representing an L1-to-L2 + - `filtered_transactions`: A list of rollup transaction entries, each representing an L1-to-L2 message transaction. ## Returns @@ -135,17 +145,22 @@ defmodule Indexer.Fetcher.Arbitrum.Messaging do in the list compatible with the database import operation. All messages in this context are considered `:relayed` as they represent completed actions from L1 to L2. """ - @spec handle_filtered_l1_to_l2_messages(maybe_improper_list(min_transaction, [])) :: [arbitrum_message] + @spec handle_filtered_l1_to_l2_messages(maybe_improper_list(min_transaction, [])) :: [Message.to_import()] def handle_filtered_l1_to_l2_messages([]) do [] end - def handle_filtered_l1_to_l2_messages(filtered_txs) when is_list(filtered_txs) do - filtered_txs - |> Enum.map(fn tx -> - log_debug("L1 to L2 message #{tx.hash} found with the type #{tx.type}") - - %{direction: :to_l2, message_id: tx.request_id, completion_transaction_hash: tx.hash, status: :relayed} + def handle_filtered_l1_to_l2_messages(filtered_transactions) when is_list(filtered_transactions) do + filtered_transactions + |> Enum.map(fn transaction -> + log_debug("L1 to L2 message #{transaction.hash} found with the type #{transaction.type}") + + %{ + direction: :to_l2, + message_id: quantity_to_integer(transaction.request_id), + completion_transaction_hash: transaction.hash, + status: :relayed + } |> complete_to_params() end) end @@ -168,8 +183,8 @@ defmodule Indexer.Fetcher.Arbitrum.Messaging do - A list of L2-to-L1 messages with detailed information and current status, ready for database import. """ - @spec handle_filtered_l2_to_l1_messages([min_log]) :: [arbitrum_message] - @spec handle_filtered_l2_to_l1_messages([min_log], module()) :: [arbitrum_message] + @spec handle_filtered_l2_to_l1_messages([min_log]) :: [Message.to_import()] + @spec handle_filtered_l2_to_l1_messages([min_log], module()) :: [Message.to_import()] def handle_filtered_l2_to_l1_messages(filtered_logs, caller \\ nil) def handle_filtered_l2_to_l1_messages([], _) do @@ -211,21 +226,40 @@ defmodule Indexer.Fetcher.Arbitrum.Messaging do # The check if messages are executed is required only for the case when l2-to-l1 # messages are found by block catchup fetcher - updated_messages_map = - case caller do - nil -> - messages_map - - _ -> - messages_map - |> find_and_update_executed_messages() - end - - updated_messages_map + caller + |> case do + nil -> + messages_map + + _ -> + messages_map + |> find_and_update_executed_messages() + end |> Map.values() end + @doc """ + Imports a list of messages into the database. + + ## Parameters + - `messages`: A list of messages to import into the database. + + ## Returns + N/A + """ + @spec import_to_db([Message.to_import()]) :: :ok + def import_to_db(messages) do + {:ok, _} = + Chain.import(%{ + arbitrum_messages: %{params: messages}, + timeout: :infinity + }) + + :ok + end + # Converts an incomplete message structure into a complete parameters map for database updates. + @spec complete_to_params(map()) :: Message.to_import() defp complete_to_params(incomplete) do [ :direction, @@ -243,6 +277,7 @@ defmodule Indexer.Fetcher.Arbitrum.Messaging do end # Parses an L2-to-L1 event, extracting relevant information from the event's data. + @spec l2_to_l1_event_parse(min_log()) :: {non_neg_integer(), binary(), non_neg_integer(), DateTime.t()} defp l2_to_l1_event_parse(event) do [ caller, @@ -260,6 +295,8 @@ defmodule Indexer.Fetcher.Arbitrum.Messaging do # Determines the status of an L2-to-L1 message based on its block number and the highest # committed and confirmed block numbers. + @spec status_l2_to_l1_message(non_neg_integer(), non_neg_integer(), non_neg_integer()) :: + :confirmed | :sent | :initiated defp status_l2_to_l1_message(msg_block, highest_committed_block, highest_confirmed_block) do cond do highest_confirmed_block >= msg_block -> :confirmed @@ -278,6 +315,9 @@ defmodule Indexer.Fetcher.Arbitrum.Messaging do # ## Returns # - The updated map of messages with the `completion_transaction_hash` and `status` fields updated # for messages that have been executed. + @spec find_and_update_executed_messages(%{non_neg_integer() => Message.to_import()}) :: %{ + non_neg_integer() => Message.to_import() + } defp find_and_update_executed_messages(messages) do messages |> Map.keys() @@ -292,4 +332,15 @@ defmodule Indexer.Fetcher.Arbitrum.Messaging do Map.put(messages_acc, execution.message_id, message) end) end + + # Checks if the given request ID is a plain message ID (starts with 56 zero + # characters that correspond to 28 zero bytes). + @spec plain_message_id?(non_neg_integer()) :: boolean() + defp plain_message_id?(request_id) when byte_size(request_id) == 66 do + String.starts_with?(request_id, @zero_hex_prefix) + end + + defp plain_message_id?(_) do + false + end end diff --git a/apps/indexer/lib/indexer/fetcher/arbitrum/rollup_messages_catchup.ex b/apps/indexer/lib/indexer/fetcher/arbitrum/rollup_messages_catchup.ex index a0ec6e0..eb66752 100644 --- a/apps/indexer/lib/indexer/fetcher/arbitrum/rollup_messages_catchup.ex +++ b/apps/indexer/lib/indexer/fetcher/arbitrum/rollup_messages_catchup.ex @@ -41,14 +41,17 @@ defmodule Indexer.Fetcher.Arbitrum.RollupMessagesCatchup do responsible for L1-to-L2 messages and then re-requests these transactions through RPC. Results are utilized to construct messages. These messages are marked as `:relayed`, indicating that they have been successfully received on - L2 and are considered completed, and are then imported into the database. This - approach is adopted because it parallels the action of re-indexing existing - transactions to include Arbitrum-specific fields, which are absent in the - currently indexed transactions. However, permanently adding these fields to the - database model for the sake of historical message catch-up is impractical. - Therefore, to avoid the extensive process of re-indexing and to minimize changes - to the database schema, fetching the required data directly from an external - node via RPC is preferred for historical message discovery. + L2 and are considered completed, and are then imported into the database. If + it is determined that a message cannot be constructed because of a hashed + message ID, the transaction is scheduled for further asynchronous processing to + match it with the corresponding L1 transaction. This approach is adopted + because it parallels the action of re-indexing existing transactions to include + Arbitrum-specific fields, which are absent in the currently indexed + transactions. However, permanently adding these fields to the database model + for the sake of historical message catch-up is impractical. Therefore, to avoid + the extensive process of re-indexing and to minimize changes to the database + schema, fetching the required data directly from an external node via RPC is + preferred for historical message discovery. """ use GenServer @@ -64,7 +67,7 @@ defmodule Indexer.Fetcher.Arbitrum.RollupMessagesCatchup do require Logger @wait_for_new_block_delay 15 - @release_cpu_delay 1 + @release_cpu_delay 2 def child_spec(start_link_arguments) do spec = %{ @@ -167,43 +170,44 @@ defmodule Indexer.Fetcher.Arbitrum.RollupMessagesCatchup do {:noreply, %{state | data: new_data}} end - # Sets the initial parameters for discovering historical messages. This function - # inspects the database for missed messages and, if any are found, identifies the - # end blocks of the ranges for both L1-to-L2 and L2-to-L1 messages. If no missed - # messages are found, the block number before the latest indexed block will be used. - # These end blocks are used to initiate the discovery process in subsequent iterations. + # Sets the end blocks of the ranges for discovering historical L1-to-L2 and L2-to-L1 messages. # - # After identifying the initial values, the function immediately transitions to - # the L2-to-L1 message discovery process by sending the `:historical_msg_from_l2` - # message. + # There is likely a way to query the DB and discover the exact block of the + # first missed message (both L1-to-L2 and L2-to-L1) and start the discovery + # process from there. However, such a query is very expensive and can take a + # long time for chains with a high number of transactions. Instead, it's + # possible to start looking for missed messages from the block before the + # latest indexed block. + # + # Although this approach is not optimal for Blockscout instances where there + # are no missed messages (assumed to be the majority), it is still preferable + # to the first approach. The reason is that a finite number of relatively + # cheap queries (which can be tuned with `missed_messages_blocks_depth`) are + # preferable to one expensive query that becomes even more expensive as the + # number of indexed transactions grows. + # + # After identifying the initial values, the function immediately transitions + # to the L2-to-L1 message discovery process by sending the + # `:historical_msg_from_l2` message. # # ## Parameters # - `:init_worker`: The message that triggers the handler. - # - `state`: The current state of the fetcher containing the first rollup block - # number and the number of the most recent block indexed. + # - `state`: The current state of the fetcher containing the number of the + # most recent block indexed. # # ## Returns - # - `{:noreply, new_state}` where the end blocks for both L1-to-L2 and L2-to-L1 - # message discovery are established. + # - `{:noreply, new_state}` where `new_state` contains the updated state with + # end blocks for both L1-to-L2 and L2-to-L1 message discovery established. @impl GenServer - def handle_info( - :init_worker, - %{config: %{rollup_rpc: %{first_block: rollup_first_block}}, data: %{new_block: just_received_block}} = state - ) do - historical_msg_from_l2_end_block = - Db.rollup_block_to_discover_missed_messages_from_l2(just_received_block, rollup_first_block) - - historical_msg_to_l2_end_block = - Db.rollup_block_to_discover_missed_messages_to_l2(just_received_block, rollup_first_block) - + def handle_info(:init_worker, %{data: %{new_block: just_received_block}} = state) do Process.send(self(), :historical_msg_from_l2, []) new_data = Map.merge(state.data, %{ duration: 0, progressed: false, - historical_msg_from_l2_end_block: historical_msg_from_l2_end_block, - historical_msg_to_l2_end_block: historical_msg_to_l2_end_block + historical_msg_from_l2_end_block: just_received_block, + historical_msg_to_l2_end_block: just_received_block }) {:noreply, %{state | data: new_data}} @@ -267,8 +271,14 @@ defmodule Indexer.Fetcher.Arbitrum.RollupMessagesCatchup do # `requestId` for every transaction. This RPC request is necessary because the # `requestId` field is not present in the transaction model of already indexed # transactions in the database. Results are used to construct messages, which are - # subsequently stored in the database. These imported messages are marked as - # `:relayed`, signifying that they represent completed actions from L1 to L2. + # subsequently stored in the database. + # + # Messages with plain (non-hashed) request IDs are imported into the database and + # marked as `:relayed`, representing completed actions from L1 to L2. + # + # For transactions where the `requestId` represents a hashed message ID, the + # function schedules asynchronous discovery to match them with corresponding L1 + # transactions. # # After importing the messages, the function immediately switches to the process # of choosing a delay prior to the next iteration of historical message discovery diff --git a/apps/indexer/lib/indexer/fetcher/arbitrum/tracking_batches_statuses.ex b/apps/indexer/lib/indexer/fetcher/arbitrum/tracking_batches_statuses.ex index af44220..8e42afd 100644 --- a/apps/indexer/lib/indexer/fetcher/arbitrum/tracking_batches_statuses.ex +++ b/apps/indexer/lib/indexer/fetcher/arbitrum/tracking_batches_statuses.ex @@ -26,7 +26,7 @@ defmodule Indexer.Fetcher.Arbitrum.TrackingBatchesStatuses do rollup blocks. - `:check_historical_executions`: Manages historical executions of L2-to-L1 messages. - - `:check_lifecycle_txs_finalization`: Finalizes the status of lifecycle + - `:check_lifecycle_transactions_finalization`: Finalizes the status of lifecycle transactions, confirming the blocks and messages involved. Discovery of rollup transaction batches is executed by requesting logs on L1 @@ -88,7 +88,7 @@ defmodule Indexer.Fetcher.Arbitrum.TrackingBatchesStatuses do config_tracker = Application.get_all_env(:indexer)[__MODULE__] recheck_interval = config_tracker[:recheck_interval] messages_to_blocks_shift = config_tracker[:messages_to_blocks_shift] - track_l1_tx_finalization = config_tracker[:track_l1_tx_finalization] + track_l1_transaction_finalization = config_tracker[:track_l1_transaction_finalization] finalized_confirmations = config_tracker[:finalized_confirmations] confirmation_batches_depth = config_tracker[:confirmation_batches_depth] new_batches_limit = config_tracker[:new_batches_limit] @@ -104,7 +104,7 @@ defmodule Indexer.Fetcher.Arbitrum.TrackingBatchesStatuses do json_rpc_named_arguments: IndexerHelper.json_rpc_named_arguments(l1_rpc), logs_block_range: l1_rpc_block_range, chunk_size: l1_rpc_chunk_size, - track_finalization: track_l1_tx_finalization, + track_finalization: track_l1_transaction_finalization, finalized_confirmations: finalized_confirmations }, rollup_rpc: %{ @@ -417,7 +417,7 @@ defmodule Indexer.Fetcher.Arbitrum.TrackingBatchesStatuses do # are executed. # # After processing, it immediately transitions to finalizing lifecycle transactions - # by sending the `:check_lifecycle_txs_finalization` message. + # by sending the `:check_lifecycle_transactions_finalization` message. # # ## Parameters # - `:check_historical_executions`: The message that triggers the function. @@ -432,7 +432,7 @@ defmodule Indexer.Fetcher.Arbitrum.TrackingBatchesStatuses do {handle_duration, {:ok, start_block}} = :timer.tc(&NewL1Executions.discover_historical_l1_messages_executions/1, [state]) - Process.send(self(), :check_lifecycle_txs_finalization, []) + Process.send(self(), :check_lifecycle_transactions_finalization, []) new_data = Map.merge(state.data, %{ @@ -454,17 +454,17 @@ defmodule Indexer.Fetcher.Arbitrum.TrackingBatchesStatuses do # message is delayed to account for the time spent on the previous handlers' execution. # # ## Parameters - # - `:check_lifecycle_txs_finalization`: The message that triggers the function. + # - `:check_lifecycle_transactions_finalization`: The message that triggers the function. # - `state`: The current state of the fetcher, containing the configuration needed for # the lifecycle transactions status update. # # ## Returns # - `{:noreply, new_state}` where `new_state` is the updated state with the reset duration. @impl GenServer - def handle_info(:check_lifecycle_txs_finalization, state) do + def handle_info(:check_lifecycle_transactions_finalization, state) do {handle_duration, _} = if state.config.l1_rpc.track_finalization do - :timer.tc(&L1Finalization.monitor_lifecycle_txs/1, [state]) + :timer.tc(&L1Finalization.monitor_lifecycle_transactions/1, [state]) else {0, nil} end diff --git a/apps/indexer/lib/indexer/fetcher/arbitrum/utils/db.ex b/apps/indexer/lib/indexer/fetcher/arbitrum/utils/db.ex index 8c9c640..849e2f6 100644 --- a/apps/indexer/lib/indexer/fetcher/arbitrum/utils/db.ex +++ b/apps/indexer/lib/indexer/fetcher/arbitrum/utils/db.ex @@ -3,7 +3,7 @@ defmodule Indexer.Fetcher.Arbitrum.Utils.Db do Common functions to simplify DB routines for Indexer.Fetcher.Arbitrum fetchers """ - import Indexer.Fetcher.Arbitrum.Utils.Logging, only: [log_warning: 1, log_info: 1] + import Indexer.Fetcher.Arbitrum.Utils.Logging, only: [log_warning: 1] alias Explorer.Chain alias Explorer.Chain.Arbitrum @@ -24,11 +24,11 @@ defmodule Indexer.Fetcher.Arbitrum.Utils.Db do the next available indices are assigned. ## Parameters - - `new_l1_txs`: A map of L1 transaction descriptions. The keys of the map are + - `new_l1_transactions`: A map of L1 transaction descriptions. The keys of the map are transaction hashes. ## Returns - - `l1_txs`: A map of L1 transaction descriptions. Each element is extended with + - `l1_transactions`: A map of L1 transaction descriptions. Each element is extended with the key `:id`, representing the index of the L1 transaction in the `arbitrum_lifecycle_l1_transactions` table. """ @@ -42,46 +42,46 @@ defmodule Indexer.Fetcher.Arbitrum.Utils.Db do } }) :: %{binary() => Arbitrum.LifecycleTransaction.to_import()} # TODO: consider a way to remove duplicate with ZkSync.Utils.Db - def get_indices_for_l1_transactions(new_l1_txs) - when is_map(new_l1_txs) do + def get_indices_for_l1_transactions(new_l1_transactions) + when is_map(new_l1_transactions) do # Get indices for l1 transactions previously handled - l1_txs = - new_l1_txs + l1_transactions = + new_l1_transactions |> Map.keys() |> Reader.lifecycle_transaction_ids() - |> Enum.reduce(new_l1_txs, fn {hash, id}, txs -> - {_, txs} = - Map.get_and_update!(txs, hash.bytes, fn l1_tx -> - {l1_tx, Map.put(l1_tx, :id, id)} + |> Enum.reduce(new_l1_transactions, fn {hash, id}, transactions -> + {_, transactions} = + Map.get_and_update!(transactions, hash.bytes, fn l1_transaction -> + {l1_transaction, Map.put(l1_transaction, :id, id)} end) - txs + transactions end) # Get the next index for the first new transaction based # on the indices existing in DB - l1_tx_next_id = Reader.next_lifecycle_transaction_id() + l1_transaction_next_id = Reader.next_lifecycle_transaction_id() # Assign new indices for the transactions which are not in # the l1 transactions table yet - {updated_l1_txs, _} = - l1_txs + {updated_l1_transactions, _} = + l1_transactions |> Map.keys() |> Enum.reduce( - {l1_txs, l1_tx_next_id}, - fn hash, {txs, next_id} -> - tx = txs[hash] - id = Map.get(tx, :id) + {l1_transactions, l1_transaction_next_id}, + fn hash, {transactions, next_id} -> + transaction = transactions[hash] + id = Map.get(transaction, :id) if is_nil(id) do - {Map.put(txs, hash, Map.put(tx, :id, next_id)), next_id + 1} + {Map.put(transactions, hash, Map.put(transaction, :id, next_id)), next_id + 1} else - {txs, next_id} + {transactions, next_id} end end ) - updated_l1_txs + updated_l1_transactions end @doc """ @@ -89,7 +89,7 @@ defmodule Indexer.Fetcher.Arbitrum.Utils.Db do `arbitrum_lifecycle_l1_transactions` table and converts them to maps. ## Parameters - - `l1_tx_hashes`: A list of hashes to retrieve L1 transactions for. + - `l1_transaction_hashes`: A list of hashes to retrieve L1 transactions for. ## Returns - A list of maps representing the `Explorer.Chain.Arbitrum.LifecycleTransaction` @@ -97,8 +97,8 @@ defmodule Indexer.Fetcher.Arbitrum.Utils.Db do compatible with the database import operation. """ @spec lifecycle_transactions([binary()]) :: [Arbitrum.LifecycleTransaction.to_import()] - def lifecycle_transactions(l1_tx_hashes) do - l1_tx_hashes + def lifecycle_transactions(l1_transaction_hashes) do + l1_transaction_hashes |> Reader.lifecycle_transactions() |> Enum.map(&lifecycle_transaction_to_map/1) end @@ -126,15 +126,24 @@ defmodule Indexer.Fetcher.Arbitrum.Utils.Db do end @doc """ - Calculates the L1 block number to start the search for committed batches that precede - the earliest batch already discovered. + Calculates the L1 block number to start the search for committed batches. + + Returns the block number of the earliest L1 block containing a transaction + that commits a batch, as found in the database. If no committed batches are + found, it returns a default value. It's safe to use the returned block number + for subsequent searches, even if it corresponds to a block we've previously + processed. This is because multiple transactions committing different batches + can exist within the same block, and revisiting this block ensures no batches + are missed. + + The batch discovery process is expected to handle potential duplicates + correctly without creating redundant database entries. ## Parameters - `value_if_nil`: The default value to return if no committed batch is found. ## Returns - - The L1 block number immediately preceding the earliest committed batch, - or `value_if_nil` if no committed batches are found. + - The L1 block number containing the earliest committed batch or `value_if_nil`. """ @spec l1_block_to_discover_earliest_committed_batch(nil | FullBlock.block_number()) :: nil | FullBlock.block_number() def l1_block_to_discover_earliest_committed_batch(value_if_nil) @@ -145,7 +154,7 @@ defmodule Indexer.Fetcher.Arbitrum.Utils.Db do value_if_nil value -> - value - 1 + value end end @@ -215,83 +224,6 @@ defmodule Indexer.Fetcher.Arbitrum.Utils.Db do end end - @doc """ - Determines the rollup block number to discover missed L2-to-L1 messages within - a specified range. - - The function checks for the first missed L2-to-L1 message and whether historical - block fetching is still in progress. If no missed messages are found and - historical fetching is complete, it returns the block number just before the - first rollup block. Otherwise, it returns the appropriate block number based on - the findings. - - ## Parameters - - `initial_value`: The initial block number to start the further search of the - missed messages from if no missed messages are found and historical blocks - are not fetched yet. - - `rollup_first_block`: The block number of the first rollup block. - - ## Returns - - The block number of the first missed L2-to-L1 message. - """ - @spec rollup_block_to_discover_missed_messages_from_l2(FullBlock.block_number(), FullBlock.block_number()) :: - nil | FullBlock.block_number() - def rollup_block_to_discover_missed_messages_from_l2(initial_value, rollup_first_block) do - arbsys_contract = Application.get_env(:indexer, Indexer.Fetcher.Arbitrum.Messaging)[:arbsys_contract] - - with {:block, nil} <- - {:block, Reader.rollup_block_of_first_missed_message_from_l2(arbsys_contract, @l2_to_l1_event)}, - {:synced, true} <- {:synced, rollup_synced?()} do - log_info("No missed messages from L2 found") - rollup_first_block - 1 - else - {:block, value} -> - log_info("First missed message from L2 found in block #{value}") - value - - {:synced, false} -> - log_info("No missed messages from L2 found but historical blocks fetching still in progress") - initial_value - end - end - - @doc """ - Determines the rollup block number to discover missed L1-to-L2 messages within - a specified range. - - The function checks for the first missed L1-to-L2 message and whether historical - block fetching is still in progress. If no missed messages are found and - historical fetching is complete, it returns the block number just before the - first rollup block. Otherwise, it returns the appropriate block number based on - the findings. - - ## Parameters - - `initial_value`: The initial block number to start the further search of the - missed messages from if no missed messages are found and historical blocks - are not fetched yet. - - `rollup_first_block`: The block number of the first rollup block. - - ## Returns - - The block number of the first missed L1-to-L2 message. - """ - @spec rollup_block_to_discover_missed_messages_to_l2(FullBlock.block_number(), FullBlock.block_number()) :: - nil | FullBlock.block_number() - def rollup_block_to_discover_missed_messages_to_l2(initial_value, rollup_first_block) do - with {:block, nil} <- {:block, Reader.rollup_block_of_first_missed_message_to_l2()}, - {:synced, true} <- {:synced, rollup_synced?()} do - log_info("No missed messages to L2 found") - rollup_first_block - 1 - else - {:block, value} -> - log_info("First missed message to L2 found in block #{value}") - value - - {:synced, false} -> - log_info("No missed messages to L2 found but historical blocks fetching still in progress") - initial_value - end - end - @doc """ Retrieves the L1 block number immediately following the block where the confirmation transaction for the highest confirmed rollup block was included. @@ -737,7 +669,7 @@ defmodule Indexer.Fetcher.Arbitrum.Utils.Db do Example #1 - Within the range from 1 to 10, the missing batch is 2. The L1 block for the batch #1 is 10, and the L1 block for the batch #3 is 31. - - The output will be `[{11, 30}]`. + - The output will be `[{10, 31}]`. Example #2 - Within the range from 1 to 10, the missing batches are 2 and 6, and @@ -745,14 +677,21 @@ defmodule Indexer.Fetcher.Arbitrum.Utils.Db do - The L1 block for the batch #3 is 31. - The L1 block for the batch #5 is 64. - The L1 block for the batch #7 is 90. - - The output will be `[{11, 30}, {65, 89}]`. + - The output will be `[{10, 31}, {64, 90}]`. Example #3 - Within the range from 1 to 10, the missing batches are 2 and 4, and - The L1 block for the batch #1 is 10. - The L1 block for the batch #3 is 31. - The L1 block for the batch #5 is 64. - - The output will be `[{11, 30}, {32, 63}]`. + - The output will be `[{10, 31}, {32, 64}]`. + + Example #4 + - Within the range from 1 to 10, the missing batches are 2 and 4, and + - The L1 block for the batch #1 is 10. + - The L1 block for the batch #3 is 31. + - The L1 block for the batch #5 is 31. + - The output will be `[{10, 31}]`. """ @spec get_l1_block_ranges_for_missing_batches(non_neg_integer(), non_neg_integer(), FullBlock.block_number()) :: [ {FullBlock.block_number(), FullBlock.block_number()} @@ -765,28 +704,7 @@ defmodule Indexer.Fetcher.Arbitrum.Utils.Db do |> list_to_chunks() |> chunks_to_neighbor_ranges() - if neighbors_of_missing_batches == [] do - [] - else - l1_blocks = - neighbors_of_missing_batches - |> Enum.reduce(MapSet.new(), fn {start_batch, end_batch}, acc -> - acc - |> MapSet.put(start_batch) - |> MapSet.put(end_batch) - end) - # To avoid error in getting L1 block for the batch 0 - |> MapSet.delete(0) - |> MapSet.to_list() - |> Reader.get_l1_blocks_of_batches_by_numbers() - # It is safe to add the block for the batch 0 even if the batch 1 is missing - |> Map.put(0, block_for_batch_0) - - neighbors_of_missing_batches - |> Enum.map(fn {start_batch, end_batch} -> - {l1_blocks[start_batch] + 1, l1_blocks[end_batch] - 1} - end) - end + batches_gaps_to_block_ranges(neighbors_of_missing_batches, block_for_batch_0) end # Splits a list into chunks of consecutive numbers, e.g., [1, 2, 3, 5, 6, 8] becomes [[1, 2, 3], [5, 6], [8]]. @@ -831,6 +749,64 @@ defmodule Indexer.Fetcher.Arbitrum.Utils.Db do end) end + # Converts batch number gaps to L1 block ranges for missing batches discovery. + # + # This function takes a list of neighboring batch number ranges representing gaps + # in the batch sequence and converts them to corresponding L1 block ranges. These + # L1 block ranges can be used to rediscover missing batches. + # + # ## Parameters + # - `neighbors_of_missing_batches`: A list of tuples, each containing the start + # and end batch numbers of a gap in the batch sequence. + # - `block_for_batch_0`: The L1 block number corresponding to batch number 0. + # + # ## Returns + # - A list of tuples, each containing the start and end L1 block numbers for + # ranges where missing batches should be rediscovered. + @spec batches_gaps_to_block_ranges([{non_neg_integer(), non_neg_integer()}], FullBlock.block_number()) :: + [{FullBlock.block_number(), FullBlock.block_number()}] + defp batches_gaps_to_block_ranges(neighbors_of_missing_batches, block_for_batch_0) + + defp batches_gaps_to_block_ranges([], _), do: [] + + defp batches_gaps_to_block_ranges(neighbors_of_missing_batches, block_for_batch_0) do + l1_blocks = + neighbors_of_missing_batches + |> Enum.reduce(MapSet.new(), fn {start_batch, end_batch}, acc -> + acc + |> MapSet.put(start_batch) + |> MapSet.put(end_batch) + end) + # To avoid error in getting L1 block for the batch 0 + |> MapSet.delete(0) + |> MapSet.to_list() + |> Reader.get_l1_blocks_of_batches_by_numbers() + # It is safe to add the block for the batch 0 even if the batch 1 is missing + |> Map.put(0, block_for_batch_0) + + neighbors_of_missing_batches + |> Enum.reduce({[], %{}}, fn {start_batch, end_batch}, {res, blocks_used} -> + range_start = l1_blocks[start_batch] + range_end = l1_blocks[end_batch] + # If the batch's block was already used as a block finishing one of the ranges + # then we should start another range from the next block to avoid discovering + # the same batches batches again. + case {Map.get(blocks_used, range_start, false), range_start == range_end} do + {true, true} -> + # Edge case when the range consists of a single block (several batches in + # the same block) which is going to be inspected up to this moment. + {res, blocks_used} + + {true, false} -> + {[{range_start + 1, range_end} | res], Map.put(blocks_used, range_end, true)} + + {false, _} -> + {[{range_start, range_end} | res], Map.put(blocks_used, range_end, true)} + end + end) + |> elem(0) + end + @doc """ Retrieves the minimum and maximum batch numbers of L1 batches. @@ -916,20 +892,21 @@ defmodule Indexer.Fetcher.Arbitrum.Utils.Db do Reader.get_da_info_by_batch_number(batch_number) end - # Checks if the rollup is synced by verifying if the block after the first block exists in the database. - @spec rollup_synced?() :: boolean() - defp rollup_synced? do - # Since zero block does not have any useful data, it make sense to consider - # the block just after it - rollup_tail = Application.get_all_env(:indexer)[:first_block] + 1 + @doc """ + Retrieves the list of uncompleted L2-to-L1 messages IDs. - Reader.rollup_block_exists?(rollup_tail) + ## Returns + - A list of the IDs of uncompleted L2-to-L1 messages. + """ + @spec get_uncompleted_l1_to_l2_messages_ids() :: [non_neg_integer()] + def get_uncompleted_l1_to_l2_messages_ids do + Reader.get_uncompleted_l1_to_l2_messages_ids() end @spec lifecycle_transaction_to_map(Arbitrum.LifecycleTransaction.t()) :: Arbitrum.LifecycleTransaction.to_import() - defp lifecycle_transaction_to_map(tx) do + defp lifecycle_transaction_to_map(transaction) do [:id, :hash, :block_number, :timestamp, :status] - |> db_record_to_map(tx) + |> db_record_to_map(transaction) end @spec rollup_block_to_map(Arbitrum.BatchBlock.t()) :: Arbitrum.BatchBlock.to_import() diff --git a/apps/indexer/lib/indexer/fetcher/arbitrum/utils/helper.ex b/apps/indexer/lib/indexer/fetcher/arbitrum/utils/helper.ex index d7abb6a..7c0dc56 100644 --- a/apps/indexer/lib/indexer/fetcher/arbitrum/utils/helper.ex +++ b/apps/indexer/lib/indexer/fetcher/arbitrum/utils/helper.ex @@ -1,6 +1,7 @@ defmodule Indexer.Fetcher.Arbitrum.Utils.Helper do alias Explorer.Chain.Arbitrum.LifecycleTransaction + import EthereumJSONRPC, only: [quantity_to_integer: 1] import Indexer.Fetcher.Arbitrum.Utils.Logging, only: [log_info: 1] @moduledoc """ @@ -44,32 +45,32 @@ defmodule Indexer.Fetcher.Arbitrum.Utils.Helper do and a status. The status is determined based on whether finalization tracking is enabled. ## Parameters - - `lifecycle_txs`: A map where each key is a transaction identifier, and the value is + - `lifecycle_transactions`: A map where each key is a transaction identifier, and the value is a map containing at least the block number (`:block`). - `blocks_to_ts`: A map linking block numbers to their corresponding timestamps. - `track_finalization?`: A boolean flag indicating whether to mark transactions as unfinalized or finalized. ## Returns - - An updated map of the same structure as `lifecycle_txs` but with each transaction extended to include: + - An updated map of the same structure as `lifecycle_transactions` but with each transaction extended to include: - `timestamp`: The timestamp of the block in which the transaction is included. - `status`: Either `:unfinalized` if `track_finalization?` is `true`, or `:finalized` otherwise. """ - @spec extend_lifecycle_txs_with_ts_and_status( + @spec extend_lifecycle_transactions_with_ts_and_status( %{binary() => %{:block => non_neg_integer(), optional(any()) => any()}}, %{non_neg_integer() => DateTime.t()}, boolean() ) :: %{binary() => LifecycleTransaction.to_import()} - def extend_lifecycle_txs_with_ts_and_status(lifecycle_txs, blocks_to_ts, track_finalization?) - when is_map(lifecycle_txs) and is_map(blocks_to_ts) and is_boolean(track_finalization?) do - lifecycle_txs + def extend_lifecycle_transactions_with_ts_and_status(lifecycle_transactions, blocks_to_ts, track_finalization?) + when is_map(lifecycle_transactions) and is_map(blocks_to_ts) and is_boolean(track_finalization?) do + lifecycle_transactions |> Map.keys() - |> Enum.reduce(%{}, fn tx_key, updated_txs -> + |> Enum.reduce(%{}, fn transaction_key, updated_transactions -> Map.put( - updated_txs, - tx_key, - Map.merge(lifecycle_txs[tx_key], %{ - timestamp: blocks_to_ts[lifecycle_txs[tx_key].block_number], + updated_transactions, + transaction_key, + Map.merge(lifecycle_transactions[transaction_key], %{ + timestamp: blocks_to_ts[lifecycle_transactions[transaction_key].block_number], status: if track_finalization? do :unfinalized @@ -87,32 +88,32 @@ defmodule Indexer.Fetcher.Arbitrum.Utils.Helper do This function checks if the given lifecycle transaction has the same block number and timestamp as the provided values. If they are the same, it returns `{:same, nil}`. If they differ, it updates the transaction with the new block number and timestamp, - logs the update, and returns `{:updated, updated_tx}`. + logs the update, and returns `{:updated, updated_transaction}`. ## Parameters - - `tx`: The lifecycle transaction to compare and potentially update. + - `transaction`: The lifecycle transaction to compare and potentially update. - `{new_block_num, new_ts}`: A tuple containing the new block number and timestamp. - - `tx_type_str`: A string describing the type of the transaction for logging purposes. + - `transaction_type_str`: A string describing the type of the transaction for logging purposes. ## Returns - `{:same, nil}` if the transaction block number and timestamp are the same as the provided values. - - `{:updated, updated_tx}` if the transaction was updated with the new block number and timestamp. + - `{:updated, updated_transaction}` if the transaction was updated with the new block number and timestamp. """ - @spec compare_lifecycle_tx_and_update( + @spec compare_lifecycle_transaction_and_update( LifecycleTransaction.to_import(), {non_neg_integer(), DateTime.t()}, String.t() ) :: {:same, nil} | {:updated, LifecycleTransaction.to_import()} - def compare_lifecycle_tx_and_update(tx, {new_block_num, new_ts}, tx_type_str) do - if tx.block_number == new_block_num and DateTime.compare(tx.timestamp, new_ts) == :eq do + def compare_lifecycle_transaction_and_update(transaction, {new_block_num, new_ts}, transaction_type_str) do + if transaction.block_number == new_block_num and DateTime.compare(transaction.timestamp, new_ts) == :eq do {:same, nil} else log_info( - "The #{tx_type_str} transaction 0x#{tx.hash |> Base.encode16(case: :lower)} will be updated with the new block number and timestamp" + "The #{transaction_type_str} transaction 0x#{transaction.hash |> Base.encode16(case: :lower)} will be updated with the new block number and timestamp" ) {:updated, - Map.merge(tx, %{ + Map.merge(transaction, %{ block_number: new_block_num, timestamp: new_ts })} @@ -205,4 +206,58 @@ defmodule Indexer.Fetcher.Arbitrum.Utils.Helper do end) |> Enum.reverse() end + + @doc """ + Converts a message ID to its hashed hexadecimal string representation. + + This function takes a message ID (either as an integer or a hexadecimal string), + concatenates it with 256 zero bits, computes a hash of the concatenation, and + then converts the resulting hash to a hexadecimal string with a "0x" prefix. + + ## Parameters + - `message_id`: The message ID to be hashed and converted. Can be either a + non-negative integer or a "0x"-prefixed hexadecimal string. + + ## Returns + - A string representing the hashed message ID in hexadecimal format, prefixed + with "0x". + + ## Examples + + iex> get_hashed_message_id_as_hex_str(1490421) + "0x9d1614591a3e0ba8854206a716e49ffdffc679131820fa815b989fdef9e5554d" + + iex> get_hashed_message_id_as_hex_str("0x000000000000000000000000000000000000000000000000000000000016bdf5") + "0x9d1614591a3e0ba8854206a716e49ffdffc679131820fa815b989fdef9e5554d" + """ + @spec get_hashed_message_id_as_hex_str(non_neg_integer() | binary()) :: String.t() + def get_hashed_message_id_as_hex_str(message_id) do + message_id + |> hash_for_message_id() + |> bytes_to_hex_str() + end + + # Calculates the hash for a given message ID. + # + # This function computes a 256-bit Keccak hash of the message ID. For integer + # inputs, it concatenates the 256-bit message ID with 256 zero bits before + # hashing. For hexadecimal string inputs, it first converts the string to an + # integer. + # + # ## Parameters + # - `message_id`: Either a non-negative integer or a "0x"-prefixed hexadecimal + # string of 66 characters (including the "0x" prefix). + # + # ## Returns + # - A binary representing the 256-bit Keccak hash of the processed message ID. + @spec hash_for_message_id(non_neg_integer() | binary()) :: binary() + defp hash_for_message_id(message_id) when is_integer(message_id) do + # As per https://github.com/OffchainLabs/nitro/blob/849348e10cf1d9c023f4748dc1211bd363422485/arbos/parse_l2.go#L40 + (<> <> <<0::size(256)>>) + |> ExKeccak.hash_256() + end + + defp hash_for_message_id(message_id) when is_binary(message_id) and byte_size(message_id) == 66 do + hash_for_message_id(quantity_to_integer(message_id)) + end end diff --git a/apps/indexer/lib/indexer/fetcher/arbitrum/utils/rpc.ex b/apps/indexer/lib/indexer/fetcher/arbitrum/utils/rpc.ex index 6f4ef02..b831022 100644 --- a/apps/indexer/lib/indexer/fetcher/arbitrum/utils/rpc.ex +++ b/apps/indexer/lib/indexer/fetcher/arbitrum/utils/rpc.ex @@ -102,7 +102,7 @@ defmodule Indexer.Fetcher.Arbitrum.Utils.Rpc do Constructs a JSON RPC request to retrieve a transaction by its hash. ## Parameters - - `%{hash: tx_hash, id: id}`: A map containing the transaction hash (`tx_hash`) and + - `%{hash: transaction_hash, id: id}`: A map containing the transaction hash (`transaction_hash`) and an identifier (`id`) for the request, which can be used later to establish correspondence between requests and responses. @@ -111,9 +111,9 @@ defmodule Indexer.Fetcher.Arbitrum.Utils.Rpc do the transaction details associated with the given hash. """ @spec transaction_by_hash_request(%{hash: EthereumJSONRPC.hash(), id: non_neg_integer()}) :: Transport.request() - def transaction_by_hash_request(%{id: id, hash: tx_hash}) - when is_binary(tx_hash) and is_integer(id) do - EthereumJSONRPC.request(%{id: id, method: "eth_getTransactionByHash", params: [tx_hash]}) + def transaction_by_hash_request(%{id: id, hash: transaction_hash}) + when is_binary(transaction_hash) and is_integer(id) do + EthereumJSONRPC.request(%{id: id, method: "eth_getTransactionByHash", params: [transaction_hash]}) end @doc """ @@ -325,7 +325,7 @@ defmodule Indexer.Fetcher.Arbitrum.Utils.Rpc do Executes a list of transaction requests and retrieves the sender (from) addresses for each. ## Parameters - - `txs_requests`: A list of `Transport.request()` instances representing the transaction requests. + - `transactions_requests`: A list of `Transport.request()` instances representing the transaction requests. - `json_rpc_named_arguments`: Configuration parameters for the JSON RPC connection. - `chunk_size`: The number of requests to be processed in each batch, defining the size of the chunks. @@ -337,9 +337,9 @@ defmodule Indexer.Fetcher.Arbitrum.Utils.Rpc do EthereumJSONRPC.json_rpc_named_arguments(), non_neg_integer() ) :: [%{EthereumJSONRPC.hash() => EthereumJSONRPC.address()}] - def execute_transactions_requests_and_get_from(txs_requests, json_rpc_named_arguments, chunk_size) - when is_list(txs_requests) and is_integer(chunk_size) do - txs_requests + def execute_transactions_requests_and_get_from(transactions_requests, json_rpc_named_arguments, chunk_size) + when is_list(transactions_requests) and is_integer(chunk_size) do + transactions_requests |> Enum.chunk_every(chunk_size) |> Enum.reduce(%{}, fn chunk, result -> chunk @@ -646,7 +646,9 @@ defmodule Indexer.Fetcher.Arbitrum.Utils.Rpc do # inspected block is in the boundary of the required batch: the current batch is the same # as one found in the previous iteration or the step is not the smallest possible. - next_block_to_inspect = max(1, inspected_block - new_step) + # it is OK to use the earliest block 0 as since the corresponding batch (0) + # will be returned by get_batch_number_for_rollup_block. + next_block_to_inspect = max(0, inspected_block - new_step) do_binary_search_of_opposite_block( next_block_to_inspect, @@ -763,13 +765,13 @@ defmodule Indexer.Fetcher.Arbitrum.Utils.Rpc do @spec string_hash_to_bytes_hash(EthereumJSONRPC.hash() | nil) :: binary() def string_hash_to_bytes_hash(hash) do hash - |> json_tx_id_to_hash() + |> json_transaction_id_to_hash() |> Base.decode16!(case: :mixed) end - defp json_tx_id_to_hash(hash) do + defp json_transaction_id_to_hash(hash) do case hash do - "0x" <> tx_hash -> tx_hash + "0x" <> transaction_hash -> transaction_hash nil -> @zero_hash end end diff --git a/apps/indexer/lib/indexer/fetcher/arbitrum/workers/historical_messages_on_l2.ex b/apps/indexer/lib/indexer/fetcher/arbitrum/workers/historical_messages_on_l2.ex index 659a787..f3b3386 100644 --- a/apps/indexer/lib/indexer/fetcher/arbitrum/workers/historical_messages_on_l2.ex +++ b/apps/indexer/lib/indexer/fetcher/arbitrum/workers/historical_messages_on_l2.ex @@ -18,8 +18,7 @@ defmodule Indexer.Fetcher.Arbitrum.Workers.HistoricalMessagesOnL2 do alias EthereumJSONRPC.Transaction, as: TransactionByRPC - alias Explorer.Chain - + alias Indexer.Fetcher.Arbitrum.MessagesToL2Matcher, as: ArbitrumMessagesToL2Matcher alias Indexer.Fetcher.Arbitrum.Messaging alias Indexer.Fetcher.Arbitrum.Utils.{Db, Rpc} @@ -127,7 +126,7 @@ defmodule Indexer.Fetcher.Arbitrum.Workers.HistoricalMessagesOnL2 do logs |> Messaging.handle_filtered_l2_to_l1_messages(__MODULE__) - import_to_db(messages) + Messaging.import_to_db(messages) end {:ok, start_block} @@ -143,9 +142,14 @@ defmodule Indexer.Fetcher.Arbitrum.Workers.HistoricalMessagesOnL2 do then their bodies are re-requested through RPC because already indexed transactions from the database cannot be utilized; the `requestId` field is not included in the transaction model. The function ensures that the block range - has been indexed before proceeding with message discovery and import. The - imported messages are marked as `:relayed`, as they represent completed actions - from L1 to L2. + has been indexed before proceeding with message discovery and import. + + Messages with plain (non-hashed) request IDs are imported into the database and + marked as `:relayed`, representing completed actions from L1 to L2. + + For transactions where the `requestId` represents a hashed message ID, the + function schedules asynchronous discovery to match them with corresponding L1 + transactions. ## Parameters - `end_block`: The ending block number for the discovery operation. If `nil` or @@ -213,25 +217,33 @@ defmodule Indexer.Fetcher.Arbitrum.Workers.HistoricalMessagesOnL2 do # Discovers and processes historical messages sent from L1 to L2 within a # specified rollup block range. # - # This function identifies which of already indexed transactions within the - # block range contains L1-to-L2 messages and makes RPC calls to fetch - # transaction data. These transactions are then processed to construct proper - # message structures, which are imported into the database. The imported - # messages are marked as `:relayed` as they represent completed actions from L1 - # to L2. + # This function identifies already indexed transactions within the block range + # that potentially contain L1-to-L2 messages. It then makes RPC calls to fetch + # complete transaction data, as the database doesn't include the Arbitrum-specific + # `requestId` field. # - # Note: Already indexed transactions from the database cannot be used because - # the `requestId` field is not included in the transaction model. + # The fetched transactions are processed to construct proper message structures. + # Messages with plain (non-hashed) request IDs are imported into the database + # and marked as `:relayed`, representing completed actions from L1 to L2. + # + # For transactions where the `requestId` represents a hashed message ID, the + # function schedules asynchronous discovery to match them with corresponding L1 + # transactions. + # + # The function processes transactions in chunks to manage memory usage and + # network load efficiently. # # ## Parameters # - `start_block`: The starting block number for the discovery range. # - `end_block`: The ending block number for the discovery range. - # - `config`: The configuration map containing settings for RPC communication - # and chunk size. + # - `config`: A map containing configuration settings, including: + # - `:rollup_rpc`: A map with RPC settings: + # - `:chunk_size`: The number of transactions to process in each chunk. + # - `:json_rpc_named_arguments`: Arguments for JSON-RPC communication. # # ## Returns # - `{:ok, start_block}`: A tuple indicating successful processing, returning - # the initial starting block number. + # the initial starting block number. @spec do_discover_historical_messages_to_l2(non_neg_integer(), non_neg_integer(), %{ :rollup_rpc => %{ :chunk_size => non_neg_integer(), @@ -253,10 +265,10 @@ defmodule Indexer.Fetcher.Arbitrum.Workers.HistoricalMessagesOnL2 do if transactions_length > 0 do log_debug("#{transactions_length} historical messages to L2 discovered") - messages = + {messages, transactions_for_further_handling} = transactions |> Enum.chunk_every(chunk_size) - |> Enum.reduce([], fn chunk, messages_acc -> + |> Enum.reduce({[], []}, fn chunk, {messages_acc, transactions_acc} -> # Since DB does not contain the field RequestId specific to Arbitrum # all transactions will be requested from the rollup RPC endpoint. # The catchup process intended to be run once and only for the BS instance @@ -264,30 +276,28 @@ defmodule Indexer.Fetcher.Arbitrum.Workers.HistoricalMessagesOnL2 do # the new field in DB requests = build_transaction_requests(chunk) - messages = + {messages, transactions_with_hashed_message_id} = requests |> Rpc.make_chunked_request(json_rpc_named_arguments, "eth_getTransactionByHash") |> Enum.map(&transaction_json_to_map/1) |> Messaging.filter_l1_to_l2_messages(false) - messages ++ messages_acc + {messages ++ messages_acc, transactions_with_hashed_message_id ++ transactions_acc} end) - # Logging of zero messages is left by intent to reveal potential cases when - # not all transactions are recognized as completed L1-to-L2 messages. - log_info("#{length(messages)} completions of L1-to-L2 messages will be imported") - import_to_db(messages) + handle_messages(messages) + handle_transactions_with_hashed_message_id(transactions_for_further_handling) end {:ok, start_block} end # Constructs a list of `eth_getTransactionByHash` requests for a given list of transaction hashes. - defp build_transaction_requests(tx_hashes) do - tx_hashes - |> Enum.reduce([], fn tx_hash, requests_list -> + defp build_transaction_requests(transaction_hashes) do + transaction_hashes + |> Enum.reduce([], fn transaction_hash, requests_list -> [ - Rpc.transaction_by_hash_request(%{id: 0, hash: tx_hash}) + Rpc.transaction_by_hash_request(%{id: 0, hash: transaction_hash}) | requests_list ] end) @@ -301,12 +311,60 @@ defmodule Indexer.Fetcher.Arbitrum.Workers.HistoricalMessagesOnL2 do |> TransactionByRPC.elixir_to_params() end - # Imports a list of messages into the database. - defp import_to_db(messages) do - {:ok, _} = - Chain.import(%{ - arbitrum_messages: %{params: messages}, - timeout: :infinity - }) + # Processes and imports completed L1-to-L2 messages. + # + # This function handles a list of completed L1-to-L2 messages, logging the number + # of messages to be imported and then importing them into the database. The + # function intentionally logs even when there are zero messages to import, which + # helps identify potential cases where not all transactions are recognized as + # completed L1-to-L2 messages. + # + # ## Parameters + # - `messages`: A list of completed L1-to-L2 messages ready for import. + # + # ## Returns + # - `:ok` + @spec handle_messages([Explorer.Chain.Arbitrum.Message.to_import()]) :: :ok + defp handle_messages(messages) do + log_info("#{length(messages)} completions of L1-to-L2 messages will be imported") + Messaging.import_to_db(messages) + end + + # Processes transactions with hashed message IDs for L1-to-L2 message completion. + # + # This function asynchronously handles transactions that contain L1-to-L2 + # messages with hashed message IDs. + # + # The asynchronous handling is beneficial because: + # - If the corresponding L1 transaction is already indexed, the message will be + # imported after the next flush of the queued tasks buffer. + # - If the corresponding L1 transaction is not yet indexed, it will be awaited by + # the queued tasks handler. + # + # Asynchronous processing prevents locking the discovery process, which would + # occur if we waited synchronously for L1 transactions to be indexed. Another + # approach for synchronous handling is to skip a message without importing it to + # the DB when an L1 transaction is not found; the absence of the message will be + # discovered after a Blockscout instance restart. In the current asynchronous + # implementation, even if the awaiting of an L1 transaction in the queued tasks + # is terminated due to a Blockscout instance shutdown, the absence of the message + # will be discovered after the restart. The system will then attempt to match it + # with the corresponding L1 message again. + # + # ## Parameters + # - `transactions_with_hashed_message_id`: A list of transactions containing L1-to-L2 + # messages with hashed message IDs. + # + # ## Returns + # - `:ok` + @spec handle_transactions_with_hashed_message_id([map()]) :: :ok + defp handle_transactions_with_hashed_message_id([]), do: :ok + + defp handle_transactions_with_hashed_message_id(transactions_with_hashed_message_id) do + log_info( + "#{length(transactions_with_hashed_message_id)} completions of L1-to-L2 messages require message ID matching discovery" + ) + + ArbitrumMessagesToL2Matcher.async_discover_match(transactions_with_hashed_message_id) end end diff --git a/apps/indexer/lib/indexer/fetcher/arbitrum/workers/l1_finalization.ex b/apps/indexer/lib/indexer/fetcher/arbitrum/workers/l1_finalization.ex index 9a5c457..3230664 100644 --- a/apps/indexer/lib/indexer/fetcher/arbitrum/workers/l1_finalization.ex +++ b/apps/indexer/lib/indexer/fetcher/arbitrum/workers/l1_finalization.ex @@ -33,7 +33,7 @@ defmodule Indexer.Fetcher.Arbitrum.Workers.L1Finalization do ## Returns - `:ok` """ - @spec monitor_lifecycle_txs(%{ + @spec monitor_lifecycle_transactions(%{ :config => %{ :l1_rpc => %{ :json_rpc_named_arguments => EthereumJSONRPC.json_rpc_named_arguments(), @@ -43,7 +43,9 @@ defmodule Indexer.Fetcher.Arbitrum.Workers.L1Finalization do }, optional(any()) => any() }) :: :ok - def monitor_lifecycle_txs(%{config: %{l1_rpc: %{json_rpc_named_arguments: json_rpc_named_arguments}}} = _state) do + def monitor_lifecycle_transactions( + %{config: %{l1_rpc: %{json_rpc_named_arguments: json_rpc_named_arguments}}} = _state + ) do {:ok, safe_block} = IndexerHelper.get_block_number_by_tag( "safe", @@ -51,20 +53,20 @@ defmodule Indexer.Fetcher.Arbitrum.Workers.L1Finalization do Rpc.get_resend_attempts() ) - lifecycle_txs = Db.lifecycle_unfinalized_transactions(safe_block) + lifecycle_transactions = Db.lifecycle_unfinalized_transactions(safe_block) - if length(lifecycle_txs) > 0 do - log_info("Discovered #{length(lifecycle_txs)} lifecycle transaction to be finalized") + if length(lifecycle_transactions) > 0 do + log_info("Discovered #{length(lifecycle_transactions)} lifecycle transaction to be finalized") - updated_lifecycle_txs = - lifecycle_txs - |> Enum.map(fn tx -> - Map.put(tx, :status, :finalized) + updated_lifecycle_transactions = + lifecycle_transactions + |> Enum.map(fn transaction -> + Map.put(transaction, :status, :finalized) end) {:ok, _} = Chain.import(%{ - arbitrum_lifecycle_transactions: %{params: updated_lifecycle_txs}, + arbitrum_lifecycle_transactions: %{params: updated_lifecycle_transactions}, timeout: :infinity }) end diff --git a/apps/indexer/lib/indexer/fetcher/arbitrum/workers/new_batches.ex b/apps/indexer/lib/indexer/fetcher/arbitrum/workers/new_batches.ex index 6a5e8c3..42eb336 100644 --- a/apps/indexer/lib/indexer/fetcher/arbitrum/workers/new_batches.ex +++ b/apps/indexer/lib/indexer/fetcher/arbitrum/workers/new_batches.ex @@ -607,7 +607,7 @@ defmodule Indexer.Fetcher.Arbitrum.Workers.NewBatches do logs |> Enum.chunk_every(new_batches_limit) |> Enum.each(fn chunked_logs -> - {batches, lifecycle_txs, rollup_blocks, rollup_txs, committed_txs, da_records} = + {batches, lifecycle_transactions, rollup_blocks, rollup_transactions, committed_transactions, da_records} = handle_batches_from_logs( chunked_logs, messages_to_blocks_shift, @@ -619,18 +619,18 @@ defmodule Indexer.Fetcher.Arbitrum.Workers.NewBatches do {:ok, _} = Chain.import(%{ - arbitrum_lifecycle_transactions: %{params: lifecycle_txs}, + arbitrum_lifecycle_transactions: %{params: lifecycle_transactions}, arbitrum_l1_batches: %{params: batches}, arbitrum_batch_blocks: %{params: rollup_blocks}, - arbitrum_batch_transactions: %{params: rollup_txs}, - arbitrum_messages: %{params: committed_txs}, + arbitrum_batch_transactions: %{params: rollup_transactions}, + arbitrum_messages: %{params: committed_transactions}, arbitrum_da_multi_purpose_records: %{params: da_records}, timeout: :infinity }) if not Enum.empty?(batches) and new_batches_discovery? do Publisher.broadcast( - [{:new_arbitrum_batches, extend_batches_with_commitment_transactions(batches, lifecycle_txs)}], + [{:new_arbitrum_batches, extend_batches_with_commitment_transactions(batches, lifecycle_transactions)}], :realtime ) end @@ -746,14 +746,14 @@ defmodule Indexer.Fetcher.Arbitrum.Workers.NewBatches do |> parse_logs_to_get_batch_numbers() |> Db.batches_exist() - {batches, txs_requests, blocks_requests, existing_commitment_txs} = + {batches, transactions_requests, blocks_requests, existing_commitment_transactions} = parse_logs_for_new_batches(logs, existing_batches) blocks_to_ts = Rpc.execute_blocks_requests_and_get_ts(blocks_requests, json_rpc_named_arguments, chunk_size) - {initial_lifecycle_txs, batches_to_import, da_info} = - execute_tx_requests_parse_txs_calldata( - txs_requests, + {initial_lifecycle_transactions, batches_to_import, da_info} = + execute_transaction_requests_parse_transactions_calldata( + transactions_requests, msg_to_block_shift, blocks_to_ts, batches, @@ -766,17 +766,18 @@ defmodule Indexer.Fetcher.Arbitrum.Workers.NewBatches do # Check if the commitment transactions for the batches which are already in the database # needs to be updated in case of reorgs - lifecycle_txs_wo_indices = - initial_lifecycle_txs - |> Map.merge(update_lifecycle_txs_for_new_blocks(existing_commitment_txs, blocks_to_ts)) + lifecycle_transactions_wo_indices = + initial_lifecycle_transactions + |> Map.merge(update_lifecycle_transactions_for_new_blocks(existing_commitment_transactions, blocks_to_ts)) - {blocks_to_import, rollup_txs_to_import} = get_rollup_blocks_and_transactions(batches_to_import, rollup_rpc_config) + {blocks_to_import, rollup_transactions_to_import} = + get_rollup_blocks_and_transactions(batches_to_import, rollup_rpc_config) - lifecycle_txs = - lifecycle_txs_wo_indices + lifecycle_transactions = + lifecycle_transactions_wo_indices |> Db.get_indices_for_l1_transactions() - tx_counts_per_batch = batches_to_rollup_txs_amounts(rollup_txs_to_import) + transaction_counts_per_batch = batches_to_rollup_transactions_amounts(rollup_transactions_to_import) batches_list_to_import = batches_to_import @@ -784,15 +785,15 @@ defmodule Indexer.Fetcher.Arbitrum.Workers.NewBatches do |> Enum.reduce([], fn batch, updated_batches_list -> [ batch - |> Map.put(:commitment_id, get_l1_tx_id_by_hash(lifecycle_txs, batch.tx_hash)) + |> Map.put(:commitment_id, get_l1_transaction_id_by_hash(lifecycle_transactions, batch.transaction_hash)) |> Map.put( :transactions_count, - case tx_counts_per_batch[batch.number] do + case transaction_counts_per_batch[batch.number] do nil -> 0 value -> value end ) - |> Map.drop([:tx_hash]) + |> Map.drop([:transaction_hash]) | updated_batches_list ] end) @@ -815,8 +816,8 @@ defmodule Indexer.Fetcher.Arbitrum.Workers.NewBatches do |> get_committed_l2_to_l1_messages() end - {batches_list_to_import, Map.values(lifecycle_txs), Map.values(blocks_to_import), rollup_txs_to_import, - committed_messages, da_records} + {batches_list_to_import, Map.values(lifecycle_transactions), Map.values(blocks_to_import), + rollup_transactions_to_import, committed_messages, da_records} end # Extracts batch numbers from logs of SequencerBatchDelivered events. @@ -864,7 +865,7 @@ defmodule Indexer.Fetcher.Arbitrum.Workers.NewBatches do :number => non_neg_integer(), :before_acc => binary(), :after_acc => binary(), - :tx_hash => binary() + :transaction_hash => binary() } }, [EthereumJSONRPC.Transport.request()], @@ -872,20 +873,20 @@ defmodule Indexer.Fetcher.Arbitrum.Workers.NewBatches do %{binary() => non_neg_integer()} } defp parse_logs_for_new_batches(logs, existing_batches) do - {batches, txs_requests, blocks_requests, existing_commitment_txs} = + {batches, transactions_requests, blocks_requests, existing_commitment_transactions} = logs |> Enum.reduce({%{}, [], %{}, %{}}, fn event, acc -> - tx_hash_raw = event["transactionHash"] + transaction_hash_raw = event["transactionHash"] blk_num = quantity_to_integer(event["blockNumber"]) handle_new_batch_data( - {sequencer_batch_delivered_event_parse(event), tx_hash_raw, blk_num}, + {sequencer_batch_delivered_event_parse(event), transaction_hash_raw, blk_num}, existing_batches, acc ) end) - {batches, txs_requests, Map.values(blocks_requests), existing_commitment_txs} + {batches, transactions_requests, Map.values(blocks_requests), existing_commitment_transactions} end # Parses SequencerBatchDelivered event to get batch sequence number and associated accumulators @@ -932,7 +933,7 @@ defmodule Indexer.Fetcher.Arbitrum.Workers.NewBatches do :number => non_neg_integer(), :before_acc => binary(), :after_acc => binary(), - :tx_hash => binary() + :transaction_hash => binary() } }, [EthereumJSONRPC.Transport.request()], @@ -948,17 +949,17 @@ defmodule Indexer.Fetcher.Arbitrum.Workers.NewBatches do defp handle_new_batch_data({{batch_num, _, _}, _, _}, _, acc) when batch_num == 0, do: acc defp handle_new_batch_data( - {{batch_num, before_acc, after_acc}, tx_hash_raw, blk_num}, + {{batch_num, before_acc, after_acc}, transaction_hash_raw, blk_num}, existing_batches, - {batches, txs_requests, blocks_requests, existing_commitment_txs} + {batches, transactions_requests, blocks_requests, existing_commitment_transactions} ) do - tx_hash = Rpc.string_hash_to_bytes_hash(tx_hash_raw) + transaction_hash = Rpc.string_hash_to_bytes_hash(transaction_hash_raw) - {updated_batches, updated_txs_requests, updated_existing_commitment_txs} = + {updated_batches, updated_transactions_requests, updated_existing_commitment_transactions} = if batch_num in existing_batches do - {batches, txs_requests, Map.put(existing_commitment_txs, tx_hash, blk_num)} + {batches, transactions_requests, Map.put(existing_commitment_transactions, transaction_hash, blk_num)} else - log_info("New batch #{batch_num} found in #{tx_hash_raw}") + log_info("New batch #{batch_num} found in #{transaction_hash_raw}") updated_batches = Map.put( @@ -968,16 +969,16 @@ defmodule Indexer.Fetcher.Arbitrum.Workers.NewBatches do number: batch_num, before_acc: before_acc, after_acc: after_acc, - tx_hash: tx_hash + transaction_hash: transaction_hash } ) - updated_txs_requests = [ - Rpc.transaction_by_hash_request(%{id: 0, hash: tx_hash_raw}) - | txs_requests + updated_transactions_requests = [ + Rpc.transaction_by_hash_request(%{id: 0, hash: transaction_hash_raw}) + | transactions_requests ] - {updated_batches, updated_txs_requests, existing_commitment_txs} + {updated_batches, updated_transactions_requests, existing_commitment_transactions} end # In order to have an ability to update commitment transaction for the existing batches @@ -989,7 +990,7 @@ defmodule Indexer.Fetcher.Arbitrum.Workers.NewBatches do BlockByNumber.request(%{id: 0, number: blk_num}, false, true) ) - {updated_batches, updated_txs_requests, updated_blocks_requests, updated_existing_commitment_txs} + {updated_batches, updated_transactions_requests, updated_blocks_requests, updated_existing_commitment_transactions} end # Executes transaction requests and parses the calldata to extract batch data. @@ -1002,7 +1003,7 @@ defmodule Indexer.Fetcher.Arbitrum.Workers.NewBatches do # constructs a list of DA info structs. # # ## Parameters - # - `txs_requests`: The list of RPC requests to fetch transaction data. + # - `transactions_requests`: The list of RPC requests to fetch transaction data. # - `msg_to_block_shift`: The shift value to adjust the message count to the correct # rollup block numbers. # - `blocks_to_ts`: A map of block numbers to their timestamps, required to complete @@ -1020,7 +1021,7 @@ defmodule Indexer.Fetcher.Arbitrum.Workers.NewBatches do # - An updated map of batch descriptions with block ranges and data availability # information. # - A list of data availability information structs for Anytrust or Celestia. - @spec execute_tx_requests_parse_txs_calldata( + @spec execute_transaction_requests_parse_transactions_calldata( [EthereumJSONRPC.Transport.request()], non_neg_integer(), %{EthereumJSONRPC.block_number() => DateTime.t()}, @@ -1053,8 +1054,8 @@ defmodule Indexer.Fetcher.Arbitrum.Workers.NewBatches do optional(any()) => any() } }, [Anytrust.t() | Celestia.t()]} - defp execute_tx_requests_parse_txs_calldata( - txs_requests, + defp execute_transaction_requests_parse_transactions_calldata( + transactions_requests, msg_to_block_shift, blocks_to_ts, batches, @@ -1065,16 +1066,17 @@ defmodule Indexer.Fetcher.Arbitrum.Workers.NewBatches do }, rollup_config ) do - txs_requests + transactions_requests |> Enum.chunk_every(chunk_size) - |> Enum.reduce({%{}, batches, []}, fn chunk, {l1_txs, updated_batches, da_info} -> + |> Enum.reduce({%{}, batches, []}, fn chunk, {l1_transactions, updated_batches, da_info} -> chunk # each eth_getTransactionByHash will take time since it returns entire batch # in `input` which is heavy because contains dozens of rollup blocks |> Rpc.make_chunked_request(json_rpc_named_arguments, "eth_getTransactionByHash") - |> Enum.reduce({l1_txs, updated_batches, da_info}, fn resp, {txs_map, batches_map, da_info_list} -> - block_num = quantity_to_integer(resp["blockNumber"]) - tx_hash = Rpc.string_hash_to_bytes_hash(resp["hash"]) + |> Enum.reduce({l1_transactions, updated_batches, da_info}, fn resp, + {transactions_map, batches_map, da_info_list} -> + block_number = quantity_to_integer(resp["blockNumber"]) + transaction_hash = Rpc.string_hash_to_bytes_hash(resp["hash"]) # Although they are called messages in the functions' ABI, in fact they are # rollup blocks @@ -1111,11 +1113,11 @@ defmodule Indexer.Fetcher.Arbitrum.Workers.NewBatches do }) ) - updated_txs_map = - Map.put(txs_map, tx_hash, %{ - hash: tx_hash, - block_number: block_num, - timestamp: blocks_to_ts[block_num], + updated_transactions_map = + Map.put(transactions_map, transaction_hash, %{ + hash: transaction_hash, + block_number: block_number, + timestamp: blocks_to_ts[block_number], status: if track_finalization? do :unfinalized @@ -1132,7 +1134,7 @@ defmodule Indexer.Fetcher.Arbitrum.Workers.NewBatches do da_info_list end - {updated_txs_map, updated_batches_map, updated_da_info_list} + {updated_transactions_map, updated_batches_map, updated_da_info_list} end) end) end @@ -1275,7 +1277,7 @@ defmodule Indexer.Fetcher.Arbitrum.Workers.NewBatches do {nil, nil} %Arbitrum.L1Batch{start_block: start_block, end_block: end_block} -> - {start_block - 1, div(end_block - start_block, 2)} + {start_block - 1, half_of_block_range(start_block, end_block, :descending)} end end @@ -1289,7 +1291,26 @@ defmodule Indexer.Fetcher.Arbitrum.Workers.NewBatches do {nil, nil} %Arbitrum.L1Batch{start_block: start_block, end_block: end_block} -> - {end_block + 1, div(start_block - end_block, 2)} + {end_block + 1, half_of_block_range(start_block, end_block, :ascending)} + end + end + + # Calculates half the range between two block numbers, with direction adjustment. + # + # ## Parameters + # - `start_block`: The starting block number. + # - `end_block`: The ending block number. + # - `direction`: The direction of calculation, either `:ascending` or `:descending`. + # + # ## Returns + # - An integer representing half the block range, adjusted for direction: + # - For `:descending`, a positive integer >= 1. + # - For `:ascending`, a negative integer <= -1. + @spec half_of_block_range(non_neg_integer(), non_neg_integer(), :ascending | :descending) :: integer() + defp half_of_block_range(start_block, end_block, direction) do + case direction do + :descending -> max(div(end_block - start_block + 1, 2), 1) + :ascending -> min(div(start_block - end_block - 1, 2), -1) end end @@ -1301,7 +1322,7 @@ defmodule Indexer.Fetcher.Arbitrum.Workers.NewBatches do # number and timestamp. # # Parameters: - # - `existing_commitment_txs`: A map where keys are transaction hashes and + # - `existing_commitment_transactions`: A map where keys are transaction hashes and # values are block numbers. # - `block_to_ts`: A map where keys are block numbers and values are timestamps. # @@ -1309,22 +1330,24 @@ defmodule Indexer.Fetcher.Arbitrum.Workers.NewBatches do # - A map where keys are transaction hashes and values are updated lifecycle # transactions with the block number and timestamp set, compatible with the # database import operation. - @spec update_lifecycle_txs_for_new_blocks(%{binary() => non_neg_integer()}, %{non_neg_integer() => non_neg_integer()}) :: + @spec update_lifecycle_transactions_for_new_blocks(%{binary() => non_neg_integer()}, %{ + non_neg_integer() => non_neg_integer() + }) :: %{binary() => Arbitrum.LifecycleTransaction.to_import()} - defp update_lifecycle_txs_for_new_blocks(existing_commitment_txs, block_to_ts) do - existing_commitment_txs + defp update_lifecycle_transactions_for_new_blocks(existing_commitment_transactions, block_to_ts) do + existing_commitment_transactions |> Map.keys() |> Db.lifecycle_transactions() - |> Enum.reduce(%{}, fn tx, txs -> - block_num = existing_commitment_txs[tx.hash] - ts = block_to_ts[block_num] + |> Enum.reduce(%{}, fn transaction, transactions -> + block_number = existing_commitment_transactions[transaction.hash] + ts = block_to_ts[block_number] - case ArbitrumHelper.compare_lifecycle_tx_and_update(tx, {block_num, ts}, "commitment") do - {:updated, updated_tx} -> - Map.put(txs, tx.hash, updated_tx) + case ArbitrumHelper.compare_lifecycle_transaction_and_update(transaction, {block_number, ts}, "commitment") do + {:updated, updated_transaction} -> + Map.put(transactions, transaction.hash, updated_transaction) _ -> - txs + transactions end end) end @@ -1373,8 +1396,8 @@ defmodule Indexer.Fetcher.Arbitrum.Workers.NewBatches do else log_debug("Identified #{length(required_blocks_numbers)} rollup blocks") - {blocks_to_import_map, txs_to_import_list} = - get_rollup_blocks_and_txs_from_db(required_blocks_numbers, blocks_to_batches) + {blocks_to_import_map, transactions_to_import_list} = + get_rollup_blocks_and_transactions_from_db(required_blocks_numbers, blocks_to_batches) # While it's not entirely aligned with data integrity principles to recover # rollup blocks and transactions from RPC that are not yet indexed, it's @@ -1382,20 +1405,20 @@ defmodule Indexer.Fetcher.Arbitrum.Workers.NewBatches do # the potential high frequency of new batch appearances and the substantial # volume of blocks and transactions, prioritizing discovery process advancement # is deemed reasonable. - {blocks_to_import, txs_to_import} = + {blocks_to_import, transactions_to_import} = recover_data_if_necessary( blocks_to_import_map, - txs_to_import_list, + transactions_to_import_list, required_blocks_numbers, blocks_to_batches, rollup_rpc_config ) log_info( - "Found #{length(Map.keys(blocks_to_import))} rollup blocks and #{length(txs_to_import)} rollup transactions in DB" + "Found #{length(Map.keys(blocks_to_import))} rollup blocks and #{length(transactions_to_import)} rollup transactions in DB" ) - {blocks_to_import, txs_to_import} + {blocks_to_import, transactions_to_import} end end @@ -1421,8 +1444,8 @@ defmodule Indexer.Fetcher.Arbitrum.Workers.NewBatches do |> Map.values() |> Enum.reduce(%{}, fn batch, b_2_b -> batch.start_block..batch.end_block - |> Enum.reduce(b_2_b, fn block_num, b_2_b_inner -> - Map.put(b_2_b_inner, block_num, batch.number) + |> Enum.reduce(b_2_b, fn block_number, b_2_b_inner -> + Map.put(b_2_b_inner, block_number, batch.number) end) end) end @@ -1445,16 +1468,16 @@ defmodule Indexer.Fetcher.Arbitrum.Workers.NewBatches do # database import. # - A list of transactions, each associated with its respective rollup block # and batch number, ready for database import. - defp get_rollup_blocks_and_txs_from_db(rollup_blocks_numbers, blocks_to_batches) do + defp get_rollup_blocks_and_transactions_from_db(rollup_blocks_numbers, blocks_to_batches) do rollup_blocks_numbers |> Db.rollup_blocks() - |> Enum.reduce({%{}, []}, fn block, {blocks_map, txs_list} -> + |> Enum.reduce({%{}, []}, fn block, {blocks_map, transactions_list} -> batch_num = blocks_to_batches[block.number] - updated_txs_list = + updated_transactions_list = block.transactions - |> Enum.reduce(txs_list, fn tx, acc -> - [%{tx_hash: tx.hash.bytes, batch_number: batch_num} | acc] + |> Enum.reduce(transactions_list, fn transaction, acc -> + [%{transaction_hash: transaction.hash.bytes, batch_number: batch_num} | acc] end) updated_blocks_map = @@ -1465,7 +1488,7 @@ defmodule Indexer.Fetcher.Arbitrum.Workers.NewBatches do confirmation_id: nil }) - {updated_blocks_map, updated_txs_list} + {updated_blocks_map, updated_transactions_list} end) end @@ -1479,7 +1502,7 @@ defmodule Indexer.Fetcher.Arbitrum.Workers.NewBatches do # # ## Parameters # - `current_rollup_blocks`: The map of rollup blocks currently held. - # - `current_rollup_txs`: The list of transactions currently held. + # - `current_rollup_transactions`: The list of transactions currently held. # - `required_blocks_numbers`: A list of block numbers that are required for # processing. # - `blocks_to_batches`: A map associating rollup block numbers with batch numbers. @@ -1502,7 +1525,7 @@ defmodule Indexer.Fetcher.Arbitrum.Workers.NewBatches do {%{non_neg_integer() => Arbitrum.BatchBlock.to_import()}, [Arbitrum.BatchTransaction.to_import()]} defp recover_data_if_necessary( current_rollup_blocks, - current_rollup_txs, + current_rollup_transactions, required_blocks_numbers, blocks_to_batches, rollup_rpc_config @@ -1515,17 +1538,18 @@ defmodule Indexer.Fetcher.Arbitrum.Workers.NewBatches do if found_blocks_numbers_length != required_blocks_amount do log_info("Only #{found_blocks_numbers_length} of #{required_blocks_amount} rollup blocks found in DB") - {recovered_blocks_map, recovered_txs_list, _} = - recover_rollup_blocks_and_txs_from_rpc( + {recovered_blocks_map, recovered_transactions_list, _} = + recover_rollup_blocks_and_transactions_from_rpc( required_blocks_numbers, found_blocks_numbers, blocks_to_batches, rollup_rpc_config ) - {Map.merge(current_rollup_blocks, recovered_blocks_map), current_rollup_txs ++ recovered_txs_list} + {Map.merge(current_rollup_blocks, recovered_blocks_map), + current_rollup_transactions ++ recovered_transactions_list} else - {current_rollup_blocks, current_rollup_txs} + {current_rollup_blocks, current_rollup_transactions} end end @@ -1555,7 +1579,7 @@ defmodule Indexer.Fetcher.Arbitrum.Workers.NewBatches do # - A list of transactions, each associated with its respective rollup block # and batch number, ready for database import. # - The updated counter of processed chunks (usually ignored). - @spec recover_rollup_blocks_and_txs_from_rpc( + @spec recover_rollup_blocks_and_transactions_from_rpc( [non_neg_integer()], [non_neg_integer()], %{non_neg_integer() => non_neg_integer()}, @@ -1567,7 +1591,7 @@ defmodule Indexer.Fetcher.Arbitrum.Workers.NewBatches do ) :: {%{non_neg_integer() => Arbitrum.BatchBlock.to_import()}, [Arbitrum.BatchTransaction.to_import()], non_neg_integer()} - defp recover_rollup_blocks_and_txs_from_rpc( + defp recover_rollup_blocks_and_transactions_from_rpc( required_blocks_numbers, found_blocks_numbers, blocks_to_batches, @@ -1582,7 +1606,7 @@ defmodule Indexer.Fetcher.Arbitrum.Workers.NewBatches do missed_blocks |> Enum.sort() |> Enum.chunk_every(rollup_chunk_size) - |> Enum.reduce({%{}, [], 0}, fn chunk, {blocks_map, txs_list, chunks_counter} -> + |> Enum.reduce({%{}, [], 0}, fn chunk, {blocks_map, transactions_list, chunks_counter} -> Logging.log_details_chunk_handling( "Collecting rollup data", {"block", "blocks"}, @@ -1593,12 +1617,12 @@ defmodule Indexer.Fetcher.Arbitrum.Workers.NewBatches do requests = chunk - |> Enum.reduce([], fn block_num, requests_list -> + |> Enum.reduce([], fn block_number, requests_list -> [ BlockByNumber.request( %{ - id: blocks_to_batches[block_num], - number: block_num + id: blocks_to_batches[block_number], + number: block_number }, false ) @@ -1606,12 +1630,12 @@ defmodule Indexer.Fetcher.Arbitrum.Workers.NewBatches do ] end) - {blocks_map_updated, txs_list_updated} = + {blocks_map_updated, transactions_list_updated} = requests |> Rpc.make_chunked_request_keep_id(rollup_json_rpc_named_arguments, "eth_getBlockByNumber") - |> prepare_rollup_block_map_and_transactions_list(blocks_map, txs_list) + |> prepare_rollup_block_map_and_transactions_list(blocks_map, transactions_list) - {blocks_map_updated, txs_list_updated, chunks_counter + length(chunk)} + {blocks_map_updated, transactions_list_updated, chunks_counter + length(chunk)} end) end @@ -1625,7 +1649,7 @@ defmodule Indexer.Fetcher.Arbitrum.Workers.NewBatches do # ## Parameters # - `json_responses`: A list of JSON RPC responses containing rollup block data. # - `rollup_blocks`: The initial map of rollup block information. - # - `rollup_txs`: The initial list of rollup transactions. + # - `rollup_transactions`: The initial list of rollup transactions. # # ## Returns # - A tuple containing: @@ -1638,9 +1662,9 @@ defmodule Indexer.Fetcher.Arbitrum.Workers.NewBatches do %{non_neg_integer() => Arbitrum.BatchBlock.to_import()}, [Arbitrum.BatchTransaction.to_import()] ) :: {%{non_neg_integer() => Arbitrum.BatchBlock.to_import()}, [Arbitrum.BatchTransaction.to_import()]} - defp prepare_rollup_block_map_and_transactions_list(json_responses, rollup_blocks, rollup_txs) do + defp prepare_rollup_block_map_and_transactions_list(json_responses, rollup_blocks, rollup_transactions) do json_responses - |> Enum.reduce({rollup_blocks, rollup_txs}, fn resp, {blocks_map, txs_list} -> + |> Enum.reduce({rollup_blocks, rollup_transactions}, fn resp, {blocks_map, transactions_list} -> batch_num = resp.id blk_num = quantity_to_integer(resp.result["number"]) @@ -1651,35 +1675,35 @@ defmodule Indexer.Fetcher.Arbitrum.Workers.NewBatches do %{block_number: blk_num, batch_number: batch_num, confirmation_id: nil} ) - updated_txs_list = + updated_transactions_list = case resp.result["transactions"] do nil -> - txs_list + transactions_list - new_txs -> - Enum.reduce(new_txs, txs_list, fn l2_tx_hash, txs_list -> - [%{tx_hash: l2_tx_hash, batch_number: batch_num} | txs_list] + new_transactions -> + Enum.reduce(new_transactions, transactions_list, fn l2_transaction_hash, transactions_list -> + [%{transaction_hash: l2_transaction_hash, batch_number: batch_num} | transactions_list] end) end - {updated_blocks_map, updated_txs_list} + {updated_blocks_map, updated_transactions_list} end) end # Retrieves the unique identifier of an L1 transaction by its hash from the given # map. `nil` if there is no such transaction in the map. - defp get_l1_tx_id_by_hash(l1_txs, hash) do - l1_txs + defp get_l1_transaction_id_by_hash(l1_transactions, hash) do + l1_transactions |> Map.get(hash) |> Kernel.||(%{id: nil}) |> Map.get(:id) end # Aggregates rollup transactions by batch number, counting the number of transactions in each batch. - defp batches_to_rollup_txs_amounts(rollup_txs) do - rollup_txs - |> Enum.reduce(%{}, fn tx, acc -> - Map.put(acc, tx.batch_number, Map.get(acc, tx.batch_number, 0) + 1) + defp batches_to_rollup_transactions_amounts(rollup_transactions) do + rollup_transactions + |> Enum.reduce(%{}, fn transaction, acc -> + Map.put(acc, transaction.batch_number, Map.get(acc, transaction.batch_number, 0) + 1) end) end @@ -1688,8 +1712,8 @@ defmodule Indexer.Fetcher.Arbitrum.Workers.NewBatches do defp get_committed_l2_to_l1_messages(block_number) do block_number |> Db.initiated_l2_to_l1_messages() - |> Enum.map(fn tx -> - Map.put(tx, :status, :sent) + |> Enum.map(fn transaction -> + Map.put(transaction, :status, :sent) end) end @@ -1704,10 +1728,12 @@ defmodule Indexer.Fetcher.Arbitrum.Workers.NewBatches do optional(any()) => any() } ] - defp extend_batches_with_commitment_transactions(batches, lifecycle_txs) do + defp extend_batches_with_commitment_transactions(batches, lifecycle_transactions) do Enum.map(batches, fn batch -> - lifecycle_tx = Enum.find(lifecycle_txs, fn tx -> tx.id == batch.commitment_id end) - Map.put(batch, :commitment_transaction, lifecycle_tx) + lifecycle_transaction = + Enum.find(lifecycle_transactions, fn transaction -> transaction.id == batch.commitment_id end) + + Map.put(batch, :commitment_transaction, lifecycle_transaction) end) end end diff --git a/apps/indexer/lib/indexer/fetcher/arbitrum/workers/new_confirmations.ex b/apps/indexer/lib/indexer/fetcher/arbitrum/workers/new_confirmations.ex index 9631ccf..d91c625 100644 --- a/apps/indexer/lib/indexer/fetcher/arbitrum/workers/new_confirmations.ex +++ b/apps/indexer/lib/indexer/fetcher/arbitrum/workers/new_confirmations.ex @@ -492,7 +492,7 @@ defmodule Indexer.Fetcher.Arbitrum.Workers.NewConfirmations do l1_rpc_config.json_rpc_named_arguments ) - {retcode, {lifecycle_txs, rollup_blocks, confirmed_txs}} = + {retcode, {lifecycle_transactions, rollup_blocks, confirmed_transactions}} = handle_confirmations_from_logs( logs, l1_rpc_config, @@ -501,9 +501,9 @@ defmodule Indexer.Fetcher.Arbitrum.Workers.NewConfirmations do {:ok, _} = Chain.import(%{ - arbitrum_lifecycle_transactions: %{params: lifecycle_txs}, + arbitrum_lifecycle_transactions: %{params: lifecycle_transactions}, arbitrum_batch_blocks: %{params: rollup_blocks}, - arbitrum_messages: %{params: confirmed_txs}, + arbitrum_messages: %{params: confirmed_transactions}, timeout: :infinity }) @@ -529,9 +529,9 @@ defmodule Indexer.Fetcher.Arbitrum.Workers.NewConfirmations do # - `outbox_address`: The address of the Arbitrum outbox contract. # # ## Returns - # - `{retcode, {lifecycle_txs, rollup_blocks, confirmed_txs}}` where + # - `{retcode, {lifecycle_transactions, rollup_blocks, confirmed_transactions}}` where # - `retcode` is either `:ok` or `:confirmation_missed` - # - `lifecycle_txs` is a list of lifecycle transactions confirming blocks in the + # - `lifecycle_transactions` is a list of lifecycle transactions confirming blocks in the # rollup # - `rollup_blocks` is a list of rollup blocks associated with the corresponding # lifecycle transactions @@ -566,24 +566,24 @@ defmodule Indexer.Fetcher.Arbitrum.Workers.NewConfirmations do # On this step there could be lifecycle transactions for the rollup blocks which are # already confirmed. It is only possible in the scenario when the confirmation # discovery process does not wait for the safe L1 block. In this case: - # - rollup_blocks_to_l1_txs will not contain the corresponding block hash associated + # - rollup_blocks_to_l1_transactions will not contain the corresponding block hash associated # with the L1 transaction hash - # - lifecycle_txs_basic will contain all discovered lifecycle transactions + # - lifecycle_transactions_basic will contain all discovered lifecycle transactions # - blocks_requests will contain all requests to fetch block data for the lifecycle # transactions - # - existing_lifecycle_txs will contain lifecycle transactions which was found in the + # - existing_lifecycle_transactions will contain lifecycle transactions which was found in the # logs and already imported into the database. - {rollup_blocks_to_l1_txs, lifecycle_txs_basic, blocks_requests, existing_lifecycle_txs} = + {rollup_blocks_to_l1_transactions, lifecycle_transactions_basic, blocks_requests, existing_lifecycle_transactions} = parse_logs_for_new_confirmations(logs) # This step must be run only if there are hashes of the confirmed rollup blocks - # in rollup_blocks_to_l1_txs - when there are newly discovered confirmations. + # in rollup_blocks_to_l1_transactions - when there are newly discovered confirmations. rollup_blocks = - if Enum.empty?(rollup_blocks_to_l1_txs) do + if Enum.empty?(rollup_blocks_to_l1_transactions) do [] else discover_rollup_blocks( - rollup_blocks_to_l1_txs, + rollup_blocks_to_l1_transactions, %{ json_rpc_named_arguments: l1_rpc_config.json_rpc_named_arguments, logs_block_range: l1_rpc_config.logs_block_range, @@ -593,17 +593,19 @@ defmodule Indexer.Fetcher.Arbitrum.Workers.NewConfirmations do end # Will return %{} if there are no new confirmations - applicable_lifecycle_txs = take_lifecycle_txs_for_confirmed_blocks(rollup_blocks, lifecycle_txs_basic) + applicable_lifecycle_transactions = + take_lifecycle_transactions_for_confirmed_blocks(rollup_blocks, lifecycle_transactions_basic) # Will contain :ok if no new confirmations are found retcode = - if Enum.count(lifecycle_txs_basic) != Enum.count(applicable_lifecycle_txs) + length(existing_lifecycle_txs) do + if Enum.count(lifecycle_transactions_basic) != + Enum.count(applicable_lifecycle_transactions) + length(existing_lifecycle_transactions) do :confirmation_missed else :ok end - if Enum.empty?(applicable_lifecycle_txs) and existing_lifecycle_txs == [] do + if Enum.empty?(applicable_lifecycle_transactions) and existing_lifecycle_transactions == [] do # Only if both new confirmations and already existing confirmations are empty {retcode, {[], [], []}} else @@ -615,9 +617,9 @@ defmodule Indexer.Fetcher.Arbitrum.Workers.NewConfirmations do ) # The lifecycle transactions for the new confirmations are finalized here. - {lifecycle_txs_for_new_confirmations, rollup_blocks, highest_confirmed_block_number} = - finalize_lifecycle_txs_and_confirmed_blocks( - applicable_lifecycle_txs, + {lifecycle_transactions_for_new_confirmations, rollup_blocks, highest_confirmed_block_number} = + finalize_lifecycle_transactions_and_confirmed_blocks( + applicable_lifecycle_transactions, rollup_blocks, l1_blocks_to_ts, l1_rpc_config.track_finalization @@ -625,16 +627,20 @@ defmodule Indexer.Fetcher.Arbitrum.Workers.NewConfirmations do # The lifecycle transactions for the already existing confirmations are updated here # to ensure correct L1 block number and timestamp that could appear due to re-orgs. - lifecycle_txs = - lifecycle_txs_for_new_confirmations ++ - update_lifecycle_txs_for_new_blocks(existing_lifecycle_txs, lifecycle_txs_basic, l1_blocks_to_ts) + lifecycle_transactions = + lifecycle_transactions_for_new_confirmations ++ + update_lifecycle_transactions_for_new_blocks( + existing_lifecycle_transactions, + lifecycle_transactions_basic, + l1_blocks_to_ts + ) # Drawback of marking messages as confirmed during a new confirmation handling # is that the status change could become stuck if confirmations are not handled. # For example, due to DB inconsistency: some blocks/batches are missed. confirmed_messages = get_confirmed_l2_to_l1_messages(highest_confirmed_block_number) - {retcode, {lifecycle_txs, rollup_blocks, confirmed_messages}} + {retcode, {lifecycle_transactions, rollup_blocks, confirmed_messages}} end end @@ -660,7 +666,7 @@ defmodule Indexer.Fetcher.Arbitrum.Workers.NewConfirmations do # database. Each transaction is compatible with the database import operation. @spec parse_logs_for_new_confirmations([%{String.t() => any()}]) :: { - %{binary() => %{l1_tx_hash: binary(), l1_block_num: non_neg_integer()}}, + %{binary() => %{l1_transaction_hash: binary(), l1_block_num: non_neg_integer()}}, %{binary() => %{hash: binary(), block_number: non_neg_integer()}}, [EthereumJSONRPC.Transport.request()], [Arbitrum.LifecycleTransaction.to_import()] @@ -669,46 +675,46 @@ defmodule Indexer.Fetcher.Arbitrum.Workers.NewConfirmations do transaction_hashes = logs |> Enum.reduce(%{}, fn event, acc -> - l1_tx_hash_raw = event["transactionHash"] - Map.put_new(acc, l1_tx_hash_raw, Rpc.string_hash_to_bytes_hash(l1_tx_hash_raw)) + l1_transaction_hash_raw = event["transactionHash"] + Map.put_new(acc, l1_transaction_hash_raw, Rpc.string_hash_to_bytes_hash(l1_transaction_hash_raw)) end) - existing_lifecycle_txs = + existing_lifecycle_transactions = transaction_hashes |> Map.values() |> Db.lifecycle_transactions() - |> Enum.reduce(%{}, fn tx, acc -> - Map.put(acc, tx.hash, tx) + |> Enum.reduce(%{}, fn transaction, acc -> + Map.put(acc, transaction.hash, transaction) end) - {rollup_block_to_l1_txs, lifecycle_txs, blocks_requests} = + {rollup_block_to_l1_transactions, lifecycle_transactions, blocks_requests} = logs - |> Enum.reduce({%{}, %{}, %{}}, fn event, {block_to_txs, lifecycle_txs, blocks_requests} -> + |> Enum.reduce({%{}, %{}, %{}}, fn event, {block_to_transactions, lifecycle_transactions, blocks_requests} -> rollup_block_hash = send_root_updated_event_parse(event) - l1_tx_hash_raw = event["transactionHash"] - l1_tx_hash = transaction_hashes[l1_tx_hash_raw] + l1_transaction_hash_raw = event["transactionHash"] + l1_transaction_hash = transaction_hashes[l1_transaction_hash_raw] l1_blk_num = quantity_to_integer(event["blockNumber"]) # There is no need to include the found block hash for the consequent confirmed # blocks discovery step since it is assumed that already existing lifecycle # transactions are already linked with the corresponding rollup blocks. - updated_block_to_txs = - if Map.has_key?(existing_lifecycle_txs, l1_tx_hash) do - block_to_txs + updated_block_to_transactions = + if Map.has_key?(existing_lifecycle_transactions, l1_transaction_hash) do + block_to_transactions else Map.put( - block_to_txs, + block_to_transactions, rollup_block_hash, - %{l1_tx_hash: l1_tx_hash, l1_block_num: l1_blk_num} + %{l1_transaction_hash: l1_transaction_hash, l1_block_num: l1_blk_num} ) end - updated_lifecycle_txs = + updated_lifecycle_transactions = Map.put( - lifecycle_txs, - l1_tx_hash, - %{hash: l1_tx_hash, block_number: l1_blk_num} + lifecycle_transactions, + l1_transaction_hash, + %{hash: l1_transaction_hash, block_number: l1_blk_num} ) updated_blocks_requests = @@ -718,12 +724,13 @@ defmodule Indexer.Fetcher.Arbitrum.Workers.NewConfirmations do BlockByNumber.request(%{id: 0, number: l1_blk_num}, false, true) ) - log_info("New confirmation for the rollup block #{rollup_block_hash} found in #{l1_tx_hash_raw}") + log_info("New confirmation for the rollup block #{rollup_block_hash} found in #{l1_transaction_hash_raw}") - {updated_block_to_txs, updated_lifecycle_txs, updated_blocks_requests} + {updated_block_to_transactions, updated_lifecycle_transactions, updated_blocks_requests} end) - {rollup_block_to_l1_txs, lifecycle_txs, Map.values(blocks_requests), Map.values(existing_lifecycle_txs)} + {rollup_block_to_l1_transactions, lifecycle_transactions, Map.values(blocks_requests), + Map.values(existing_lifecycle_transactions)} end # Transforms rollup block hashes to numbers and associates them with their confirmation descriptions. @@ -737,14 +744,14 @@ defmodule Indexer.Fetcher.Arbitrum.Workers.NewConfirmations do # confirmations. # # ## Parameters - # - `rollup_blocks_to_l1_txs`: A map of rollup block hashes to confirmation descriptions. + # - `rollup_blocks_to_l1_transactions`: A map of rollup block hashes to confirmation descriptions. # - `outbox_config`: Configuration for the Arbitrum outbox contract. # # ## Returns # - A list of rollup blocks each associated with the transaction's hash that # confirms the block. @spec discover_rollup_blocks( - %{binary() => %{l1_tx_hash: binary(), l1_block_num: non_neg_integer()}}, + %{binary() => %{l1_transaction_hash: binary(), l1_block_num: non_neg_integer()}}, %{ :logs_block_range => non_neg_integer(), :outbox_address => binary(), @@ -752,9 +759,9 @@ defmodule Indexer.Fetcher.Arbitrum.Workers.NewConfirmations do optional(any()) => any() } ) :: [Arbitrum.BatchBlock.to_import()] - defp discover_rollup_blocks(rollup_blocks_to_l1_txs, outbox_config) do - block_to_l1_txs = - rollup_blocks_to_l1_txs + defp discover_rollup_blocks(rollup_blocks_to_l1_transactions, outbox_config) do + block_to_l1_transactions = + rollup_blocks_to_l1_transactions |> Map.keys() |> Enum.reduce(%{}, fn block_hash, transformed -> rollup_block_num = Db.rollup_block_hash_to_num(block_hash) @@ -767,24 +774,24 @@ defmodule Indexer.Fetcher.Arbitrum.Workers.NewConfirmations do transformed value -> - Map.put(transformed, value, rollup_blocks_to_l1_txs[block_hash]) + Map.put(transformed, value, rollup_blocks_to_l1_transactions[block_hash]) end end) - if Enum.empty?(block_to_l1_txs) do + if Enum.empty?(block_to_l1_transactions) do [] else # Oldest (with the lowest number) block is first - rollup_block_numbers = Enum.sort(Map.keys(block_to_l1_txs), :asc) + rollup_block_numbers = Enum.sort(Map.keys(block_to_l1_transactions), :asc) rollup_block_numbers - |> Enum.reduce([], fn block_num, updated_rollup_blocks -> - log_info("Attempting to mark all rollup blocks including ##{block_num} and lower as confirmed") + |> Enum.reduce([], fn block_number, updated_rollup_blocks -> + log_info("Attempting to mark all rollup blocks including ##{block_number} and lower as confirmed") {_, confirmed_blocks} = discover_rollup_blocks_belonging_to_one_confirmation( - block_num, - block_to_l1_txs[block_num], + block_number, + block_to_l1_transactions[block_number], outbox_config ) @@ -792,7 +799,7 @@ defmodule Indexer.Fetcher.Arbitrum.Workers.NewConfirmations do if length(confirmed_blocks) > 0 do log_info("Found #{length(confirmed_blocks)} confirmed blocks") - add_confirmation_transaction(confirmed_blocks, block_to_l1_txs[block_num].l1_tx_hash) ++ + add_confirmation_transaction(confirmed_blocks, block_to_l1_transactions[block_number].l1_transaction_hash) ++ updated_rollup_blocks else log_info("Either no unconfirmed blocks found or DB inconsistency error discovered") @@ -1413,28 +1420,32 @@ defmodule Indexer.Fetcher.Arbitrum.Workers.NewConfirmations do # Adds the confirmation transaction hash to each rollup block description in the list. @spec add_confirmation_transaction([Arbitrum.BatchBlock.to_import()], binary()) :: [Arbitrum.BatchBlock.to_import()] - defp add_confirmation_transaction(block_descriptions_list, confirm_tx_hash) do + defp add_confirmation_transaction(block_descriptions_list, confirm_transaction_hash) do block_descriptions_list |> Enum.reduce([], fn block_descr, updated -> new_block_descr = block_descr - |> Map.put(:confirmation_transaction, confirm_tx_hash) + |> Map.put(:confirmation_transaction, confirm_transaction_hash) [new_block_descr | updated] end) end # Selects lifecycle transaction descriptions used for confirming a given list of rollup blocks. - @spec take_lifecycle_txs_for_confirmed_blocks( + @spec take_lifecycle_transactions_for_confirmed_blocks( [Arbitrum.BatchBlock.to_import()], %{binary() => %{hash: binary(), block_number: non_neg_integer()}} ) :: %{binary() => %{hash: binary(), block_number: non_neg_integer()}} - defp take_lifecycle_txs_for_confirmed_blocks(confirmed_rollup_blocks, lifecycle_txs) do + defp take_lifecycle_transactions_for_confirmed_blocks(confirmed_rollup_blocks, lifecycle_transactions) do confirmed_rollup_blocks - |> Enum.reduce(%{}, fn block_descr, updated_txs -> - confirmation_tx_hash = block_descr.confirmation_transaction + |> Enum.reduce(%{}, fn block_descr, updated_transactions -> + confirmation_transaction_hash = block_descr.confirmation_transaction - Map.put_new(updated_txs, confirmation_tx_hash, lifecycle_txs[confirmation_tx_hash]) + Map.put_new( + updated_transactions, + confirmation_transaction_hash, + lifecycle_transactions[confirmation_transaction_hash] + ) end) end @@ -1448,7 +1459,7 @@ defmodule Indexer.Fetcher.Arbitrum.Workers.NewConfirmations do # for import. # # ## Parameters - # - `basic_lifecycle_txs`: The initial list of partially filled lifecycle transaction + # - `basic_lifecycle_transactions`: The initial list of partially filled lifecycle transaction # descriptions. # - `confirmed_rollup_blocks`: Rollup blocks to be considered as confirmed. # - `l1_blocks_requests`: RPC requests of `eth_getBlockByNumber` to fetch L1 block data @@ -1461,7 +1472,7 @@ defmodule Indexer.Fetcher.Arbitrum.Workers.NewConfirmations do # - The map of lifecycle transactions where each transaction is ready for import. # - The list of confirmed rollup blocks, ready for import. # - The highest confirmed block number processed during this run. - @spec finalize_lifecycle_txs_and_confirmed_blocks( + @spec finalize_lifecycle_transactions_and_confirmed_blocks( %{binary() => %{hash: binary(), block_number: non_neg_integer()}}, [Arbitrum.BatchBlock.to_import()], %{required(EthereumJSONRPC.block_number()) => DateTime.t()}, @@ -1471,27 +1482,27 @@ defmodule Indexer.Fetcher.Arbitrum.Workers.NewConfirmations do [Arbitrum.BatchBlock.to_import()], integer() } - defp finalize_lifecycle_txs_and_confirmed_blocks( - basic_lifecycle_txs, + defp finalize_lifecycle_transactions_and_confirmed_blocks( + basic_lifecycle_transactions, confirmed_rollup_blocks, l1_blocks_to_ts, track_finalization? ) - defp finalize_lifecycle_txs_and_confirmed_blocks(basic_lifecycle_txs, _, _, _) - when map_size(basic_lifecycle_txs) == 0 do + defp finalize_lifecycle_transactions_and_confirmed_blocks(basic_lifecycle_transactions, _, _, _) + when map_size(basic_lifecycle_transactions) == 0 do {[], [], -1} end - defp finalize_lifecycle_txs_and_confirmed_blocks( - basic_lifecycle_txs, + defp finalize_lifecycle_transactions_and_confirmed_blocks( + basic_lifecycle_transactions, confirmed_rollup_blocks, l1_blocks_to_ts, track_finalization? ) do - lifecycle_txs = - basic_lifecycle_txs - |> ArbitrumHelper.extend_lifecycle_txs_with_ts_and_status(l1_blocks_to_ts, track_finalization?) + lifecycle_transactions = + basic_lifecycle_transactions + |> ArbitrumHelper.extend_lifecycle_transactions_with_ts_and_status(l1_blocks_to_ts, track_finalization?) |> Db.get_indices_for_l1_transactions() {updated_rollup_blocks, highest_confirmed_block_number} = @@ -1501,41 +1512,45 @@ defmodule Indexer.Fetcher.Arbitrum.Workers.NewConfirmations do updated_block = block - |> Map.put(:confirmation_id, lifecycle_txs[block.confirmation_transaction].id) + |> Map.put(:confirmation_id, lifecycle_transactions[block.confirmation_transaction].id) |> Map.drop([:confirmation_transaction]) {[updated_block | updated_list], chosen_highest_confirmed} end) - {Map.values(lifecycle_txs), updated_rollup_blocks, highest_confirmed_block_number} + {Map.values(lifecycle_transactions), updated_rollup_blocks, highest_confirmed_block_number} end # Updates lifecycle transactions with new L1 block numbers and timestamps which could appear due to re-orgs. # # ## Parameters - # - `existing_commitment_txs`: A list of existing confirmation transactions to be checked and updated. - # - `tx_to_l1_block`: A map from transaction hashes to their corresponding new L1 block numbers. + # - `existing_commitment_transactions`: A list of existing confirmation transactions to be checked and updated. + # - `transaction_to_l1_block`: A map from transaction hashes to their corresponding new L1 block numbers. # - `l1_block_to_ts`: A map from L1 block numbers to their corresponding new timestamps. # # ## Returns # - A list of updated confirmation transactions with new block numbers and timestamps. - @spec update_lifecycle_txs_for_new_blocks( + @spec update_lifecycle_transactions_for_new_blocks( [Arbitrum.LifecycleTransaction.to_import()], %{binary() => non_neg_integer()}, %{non_neg_integer() => DateTime.t()} ) :: [Arbitrum.LifecycleTransaction.to_import()] - defp update_lifecycle_txs_for_new_blocks(existing_commitment_txs, tx_to_l1_block, l1_block_to_ts) do - existing_commitment_txs - |> Enum.reduce([], fn tx, updated_txs -> - new_block_num = tx_to_l1_block[tx.hash].block_number + defp update_lifecycle_transactions_for_new_blocks( + existing_commitment_transactions, + transaction_to_l1_block, + l1_block_to_ts + ) do + existing_commitment_transactions + |> Enum.reduce([], fn transaction, updated_transactions -> + new_block_num = transaction_to_l1_block[transaction.hash].block_number new_ts = l1_block_to_ts[new_block_num] - case ArbitrumHelper.compare_lifecycle_tx_and_update(tx, {new_block_num, new_ts}, "confirmation") do - {:updated, updated_tx} -> - [updated_tx | updated_txs] + case ArbitrumHelper.compare_lifecycle_transaction_and_update(transaction, {new_block_num, new_ts}, "confirmation") do + {:updated, updated_transaction} -> + [updated_transaction | updated_transactions] _ -> - updated_txs + updated_transactions end end) end @@ -1551,8 +1566,8 @@ defmodule Indexer.Fetcher.Arbitrum.Workers.NewConfirmations do defp get_confirmed_l2_to_l1_messages(block_number) do block_number |> Db.sent_l2_to_l1_messages() - |> Enum.map(fn tx -> - Map.put(tx, :status, :confirmed) + |> Enum.map(fn transaction -> + Map.put(transaction, :status, :confirmed) end) end end diff --git a/apps/indexer/lib/indexer/fetcher/arbitrum/workers/new_l1_executions.ex b/apps/indexer/lib/indexer/fetcher/arbitrum/workers/new_l1_executions.ex index 7aedf32..ba572c0 100644 --- a/apps/indexer/lib/indexer/fetcher/arbitrum/workers/new_l1_executions.ex +++ b/apps/indexer/lib/indexer/fetcher/arbitrum/workers/new_l1_executions.ex @@ -199,14 +199,14 @@ defmodule Indexer.Fetcher.Arbitrum.Workers.NewL1Executions do l1_rpc_config.json_rpc_named_arguments ) - {lifecycle_txs, executions} = get_executions_from_logs(logs, l1_rpc_config) + {lifecycle_transactions, executions} = get_executions_from_logs(logs, l1_rpc_config) unless executions == [] do log_info("Executions for #{length(executions)} L2 messages will be imported") {:ok, _} = Chain.import(%{ - arbitrum_lifecycle_transactions: %{params: lifecycle_txs}, + arbitrum_lifecycle_transactions: %{params: lifecycle_transactions}, arbitrum_l1_executions: %{params: executions}, timeout: :infinity }) @@ -291,13 +291,13 @@ defmodule Indexer.Fetcher.Arbitrum.Workers.NewL1Executions do track_finalization: track_finalization? } = _l1_rpc_config ) do - {basics_executions, basic_lifecycle_txs, blocks_requests} = parse_logs_for_new_executions(logs) + {basics_executions, basic_lifecycle_transactions, blocks_requests} = parse_logs_for_new_executions(logs) blocks_to_ts = Rpc.execute_blocks_requests_and_get_ts(blocks_requests, json_rpc_named_arguments, chunk_size) - lifecycle_txs = - basic_lifecycle_txs - |> ArbitrumHelper.extend_lifecycle_txs_with_ts_and_status(blocks_to_ts, track_finalization?) + lifecycle_transactions = + basic_lifecycle_transactions + |> ArbitrumHelper.extend_lifecycle_transactions_with_ts_and_status(blocks_to_ts, track_finalization?) |> Db.get_indices_for_l1_transactions() executions = @@ -305,13 +305,13 @@ defmodule Indexer.Fetcher.Arbitrum.Workers.NewL1Executions do |> Enum.reduce([], fn execution, updated_executions -> updated = execution - |> Map.put(:execution_id, lifecycle_txs[execution.execution_tx_hash].id) - |> Map.drop([:execution_tx_hash]) + |> Map.put(:execution_id, lifecycle_transactions[execution.execution_transaction_hash].id) + |> Map.drop([:execution_transaction_hash]) [updated | updated_executions] end) - {Map.values(lifecycle_txs), executions} + {Map.values(lifecycle_transactions), executions} end # Parses logs to extract new execution transactions for L2-to-L1 messages. @@ -330,33 +330,33 @@ defmodule Indexer.Fetcher.Arbitrum.Workers.NewL1Executions do # - A tuple containing: # - `executions`: A list of details for execution transactions related to # L2-to-L1 messages. - # - `lifecycle_txs`: A map of lifecycle transaction details, keyed by L1 + # - `lifecycle_transactions`: A map of lifecycle transaction details, keyed by L1 # transaction hash. # - `blocks_requests`: A list of RPC requests for fetching block data where # the executions occurred. defp parse_logs_for_new_executions(logs) do - {executions, lifecycle_txs, blocks_requests} = + {executions, lifecycle_transactions, blocks_requests} = logs - |> Enum.reduce({[], %{}, %{}}, fn event, {executions, lifecycle_txs, blocks_requests} -> + |> Enum.reduce({[], %{}, %{}}, fn event, {executions, lifecycle_transactions, blocks_requests} -> msg_id = outbox_transaction_executed_event_parse(event) - l1_tx_hash_raw = event["transactionHash"] - l1_tx_hash = Rpc.string_hash_to_bytes_hash(l1_tx_hash_raw) + l1_transaction_hash_raw = event["transactionHash"] + l1_transaction_hash = Rpc.string_hash_to_bytes_hash(l1_transaction_hash_raw) l1_blk_num = quantity_to_integer(event["blockNumber"]) updated_executions = [ %{ message_id: msg_id, - execution_tx_hash: l1_tx_hash + execution_transaction_hash: l1_transaction_hash } | executions ] - updated_lifecycle_txs = + updated_lifecycle_transactions = Map.put( - lifecycle_txs, - l1_tx_hash, - %{hash: l1_tx_hash, block_number: l1_blk_num} + lifecycle_transactions, + l1_transaction_hash, + %{hash: l1_transaction_hash, block_number: l1_blk_num} ) updated_blocks_requests = @@ -366,12 +366,12 @@ defmodule Indexer.Fetcher.Arbitrum.Workers.NewL1Executions do BlockByNumber.request(%{id: 0, number: l1_blk_num}, false, true) ) - log_debug("Execution for L2 message ##{msg_id} found in #{l1_tx_hash_raw}") + log_debug("Execution for L2 message ##{msg_id} found in #{l1_transaction_hash_raw}") - {updated_executions, updated_lifecycle_txs, updated_blocks_requests} + {updated_executions, updated_lifecycle_transactions, updated_blocks_requests} end) - {executions, lifecycle_txs, Map.values(blocks_requests)} + {executions, lifecycle_transactions, Map.values(blocks_requests)} end # Parses `OutBoxTransactionExecuted` event data to extract the transaction index parameter diff --git a/apps/indexer/lib/indexer/fetcher/arbitrum/workers/new_messages_to_l2.ex b/apps/indexer/lib/indexer/fetcher/arbitrum/workers/new_messages_to_l2.ex index ab03073..ad586ab 100644 --- a/apps/indexer/lib/indexer/fetcher/arbitrum/workers/new_messages_to_l2.ex +++ b/apps/indexer/lib/indexer/fetcher/arbitrum/workers/new_messages_to_l2.ex @@ -281,13 +281,14 @@ defmodule Indexer.Fetcher.Arbitrum.Workers.NewMessagesToL2 do defp get_messages_from_logs([], _, _), do: [] defp get_messages_from_logs(logs, json_rpc_named_arguments, chunk_size) do - {messages, txs_requests} = parse_logs_for_l1_to_l2_messages(logs) + {messages, transactions_requests} = parse_logs_for_l1_to_l2_messages(logs) - txs_to_from = Rpc.execute_transactions_requests_and_get_from(txs_requests, json_rpc_named_arguments, chunk_size) + transactions_to_from = + Rpc.execute_transactions_requests_and_get_from(transactions_requests, json_rpc_named_arguments, chunk_size) Enum.map(messages, fn msg -> Map.merge(msg, %{ - originator_address: txs_to_from[msg.originating_transaction_hash], + originator_address: transactions_to_from[msg.originating_transaction_hash], status: :initiated }) end) @@ -307,45 +308,45 @@ defmodule Indexer.Fetcher.Arbitrum.Workers.NewMessagesToL2 do # ## Returns # - A tuple comprising: # - `messages`: A list of maps, each containing an incomplete representation of a message. - # - `txs_requests`: A list of RPC request `eth_getTransactionByHash` structured to fetch + # - `transactions_requests`: A list of RPC request `eth_getTransactionByHash` structured to fetch # additional data needed to finalize the message descriptions. defp parse_logs_for_l1_to_l2_messages(logs) do - {messages, txs_requests} = + {messages, transactions_requests} = logs - |> Enum.reduce({[], %{}}, fn event, {messages, txs_requests} -> + |> Enum.reduce({[], %{}}, fn event, {messages, transactions_requests} -> {msg_id, type, ts} = message_delivered_event_parse(event) if type in @types_of_l1_messages_forwarded_to_l2 do - tx_hash = event["transactionHash"] + transaction_hash = event["transactionHash"] blk_num = quantity_to_integer(event["blockNumber"]) updated_messages = [ %{ direction: :to_l2, message_id: msg_id, - originating_transaction_hash: tx_hash, + originating_transaction_hash: transaction_hash, origination_timestamp: ts, originating_transaction_block_number: blk_num } | messages ] - updated_txs_requests = + updated_transactions_requests = Map.put( - txs_requests, - tx_hash, - Rpc.transaction_by_hash_request(%{id: 0, hash: tx_hash}) + transactions_requests, + transaction_hash, + Rpc.transaction_by_hash_request(%{id: 0, hash: transaction_hash}) ) - log_debug("L1 to L2 message #{tx_hash} found with the type #{type}") + log_debug("L1 to L2 message #{transaction_hash} found with the type #{type}") - {updated_messages, updated_txs_requests} + {updated_messages, updated_transactions_requests} else - {messages, txs_requests} + {messages, transactions_requests} end end) - {messages, Map.values(txs_requests)} + {messages, Map.values(transactions_requests)} end # Parses the `MessageDelivered` event to extract relevant message details. diff --git a/apps/indexer/lib/indexer/fetcher/blackfort/validator.ex b/apps/indexer/lib/indexer/fetcher/blackfort/validator.ex new file mode 100644 index 0000000..75cbc79 --- /dev/null +++ b/apps/indexer/lib/indexer/fetcher/blackfort/validator.ex @@ -0,0 +1,49 @@ +defmodule Indexer.Fetcher.Blackfort.Validator do + @moduledoc """ + GenServer responsible for updating the list of blackfort validators in the database. + """ + use GenServer + + alias Explorer.Chain.Blackfort.Validator + + def start_link(_) do + GenServer.start_link(__MODULE__, [], name: __MODULE__) + end + + @impl true + def init(state) do + GenServer.cast(__MODULE__, :update_validators_list) + + {:ok, state} + end + + @impl true + def handle_cast(:update_validators_list, state) do + case Validator.fetch_validators_list() do + {:ok, validators} -> + validators_from_db = Validator.get_all_validators() + + validators_map = + Enum.reduce(validators, %{}, fn %{address_hash: address_hash}, map -> + Map.put(map, address_hash.bytes, true) + end) + + address_hashes_to_drop_from_db = + Enum.flat_map(validators_from_db, fn validator -> + (is_nil(validators_map[validator.address_hash.bytes]) && + [validator.address_hash]) || [] + end) + + Validator.delete_validators_by_address_hashes(address_hashes_to_drop_from_db) + + validators + |> Enum.map(&Validator.append_timestamps/1) + |> Validator.insert_validators() + + _ -> + nil + end + + {:noreply, state} + end +end diff --git a/apps/indexer/lib/indexer/fetcher/celo/epoch_block_operations.ex b/apps/indexer/lib/indexer/fetcher/celo/epoch_block_operations.ex index e023ac2..6d110f4 100644 --- a/apps/indexer/lib/indexer/fetcher/celo/epoch_block_operations.ex +++ b/apps/indexer/lib/indexer/fetcher/celo/epoch_block_operations.ex @@ -35,7 +35,7 @@ defmodule Indexer.Fetcher.Celo.EpochBlockOperations do unless state do raise ArgumentError, - ":json_rpc_named_arguments must be provided to `#{__MODULE__}.child_spec " <> + ":json_rpc_named_arguments must be provided to `#{__MODULE__}.child_spec` " <> "to allow for json_rpc calls when running." end diff --git a/apps/indexer/lib/indexer/fetcher/celo/validator_group_votes.ex b/apps/indexer/lib/indexer/fetcher/celo/validator_group_votes.ex index f51e736..0369dfb 100644 --- a/apps/indexer/lib/indexer/fetcher/celo/validator_group_votes.ex +++ b/apps/indexer/lib/indexer/fetcher/celo/validator_group_votes.ex @@ -133,7 +133,7 @@ defmodule Indexer.Fetcher.Celo.ValidatorGroupVotes do end end - defp process_chunk(_..chunk_to_block = chunk, block_range, json_rpc_named_arguments) do + defp process_chunk(_..chunk_to_block//_ = chunk, block_range, json_rpc_named_arguments) do validator_group_votes = chunk |> fetch_logs_chunk(block_range, json_rpc_named_arguments) @@ -156,8 +156,8 @@ defmodule Indexer.Fetcher.Celo.ValidatorGroupVotes do end defp fetch_logs_chunk( - chunk_from_block..chunk_to_block, - from_block..to_block, + chunk_from_block..chunk_to_block//_, + from_block..to_block//_, json_rpc_named_arguments ) do IndexerHelper.log_blocks_chunk_handling(chunk_from_block, chunk_to_block, from_block, to_block, nil, :L1) diff --git a/apps/indexer/lib/indexer/fetcher/coin_balance/helper.ex b/apps/indexer/lib/indexer/fetcher/coin_balance/helper.ex index e4da98e..759bd6e 100644 --- a/apps/indexer/lib/indexer/fetcher/coin_balance/helper.ex +++ b/apps/indexer/lib/indexer/fetcher/coin_balance/helper.ex @@ -9,7 +9,7 @@ defmodule Indexer.Fetcher.CoinBalance.Helper do alias EthereumJSONRPC.{Blocks, FetchedBalances, Utility.RangesHelper} alias Explorer.Chain - alias Explorer.Chain.Cache.Accounts + alias Explorer.Chain.Cache.{Accounts, BlockNumber} alias Explorer.Chain.Hash alias Indexer.BufferedTask @@ -55,7 +55,7 @@ defmodule Indexer.Fetcher.CoinBalance.Helper do unique_filtered_entries |> Enum.map(&entry_to_params/1) - |> EthereumJSONRPC.fetch_balances(json_rpc_named_arguments) + |> EthereumJSONRPC.fetch_balances(json_rpc_named_arguments, BlockNumber.get_max()) |> case do {:ok, fetched_balances} -> run_fetched_balances(fetched_balances, fetcher_type) diff --git a/apps/indexer/lib/indexer/fetcher/contract_code.ex b/apps/indexer/lib/indexer/fetcher/contract_code.ex index bd2809d..49778f3 100644 --- a/apps/indexer/lib/indexer/fetcher/contract_code.ex +++ b/apps/indexer/lib/indexer/fetcher/contract_code.ex @@ -12,7 +12,7 @@ defmodule Indexer.Fetcher.ContractCode do alias Explorer.Chain alias Explorer.Chain.{Block, Hash} - alias Explorer.Chain.Cache.Accounts + alias Explorer.Chain.Cache.{Accounts, BlockNumber} alias Indexer.{BufferedTask, Tracer} alias Indexer.Fetcher.CoinBalance.Helper, as: CoinBalanceHelper alias Indexer.Transform.Addresses @@ -120,7 +120,7 @@ defmodule Indexer.Fetcher.ContractCode do defp import_with_balances(addresses_params, entries, json_rpc_named_arguments) do entries |> coin_balances_request_params() - |> EthereumJSONRPC.fetch_balances(json_rpc_named_arguments) + |> EthereumJSONRPC.fetch_balances(json_rpc_named_arguments, BlockNumber.get_max()) |> case do {:ok, fetched_balances} -> balance_addresses_params = CoinBalanceHelper.balances_params_to_address_params(fetched_balances.params_list) diff --git a/apps/indexer/lib/indexer/fetcher/filecoin/address_info.ex b/apps/indexer/lib/indexer/fetcher/filecoin/address_info.ex new file mode 100644 index 0000000..1d7a9d6 --- /dev/null +++ b/apps/indexer/lib/indexer/fetcher/filecoin/address_info.ex @@ -0,0 +1,221 @@ +defmodule Indexer.Fetcher.Filecoin.AddressInfo do + @moduledoc """ + A task for fetching Filecoin addresses info in the Address table using the + Beryx API. + + Due to the lack of batch support in the API, addresses are fetched + individually, making this fetching an expensive operation. + """ + use Indexer.Fetcher, restart: :permanent + use Spandex.Decorators + + alias Ecto.Multi + alias Explorer.Chain.{Address, Filecoin.PendingAddressOperation} + alias Explorer.Repo + alias Indexer.Fetcher.Filecoin.AddressInfo.Supervisor, as: FilecoinAddressInfoSupervisor + alias Indexer.Fetcher.Filecoin.BeryxAPI + alias Indexer.{BufferedTask, Tracer} + + @http_error_codes 400..526 + + @batch_size 1 + + @behaviour BufferedTask + + require Logger + + @doc """ + Asynchronously fetches filecoin addresses info + """ + @spec async_fetch([PendingAddressOperation.t()], boolean(), integer()) :: :ok + def async_fetch(pending_operations, realtime?, timeout \\ 5000) + when is_list(pending_operations) do + if FilecoinAddressInfoSupervisor.disabled?() do + :ok + else + unique_operations = + Enum.uniq_by( + pending_operations, + &to_string(&1.address_hash) + ) + + BufferedTask.buffer(__MODULE__, unique_operations, realtime?, timeout) + end + end + + @doc false + @spec child_spec([...]) :: Supervisor.child_spec() + def child_spec([init_options, gen_server_options]) do + merged_init_opts = + defaults() + |> Keyword.merge(init_options) + |> Keyword.put(:state, nil) + + Supervisor.child_spec( + {BufferedTask, [{__MODULE__, merged_init_opts}, gen_server_options]}, + id: __MODULE__ + ) + end + + @doc false + @impl BufferedTask + def init(initial, reducer, _) do + {:ok, final} = + PendingAddressOperation.stream( + initial, + fn op, acc -> reducer.(op, acc) end + ) + + final + end + + @doc false + @spec defaults() :: Keyword.t() + def defaults do + env = Application.get_env(:indexer, __MODULE__) + + [ + poll: false, + flush_interval: :timer.seconds(30), + max_concurrency: env[:concurrency], + max_batch_size: @batch_size, + task_supervisor: __MODULE__.TaskSupervisor, + metadata: [fetcher: :filecoin_address_info] + ] + end + + @doc """ + Fetches the Filecoin address info for the given pending operation. + """ + @impl BufferedTask + @decorate trace( + name: "fetch", + resource: "Indexer.Fetcher.InternalTransaction.run/2", + service: :indexer, + tracer: Tracer + ) + @spec run([Explorer.Chain.Filecoin.PendingAddressOperation.t(), ...], any()) :: :ok | :retry + def run([pending_operation], _state) do + fetch_and_update(pending_operation) + end + + @spec fetch_and_update(PendingAddressOperation.t()) :: :ok | :retry + defp fetch_and_update(%PendingAddressOperation{address_hash: address_hash} = operation) do + with {:ok, new_params} <- fetch_address_info_using_beryx_api(operation), + {:ok, _} <- update_address_and_remove_pending_operation(operation, new_params) do + Logger.debug("Fetched Filecoin address info for: #{to_string(address_hash)}") + :ok + else + _ -> + Logger.error("Could not fetch Filecoin address info: #{to_string(address_hash)}") + # TODO: We should consider implementing retry logic when fetching + # becomes more stable + :ok + end + end + + @spec update_address_and_remove_pending_operation( + PendingAddressOperation.t(), + %{ + filecoin_id: String.t(), + filecoin_robust: String.t(), + filecoin_actor_type: String.t() + } + ) :: + {:ok, PendingAddressOperation.t()} + | {:error, Ecto.Changeset.t()} + | Ecto.Multi.failure() + defp update_address_and_remove_pending_operation( + %PendingAddressOperation{} = operation, + new_address_params + ) do + Multi.new() + |> Multi.run( + :acquire_address, + fn repo, _ -> + case repo.get_by( + Address, + [hash: operation.address_hash], + lock: "FOR UPDATE" + ) do + nil -> {:error, :not_found} + address -> {:ok, address} + end + end + ) + |> Multi.run( + :acquire_pending_address_operation, + fn repo, _ -> + case repo.get_by( + PendingAddressOperation, + [address_hash: operation.address_hash], + lock: "FOR UPDATE" + ) do + nil -> {:error, :not_found} + pending_operation -> {:ok, pending_operation} + end + end + ) + |> Multi.run( + :update_address, + fn repo, %{acquire_address: address} -> + address + |> Address.changeset(new_address_params) + |> repo.update() + end + ) + |> Multi.run( + :delete_pending_operation, + fn repo, %{acquire_pending_address_operation: operation} -> + repo.delete(operation) + end + ) + |> Repo.transaction() + |> tap(fn + {:ok, _} -> :ok + error -> Logger.error("Error updating address and removing pending operation: #{inspect(error)}") + end) + end + + @spec fetch_address_info_using_beryx_api(PendingAddressOperation.t()) :: + {:ok, + %{ + filecoin_id: String.t(), + filecoin_robust: String.t(), + filecoin_actor_type: String.t() + }} + | :error + defp fetch_address_info_using_beryx_api(%PendingAddressOperation{} = operation) do + with {:ok, body_json} <- operation.address_hash |> to_string() |> BeryxAPI.fetch_account_info(), + {:ok, id_address_string} <- Map.fetch(body_json, "short"), + {:ok, robust_address_string} <- Map.fetch(body_json, "robust"), + {:ok, actor_type_string} <- Map.fetch(body_json, "actor_type") do + {:ok, + %{ + filecoin_id: id_address_string, + filecoin_robust: robust_address_string, + filecoin_actor_type: actor_type_string + }} + else + {:error, status_code, %{"error" => reason}} when status_code in @http_error_codes -> + Logger.error("Beryx API returned error code #{status_code} with reason: #{reason}") + + operation + |> PendingAddressOperation.changeset(%{http_status_code: status_code}) + |> Repo.update() + |> case do + {:ok, _} -> + Logger.info("Updated pending operation with error status code") + + {:error, changeset} -> + Logger.error("Could not update pending operation with error status code: #{inspect(changeset)}") + end + + :error + + error -> + Logger.error("Error processing Beryx API response: #{inspect(error)}") + :error + end + end +end diff --git a/apps/indexer/lib/indexer/fetcher/filecoin/beryx_api.ex b/apps/indexer/lib/indexer/fetcher/filecoin/beryx_api.ex new file mode 100644 index 0000000..494880c --- /dev/null +++ b/apps/indexer/lib/indexer/fetcher/filecoin/beryx_api.ex @@ -0,0 +1,50 @@ +defmodule Indexer.Fetcher.Filecoin.BeryxAPI do + @moduledoc """ + Interacts with the Beryx API to fetch account information based on an Ethereum + address hash + """ + + alias Explorer.Helper + alias HTTPoison.Response + + @doc """ + Fetches account information for a given Ethereum address hash from the Beryx API. + + ## Parameters + - `eth_address_hash` - The Ethereum address hash to fetch information for. + + ## Returns + - `{:ok, map()}`: On success, returns the account information as a map. + - `{:error, integer(), map()}`: On failure, returns the HTTP status code and the error message as a map. + - `{:error, HTTPoison.Error.t()}`: On network or other HTTP errors, returns the error structure. + """ + @spec fetch_account_info(EthereumJSONRPC.address()) :: + {:ok, map()} + | {:error, integer(), map()} + | {:error, HTTPoison.Error.t()} + def fetch_account_info(eth_address_hash) do + config = Application.get_env(:indexer, __MODULE__) + base_url = config |> Keyword.get(:base_url) |> String.trim_trailing("/") + api_token = config[:api_token] + + url = "#{base_url}/account/info/#{eth_address_hash}" + + headers = [ + {"Authorization", "Bearer #{api_token}"}, + {"Content-Type", "application/json"} + ] + + case HTTPoison.get(url, headers) do + {:ok, %Response{body: body, status_code: 200}} -> + json = Helper.decode_json(body) + {:ok, json} + + {:ok, %Response{body: body, status_code: status_code}} -> + json = Helper.decode_json(body) + {:error, status_code, json} + + {:error, %HTTPoison.Error{}} = error -> + error + end + end +end diff --git a/apps/indexer/lib/indexer/fetcher/internal_transaction.ex b/apps/indexer/lib/indexer/fetcher/internal_transaction.ex index 91d2341..56d9090 100644 --- a/apps/indexer/lib/indexer/fetcher/internal_transaction.ex +++ b/apps/indexer/lib/indexer/fetcher/internal_transaction.ex @@ -24,7 +24,7 @@ defmodule Indexer.Fetcher.InternalTransaction do alias Indexer.{BufferedTask, Tracer} alias Indexer.Fetcher.InternalTransaction.Supervisor, as: InternalTransactionSupervisor alias Indexer.Transform.Celo.TransactionTokenTransfers, as: CeloTransactionTokenTransfers - alias Indexer.Transform.{Addresses, AddressTokenBalances} + alias Indexer.Transform.{AddressCoinBalances, Addresses, AddressTokenBalances} @behaviour BufferedTask @@ -76,13 +76,23 @@ 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, + stream_reducer = + if RangesHelper.trace_ranges_present?() do + trace_block_ranges = RangesHelper.get_trace_block_ranges() + + fn block_number, acc -> + # credo:disable-for-next-line Credo.Check.Refactor.Nesting + if RangesHelper.number_in_ranges?(block_number, trace_block_ranges), + do: reducer.(block_number, acc), + else: acc + end + else fn block_number, acc -> reducer.(block_number, acc) end - ) + end + + {:ok, final} = Chain.stream_blocks_with_unfetched_internal_transactions(initial, stream_reducer) final end @@ -211,7 +221,7 @@ defmodule Indexer.Fetcher.InternalTransaction do Logger.error( fn -> [ - "failed to import first trace for tx: ", + "failed to import first trace for transaction: ", inspect(reason) ] end, @@ -278,6 +288,9 @@ defmodule Indexer.Fetcher.InternalTransaction do {String.downcase(hash), block_number} end) + address_coin_balances_params_set = + AddressCoinBalances.params_set(%{internal_transactions_params: internal_transactions_params_marked}) + empty_block_numbers = unique_numbers |> MapSet.new() @@ -310,6 +323,7 @@ defmodule Indexer.Fetcher.InternalTransaction do token_transfers: %{params: celo_token_transfers}, tokens: %{params: celo_tokens}, addresses: %{params: addresses_params}, + address_coin_balances: %{params: address_coin_balances_params_set}, internal_transactions: %{params: internal_transactions_and_empty_block_numbers, with: :blockless_changeset}, timeout: :infinity }) diff --git a/apps/indexer/lib/indexer/fetcher/on_demand/coin_balance.ex b/apps/indexer/lib/indexer/fetcher/on_demand/coin_balance.ex index 626520b..2170ee4 100644 --- a/apps/indexer/lib/indexer/fetcher/on_demand/coin_balance.ex +++ b/apps/indexer/lib/indexer/fetcher/on_demand/coin_balance.ex @@ -205,7 +205,7 @@ defmodule Indexer.Fetcher.OnDemand.CoinBalance do defp fetch_balances(block_number, address, json_rpc_named_arguments) do params = %{block_quantity: integer_to_quantity(block_number), hash_data: to_string(address.hash)} - EthereumJSONRPC.fetch_balances([params], json_rpc_named_arguments) + EthereumJSONRPC.fetch_balances([params], json_rpc_named_arguments, latest_block_number()) end defp do_import(%FetchedBalances{} = fetched_balances) do diff --git a/apps/indexer/lib/indexer/fetcher/on_demand/contract_code.ex b/apps/indexer/lib/indexer/fetcher/on_demand/contract_code.ex index 3b48230..12385f2 100644 --- a/apps/indexer/lib/indexer/fetcher/on_demand/contract_code.ex +++ b/apps/indexer/lib/indexer/fetcher/on_demand/contract_code.ex @@ -24,8 +24,23 @@ defmodule Indexer.Fetcher.OnDemand.ContractCode do end end + # Attempts to fetch the contract code for a given address. + # + # This function checks if the contract code needs to be fetched and if enough time + # has passed since the last attempt. If conditions are met, it triggers the fetch + # and broadcast process. + # + # ## Parameters + # address: The address of the contract. + # state: The current state of the fetcher, containing JSON-RPC configuration. + # + # ## Returns + # `:ok` in all cases. + @spec fetch_contract_code(Address.t(), %{ + json_rpc_named_arguments: EthereumJSONRPC.json_rpc_named_arguments() + }) :: :ok defp fetch_contract_code(address, state) do - with {:empty_nonce, true} <- {:empty_nonce, is_nil(address.nonce)}, + with {:need_to_fetch, true} <- {:need_to_fetch, fetch?(address)}, {:retries_number, {retries_number, updated_at}} <- {:retries_number, AddressContractCodeFetchAttempt.get_retries_number(address.hash)}, updated_at_ms = DateTime.to_unix(updated_at, :millisecond), @@ -35,7 +50,7 @@ defmodule Indexer.Fetcher.OnDemand.ContractCode do threshold(retries_number)} do fetch_and_broadcast_bytecode(address.hash, state) else - {:empty_nonce, false} -> + {:need_to_fetch, false} -> :ok {:retries_number, nil} -> @@ -47,7 +62,35 @@ defmodule Indexer.Fetcher.OnDemand.ContractCode do end end - defp fetch_and_broadcast_bytecode(address_hash, state) do + # Determines if contract code should be fetched for an address + @spec fetch?(Address.t()) :: boolean() + defp fetch?(address) when is_nil(address.nonce), do: true + # if the address has a signed authorization, it might have a bytecode + # according to EIP-7702 + defp fetch?(%{signed_authorization: %{authority: _}}), do: true + defp fetch?(_), do: false + + # Fetches and broadcasts the bytecode for a given address. + # + # This function attempts to retrieve the contract bytecode for the specified address + # using the Ethereum JSON-RPC API. If successful, it updates the database as described below + # and broadcasts the result: + # 1. Updates the `addresses` table with the contract code if fetched successfully. + # 2. Modifies the `address_contract_code_fetch_attempts` table: + # - Deletes the entry if the code is successfully set. + # - Increments the retry count if the fetch fails or returns empty code. + # 3. Broadcasts a message with the fetched bytecode if successful. + # + # ## Parameters + # address_hash: The `t:Explorer.Chain.Hash.Address.t/0` of the contract. + # state: The current state of the fetcher, containing JSON-RPC configuration. + # + # ## Returns + # `:ok` (the function always returns `:ok`, actual results are handled via side effects) + @spec fetch_and_broadcast_bytecode(Explorer.Chain.Hash.Address.t(), %{ + json_rpc_named_arguments: EthereumJSONRPC.json_rpc_named_arguments() + }) :: :ok + defp fetch_and_broadcast_bytecode(address_hash, %{json_rpc_named_arguments: _} = state) do with {:fetched_code, {:ok, %EthereumJSONRPC.FetchedCodes{params_list: fetched_codes}}} <- {:fetched_code, fetch_codes( @@ -90,10 +133,14 @@ defmodule Indexer.Fetcher.OnDemand.ContractCode do {:noreply, state} end + # An initial threshold to fetch smart-contract bytecode on-demand + @spec update_threshold_ms() :: non_neg_integer() defp update_threshold_ms do Application.get_env(:indexer, __MODULE__)[:threshold] end + # Calculates the delay for the next fetch attempt based on the number of retries + @spec threshold(non_neg_integer()) :: non_neg_integer() defp threshold(retries_number) do delay_in_ms = trunc(update_threshold_ms() * :math.pow(2, retries_number)) diff --git a/apps/indexer/lib/indexer/fetcher/on_demand/first_trace.ex b/apps/indexer/lib/indexer/fetcher/on_demand/first_trace.ex index 4a5bf11..13ea834 100644 --- a/apps/indexer/lib/indexer/fetcher/on_demand/first_trace.ex +++ b/apps/indexer/lib/indexer/fetcher/on_demand/first_trace.ex @@ -53,7 +53,7 @@ defmodule Indexer.Fetcher.OnDemand.FirstTrace do {:error, reason} -> Logger.error(fn -> - ["Error while fetching first trace for tx: #{hash_string} error reason: ", reason] + ["Error while fetching first trace for transaction: #{hash_string} error reason: ", reason] end) :ignore -> diff --git a/apps/indexer/lib/indexer/fetcher/on_demand/token_instance_metadata_refetch.ex b/apps/indexer/lib/indexer/fetcher/on_demand/token_instance_metadata_refetch.ex index 652b4b3..02792dd 100644 --- a/apps/indexer/lib/indexer/fetcher/on_demand/token_instance_metadata_refetch.ex +++ b/apps/indexer/lib/indexer/fetcher/on_demand/token_instance_metadata_refetch.ex @@ -20,9 +20,7 @@ defmodule Indexer.Fetcher.OnDemand.TokenInstanceMetadataRefetch do @spec trigger_refetch(TokenInstance.t()) :: :ok def trigger_refetch(token_instance) do - unless is_nil(token_instance.metadata) do - GenServer.cast(__MODULE__, {:refetch, token_instance}) - end + GenServer.cast(__MODULE__, {:refetch, token_instance}) end defp fetch_metadata(token_instance, state) do diff --git a/apps/indexer/lib/indexer/fetcher/optimism.ex b/apps/indexer/lib/indexer/fetcher/optimism.ex index d4605d9..9e4cc38 100644 --- a/apps/indexer/lib/indexer/fetcher/optimism.ex +++ b/apps/indexer/lib/indexer/fetcher/optimism.ex @@ -56,7 +56,7 @@ defmodule Indexer.Fetcher.Optimism do """ @spec get_block_check_interval(list()) :: {:ok, non_neg_integer(), non_neg_integer()} | {:error, any()} def get_block_check_interval(json_rpc_named_arguments) do - {last_safe_block, _} = get_safe_block(json_rpc_named_arguments) + {last_safe_block, _} = Helper.get_safe_block(json_rpc_named_arguments) first_block = max(last_safe_block - @block_check_interval_range_size, 1) @@ -91,26 +91,6 @@ defmodule Indexer.Fetcher.Optimism do ) end - @doc """ - Tries to get `safe` block number from the RPC node. - If it's not available, gets the `latest` one. - Returns a tuple of `{block_number, is_latest}` - where `is_latest` is true if the `safe` is not available. - """ - @spec get_safe_block(list()) :: {non_neg_integer(), boolean()} - def 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, Helper.infinite_retries_number()) - - {latest_block, true} - end - end - defp get_block_timestamp_by_number_inner(number, json_rpc_named_arguments) do result = %{id: 0, number: number} @@ -249,7 +229,7 @@ defmodule Indexer.Fetcher.Optimism do optimism_env = Application.get_all_env(:indexer)[__MODULE__] system_config = optimism_env[:optimism_l1_system_config] - optimism_l1_rpc = optimism_env[:optimism_l1_rpc] + optimism_l1_rpc = l1_rpc_url() with {:system_config_valid, true} <- {:system_config_valid, Helper.address_correct?(system_config)}, {:reorg_monitor_started, true} <- @@ -264,8 +244,9 @@ defmodule Indexer.Fetcher.Optimism do {last_l1_block_number, last_l1_transaction_hash} <- caller.get_last_l1_item(), {:start_block_l1_valid, true} <- {:start_block_l1_valid, start_block_l1 <= last_l1_block_number || last_l1_block_number == 0}, - {:ok, last_l1_tx} <- get_transaction_by_hash(last_l1_transaction_hash, json_rpc_named_arguments), - {:l1_tx_not_found, false} <- {:l1_tx_not_found, !is_nil(last_l1_transaction_hash) && is_nil(last_l1_tx)}, + {:ok, last_l1_transaction} <- get_transaction_by_hash(last_l1_transaction_hash, json_rpc_named_arguments), + {:l1_transaction_not_found, false} <- + {:l1_transaction_not_found, !is_nil(last_l1_transaction_hash) && is_nil(last_l1_transaction)}, {:ok, block_check_interval, last_safe_block} <- get_block_check_interval(json_rpc_named_arguments) do contract_address = if caller == Indexer.Fetcher.Optimism.WithdrawalEvent do @@ -318,7 +299,7 @@ defmodule Indexer.Fetcher.Optimism do {:stop, :normal, %{}} - {:l1_tx_not_found, true} -> + {:l1_transaction_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." ) @@ -378,4 +359,25 @@ defmodule Indexer.Fetcher.Optimism do nil end end + + @doc """ + Returns L1 RPC URL for an OP module. + """ + @spec l1_rpc_url() :: binary() | nil + def l1_rpc_url do + Application.get_all_env(:indexer)[__MODULE__][:optimism_l1_rpc] + end + + @doc """ + Determines if `Indexer.Fetcher.RollupL1ReorgMonitor` module must be up + before an OP fetcher starts. + + ## Returns + - `true` if the reorg monitor must be active, `false` otherwise. + """ + @spec requires_l1_reorg_monitor?() :: boolean() + def requires_l1_reorg_monitor? do + optimism_config = Application.get_all_env(:indexer)[__MODULE__] + not is_nil(optimism_config[:optimism_l1_system_config]) + end end diff --git a/apps/indexer/lib/indexer/fetcher/optimism/deposit.ex b/apps/indexer/lib/indexer/fetcher/optimism/deposit.ex index cd1116b..8e7155d 100644 --- a/apps/indexer/lib/indexer/fetcher/optimism/deposit.ex +++ b/apps/indexer/lib/indexer/fetcher/optimism/deposit.ex @@ -28,6 +28,7 @@ defmodule Indexer.Fetcher.Optimism.Deposit do :safe_block, :optimism_portal, :json_rpc_named_arguments, + :transaction_type, mode: :catch_up, filter_id: nil, check_interval: nil @@ -75,10 +76,12 @@ defmodule Indexer.Fetcher.Optimism.Deposit do json_rpc_named_arguments = Optimism.json_rpc_named_arguments(optimism_l1_rpc), {optimism_portal, start_block_l1} <- Optimism.read_system_config(system_config, json_rpc_named_arguments), true <- start_block_l1 > 0, - {last_l1_block_number, last_l1_tx_hash} <- get_last_l1_item(), - {:ok, last_l1_tx} <- Optimism.get_transaction_by_hash(last_l1_tx_hash, json_rpc_named_arguments), - {:l1_tx_not_found, false} <- {:l1_tx_not_found, !is_nil(last_l1_tx_hash) && is_nil(last_l1_tx)}, - {safe_block, _} = Optimism.get_safe_block(json_rpc_named_arguments), + {last_l1_block_number, last_l1_transaction_hash} <- get_last_l1_item(), + {:ok, last_l1_transaction} <- + Optimism.get_transaction_by_hash(last_l1_transaction_hash, json_rpc_named_arguments), + {:l1_transaction_not_found, false} <- + {:l1_transaction_not_found, !is_nil(last_l1_transaction_hash) && is_nil(last_l1_transaction)}, + {safe_block, _} = Helper.get_safe_block(json_rpc_named_arguments), {:start_block_l1_valid, true} <- {:start_block_l1_valid, (start_block_l1 <= last_l1_block_number || last_l1_block_number == 0) && start_block_l1 <= safe_block} do @@ -97,7 +100,8 @@ defmodule Indexer.Fetcher.Optimism.Deposit do safe_block: safe_block, optimism_portal: optimism_portal, json_rpc_named_arguments: json_rpc_named_arguments, - batch_size: parse_integer(env[:batch_size]) || @batch_size + batch_size: parse_integer(env[:batch_size]) || @batch_size, + transaction_type: env[:transaction_type] }} else {:start_block_l1_valid, false} -> @@ -117,7 +121,7 @@ defmodule Indexer.Fetcher.Optimism.Deposit do {:stop, :normal, state} - {:l1_tx_not_found, true} -> + {:l1_transaction_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 op_deposits table." ) @@ -144,7 +148,8 @@ defmodule Indexer.Fetcher.Optimism.Deposit do optimism_portal: optimism_portal, json_rpc_named_arguments: json_rpc_named_arguments, mode: :catch_up, - batch_size: batch_size + batch_size: batch_size, + transaction_type: transaction_type } = state ) do to_block = min(from_block + batch_size, safe_block) @@ -160,7 +165,7 @@ defmodule Indexer.Fetcher.Optimism.Deposit do 3 )}, _ = Helper.log_blocks_chunk_handling(from_block, to_block, start_block, safe_block, nil, :L1), - deposits = events_to_deposits(logs, json_rpc_named_arguments), + deposits = events_to_deposits(logs, transaction_type, json_rpc_named_arguments), {:import, {:ok, _imported}} <- {:import, Chain.import(%{optimism_deposits: %{params: deposits}, timeout: :infinity})} do Publisher.broadcast(%{optimism_deposits: deposits}, :realtime) @@ -212,7 +217,8 @@ defmodule Indexer.Fetcher.Optimism.Deposit do optimism_portal: optimism_portal, json_rpc_named_arguments: json_rpc_named_arguments, batch_size: batch_size, - mode: :catch_up + mode: :catch_up, + transaction_type: transaction_type } = state ) do with {:check_interval, {:ok, check_interval, new_safe}} <- @@ -236,7 +242,7 @@ defmodule Indexer.Fetcher.Optimism.Deposit do @transaction_deposited_event, json_rpc_named_arguments ) do - handle_new_logs(logs, json_rpc_named_arguments) + handle_new_logs(logs, transaction_type, json_rpc_named_arguments) Process.send(self(), :fetch, []) {:noreply, %{state | mode: :realtime, filter_id: filter_id, check_interval: check_interval}} else @@ -268,12 +274,13 @@ defmodule Indexer.Fetcher.Optimism.Deposit do json_rpc_named_arguments: json_rpc_named_arguments, mode: :realtime, filter_id: filter_id, - check_interval: check_interval + check_interval: check_interval, + transaction_type: transaction_type } = state ) do case get_filter_changes(filter_id, json_rpc_named_arguments) do {:ok, logs} -> - handle_new_logs(logs, json_rpc_named_arguments) + handle_new_logs(logs, transaction_type, json_rpc_named_arguments) Process.send_after(self(), :fetch, check_interval) {:noreply, state} @@ -342,7 +349,7 @@ defmodule Indexer.Fetcher.Optimism.Deposit do :ok end - defp handle_new_logs(logs, json_rpc_named_arguments) do + defp handle_new_logs(logs, transaction_type, json_rpc_named_arguments) do {reorgs, logs_to_parse, min_block, max_block, cnt} = logs |> Enum.reduce({MapSet.new(), [], nil, 0, 0}, fn @@ -362,7 +369,7 @@ defmodule Indexer.Fetcher.Optimism.Deposit do handle_reorgs(reorgs) unless Enum.empty?(logs_to_parse) do - deposits = events_to_deposits(logs_to_parse, json_rpc_named_arguments) + deposits = events_to_deposits(logs_to_parse, transaction_type, json_rpc_named_arguments) {:ok, _imported} = Chain.import(%{optimism_deposits: %{params: deposits}, timeout: :infinity}) Publisher.broadcast(%{optimism_deposits: deposits}, :realtime) @@ -378,7 +385,7 @@ defmodule Indexer.Fetcher.Optimism.Deposit do end end - defp events_to_deposits(logs, json_rpc_named_arguments) do + defp events_to_deposits(logs, transaction_type, json_rpc_named_arguments) do timestamps = logs |> Enum.reduce(MapSet.new(), fn %{"blockNumber" => block_number_quantity}, acc -> @@ -399,7 +406,7 @@ defmodule Indexer.Fetcher.Optimism.Deposit do %{} end - Enum.map(logs, &event_to_deposit(&1, timestamps)) + Enum.map(logs, &event_to_deposit(&1, timestamps, transaction_type)) end defp event_to_deposit( @@ -411,7 +418,8 @@ defmodule Indexer.Fetcher.Optimism.Deposit do "topics" => [_, @address_prefix <> from_stripped, @address_prefix <> to_stripped, _], "data" => opaque_data }, - timestamps + timestamps, + transaction_type ) do {_, prefixed_block_hash} = (String.pad_leading("", 64, "0") <> stripped_block_hash) |> String.split_at(-64) {_, prefixed_log_index} = (String.pad_leading("", 64, "0") <> stripped_log_index) |> String.split_at(-64) @@ -452,8 +460,17 @@ defmodule Indexer.Fetcher.Optimism.Deposit do encoding: :hex ) - l2_tx_hash = - "0x" <> ("7e#{rlp_encoded}" |> Base.decode16!(case: :mixed) |> ExKeccak.hash_256() |> Base.encode16(case: :lower)) + transaction_type = + transaction_type + |> Integer.to_string(16) + |> String.downcase() + + l2_transaction_hash = + "0x" <> + ((transaction_type <> "#{rlp_encoded}") + |> Base.decode16!(case: :mixed) + |> ExKeccak.hash_256() + |> Base.encode16(case: :lower)) block_number = quantity_to_integer(block_number_quantity) @@ -462,7 +479,7 @@ defmodule Indexer.Fetcher.Optimism.Deposit do l1_block_timestamp: Map.get(timestamps, block_number), l1_transaction_hash: transaction_hash, l1_transaction_origin: "0x" <> from_stripped, - l2_transaction_hash: l2_tx_hash + l2_transaction_hash: l2_transaction_hash } end diff --git a/apps/indexer/lib/indexer/fetcher/optimism/output_root.ex b/apps/indexer/lib/indexer/fetcher/optimism/output_root.ex index cab47ce..c032a87 100644 --- a/apps/indexer/lib/indexer/fetcher/optimism/output_root.ex +++ b/apps/indexer/lib/indexer/fetcher/optimism/output_root.ex @@ -15,7 +15,8 @@ defmodule Indexer.Fetcher.Optimism.OutputRoot do alias Explorer.Application.Constants alias Explorer.{Chain, Helper, Repo} alias Explorer.Chain.Optimism.{DisputeGame, OutputRoot} - alias Indexer.Fetcher.{Optimism, RollupL1ReorgMonitor} + alias Explorer.Chain.RollupReorgMonitorQueue + alias Indexer.Fetcher.Optimism alias Indexer.Helper, as: IndexerHelper @fetcher_name :optimism_output_roots @@ -112,7 +113,7 @@ defmodule Indexer.Fetcher.Optimism.OutputRoot do ) end - reorg_block = RollupL1ReorgMonitor.reorg_block_pop(__MODULE__) + reorg_block = RollupReorgMonitorQueue.reorg_block_pop(__MODULE__) if !is_nil(reorg_block) && reorg_block > 0 do {deleted_count, _} = Repo.delete_all(from(r in OutputRoot, where: r.l1_block_number >= ^reorg_block)) @@ -195,4 +196,24 @@ defmodule Indexer.Fetcher.Optimism.OutputRoot do |> Repo.one() |> Kernel.||({0, nil}) end + + @doc """ + Returns L1 RPC URL for this module. + """ + @spec l1_rpc_url() :: binary() | nil + def l1_rpc_url do + Optimism.l1_rpc_url() + end + + @doc """ + Determines if `Indexer.Fetcher.RollupL1ReorgMonitor` module must be up + before this fetcher starts. + + ## Returns + - `true` if the reorg monitor must be active, `false` otherwise. + """ + @spec requires_l1_reorg_monitor?() :: boolean() + def requires_l1_reorg_monitor? do + Optimism.requires_l1_reorg_monitor?() + end end diff --git a/apps/indexer/lib/indexer/fetcher/optimism/txn_batch.ex b/apps/indexer/lib/indexer/fetcher/optimism/transaction_batch.ex similarity index 90% rename from apps/indexer/lib/indexer/fetcher/optimism/txn_batch.ex rename to apps/indexer/lib/indexer/fetcher/optimism/transaction_batch.ex index c122d17..8185dec 100644 --- a/apps/indexer/lib/indexer/fetcher/optimism/txn_batch.ex +++ b/apps/indexer/lib/indexer/fetcher/optimism/transaction_batch.ex @@ -1,4 +1,4 @@ -defmodule Indexer.Fetcher.Optimism.TxnBatch do +defmodule Indexer.Fetcher.Optimism.TransactionBatch do @moduledoc """ Fills op_transaction_batches, op_frame_sequence, and op_frame_sequence_blobs DB tables. @@ -32,9 +32,9 @@ defmodule Indexer.Fetcher.Optimism.TxnBatch do alias EthereumJSONRPC.{Blocks, Contract} alias Explorer.{Chain, Repo} alias Explorer.Chain.Beacon.Blob, as: BeaconBlob - alias Explorer.Chain.{Block, Hash} + alias Explorer.Chain.{Block, Hash, RollupReorgMonitorQueue} alias Explorer.Chain.Optimism.{FrameSequence, FrameSequenceBlob} - alias Explorer.Chain.Optimism.TxnBatch, as: OptimismTxnBatch + alias Explorer.Chain.Optimism.TransactionBatch, as: OptimismTransactionBatch alias HTTPoison.Response alias Indexer.Fetcher.Beacon.Blob alias Indexer.Fetcher.Beacon.Client, as: BeaconClient @@ -42,7 +42,7 @@ defmodule Indexer.Fetcher.Optimism.TxnBatch do alias Indexer.Helper alias Varint.LEB128 - @fetcher_name :optimism_txn_batches + @fetcher_name :optimism_transaction_batches # Optimism chain block time is a constant (2 seconds) @op_chain_block_time 2 @@ -86,7 +86,7 @@ defmodule Indexer.Fetcher.Optimism.TxnBatch do optimism_env = Application.get_all_env(:indexer)[Indexer.Fetcher.Optimism] system_config = optimism_env[:optimism_l1_system_config] - optimism_l1_rpc = optimism_env[:optimism_l1_rpc] + optimism_l1_rpc = l1_rpc_url() with {:system_config_valid, true} <- {:system_config_valid, Helper.address_correct?(system_config)}, @@ -96,7 +96,8 @@ defmodule Indexer.Fetcher.Optimism.TxnBatch do {:reorg_monitor_started, !is_nil(Process.whereis(RollupL1ReorgMonitor))}, {:rpc_l1_undefined, false} <- {:rpc_l1_undefined, is_nil(optimism_l1_rpc)}, json_rpc_named_arguments = Optimism.json_rpc_named_arguments(optimism_l1_rpc), - {start_block_l1, batch_inbox, batch_submitter} = read_system_config(system_config, json_rpc_named_arguments), + {:system_config_read, {start_block_l1, batch_inbox, batch_submitter}} <- + {:system_config_read, read_system_config(system_config, json_rpc_named_arguments)}, {:batch_inbox_valid, true} <- {:batch_inbox_valid, Helper.address_correct?(batch_inbox)}, {:batch_submitter_valid, true} <- {:batch_submitter_valid, Helper.address_correct?(batch_submitter)}, @@ -104,11 +105,12 @@ defmodule Indexer.Fetcher.Optimism.TxnBatch do true <- start_block_l1 > 0, chunk_size = parse_integer(env[:blocks_chunk_size]), {:chunk_size_valid, true} <- {:chunk_size_valid, !is_nil(chunk_size) && chunk_size > 0}, - {last_l1_block_number, last_l1_transaction_hash, last_l1_tx} = get_last_l1_item(json_rpc_named_arguments), + {last_l1_block_number, last_l1_transaction_hash, last_l1_transaction} = + get_last_l1_item(json_rpc_named_arguments), {:start_block_l1_valid, true} <- {:start_block_l1_valid, start_block_l1 <= last_l1_block_number || last_l1_block_number == 0}, - {:l1_tx_not_found, false} <- - {:l1_tx_not_found, !is_nil(last_l1_transaction_hash) && is_nil(last_l1_tx)}, + {:l1_transaction_not_found, false} <- + {:l1_transaction_not_found, !is_nil(last_l1_transaction_hash) && is_nil(last_l1_transaction)}, {:ok, block_check_interval, last_safe_block} <- Optimism.get_block_check_interval(json_rpc_named_arguments) do start_block = max(start_block_l1, last_l1_block_number) @@ -172,14 +174,14 @@ defmodule Indexer.Fetcher.Optimism.TxnBatch do {:stop, :normal, state} - {:l1_tx_not_found, true} -> + {:l1_transaction_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 op_transaction_batches table." ) {:stop, :normal, state} - nil -> + {:system_config_read, nil} -> Logger.error("Cannot read SystemConfig contract.") {:stop, :normal, state} @@ -254,7 +256,7 @@ defmodule Indexer.Fetcher.Optimism.TxnBatch do ) {:ok, new_incomplete_channels, batches, sequences, blobs} = - get_txn_batches( + get_transaction_batches( Range.new(chunk_start, chunk_end), batch_inbox, batch_submitter, @@ -276,7 +278,7 @@ defmodule Indexer.Fetcher.Optimism.TxnBatch do {:ok, inserted} = Chain.import(%{ optimism_frame_sequence_blobs: %{params: blobs}, - optimism_txn_batches: %{params: batches}, + optimism_transaction_batches: %{params: batches}, timeout: :infinity }) @@ -297,7 +299,7 @@ defmodule Indexer.Fetcher.Optimism.TxnBatch do incomplete_channels_acc end - reorg_block = RollupL1ReorgMonitor.reorg_block_pop(__MODULE__) + reorg_block = RollupReorgMonitorQueue.reorg_block_pop(__MODULE__) if !is_nil(reorg_block) && reorg_block > 0 do new_incomplete_channels = handle_l1_reorg(reorg_block, new_incomplete_channels) @@ -404,7 +406,7 @@ defmodule Indexer.Fetcher.Optimism.TxnBatch do l1_transaction_hashes = Repo.one( from( - tb in OptimismTxnBatch, + tb in OptimismTransactionBatch, inner_join: fs in FrameSequence, on: fs.id == tb.frame_sequence_id, select: fs.l1_transaction_hashes, @@ -418,14 +420,14 @@ defmodule Indexer.Fetcher.Optimism.TxnBatch do else last_l1_transaction_hash = List.last(l1_transaction_hashes) - {:ok, last_l1_tx} = Optimism.get_transaction_by_hash(last_l1_transaction_hash, json_rpc_named_arguments) + {:ok, last_l1_transaction} = Optimism.get_transaction_by_hash(last_l1_transaction_hash, json_rpc_named_arguments) - last_l1_block_number = quantity_to_integer(Map.get(last_l1_tx || %{}, "blockNumber", 0)) - {last_l1_block_number, last_l1_transaction_hash, last_l1_tx} + last_l1_block_number = quantity_to_integer(Map.get(last_l1_transaction || %{}, "blockNumber", 0)) + {last_l1_block_number, last_l1_transaction_hash, last_l1_transaction} end end - defp get_txn_batches( + defp get_transaction_batches( block_range, batch_inbox, batch_submitter, @@ -438,8 +440,8 @@ defmodule Indexer.Fetcher.Optimism.TxnBatch do case fetch_blocks_by_range(block_range, json_rpc_named_arguments) do {:ok, %Blocks{transactions_params: transactions_params, blocks_params: blocks_params, errors: []}} -> transactions_params - |> txs_filter(batch_submitter, batch_inbox) - |> get_txn_batches_inner( + |> transactions_filter(batch_submitter, batch_inbox) + |> get_transaction_batches_inner( blocks_params, genesis_block_l2, incomplete_channels, @@ -465,7 +467,7 @@ defmodule Indexer.Fetcher.Optimism.TxnBatch do Logger.error("#{error_message} Retrying...") :timer.sleep(3000) - get_txn_batches( + get_transaction_batches( block_range, batch_inbox, batch_submitter, @@ -503,7 +505,7 @@ defmodule Indexer.Fetcher.Optimism.TxnBatch do blob_data |> String.trim_leading("0x") |> Base.decode16!(case: :lower) - |> OptimismTxnBatch.decode_eip4844_blob() + |> OptimismTransactionBatch.decode_eip4844_blob() if is_nil(decoded) do Logger.warning("Cannot decode the blob #{blob_hash} taken from the Blockscout Blobs API.") @@ -573,7 +575,7 @@ defmodule Indexer.Fetcher.Optimism.TxnBatch do |> Map.get("blob") |> String.trim_leading("0x") |> Base.decode16!(case: :lower) - |> OptimismTxnBatch.decode_eip4844_blob() + |> OptimismTransactionBatch.decode_eip4844_blob() if is_nil(decoded_blob_data) do raise "Invalid blob" @@ -596,24 +598,24 @@ defmodule Indexer.Fetcher.Optimism.TxnBatch do inputs_acc end - defp celestia_blob_to_input("0x" <> tx_input, tx_hash, blobs_api_url) do - tx_input + defp celestia_blob_to_input("0x" <> transaction_input, transaction_hash, blobs_api_url) do + transaction_input |> Base.decode16!(case: :mixed) - |> celestia_blob_to_input(tx_hash, blobs_api_url) + |> celestia_blob_to_input(transaction_hash, blobs_api_url) end - defp celestia_blob_to_input(tx_input, _tx_hash, blobs_api_url) - when byte_size(tx_input) == 1 + 8 + 32 and blobs_api_url != "" do + defp celestia_blob_to_input(transaction_input, _transaction_hash, blobs_api_url) + when byte_size(transaction_input) == 1 + 8 + 32 and blobs_api_url != "" do # the first byte encodes Celestia sign 0xCE # the next 8 bytes encode little-endian Celestia blob height height = - tx_input + transaction_input |> binary_part(1, 8) |> :binary.decode_unsigned(:little) # the next 32 bytes contain the commitment - commitment = binary_part(tx_input, 1 + 8, 32) + commitment = binary_part(transaction_input, 1 + 8, 32) commitment_string = Base.encode16(commitment, case: :lower) url = blobs_api_url <> "?height=#{height}&commitment=" <> commitment_string @@ -646,13 +648,13 @@ defmodule Indexer.Fetcher.Optimism.TxnBatch do end end - defp celestia_blob_to_input(_tx_input, tx_hash, blobs_api_url) when blobs_api_url != "" do - Logger.error("L1 transaction with Celestia commitment has incorrect input length. Tx hash: #{tx_hash}") + defp celestia_blob_to_input(_transaction_input, transaction_hash, blobs_api_url) when blobs_api_url != "" do + Logger.error("L1 transaction with Celestia commitment has incorrect input length. Tx hash: #{transaction_hash}") [] end - defp celestia_blob_to_input(_tx_input, _tx_hash, "") do + defp celestia_blob_to_input(_transaction_input, _transaction_hash, "") do Logger.error( "Cannot read Celestia blobs from the server as the API URL is not defined. Please, check INDEXER_OPTIMISM_L1_BATCH_CELESTIA_BLOBS_API_URL env variable." ) @@ -660,7 +662,7 @@ defmodule Indexer.Fetcher.Optimism.TxnBatch do [] end - defp get_txn_batches_inner( + defp get_transaction_batches_inner( transactions_filtered, blocks_params, genesis_block_l2, @@ -669,29 +671,29 @@ defmodule Indexer.Fetcher.Optimism.TxnBatch do {eip4844_blobs_api_url, celestia_blobs_api_url} ) do transactions_filtered - |> Enum.reduce({:ok, incomplete_channels, [], [], []}, fn tx, + |> Enum.reduce({:ok, incomplete_channels, [], [], []}, fn transaction, {_, incomplete_channels_acc, batches_acc, sequences_acc, blobs_acc} -> inputs = cond do - tx.type == 3 -> + transaction.type == 3 -> # this is EIP-4844 transaction, so we get the inputs from the blobs - block_timestamp = get_block_timestamp_by_number(tx.block_number, blocks_params) + block_timestamp = get_block_timestamp_by_number(transaction.block_number, blocks_params) eip4844_blobs_to_inputs( - tx.hash, - tx.blob_versioned_hashes, + transaction.hash, + transaction.blob_versioned_hashes, block_timestamp, eip4844_blobs_api_url ) - first_byte(tx.input) == 0xCE -> + first_byte(transaction.input) == 0xCE -> # this is Celestia DA transaction, so we get the data from Celestia blob - celestia_blob_to_input(tx.input, tx.hash, celestia_blobs_api_url) + celestia_blob_to_input(transaction.input, transaction.hash, celestia_blobs_api_url) true -> # this is calldata transaction, so the data is in the transaction input - [%{bytes: tx.input}] + [%{bytes: transaction.input}] end Enum.reduce( @@ -700,7 +702,7 @@ defmodule Indexer.Fetcher.Optimism.TxnBatch do fn input, {_, new_incomplete_channels_acc, new_batches_acc, new_sequences_acc, new_blobs_acc} -> handle_input( input, - tx, + transaction, blocks_params, new_incomplete_channels_acc, {new_batches_acc, new_sequences_acc, new_blobs_acc}, @@ -714,7 +716,7 @@ defmodule Indexer.Fetcher.Optimism.TxnBatch do defp handle_input( input, - tx, + transaction, blocks_params, incomplete_channels_acc, {batches_acc, sequences_acc, blobs_acc}, @@ -724,11 +726,11 @@ defmodule Indexer.Fetcher.Optimism.TxnBatch do frame = input_to_frame(input.bytes) if frame == :invalid_frame do - Logger.warning("The frame in transaction #{tx.hash} is invalid.") + Logger.warning("The frame in transaction #{transaction.hash} is invalid.") raise "Invalid frame" end - block_timestamp = get_block_timestamp_by_number(tx.block_number, blocks_params) + block_timestamp = get_block_timestamp_by_number(transaction.block_number, blocks_params) channel = Map.get(incomplete_channels_acc, frame.channel_id, %{frames: %{}}) @@ -736,9 +738,9 @@ defmodule Indexer.Fetcher.Optimism.TxnBatch do Map.put(channel.frames, frame.number, %{ data: frame.data, is_last: frame.is_last, - block_number: tx.block_number, + block_number: transaction.block_number, block_timestamp: block_timestamp, - tx_hash: tx.hash, + transaction_hash: transaction.hash, eip4844_blob_hash: Map.get(input, :eip4844_blob_hash), celestia_blob_metadata: Map.get(input, :celestia_blob_metadata) }) @@ -788,7 +790,7 @@ defmodule Indexer.Fetcher.Optimism.TxnBatch do {bytes, l1_transaction_hashes, new_blobs_acc} = 0..(Enum.count(channel.frames) - 1) - |> Enum.reduce({<<>>, [], blobs_acc}, fn frame_number, {bytes_acc, tx_hashes_acc, new_blobs_acc} -> + |> Enum.reduce({<<>>, [], blobs_acc}, fn frame_number, {bytes_acc, transaction_hashes_acc, new_blobs_acc} -> frame = Map.get(channel.frames, frame_number) next_blob_id = next_blob_id(List.last(new_blobs_acc)) @@ -807,7 +809,7 @@ defmodule Indexer.Fetcher.Optimism.TxnBatch do ), type: :eip4844, metadata: %{hash: frame.eip4844_blob_hash}, - l1_transaction_hash: frame.tx_hash, + l1_transaction_hash: frame.transaction_hash, l1_timestamp: frame.block_timestamp, frame_sequence_id: frame_sequence_id } @@ -829,7 +831,7 @@ defmodule Indexer.Fetcher.Optimism.TxnBatch do key: :crypto.hash(:sha256, height <> commitment), type: :celestia, metadata: frame.celestia_blob_metadata, - l1_transaction_hash: frame.tx_hash, + l1_transaction_hash: frame.transaction_hash, l1_timestamp: frame.block_timestamp, frame_sequence_id: frame_sequence_id } @@ -839,7 +841,7 @@ defmodule Indexer.Fetcher.Optimism.TxnBatch do new_blobs_acc end - {bytes_acc <> frame.data, [frame.tx_hash | tx_hashes_acc], new_blobs_acc} + {bytes_acc <> frame.data, [frame.transaction_hash | transaction_hashes_acc], new_blobs_acc} end) batches_parsed = @@ -899,14 +901,15 @@ defmodule Indexer.Fetcher.Optimism.TxnBatch do frame_sequence_ids = Repo.all( from( - tb in OptimismTxnBatch, + tb in OptimismTransactionBatch, select: tb.frame_sequence_id, where: tb.l2_block_number >= ^reorg_block ), timeout: :infinity ) - {deleted_count, _} = Repo.delete_all(from(tb in OptimismTxnBatch, where: tb.l2_block_number >= ^reorg_block)) + {deleted_count, _} = + Repo.delete_all(from(tb in OptimismTransactionBatch, where: tb.l2_block_number >= ^reorg_block)) Repo.delete_all(from(fs in FrameSequence, where: fs.id in ^frame_sequence_ids)) @@ -917,6 +920,26 @@ defmodule Indexer.Fetcher.Optimism.TxnBatch do end end + @doc """ + Returns L1 RPC URL for this module. + """ + @spec l1_rpc_url() :: binary() | nil + def l1_rpc_url do + Optimism.l1_rpc_url() + end + + @doc """ + Determines if `Indexer.Fetcher.RollupL1ReorgMonitor` module must be up + before this fetcher starts. + + ## Returns + - `true` if the reorg monitor must be active, `false` otherwise. + """ + @spec requires_l1_reorg_monitor?() :: boolean() + def requires_l1_reorg_monitor? do + Optimism.requires_l1_reorg_monitor?() + end + defp http_get_request(url, attempts_done \\ 0) do case Application.get_env(:explorer, :http_adapter).get(url) do {:ok, %Response{body: body, status_code: 200}} -> @@ -1260,12 +1283,20 @@ defmodule Indexer.Fetcher.Optimism.TxnBatch do defp remove_prev_frame_sequences(inserted) do ids = inserted - |> Map.get(:insert_txn_batches, []) + |> Map.get(:insert_transaction_batches, []) |> Enum.map(fn tb -> tb.frame_sequence_id_prev end) |> Enum.uniq() |> Enum.filter(fn id -> id > 0 end) - Repo.delete_all(from(fs in FrameSequence, where: fs.id in ^ids)) + try do + Repo.delete_all(from(fs in FrameSequence, where: fs.id in ^ids)) + rescue + # we need to ignore `foreign_key_violation` exception when deleting the rows + # because there can be a case when the chain partially replaces the frame sequence + # (e.g. Unichain Private Testnet), and so some rows in `op_transaction_batches` table + # can still reference to the `op_frame_sequences` table + _ -> nil + end end defp set_frame_sequences_view_ready(sequences) do @@ -1277,7 +1308,7 @@ defmodule Indexer.Fetcher.Optimism.TxnBatch do ) end - defp txs_filter(transactions_params, batch_submitter, batch_inbox) do + defp transactions_filter(transactions_params, batch_submitter, batch_inbox) do transactions_params |> Enum.filter(fn t -> from_address_hash = Map.get(t, :from_address_hash) @@ -1324,8 +1355,8 @@ defmodule Indexer.Fetcher.Optimism.TxnBatch do end end - defp first_byte("0x" <> tx_input) do - tx_input + defp first_byte("0x" <> transaction_input) do + transaction_input |> Base.decode16!(case: :mixed) |> first_byte() end @@ -1334,7 +1365,7 @@ defmodule Indexer.Fetcher.Optimism.TxnBatch do version_byte end - defp first_byte(_tx_input) do + defp first_byte(_transaction_input) do nil end diff --git a/apps/indexer/lib/indexer/fetcher/optimism/withdrawal.ex b/apps/indexer/lib/indexer/fetcher/optimism/withdrawal.ex index d993111..113a56f 100644 --- a/apps/indexer/lib/indexer/fetcher/optimism/withdrawal.ex +++ b/apps/indexer/lib/indexer/fetcher/optimism/withdrawal.ex @@ -57,12 +57,14 @@ defmodule Indexer.Fetcher.Optimism.Withdrawal do false <- is_nil(start_block_l2), true <- start_block_l2 > 0, {last_l2_block_number, last_l2_transaction_hash} <- get_last_l2_item(), - {safe_block, safe_block_is_latest} = Optimism.get_safe_block(json_rpc_named_arguments), + {safe_block, safe_block_is_latest} = Helper.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} <- Optimism.get_transaction_by_hash(last_l2_transaction_hash, json_rpc_named_arguments), - {:l2_tx_not_found, false} <- {:l2_tx_not_found, !is_nil(last_l2_transaction_hash) && is_nil(last_l2_tx)} do + {:ok, last_l2_transaction} <- + Optimism.get_transaction_by_hash(last_l2_transaction_hash, json_rpc_named_arguments), + {:l2_transaction_not_found, false} <- + {:l2_transaction_not_found, !is_nil(last_l2_transaction_hash) && is_nil(last_l2_transaction)} do Process.send(self(), :continue, []) {:noreply, @@ -92,7 +94,7 @@ defmodule Indexer.Fetcher.Optimism.Withdrawal do {:stop, :normal, state} - {:l2_tx_not_found, true} -> + {:l2_transaction_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 op_withdrawals table." ) diff --git a/apps/indexer/lib/indexer/fetcher/optimism/withdrawal_event.ex b/apps/indexer/lib/indexer/fetcher/optimism/withdrawal_event.ex index f2472a5..3cb2a55 100644 --- a/apps/indexer/lib/indexer/fetcher/optimism/withdrawal_event.ex +++ b/apps/indexer/lib/indexer/fetcher/optimism/withdrawal_event.ex @@ -16,7 +16,8 @@ defmodule Indexer.Fetcher.Optimism.WithdrawalEvent do alias EthereumJSONRPC.Blocks alias Explorer.{Chain, Repo} alias Explorer.Chain.Optimism.WithdrawalEvent - alias Indexer.Fetcher.{Optimism, RollupL1ReorgMonitor} + alias Explorer.Chain.RollupReorgMonitorQueue + alias Indexer.Fetcher.Optimism alias Indexer.Helper @fetcher_name :optimism_withdrawal_events @@ -119,7 +120,7 @@ defmodule Indexer.Fetcher.Optimism.WithdrawalEvent do ) end - reorg_block = RollupL1ReorgMonitor.reorg_block_pop(__MODULE__) + reorg_block = RollupReorgMonitorQueue.reorg_block_pop(__MODULE__) if !is_nil(reorg_block) && reorg_block > 0 do {deleted_count, _} = Repo.delete_all(from(we in WithdrawalEvent, where: we.l1_block_number >= ^reorg_block)) @@ -164,15 +165,15 @@ defmodule Indexer.Fetcher.Optimism.WithdrawalEvent do end end - defp get_transaction_input_by_hash(blocks, tx_hashes) do + defp get_transaction_input_by_hash(blocks, transaction_hashes) do Enum.reduce(blocks, %{}, fn block, acc -> block |> Map.get("transactions", []) - |> Enum.filter(fn tx -> - Enum.member?(tx_hashes, tx["hash"]) + |> Enum.filter(fn transaction -> + Enum.member?(transaction_hashes, transaction["hash"]) end) - |> Enum.map(fn tx -> - {tx["hash"], tx["input"]} + |> Enum.map(fn transaction -> + {transaction["hash"], transaction["input"]} end) |> Enum.into(%{}) |> Map.merge(acc) @@ -184,7 +185,7 @@ defmodule Indexer.Fetcher.Optimism.WithdrawalEvent do events |> get_blocks_by_events(json_rpc_named_arguments, Helper.infinite_retries_number()) - tx_hashes = + transaction_hashes = events |> Enum.reduce([], fn event, acc -> if Enum.member?([@withdrawal_proven_event, @withdrawal_proven_event_blast], Enum.at(event["topics"], 0)) do @@ -194,7 +195,7 @@ defmodule Indexer.Fetcher.Optimism.WithdrawalEvent do end end) - input_by_hash = get_transaction_input_by_hash(blocks, tx_hashes) + input_by_hash = get_transaction_input_by_hash(blocks, transaction_hashes) timestamps = blocks @@ -206,13 +207,13 @@ defmodule Indexer.Fetcher.Optimism.WithdrawalEvent do events |> Enum.map(fn event -> - tx_hash = event["transactionHash"] + transaction_hash = event["transactionHash"] {l1_event_type, game_index} = if Enum.member?([@withdrawal_proven_event, @withdrawal_proven_event_blast], Enum.at(event["topics"], 0)) do game_index = input_by_hash - |> Map.get(tx_hash) + |> Map.get(transaction_hash) |> input_to_game_index() {"WithdrawalProven", game_index} @@ -226,7 +227,7 @@ defmodule Indexer.Fetcher.Optimism.WithdrawalEvent do withdrawal_hash: Enum.at(event["topics"], 1), l1_event_type: l1_event_type, l1_timestamp: Map.get(timestamps, l1_block_number), - l1_transaction_hash: tx_hash, + l1_transaction_hash: transaction_hash, l1_block_number: l1_block_number, game_index: game_index } @@ -257,6 +258,26 @@ defmodule Indexer.Fetcher.Optimism.WithdrawalEvent do |> Kernel.||({0, nil}) end + @doc """ + Returns L1 RPC URL for this module. + """ + @spec l1_rpc_url() :: binary() | nil + def l1_rpc_url do + Optimism.l1_rpc_url() + end + + @doc """ + Determines if `Indexer.Fetcher.RollupL1ReorgMonitor` module must be up + before this fetcher starts. + + ## Returns + - `true` if the reorg monitor must be active, `false` otherwise. + """ + @spec requires_l1_reorg_monitor?() :: boolean() + def requires_l1_reorg_monitor? do + Optimism.requires_l1_reorg_monitor?() + end + defp get_blocks_by_events(events, json_rpc_named_arguments, retries) do request = events @@ -280,10 +301,10 @@ defmodule Indexer.Fetcher.Optimism.WithdrawalEvent do method_signature = String.slice(input, 0..9) if method_signature == "0x4870496f" do - # the signature of `proveWithdrawalTransaction(tuple _tx, uint256 _disputeGameIndex, tuple _outputRootProof, bytes[] _withdrawalProof)` method + # the signature of `proveWithdrawalTransaction(tuple _transaction, uint256 _disputeGameIndex, tuple _outputRootProof, bytes[] _withdrawalProof)` method - # to get (slice) `_disputeGameIndex` from the tx input, we need to know its offset in the input string (represented as 0x...): - # offset = 10 symbols of signature (incl. `0x` prefix) + 64 symbols (representing 32 bytes) of the `_tx` tuple offset, totally is 74 + # to get (slice) `_disputeGameIndex` from the transaction input, we need to know its offset in the input string (represented as 0x...): + # offset = 10 symbols of signature (incl. `0x` prefix) + 64 symbols (representing 32 bytes) of the `_transaction` tuple offset, totally is 74 game_index_offset = String.length(method_signature) + 32 * 2 game_index_length = 32 * 2 diff --git a/apps/indexer/lib/indexer/fetcher/polygon_edge.ex b/apps/indexer/lib/indexer/fetcher/polygon_edge.ex index 73a42f8..649255b 100644 --- a/apps/indexer/lib/indexer/fetcher/polygon_edge.ex +++ b/apps/indexer/lib/indexer/fetcher/polygon_edge.ex @@ -66,13 +66,14 @@ defmodule Indexer.Fetcher.PolygonEdge do {: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} <- + {:ok, last_l1_transaction} <- Helper.get_transaction_by_hash( last_l1_transaction_hash, json_rpc_named_arguments, Helper.infinite_retries_number() ), - {:l1_tx_not_found, false} <- {:l1_tx_not_found, !is_nil(last_l1_transaction_hash) && is_nil(last_l1_tx)}, + {:l1_transaction_not_found, false} <- + {:l1_transaction_not_found, !is_nil(last_l1_transaction_hash) && is_nil(last_l1_transaction)}, {:ok, block_check_interval, last_safe_block} <- Helper.get_block_check_interval(json_rpc_named_arguments) do start_block = max(start_block_l1, last_l1_block_number) @@ -112,7 +113,7 @@ defmodule Indexer.Fetcher.PolygonEdge do :ignore - {:l1_tx_not_found, true} -> + {:l1_transaction_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." ) @@ -147,13 +148,14 @@ defmodule Indexer.Fetcher.PolygonEdge do {: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} <- + {:ok, last_l2_transaction} <- Helper.get_transaction_by_hash( last_l2_transaction_hash, json_rpc_named_arguments, Helper.infinite_retries_number() ), - {:l2_tx_not_found, false} <- {:l2_tx_not_found, !is_nil(last_l2_transaction_hash) && is_nil(last_l2_tx)} do + {:l2_transaction_not_found, false} <- + {:l2_transaction_not_found, !is_nil(last_l2_transaction_hash) && is_nil(last_l2_transaction)} do Process.send(pid, :continue, []) {:ok, @@ -184,7 +186,7 @@ defmodule Indexer.Fetcher.PolygonEdge do :ignore - {:l2_tx_not_found, true} -> + {:l2_transaction_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." ) @@ -608,4 +610,12 @@ defmodule Indexer.Fetcher.PolygonEdge do def repeated_request(req, error_message, json_rpc_named_arguments, retries) do Helper.repeated_call(&json_rpc/2, [req, json_rpc_named_arguments], error_message, retries) end + + @doc """ + Returns L1 RPC URL for a Polygon Edge module. + """ + @spec l1_rpc_url() :: binary() + def l1_rpc_url do + Application.get_all_env(:indexer)[__MODULE__][:polygon_edge_l1_rpc] + end end diff --git a/apps/indexer/lib/indexer/fetcher/polygon_edge/deposit.ex b/apps/indexer/lib/indexer/fetcher/polygon_edge/deposit.ex index 6864d66..ceb65d5 100644 --- a/apps/indexer/lib/indexer/fetcher/polygon_edge/deposit.ex +++ b/apps/indexer/lib/indexer/fetcher/polygon_edge/deposit.ex @@ -111,6 +111,27 @@ defmodule Indexer.Fetcher.PolygonEdge.Deposit do end) end + @doc """ + Returns L1 RPC URL for this module. + """ + @spec l1_rpc_url() :: binary() | nil + def l1_rpc_url do + PolygonEdge.l1_rpc_url() + end + + @doc """ + Determines if `Indexer.Fetcher.RollupL1ReorgMonitor` module must be up + for this module. + + ## Returns + - `true` if the reorg monitor must be active, `false` otherwise. + """ + @spec requires_l1_reorg_monitor?() :: boolean() + def requires_l1_reorg_monitor? do + module_config = Application.get_all_env(:indexer)[__MODULE__] + not is_nil(module_config[:start_block_l1]) + end + defp get_blocks_by_events(events, json_rpc_named_arguments, retries) do request = events diff --git a/apps/indexer/lib/indexer/fetcher/polygon_edge/withdrawal_exit.ex b/apps/indexer/lib/indexer/fetcher/polygon_edge/withdrawal_exit.ex index f949dbe..64d82b3 100644 --- a/apps/indexer/lib/indexer/fetcher/polygon_edge/withdrawal_exit.ex +++ b/apps/indexer/lib/indexer/fetcher/polygon_edge/withdrawal_exit.ex @@ -82,4 +82,25 @@ defmodule Indexer.Fetcher.PolygonEdge.WithdrawalExit do } end) end + + @doc """ + Returns L1 RPC URL for this module. + """ + @spec l1_rpc_url() :: binary() | nil + def l1_rpc_url do + PolygonEdge.l1_rpc_url() + end + + @doc """ + Determines if `Indexer.Fetcher.RollupL1ReorgMonitor` module must be up + for this module. + + ## Returns + - `true` if the reorg monitor must be active, `false` otherwise. + """ + @spec requires_l1_reorg_monitor?() :: boolean() + def requires_l1_reorg_monitor? do + module_config = Application.get_all_env(:indexer)[__MODULE__] + not is_nil(module_config[:start_block_l1]) + end end diff --git a/apps/indexer/lib/indexer/fetcher/polygon_zkevm/bridge.ex b/apps/indexer/lib/indexer/fetcher/polygon_zkevm/bridge.ex index 98c8ef5..ec404cf 100644 --- a/apps/indexer/lib/indexer/fetcher/polygon_zkevm/bridge.ex +++ b/apps/indexer/lib/indexer/fetcher/polygon_zkevm/bridge.ex @@ -7,10 +7,7 @@ defmodule Indexer.Fetcher.PolygonZkevm.Bridge do import EthereumJSONRPC, only: [ - integer_to_quantity: 1, - json_rpc: 2, quantity_to_integer: 1, - request: 1, timestamp_to_datetime: 1 ] @@ -82,40 +79,19 @@ defmodule Indexer.Fetcher.PolygonZkevm.Bridge do @spec get_logs_all({non_neg_integer(), non_neg_integer()}, binary(), list()) :: list() def get_logs_all({chunk_start, chunk_end}, bridge_contract, json_rpc_named_arguments) do {:ok, result} = - get_logs( + IndexerHelper.get_logs( chunk_start, chunk_end, bridge_contract, [[@bridge_event, @claim_event_v1, @claim_event_v2]], - json_rpc_named_arguments + json_rpc_named_arguments, + 0, + IndexerHelper.infinite_retries_number() ) Logs.elixir_to_params(result) end - defp get_logs(from_block, to_block, address, topics, json_rpc_named_arguments, retries \\ 100_000_000) 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 => topics - } - ] - }) - - error_message = &"Cannot fetch logs for the block range #{from_block}..#{to_block}. Error: #{inspect(&1)}" - - IndexerHelper.repeated_call(&json_rpc/2, [req, json_rpc_named_arguments], error_message, retries) - end - @doc """ Imports the given zkEVM bridge operations into database. Used by Indexer.Fetcher.PolygonZkevm.BridgeL1 and Indexer.Fetcher.PolygonZkevm.BridgeL2 fetchers. @@ -241,7 +217,7 @@ defmodule Indexer.Fetcher.PolygonZkevm.Bridge do defp blocks_to_timestamps(events, json_rpc_named_arguments) do events - |> IndexerHelper.get_blocks_by_events(json_rpc_named_arguments, 100_000_000) + |> IndexerHelper.get_blocks_by_events(json_rpc_named_arguments, IndexerHelper.infinite_retries_number()) |> Enum.reduce(%{}, fn block, acc -> block_number = quantity_to_integer(Map.get(block, "number")) timestamp = timestamp_to_datetime(Map.get(block, "timestamp")) @@ -473,7 +449,7 @@ defmodule Indexer.Fetcher.PolygonZkevm.Bridge do defp get_new_data(data, request, response) do if atomized_key(request.method_id) == :symbol do - Map.put(data, :symbol, response) + Map.put(data, :symbol, Reader.sanitize_symbol(response)) else Map.put(data, :decimals, Reader.sanitize_decimals(response)) end diff --git a/apps/indexer/lib/indexer/fetcher/polygon_zkevm/bridge_l1.ex b/apps/indexer/lib/indexer/fetcher/polygon_zkevm/bridge_l1.ex index 6e01b28..6d086d5 100644 --- a/apps/indexer/lib/indexer/fetcher/polygon_zkevm/bridge_l1.ex +++ b/apps/indexer/lib/indexer/fetcher/polygon_zkevm/bridge_l1.ex @@ -15,6 +15,7 @@ defmodule Indexer.Fetcher.PolygonZkevm.BridgeL1 do only: [get_logs_all: 3, import_operations: 1, prepare_operations: 7] alias Explorer.Chain.PolygonZkevm.{Bridge, Reader} + alias Explorer.Chain.RollupReorgMonitorQueue alias Explorer.Repo alias Indexer.Fetcher.RollupL1ReorgMonitor alias Indexer.Helper @@ -77,8 +78,10 @@ defmodule Indexer.Fetcher.PolygonZkevm.BridgeL1 do {:start_block_valid, (start_block <= last_l1_block_number || last_l1_block_number == 0) && start_block <= safe_block, last_l1_block_number, safe_block}, - {:ok, last_l1_tx} <- Helper.get_transaction_by_hash(last_l1_transaction_hash, json_rpc_named_arguments), - {:l1_tx_not_found, false} <- {:l1_tx_not_found, !is_nil(last_l1_transaction_hash) && is_nil(last_l1_tx)} do + {:ok, last_l1_transaction} <- + Helper.get_transaction_by_hash(last_l1_transaction_hash, json_rpc_named_arguments), + {:l1_transaction_not_found, false} <- + {:l1_transaction_not_found, !is_nil(last_l1_transaction_hash) && is_nil(last_l1_transaction)} do Process.send(self(), :continue, []) {:noreply, @@ -144,7 +147,7 @@ defmodule Indexer.Fetcher.PolygonZkevm.BridgeL1 do {:stop, :normal, %{}} - {:l1_tx_not_found, true} -> + {:l1_transaction_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 polygon_zkevm_bridge table." ) @@ -208,7 +211,7 @@ defmodule Indexer.Fetcher.PolygonZkevm.BridgeL1 do ) end - reorg_block = RollupL1ReorgMonitor.reorg_block_pop(__MODULE__) + reorg_block = RollupReorgMonitorQueue.reorg_block_pop(__MODULE__) if !is_nil(reorg_block) && reorg_block > 0 do reorg_handle(reorg_block) @@ -219,7 +222,9 @@ defmodule Indexer.Fetcher.PolygonZkevm.BridgeL1 do end) new_start_block = last_written_block + 1 - {:ok, new_end_block} = Helper.get_block_number_by_tag("latest", json_rpc_named_arguments, 100_000_000) + + {:ok, new_end_block} = + Helper.get_block_number_by_tag("latest", json_rpc_named_arguments, Helper.infinite_retries_number()) delay = if new_end_block == last_written_block do @@ -240,6 +245,27 @@ defmodule Indexer.Fetcher.PolygonZkevm.BridgeL1 do {:noreply, state} end + @doc """ + Returns L1 RPC URL for this module. + """ + @spec l1_rpc_url() :: binary() + def l1_rpc_url do + Application.get_all_env(:indexer)[__MODULE__][:rpc] + end + + @doc """ + Determines if `Indexer.Fetcher.RollupL1ReorgMonitor` module must be up + for this module. + + ## Returns + - `true` if the reorg monitor must be active, `false` otherwise. + """ + @spec requires_l1_reorg_monitor?() :: boolean() + def requires_l1_reorg_monitor? do + module_config = Application.get_all_env(:indexer)[__MODULE__] + not is_nil(module_config[:start_block]) + end + defp reorg_handle(reorg_block) do {deleted_count, _} = Repo.delete_all(from(b in Bridge, where: b.type == :deposit and b.block_number >= ^reorg_block)) diff --git a/apps/indexer/lib/indexer/fetcher/polygon_zkevm/bridge_l2.ex b/apps/indexer/lib/indexer/fetcher/polygon_zkevm/bridge_l2.ex index 66bdba4..983f69a 100644 --- a/apps/indexer/lib/indexer/fetcher/polygon_zkevm/bridge_l2.ex +++ b/apps/indexer/lib/indexer/fetcher/polygon_zkevm/bridge_l2.ex @@ -71,12 +71,15 @@ defmodule Indexer.Fetcher.PolygonZkevm.BridgeL2 do false <- is_nil(start_block), true <- start_block > 0, {last_l2_block_number, last_l2_transaction_hash} = Reader.last_l2_item(), - {:ok, latest_block} = Helper.get_block_number_by_tag("latest", json_rpc_named_arguments, 100_000_000), + {:ok, latest_block} = + Helper.get_block_number_by_tag("latest", json_rpc_named_arguments, Helper.infinite_retries_number()), {:start_block_valid, true} <- {:start_block_valid, (start_block <= last_l2_block_number || last_l2_block_number == 0) && start_block <= latest_block}, - {:ok, last_l2_tx} <- Helper.get_transaction_by_hash(last_l2_transaction_hash, json_rpc_named_arguments), - {:l2_tx_not_found, false} <- {:l2_tx_not_found, !is_nil(last_l2_transaction_hash) && is_nil(last_l2_tx)} do + {:ok, last_l2_transaction} <- + Helper.get_transaction_by_hash(last_l2_transaction_hash, json_rpc_named_arguments), + {:l2_transaction_not_found, false} <- + {:l2_transaction_not_found, !is_nil(last_l2_transaction_hash) && is_nil(last_l2_transaction)} do Process.send(self(), :continue, []) {:noreply, @@ -136,7 +139,7 @@ defmodule Indexer.Fetcher.PolygonZkevm.BridgeL2 do {:stop, :normal, state} - {:l2_tx_not_found, true} -> + {:l2_transaction_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 polygon_zkevm_bridge table." ) diff --git a/apps/indexer/lib/indexer/fetcher/polygon_zkevm/transaction_batch.ex b/apps/indexer/lib/indexer/fetcher/polygon_zkevm/transaction_batch.ex index 379d19d..0ec6175 100644 --- a/apps/indexer/lib/indexer/fetcher/polygon_zkevm/transaction_batch.ex +++ b/apps/indexer/lib/indexer/fetcher/polygon_zkevm/transaction_batch.ex @@ -173,23 +173,23 @@ defmodule Indexer.Fetcher.PolygonZkevm.TransactionBatch do {:ok, responses} = Helper.repeated_call(&json_rpc/2, [requests, json_rpc_named_arguments], error_message, 3) - # For every batch info extract batches' L1 sequence tx and L1 verify tx + # For every batch info extract batches' L1 sequence transaction and L1 verify transaction {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") + send_sequences_transaction_hash = get_transaction_hash(res.result, "sendSequencesTxHash") + verify_batch_transaction_hash = get_transaction_hash(res.result, "verifyBatchTxHash") sequences = - if send_sequences_tx_hash != @zero_hash do - [Base.decode16!(send_sequences_tx_hash, case: :mixed) | sequences] + if send_sequences_transaction_hash != @zero_hash do + [Base.decode16!(send_sequences_transaction_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] + if verify_batch_transaction_hash != @zero_hash do + [Base.decode16!(verify_batch_transaction_hash, case: :mixed) | verifies] else verifies end @@ -198,22 +198,22 @@ defmodule Indexer.Fetcher.PolygonZkevm.TransactionBatch do end) # All L1 transactions in one list without repetition - l1_tx_hashes = Enum.uniq(sequence_hashes ++ verify_hashes) + l1_transaction_hashes = Enum.uniq(sequence_hashes ++ verify_hashes) - # Receive all IDs for L1 txs + # Receive all IDs for L1 transactions hash_to_id = - l1_tx_hashes + l1_transaction_hashes |> Reader.lifecycle_transactions() |> Enum.reduce(%{}, fn {hash, id}, acc -> Map.put(acc, hash.bytes, id) end) # For every batch build batch representation, collect associated L1 and L2 transactions - {batches_to_import, l2_txs_to_import, l1_txs_to_import, _, _} = + {batches_to_import, l2_transactions_to_import, l1_transactions_to_import, _, _} = responses |> Enum.reduce({[], [], [], Reader.next_id(), hash_to_id}, fn res, - {batches, l2_txs, l1_txs, next_id, hash_to_id} = - _acc -> + {batches, l2_transactions, l1_transactions, next_id, + hash_to_id} = _acc -> number = quantity_to_integer(Map.get(res.result, "number")) # the timestamp is undefined for unfinalized batches @@ -229,32 +229,32 @@ defmodule Indexer.Fetcher.PolygonZkevm.TransactionBatch do state_root = Map.get(res.result, "stateRoot") # Get ID for sequence transaction (new ID if the batch is just sequenced) - {sequence_id, l1_txs, next_id, hash_to_id} = + {sequence_id, l1_transactions, next_id, hash_to_id} = res.result - |> get_tx_hash("sendSequencesTxHash") - |> handle_tx_hash(hash_to_id, next_id, l1_txs, false) + |> get_transaction_hash("sendSequencesTxHash") + |> handle_transaction_hash(hash_to_id, next_id, l1_transactions, false) # Get ID for verify transaction (new ID if the batch is just verified) - {verify_id, l1_txs, next_id, hash_to_id} = + {verify_id, l1_transactions, next_id, hash_to_id} = res.result - |> get_tx_hash("verifyBatchTxHash") - |> handle_tx_hash(hash_to_id, next_id, l1_txs, true) + |> get_transaction_hash("verifyBatchTxHash") + |> handle_transaction_hash(hash_to_id, next_id, l1_transactions, true) # Associate every transaction from batch with the batch number - l2_txs_append = + l2_transactions_append = l2_transaction_hashes |> Kernel.||([]) - |> Enum.map(fn l2_tx_hash -> + |> Enum.map(fn l2_transaction_hash -> %{ batch_number: number, - hash: l2_tx_hash + hash: l2_transaction_hash } end) batch = %{ number: number, timestamp: timestamp, - l2_transactions_count: Enum.count(l2_txs_append), + l2_transactions_count: Enum.count(l2_transactions_append), global_exit_root: global_exit_root, acc_input_hash: acc_input_hash, state_root: state_root, @@ -262,15 +262,15 @@ defmodule Indexer.Fetcher.PolygonZkevm.TransactionBatch do verify_id: verify_id } - {[batch | batches], l2_txs ++ l2_txs_append, l1_txs, next_id, hash_to_id} + {[batch | batches], l2_transactions ++ l2_transactions_append, l1_transactions, next_id, hash_to_id} end) # Update batches list, L1 transactions list and L2 transaction list {:ok, _} = Chain.import(%{ - polygon_zkevm_lifecycle_transactions: %{params: l1_txs_to_import}, + polygon_zkevm_lifecycle_transactions: %{params: l1_transactions_to_import}, polygon_zkevm_transaction_batches: %{params: batches_to_import}, - polygon_zkevm_batch_transactions: %{params: l2_txs_to_import}, + polygon_zkevm_batch_transactions: %{params: l2_transactions_to_import}, timeout: :infinity }) @@ -306,27 +306,27 @@ defmodule Indexer.Fetcher.PolygonZkevm.TransactionBatch do {latest_batch_number, virtual_batch_number, verified_batch_number} end - defp get_tx_hash(result, type) do + defp get_transaction_hash(result, type) do case Map.get(result, type) do - "0x" <> tx_hash -> tx_hash + "0x" <> transaction_hash -> transaction_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) + defp handle_transaction_hash(encoded_transaction_hash, hash_to_id, next_id, l1_transactions, is_verify) do + if encoded_transaction_hash != @zero_hash do + transaction_hash = Base.decode16!(encoded_transaction_hash, case: :mixed) - id = Map.get(hash_to_id, tx_hash) + id = Map.get(hash_to_id, transaction_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)} + {next_id, [%{id: next_id, hash: transaction_hash, is_verify: is_verify} | l1_transactions], next_id + 1, + Map.put(hash_to_id, transaction_hash, next_id)} else - {id, l1_txs, next_id, hash_to_id} + {id, l1_transactions, next_id, hash_to_id} end else - {nil, l1_txs, next_id, hash_to_id} + {nil, l1_transactions, next_id, hash_to_id} end end end diff --git a/apps/indexer/lib/indexer/fetcher/rollup_l1_reorg_monitor.ex b/apps/indexer/lib/indexer/fetcher/rollup_l1_reorg_monitor.ex index 20b5c27..825dec0 100644 --- a/apps/indexer/lib/indexer/fetcher/rollup_l1_reorg_monitor.ex +++ b/apps/indexer/lib/indexer/fetcher/rollup_l1_reorg_monitor.ex @@ -1,6 +1,10 @@ defmodule Indexer.Fetcher.RollupL1ReorgMonitor do @moduledoc """ - A module to catch L1 reorgs and notify a rollup module about it. + A module to monitor and catch L1 reorgs and make queue of the reorg blocks + (if there are multiple reorgs) for rollup modules using this monitor. + + A rollup module uses the queue to detect a reorg and to do required actions. + In case of reorg, the block number is popped from the queue by that rollup module. """ use GenServer @@ -8,10 +12,45 @@ defmodule Indexer.Fetcher.RollupL1ReorgMonitor do require Logger - alias Indexer.{BoundQueue, Helper} + alias Explorer.Chain.RollupReorgMonitorQueue + alias Indexer.Helper @fetcher_name :rollup_l1_reorg_monitor + @modules_can_use_reorg_monitor (case Application.compile_env(:explorer, :chain_type) do + :optimism -> + [ + Indexer.Fetcher.Optimism.OutputRoot, + Indexer.Fetcher.Optimism.TransactionBatch, + Indexer.Fetcher.Optimism.WithdrawalEvent + ] + + :polygon_edge -> + [ + Indexer.Fetcher.PolygonEdge.Deposit, + Indexer.Fetcher.PolygonEdge.WithdrawalExit + ] + + :polygon_zkevm -> + [ + Indexer.Fetcher.PolygonZkevm.BridgeL1 + ] + + :scroll -> + [ + Indexer.Fetcher.Scroll.Batch, + Indexer.Fetcher.Scroll.BridgeL1 + ] + + :shibarium -> + [ + Indexer.Fetcher.Shibarium.L1 + ] + + _ -> + [] + end) + def child_spec(start_link_arguments) do spec = %{ id: __MODULE__, @@ -27,66 +66,38 @@ defmodule Indexer.Fetcher.RollupL1ReorgMonitor do GenServer.start_link(__MODULE__, args, Keyword.put_new(gen_server_options, :name, __MODULE__)) end + @doc """ + This function initializes L1 blocks reorg monitor for the current rollup + defined by CHAIN_TYPE. If the current chain is not a rollup, the module just doesn't start. + + The monitor is launched for certain modules of the rollup defined in @modules_can_use_reorg_monitor attribute + if a module starts (it can be switched off by configuration parameters). Whether each module starts or not + is defined by the `requires_l1_reorg_monitor?` function of that module. + + The monitor starts an infinite loop of `eth_getBlockByNumber` requests sending them every + `block_check_interval` milliseconds to retrieve the latest block number. To read the latest + block number, RPC node of Layer 1 is used, which URL is defined by `l1_rpc_url` function of the rollup module. + The `block_check_interval` is determined by the `get_block_check_interval` helper function. + After the `block_check_interval` is defined, the function sends `:reorg_monitor` message to the GenServer + to start the monitor loop. + + ## Returns + - `{:ok, state}` with the determined parameters for the monitor loop if at least one rollup module is launched. + - `:ignore` if the monitor is not needed. + """ @impl GenServer def init(_args) do Logger.metadata(fetcher: @fetcher_name) - optimism_modules = [ - Indexer.Fetcher.Optimism.OutputRoot, - Indexer.Fetcher.Optimism.TxnBatch, - Indexer.Fetcher.Optimism.WithdrawalEvent - ] - - modules_can_use_reorg_monitor = - optimism_modules ++ - [ - Indexer.Fetcher.PolygonEdge.Deposit, - Indexer.Fetcher.PolygonEdge.WithdrawalExit, - Indexer.Fetcher.PolygonZkevm.BridgeL1, - Indexer.Fetcher.Shibarium.L1 - ] - modules_using_reorg_monitor = - modules_can_use_reorg_monitor - |> Enum.reject(fn module -> - if module in optimism_modules do - optimism_config = Application.get_all_env(:indexer)[Indexer.Fetcher.Optimism] - is_nil(optimism_config[:optimism_l1_system_config]) - else - module_config = Application.get_all_env(:indexer)[module] - is_nil(module_config[:start_block]) and is_nil(module_config[:start_block_l1]) - end - end) + @modules_can_use_reorg_monitor + |> Enum.filter(& &1.requires_l1_reorg_monitor?()) if Enum.empty?(modules_using_reorg_monitor) do # don't start reorg monitor as there is no module which would use it :ignore else - # As there cannot be different modules for different rollups at the same time, - # it's correct to only get the first item of the list. - # For example, Indexer.Fetcher.PolygonEdge.Deposit and Indexer.Fetcher.PolygonEdge.WithdrawalExit can be in the list - # because they are for the same rollup, but Indexer.Fetcher.Shibarium.L1 and Indexer.Fetcher.PolygonZkevm.BridgeL1 cannot (as they are for different rollups). - module_using_reorg_monitor = Enum.at(modules_using_reorg_monitor, 0) - - l1_rpc = - cond do - Enum.member?( - [Indexer.Fetcher.PolygonEdge.Deposit, Indexer.Fetcher.PolygonEdge.WithdrawalExit], - module_using_reorg_monitor - ) -> - # there can be more than one PolygonEdge.* modules, so we get the common L1 RPC URL for them from Indexer.Fetcher.PolygonEdge - Application.get_all_env(:indexer)[Indexer.Fetcher.PolygonEdge][:polygon_edge_l1_rpc] - - Enum.member?( - optimism_modules, - module_using_reorg_monitor - ) -> - # there can be more than one Optimism.* modules, so we get the common L1 RPC URL for them from Indexer.Fetcher.Optimism - Application.get_all_env(:indexer)[Indexer.Fetcher.Optimism][:optimism_l1_rpc] - - true -> - Application.get_all_env(:indexer)[module_using_reorg_monitor][:rpc] - end + l1_rpc = Enum.at(modules_using_reorg_monitor, 0).l1_rpc_url() json_rpc_named_arguments = Helper.json_rpc_named_arguments(l1_rpc) @@ -104,6 +115,24 @@ defmodule Indexer.Fetcher.RollupL1ReorgMonitor do end end + @doc """ + Implements the monitor loop which requests RPC node for the latest block every + `block_check_interval` milliseconds using `eth_getBlockByNumber` request. + + In case of reorg, the reorg block number is pushed into rollup module's queue. + The block numbers are then popped by the rollup module from its queue and + used to do some actions needed after reorg. + + ## Parameters + - `:reorg_monitor`: The message triggering the next monitoring iteration. + - `state`: The current state of the process, containing parameters for the + monitoring (such as `block_check_interval`, `json_rpc_named_arguments`, + the list of rollup modules in need of monitoring, the previous latest + block number). + + ## Returns + - `{:noreply, state}` where `state` contains the updated previous latest block number. + """ @impl GenServer def handle_info( :reorg_monitor, @@ -114,62 +143,15 @@ defmodule Indexer.Fetcher.RollupL1ReorgMonitor do prev_latest: prev_latest } = state ) do - {:ok, latest} = Helper.get_block_number_by_tag("latest", json_rpc_named_arguments, 100_000_000) + {:ok, latest} = Helper.get_block_number_by_tag("latest", json_rpc_named_arguments, Helper.infinite_retries_number()) if latest < prev_latest do Logger.warning("Reorg detected: previous latest block ##{prev_latest}, current latest block ##{latest}.") - Enum.each(modules, &reorg_block_push(latest, &1)) + Enum.each(modules, &RollupReorgMonitorQueue.reorg_block_push(latest, &1)) end Process.send_after(self(), :reorg_monitor, block_check_interval) {:noreply, %{state | prev_latest: latest}} end - - @doc """ - Pops the number of reorg block from the front of the queue for the specified rollup module. - Returns `nil` if the reorg queue is empty. - """ - @spec reorg_block_pop(module()) :: non_neg_integer() | nil - def reorg_block_pop(module) do - table_name = reorg_table_name(module) - - 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 - - defp reorg_block_push(block_number, module) do - table_name = reorg_table_name(module) - {:ok, updated_queue} = BoundQueue.push_back(reorg_queue_get(table_name), block_number) - :ets.insert(table_name, {:queue, updated_queue}) - 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(module) do - :"#{module}#{:_reorgs}" - end end diff --git a/apps/indexer/lib/indexer/fetcher/scroll/batch.ex b/apps/indexer/lib/indexer/fetcher/scroll/batch.ex new file mode 100644 index 0000000..10e0c3a --- /dev/null +++ b/apps/indexer/lib/indexer/fetcher/scroll/batch.ex @@ -0,0 +1,588 @@ +defmodule Indexer.Fetcher.Scroll.Batch do + @moduledoc """ + The module for scanning L1 RPC node for the `CommitBatch` and `FinalizeBatch` events + which commit and finalize Scroll batches. + + The main function splits the whole block range by chunks and scans L1 Scroll Chain contract + for the batch logs (events) for each chunk. The found events are handled and then imported to the + `scroll_batches` and `scroll_batch_bundles` database tables. + + After historical block range is covered, the process switches to realtime mode and + searches for the batch events in every new block. Reorg blocks are taken into account. + """ + + use GenServer + use Indexer.Fetcher + + require Logger + + import Ecto.Query + + import EthereumJSONRPC, only: [quantity_to_integer: 1] + + alias ABI.{FunctionSelector, TypeDecoder} + alias Ecto.Multi + alias EthereumJSONRPC.Logs + alias Explorer.Chain.Block.Range, as: BlockRange + alias Explorer.Chain.RollupReorgMonitorQueue + alias Explorer.Chain.Scroll.{Batch, BatchBundle, Reader} + alias Explorer.{Chain, Repo} + alias Indexer.Fetcher.RollupL1ReorgMonitor + alias Indexer.Fetcher.Scroll.Helper, as: ScrollHelper + alias Indexer.Helper + + # 32-byte signature of the event CommitBatch(uint256 indexed batchIndex, bytes32 indexed batchHash) + @commit_batch_event "0x2c32d4ae151744d0bf0b9464a3e897a1d17ed2f1af71f7c9a75f12ce0d28238f" + + # 32-byte signature of the event FinalizeBatch(uint256 indexed batchIndex, bytes32 indexed batchHash, bytes32 stateRoot, bytes32 withdrawRoot) + @finalize_batch_event "0x26ba82f907317eedc97d0cbef23de76a43dd6edb563bdb6e9407645b950a7a2d" + + @fetcher_name :scroll_batch + + 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 + {:ok, %{}, {:continue, :ok}} + end + + @impl GenServer + def handle_continue(_, state) do + Logger.metadata(fetcher: @fetcher_name) + # two seconds pause needed to avoid exceeding Supervisor restart intensity when DB issues + Process.send_after(self(), :init_with_delay, 2000) + {:noreply, state} + end + + # Validates parameters and initiates searching of the events. + # + # When first launch, the events searching will start from the first block + # and end on the `safe` block (or `latest` one if `safe` is not available). + # If this is not the first launch, the process will start from the block which was + # the last on the previous launch. + @impl GenServer + def handle_info(:init_with_delay, _state) do + env = Application.get_all_env(:indexer)[__MODULE__] + + with {:start_block_undefined, false} <- {:start_block_undefined, is_nil(env[:start_block])}, + {:reorg_monitor_started, true} <- {:reorg_monitor_started, !is_nil(Process.whereis(RollupL1ReorgMonitor))}, + rpc = l1_rpc_url(), + {:rpc_undefined, false} <- {:rpc_undefined, is_nil(rpc)}, + {:scroll_chain_contract_address_is_valid, true} <- + {:scroll_chain_contract_address_is_valid, Helper.address_correct?(env[:scroll_chain_contract])}, + start_block = env[:start_block], + true <- start_block > 0, + {last_l1_block_number, last_l1_transaction_hash} = Reader.last_l1_batch_item(), + json_rpc_named_arguments = Helper.json_rpc_named_arguments(rpc), + {:ok, block_check_interval, safe_block} <- Helper.get_block_check_interval(json_rpc_named_arguments), + {:start_block_valid, true, _, _} <- + {:start_block_valid, + (start_block <= last_l1_block_number || last_l1_block_number == 0) && start_block <= safe_block, + last_l1_block_number, safe_block}, + {:ok, last_l1_transaction} <- + Helper.get_transaction_by_hash(last_l1_transaction_hash, json_rpc_named_arguments), + # here we check for the last known L1 transaction existence to make sure there wasn't reorg + # on L1 while the instance was down, and so we can use `last_l1_block_number` as the starting point + {:l1_transaction_not_found, false} <- + {:l1_transaction_not_found, !is_nil(last_l1_transaction_hash) && is_nil(last_l1_transaction)} do + Process.send(self(), :continue, []) + + {:noreply, + %{ + block_check_interval: block_check_interval, + scroll_chain_contract: env[:scroll_chain_contract], + json_rpc_named_arguments: json_rpc_named_arguments, + end_block: safe_block, + start_block: max(start_block, last_l1_block_number), + eth_get_logs_range_size: Application.get_all_env(:indexer)[Indexer.Fetcher.Scroll][:l1_eth_get_logs_range_size] + }} + else + {:start_block_undefined, true} -> + # the process shouldn't start if the start block is not defined + {:stop, :normal, %{}} + + {:reorg_monitor_started, false} -> + Logger.error("Cannot start this process as Indexer.Fetcher.RollupL1ReorgMonitor is not started.") + {:stop, :normal, %{}} + + {:rpc_undefined, true} -> + Logger.error("L1 RPC URL is not defined.") + {:stop, :normal, %{}} + + {:scroll_chain_contract_address_is_valid, false} -> + Logger.error("L1 ScrollChain contract address is invalid or not defined.") + {:stop, :normal, %{}} + + {:start_block_valid, false, last_l1_block_number, safe_block} -> + Logger.error("Invalid L1 Start Block value. Please, check the value and scroll_batches table.") + Logger.error("last_l1_block_number = #{inspect(last_l1_block_number)}") + Logger.error("safe_block = #{inspect(safe_block)}") + {:stop, :normal, %{}} + + {:error, error_data} -> + Logger.error( + "Cannot get last L1 transaction from RPC by its hash, latest block, or block timestamp by its number due to RPC error: #{inspect(error_data)}" + ) + + {:stop, :normal, %{}} + + {:l1_transaction_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 scroll_batches table." + ) + + {:stop, :normal, %{}} + + _ -> + Logger.error("L1 Start Block is invalid or zero.") + {:stop, :normal, %{}} + end + end + + @doc """ + The main function that scans RPC node for the batch logs (events), parses them, + and imports to the database (into the `scroll_batches` and `scroll_batch_bundles` tables). + + The function splits a given block range by chunks and scans the Scroll Chain contract + for the batch logs (events) for each chunk. The found events are handled and then imported + to the `scroll_batches` and `scroll_batch_bundles` database tables. + + After historical block range is covered, the function switches to realtime mode and + searches for the batch events in every new block. Reorg blocks are taken into account. + + ## Parameters + - `:continue`: The message that triggers the working loop. + - `state`: The state map containing needed data such as the chain contract address and the block range. + + ## Returns + - {:noreply, state} tuple with the updated block range in the `state` to scan logs in. + """ + @impl GenServer + def handle_info( + :continue, + %{ + block_check_interval: block_check_interval, + scroll_chain_contract: scroll_chain_contract, + json_rpc_named_arguments: json_rpc_named_arguments, + end_block: end_block, + start_block: start_block, + eth_get_logs_range_size: eth_get_logs_range_size + } = state + ) do + time_before = Timex.now() + + last_written_block = + start_block..end_block + |> Enum.chunk_every(eth_get_logs_range_size) + |> Enum.reduce_while(start_block - 1, fn current_chunk, _ -> + chunk_start = List.first(current_chunk) + chunk_end = List.last(current_chunk) + + if chunk_start <= chunk_end do + Helper.log_blocks_chunk_handling(chunk_start, chunk_end, start_block, end_block, nil, :L1) + + {batches, bundles, start_by_final_batch_number} = + {chunk_start, chunk_end} + |> get_logs_all(scroll_chain_contract, json_rpc_named_arguments) + |> prepare_items(json_rpc_named_arguments) + + import_items(batches, bundles, start_by_final_batch_number) + + Helper.log_blocks_chunk_handling( + chunk_start, + chunk_end, + start_block, + end_block, + "#{Enum.count(batches)} L1 batch(es), #{Enum.count(bundles)} L1 bundle(s)", + :L1 + ) + end + + reorg_block = RollupReorgMonitorQueue.reorg_block_pop(__MODULE__) + + if !is_nil(reorg_block) && reorg_block > 0 do + reorg_handle(reorg_block) + {: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} = + Helper.get_block_number_by_tag("latest", json_rpc_named_arguments, Helper.infinite_retries_number()) + + 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 + + @impl GenServer + def handle_info({ref, _result}, state) do + Process.demonitor(ref, [:flush]) + {:noreply, state} + end + + @doc """ + Returns L1 RPC URL for this module. + Returns `nil` if not defined. + """ + @spec l1_rpc_url() :: binary() | nil + def l1_rpc_url do + ScrollHelper.l1_rpc_url() + end + + @doc """ + Determines if `Indexer.Fetcher.RollupL1ReorgMonitor` module must be up + for this module. + + ## Returns + - `true` if the reorg monitor must be active, `false` otherwise. + """ + @spec requires_l1_reorg_monitor?() :: boolean() + def requires_l1_reorg_monitor? do + module_config = Application.get_all_env(:indexer)[__MODULE__] + not is_nil(module_config[:start_block]) + end + + # Fetches `CommitBatch` and `FinalizeBatch` events of the Scroll Chain contract from an RPC node + # for the given range of L1 blocks. + @spec get_logs_all({non_neg_integer(), non_neg_integer()}, binary(), EthereumJSONRPC.json_rpc_named_arguments()) :: [ + %{atom() => any()} + ] + defp get_logs_all({chunk_start, chunk_end}, scroll_chain_contract, json_rpc_named_arguments) do + {:ok, result} = + Helper.get_logs( + chunk_start, + chunk_end, + scroll_chain_contract, + [[@commit_batch_event, @finalize_batch_event]], + json_rpc_named_arguments, + 0, + Helper.infinite_retries_number() + ) + + Logs.elixir_to_params(result) + end + + # Extracts transaction inputs for specified transaction hashes from a list of blocks. + # + # ## Parameters + # - `blocks`: A list of block maps, each containing a "transactions" key with transaction data. + # - `transaction_hashes`: A list of transaction hashes to filter for. + # + # ## Returns + # A map where keys are transaction hashes and values are the corresponding transaction inputs and blob versioned hashes. + @spec get_transaction_input_by_hash([%{String.t() => any()}], [binary()]) :: %{ + binary() => {binary(), [binary()] | [] | nil} + } + defp get_transaction_input_by_hash(blocks, transaction_hashes) do + Enum.reduce(blocks, %{}, fn block, acc -> + block + |> Map.get("transactions", []) + |> Enum.filter(fn transaction -> + Enum.member?(transaction_hashes, transaction["hash"]) + end) + |> Enum.map(fn transaction -> + {transaction["hash"], {transaction["input"], transaction["blobVersionedHashes"]}} + end) + |> Enum.into(%{}) + |> Map.merge(acc) + end) + end + + # Extracts the L2 block range from the call data of a batch commitment + # transaction. + # + # This function decodes the input data from either a `commitBatch` or + # `commitBatchWithBlobProof` function call, extracts the chunks containing L2 + # block numbers, and by identifying the minimum and maximum block numbers in the + # chunks, determines the range of L2 block numbers included in the batch. + # + # ## Parameters + # - `input`: A binary string representing the input data of a batch commitment + # transaction. + # + # ## Returns + # - A `BlockRange.t()` struct containing the minimum and maximum L2 block + # numbers included in the batch. + @spec input_to_l2_block_range(binary()) :: BlockRange.t() + defp input_to_l2_block_range(input) do + chunks = + case input do + # commitBatch(uint8 _version, bytes _parentBatchHeader, bytes[] _chunks, bytes _skippedL1MessageBitmap) + "0x1325aca0" <> encoded_params -> + [_version, _parent_batch_header, chunks, _skipped_l1_message_bitmap] = + TypeDecoder.decode( + Base.decode16!(encoded_params, case: :lower), + %FunctionSelector{ + function: "commitBatch", + types: [ + {:uint, 8}, + :bytes, + {:array, :bytes}, + :bytes + ] + } + ) + + chunks + + # commitBatchWithBlobProof(uint8 _version, bytes _parentBatchHeader, bytes[] _chunks, bytes _skippedL1MessageBitmap, bytes _blobDataProof) + "0x86b053a9" <> encoded_params -> + [_version, _parent_batch_header, chunks, _skipped_l1_message_bitmap, _blob_data_proof] = + TypeDecoder.decode( + Base.decode16!(encoded_params, case: :lower), + %FunctionSelector{ + function: "commitBatchWithBlobProof", + types: [ + {:uint, 8}, + :bytes, + {:array, :bytes}, + :bytes, + :bytes + ] + } + ) + + chunks + end + + {:ok, l2_block_range} = + chunks + |> Enum.reduce([], fn chunk, acc -> + <> = chunk + + chunk_l2_block_numbers = + Enum.map(Range.new(0, chunk_length - 1, 1), fn i -> + # chunk format is described here: https://github.com/scroll-tech/scroll-contracts/blob/main/src/libraries/codec/ChunkCodecV1.sol + chunk_data + |> :binary.part(i * 60, 8) + |> :binary.decode_unsigned() + end) + + acc ++ chunk_l2_block_numbers + end) + |> Enum.min_max() + |> BlockRange.cast() + + l2_block_range + end + + # Imports batches and bundles into the database. + # + # ## Parameters + # - `batches`: List of batch data to be imported. + # - `bundles`: List of bundle data to be imported. + # - `start_by_final_batch_number`: A map defining start batch number by final one for bundles. + # + # ## Returns + # - The result of the database operations. + @spec import_items([Batch.to_import()], [%{atom() => any()}], %{non_neg_integer() => non_neg_integer()}) :: any() + defp import_items([], [], _), do: :ok + + defp import_items(batches, bundles, start_by_final_batch_number) do + {:ok, inserts} = + Chain.import(%{ + scroll_batch_bundles: %{params: bundles}, + scroll_batches: %{params: batches}, + timeout: :infinity + }) + + multi = + inserts + |> Map.get(:insert_scroll_batch_bundles, []) + |> Enum.reduce(Multi.new(), fn bundle, multi_acc -> + start_batch_number = start_by_final_batch_number[bundle.final_batch_number] + + Multi.update_all( + multi_acc, + bundle.id, + from(b in Batch, where: b.number >= ^start_batch_number and b.number <= ^bundle.final_batch_number), + set: [bundle_id: bundle.id] + ) + end) + + Repo.transaction(multi) + end + + # Prepares batch and bundle items from Scroll events for database import. + # + # This function processes a list of CommitBatch and FinalizeBatch events, + # extracting relevant information to create batch and bundle records. + # + # ## Parameters + # - `events`: A list of Scroll events to process. + # - `json_rpc_named_arguments`: Configuration parameters for the JSON RPC connection. + # + # ## Returns + # A tuple containing two lists and a map: + # - List of batches, ready for import to the DB. + # - List of structures describing L1 transactions finalizing batches in form of + # bundles, ready for import to the DB. + # - A map defining start batch number by final one for bundles. + @spec prepare_items([%{atom() => any()}], EthereumJSONRPC.json_rpc_named_arguments()) :: + {[Batch.to_import()], [%{atom() => any()}], %{non_neg_integer() => non_neg_integer()}} + defp prepare_items([], _), do: {[], [], %{}} + + defp prepare_items(events, json_rpc_named_arguments) do + blocks = Helper.get_blocks_by_events(events, json_rpc_named_arguments, Helper.infinite_retries_number(), true) + + commit_transaction_hashes = + events + |> Enum.reduce([], fn event, acc -> + if event.first_topic == @commit_batch_event do + [event.transaction_hash | acc] + else + acc + end + end) + + commit_transaction_input_by_hash = get_transaction_input_by_hash(blocks, commit_transaction_hashes) + + timestamps = + blocks + |> 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) + + prev_final_batch_number = Reader.last_final_batch_number() + + {_, batches, bundles, start_by_final_batch_number} = + events + |> Enum.reduce({prev_final_batch_number, [], [], %{}}, fn event, + {prev_final_batch_number_acc, batches_acc, bundles_acc, + start_by_final_batch_number_acc} -> + batch_number = quantity_to_integer(event.second_topic) + + if event.first_topic == @commit_batch_event do + commit_block_number = quantity_to_integer(event.block_number) + + # credo:disable-for-lines:2 Credo.Check.Refactor.Nesting + {l2_block_range, container} = + if batch_number == 0 do + {:ok, range} = BlockRange.cast("[0,0]") + {range, :in_calldata} + else + {input, blob_versioned_hashes} = + commit_transaction_input_by_hash + |> Map.get(event.transaction_hash) + + # credo:disable-for-lines:2 Credo.Check.Refactor.Nesting + container = + if is_nil(blob_versioned_hashes) or blob_versioned_hashes == [] do + :in_calldata + else + :in_blob4844 + end + + {input_to_l2_block_range(input), container} + end + + new_batches_acc = [ + %{ + number: batch_number, + commit_transaction_hash: event.transaction_hash, + commit_block_number: commit_block_number, + commit_timestamp: Map.get(timestamps, commit_block_number), + l2_block_range: l2_block_range, + container: container + } + | batches_acc + ] + + {prev_final_batch_number_acc, new_batches_acc, bundles_acc, start_by_final_batch_number_acc} + else + finalize_block_number = quantity_to_integer(event.block_number) + + new_bundles_acc = [ + %{ + final_batch_number: batch_number, + finalize_transaction_hash: event.transaction_hash, + finalize_block_number: finalize_block_number, + finalize_timestamp: Map.get(timestamps, finalize_block_number) + } + | bundles_acc + ] + + start_batch_number = prev_final_batch_number_acc + 1 + + new_start_by_final_batch_number_acc = + Map.put(start_by_final_batch_number_acc, batch_number, start_batch_number) + + {batch_number, batches_acc, new_bundles_acc, new_start_by_final_batch_number_acc} + end + end) + + {batches, bundles, start_by_final_batch_number} + end + + # Handles L1 block reorg: removes all batch rows from the `scroll_batches` table + # created beginning from the reorged block. Also, removes the corresponding rows from + # the `scroll_batch_bundles` table. + # + # ## Parameters + # - `reorg_block`: the block number where reorg has occurred. + # + # ## Returns + # - nothing + @spec reorg_handle(non_neg_integer()) :: any() + defp reorg_handle(reorg_block) do + bundle_ids = + Repo.all( + from(b in Batch, + select: b.bundle_id, + where: b.commit_block_number >= ^reorg_block, + group_by: b.bundle_id + ) + ) + + {:ok, result} = + Multi.new() + |> Multi.delete_all(:delete_batches, from(b in Batch, where: b.bundle_id in ^bundle_ids)) + |> Multi.delete_all( + :delete_bundles, + from(bb in BatchBundle, where: bb.id in ^bundle_ids or bb.finalize_block_number >= ^reorg_block) + ) + |> Repo.transaction() + + deleted_batches_count = elem(result.delete_batches, 0) + deleted_bundles_count = elem(result.delete_bundles, 0) + + if deleted_batches_count > 0 do + Logger.warning( + "As L1 reorg was detected, some batches with commit_block_number >= #{reorg_block} were removed from the scroll_batches table. Number of removed rows: #{deleted_batches_count}." + ) + end + + if deleted_bundles_count > 0 do + Logger.warning( + "As L1 reorg was detected, some bundles with finalize_block_number >= #{reorg_block} were removed from the scroll_batch_bundles table. Number of removed rows: #{deleted_bundles_count}." + ) + end + end +end diff --git a/apps/indexer/lib/indexer/fetcher/scroll/bridge.ex b/apps/indexer/lib/indexer/fetcher/scroll/bridge.ex new file mode 100644 index 0000000..945e62d --- /dev/null +++ b/apps/indexer/lib/indexer/fetcher/scroll/bridge.ex @@ -0,0 +1,338 @@ +defmodule Indexer.Fetcher.Scroll.Bridge do + @moduledoc """ + Contains common functions for Indexer.Fetcher.Scroll.Bridge* modules. + """ + + require Logger + + import EthereumJSONRPC, + only: [ + quantity_to_integer: 1, + timestamp_to_datetime: 1 + ] + + import Explorer.Helper, only: [decode_data: 2] + + alias EthereumJSONRPC.Logs + alias Explorer.Chain + alias Explorer.Chain.RollupReorgMonitorQueue + alias Indexer.Fetcher.Scroll.BridgeL1 + alias Indexer.Helper, as: IndexerHelper + + # 32-byte signature of the event SentMessage(address indexed sender, address indexed target, uint256 value, uint256 messageNonce, uint256 gasLimit, bytes message) + @sent_message_event "0x104371f3b442861a2a7b82a070afbbaab748bb13757bf47769e170e37809ec1e" + @sent_message_event_params [{:uint, 256}, {:uint, 256}, {:uint, 256}, :bytes] + + # 32-byte signature of the event RelayedMessage(bytes32 indexed messageHash) + @relayed_message_event "0x4641df4a962071e12719d8c8c8e5ac7fc4d97b927346a3d7a335b1f7517e133c" + + @doc """ + The main function that scans RPC node for the message logs (events), parses them, + and imports to the database (scroll_bridge table). + + The function works for both L1 and L2 layers (depending on the `module` parameter). + It splits a given block range by chunks and scans the messenger contract for the message + logs (events) for each chunk. The found events are handled and then imported to the + `scroll_bridge` database table. + + After historical block range is covered, the function switches to realtime mode and + searches for the message events in every new block. Reorg blocks are taken into account. + + ## Parameters + - `module`: The module from which the function was called: Indexer.Fetcher.Scroll.BridgeL1 or Indexer.Fetcher.Scroll.BridgeL2. + - `state`: The state map containing needed data such as messenger contract address and the block range. + + ## Returns + - {:noreply, state} tuple with the updated block range in the `state` to scan logs in. + """ + @spec loop(module(), %{ + block_check_interval: non_neg_integer(), + messenger_contract: binary(), + json_rpc_named_arguments: EthereumJSONRPC.json_rpc_named_arguments(), + end_block: non_neg_integer(), + start_block: non_neg_integer() + }) :: + {:noreply, + %{ + block_check_interval: non_neg_integer(), + messenger_contract: binary(), + json_rpc_named_arguments: EthereumJSONRPC.json_rpc_named_arguments(), + end_block: non_neg_integer(), + start_block: non_neg_integer() + }} + def loop( + module, + %{ + block_check_interval: block_check_interval, + messenger_contract: messenger_contract, + json_rpc_named_arguments: json_rpc_named_arguments, + end_block: end_block, + start_block: start_block + } = state + ) do + {layer, eth_get_logs_range_size_config} = + if module == BridgeL1 do + {:L1, :l1_eth_get_logs_range_size} + else + {:L2, :l2_eth_get_logs_range_size} + end + + eth_get_logs_range_size = Application.get_all_env(:indexer)[Indexer.Fetcher.Scroll][eth_get_logs_range_size_config] + + time_before = Timex.now() + + last_written_block = + start_block..end_block + |> Enum.chunk_every(eth_get_logs_range_size) + |> Enum.reduce_while(start_block - 1, fn current_chunk, _ -> + chunk_start = List.first(current_chunk) + chunk_end = List.last(current_chunk) + + if chunk_start <= chunk_end do + IndexerHelper.log_blocks_chunk_handling(chunk_start, chunk_end, start_block, end_block, nil, layer) + + operations = + {chunk_start, chunk_end} + |> get_logs_all(messenger_contract, json_rpc_named_arguments) + |> prepare_operations(layer == :L1, json_rpc_named_arguments) + + import_operations(operations) + + IndexerHelper.log_blocks_chunk_handling( + chunk_start, + chunk_end, + start_block, + end_block, + "#{Enum.count(operations)} #{layer} operation(s)", + layer + ) + end + + reorg_block = RollupReorgMonitorQueue.reorg_block_pop(module) + + if !is_nil(reorg_block) && reorg_block > 0 do + # credo:disable-for-next-line Credo.Check.Refactor.Nesting + if layer == :L1 do + BridgeL1.reorg_handle(reorg_block) + end + + {: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} = + IndexerHelper.get_block_number_by_tag("latest", json_rpc_named_arguments, IndexerHelper.infinite_retries_number()) + + 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(Process.whereis(module), :continue, delay) + + {:noreply, %{state | start_block: new_start_block, end_block: new_end_block}} + end + + # Fetches `SentMessage` and `RelayedMessage` events of the messenger contract from an RPC node + # for the given range of blocks. + @spec get_logs_all({non_neg_integer(), non_neg_integer()}, binary(), EthereumJSONRPC.json_rpc_named_arguments()) :: + [%{atom() => any()}] + defp get_logs_all({chunk_start, chunk_end}, messenger_contract, json_rpc_named_arguments) do + {:ok, result} = + IndexerHelper.get_logs( + chunk_start, + chunk_end, + messenger_contract, + [[@sent_message_event, @relayed_message_event]], + json_rpc_named_arguments, + 0, + IndexerHelper.infinite_retries_number() + ) + + Logs.elixir_to_params(result) + end + + # Imports the given Scroll messages into database. + # Used by Indexer.Fetcher.Scroll.BridgeL1 and Indexer.Fetcher.Scroll.BridgeL2 fetchers. + # Doesn't return anything. + @spec import_operations([Chain.Scroll.Bridge.to_import()]) :: no_return() + defp import_operations(operations) do + {:ok, _} = + Chain.import(%{ + scroll_bridge_operations: %{params: operations}, + timeout: :infinity + }) + end + + # Converts the list of Scroll messenger events to the list of operations + # preparing them for importing to the database. + @spec prepare_operations([%{atom() => any()}], boolean(), EthereumJSONRPC.json_rpc_named_arguments()) :: [ + Chain.Scroll.Bridge.to_import() + ] + defp prepare_operations(events, is_l1, json_rpc_named_arguments) do + block_to_timestamp = + events + |> Enum.filter(fn event -> event.first_topic == @sent_message_event end) + |> blocks_to_timestamps(json_rpc_named_arguments) + + events + |> Enum.map(fn event -> + {index, amount, block_number, block_timestamp, message_hash} = + case event.first_topic do + @sent_message_event -> + { + sender, + target, + amount, + index, + message + } = sent_message_event_parse(event) + + block_number = quantity_to_integer(event.block_number) + block_timestamp = Map.get(block_to_timestamp, block_number) + + operation_encoded = + ABI.encode("relayMessage(address,address,uint256,uint256,bytes)", [ + sender |> :binary.decode_unsigned(), + target |> :binary.decode_unsigned(), + amount, + index, + message + ]) + + message_hash = + "0x" <> + (operation_encoded + |> ExKeccak.hash_256() + |> Base.encode16(case: :lower)) + + {index, amount, block_number, block_timestamp, message_hash} + + @relayed_message_event -> + message_hash = + event.second_topic + |> String.trim_leading("0x") + |> Base.decode16!(case: :mixed) + + {nil, nil, nil, nil, message_hash} + end + + result = %{ + type: operation_type(event.first_topic, is_l1), + message_hash: message_hash + } + + transaction_hash_field = + if is_l1 do + :l1_transaction_hash + else + :l2_transaction_hash + end + + result + |> extend_result(:index, index) + |> extend_result(transaction_hash_field, event.transaction_hash) + |> extend_result(:amount, amount) + |> extend_result(:block_number, block_number) + |> extend_result(:block_timestamp, block_timestamp) + end) + end + + # Constructs a map defining block timestamps by block numbers (key-value pairs) + # from the list of events. The keys of the resulting map consist of block numbers, + # the values are timestamps. + # + # ## Parameters + # - `events`: The list of events based on which the map is constructed. + # - `json_rpc_named_arguments`: Configuration parameters for the JSON RPC connection. + # + # ## Returns + # - A dictionary associating block timestamps with the their numbers. + @spec blocks_to_timestamps([%{atom() => any()}], EthereumJSONRPC.json_rpc_named_arguments()) :: %{ + non_neg_integer() => DateTime.t() + } + defp blocks_to_timestamps(events, json_rpc_named_arguments) do + events + |> IndexerHelper.get_blocks_by_events(json_rpc_named_arguments, IndexerHelper.infinite_retries_number()) + |> Enum.reduce(%{}, fn block, acc -> + block_number = quantity_to_integer(Map.get(block, "number")) + timestamp = timestamp_to_datetime(Map.get(block, "timestamp")) + Map.put(acc, block_number, timestamp) + end) + end + + # Parses the `SentMessage` event to retrieve message sender address, + # target address, amount, index, body. This components are returned + # in the tuple. + # + # ## Parameters + # - `event`: A map describing the event which needs to be parsed. + # + # ## Returns + # - `{sender, target, amount, index, message}` where + # the `sender` is the message sender address, + # the `target` is the target address, + # the `amount` is the amount of the native token sent within this message, + # the `index` is the message numeric index, + # the `message` is the message body. + @spec sent_message_event_parse(%{atom() => any()}) :: + {binary(), binary(), non_neg_integer(), non_neg_integer(), binary()} + defp sent_message_event_parse(event) do + sender = + event.second_topic + |> String.trim_leading("0x") + |> Base.decode16!(case: :mixed) + + target = + event.third_topic + |> String.trim_leading("0x") + |> Base.decode16!(case: :mixed) + + [ + amount, + index, + _gas_limit, + message + ] = decode_data(event.data, @sent_message_event_params) + + {sender, target, amount, index, message} + end + + # Determines the type of the bridge operation based on the event and the chain layer (L1 or L2). + # + # ## Parameters + # - `first_topic`: The signature of the event (in 0x-prefixed hex string). + # - `is_l1`: A boolean flag defining whether the chain layer is L1. + # + # ## Returns + # - :deposit or :withdrawal + @spec operation_type(binary(), boolean()) :: :deposit | :withdrawal + defp operation_type(first_topic, is_l1) do + if first_topic == @sent_message_event do + if is_l1, do: :deposit, else: :withdrawal + else + if is_l1, do: :withdrawal, else: :deposit + end + end + + # Extends the resulting map with the key-value pair. Used by the `prepare_operations` function. + # If the value is `nil`, the key-value pair is not added. + # + # ## Parameters + # - `result`: The resulting map to be extended. + # - `key`: The key component of the key-value pair. + # - `value`: The value component of the key-value pair. + # + # ## Returns + # - The extended map. + @spec extend_result(map(), atom(), any()) :: map() + defp extend_result(result, _key, value) when is_nil(value), do: result + defp extend_result(result, key, value) when is_atom(key), do: Map.put(result, key, value) +end diff --git a/apps/indexer/lib/indexer/fetcher/scroll/bridge_l1.ex b/apps/indexer/lib/indexer/fetcher/scroll/bridge_l1.ex new file mode 100644 index 0000000..be75a22 --- /dev/null +++ b/apps/indexer/lib/indexer/fetcher/scroll/bridge_l1.ex @@ -0,0 +1,206 @@ +defmodule Indexer.Fetcher.Scroll.BridgeL1 do + @moduledoc """ + The module for scanning Scroll RPC node on L1 for the message logs (events), parsing them, + and importing to the database (scroll_bridge table). + + The events discovery logic is located in the `Indexer.Fetcher.Scroll.Bridge` module whereas this module + only prepares required parameters for the discovery loop. + + The main function splits the whole block range by chunks and scans L1 Scroll Messenger contract + for the message logs (events) for each chunk. The found events are handled and then imported to the + `scroll_bridge` database table. + + After historical block range is covered, the process switches to realtime mode and + searches for the message events in every new block. Reorg blocks are taken into account. + """ + + use GenServer + use Indexer.Fetcher + + require Logger + + import Ecto.Query + + alias Explorer.Chain.Scroll.{Bridge, Reader} + alias Explorer.Repo + alias Indexer.Fetcher.RollupL1ReorgMonitor + alias Indexer.Fetcher.Scroll.Bridge, as: BridgeFetcher + alias Indexer.Fetcher.Scroll.Helper, as: ScrollHelper + alias Indexer.Helper + + @fetcher_name :scroll_bridge_l1 + + 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 + {:ok, %{}, {:continue, :ok}} + end + + @impl GenServer + def handle_continue(_, state) do + Logger.metadata(fetcher: @fetcher_name) + # two seconds pause needed to avoid exceeding Supervisor restart intensity when DB issues + Process.send_after(self(), :init_with_delay, 2000) + {:noreply, state} + end + + # Validates parameters and initiates searching of the events. + # + # When first launch, the events searching will start from the given start block + # and end on the `safe` block (or `latest` one if `safe` is not available). + # If this is not the first launch, the process will start from the block which was + # the last on the previous launch. + @impl GenServer + def handle_info(:init_with_delay, _state) do + env = Application.get_all_env(:indexer)[__MODULE__] + + with {:start_block_undefined, false} <- {:start_block_undefined, is_nil(env[:start_block])}, + {:reorg_monitor_started, true} <- {:reorg_monitor_started, !is_nil(Process.whereis(RollupL1ReorgMonitor))}, + rpc = l1_rpc_url(), + {:rpc_undefined, false} <- {:rpc_undefined, is_nil(rpc)}, + {:messenger_contract_address_is_valid, true} <- + {:messenger_contract_address_is_valid, Helper.address_correct?(env[:messenger_contract])}, + start_block = env[:start_block], + true <- start_block > 0, + {last_l1_block_number, last_l1_transaction_hash} = Reader.last_l1_bridge_item(), + json_rpc_named_arguments = Helper.json_rpc_named_arguments(rpc), + {:ok, block_check_interval, safe_block} <- Helper.get_block_check_interval(json_rpc_named_arguments), + {:start_block_valid, true, _, _} <- + {:start_block_valid, + (start_block <= last_l1_block_number || last_l1_block_number == 0) && start_block <= safe_block, + last_l1_block_number, safe_block}, + {:ok, last_l1_transaction} <- + Helper.get_transaction_by_hash(last_l1_transaction_hash, json_rpc_named_arguments), + # here we check for the last known L1 transaction existence to make sure there wasn't reorg + # on L1 while the instance was down, and so we can use `last_l1_block_number` as the starting point + {:l1_transaction_not_found, false} <- + {:l1_transaction_not_found, !is_nil(last_l1_transaction_hash) && is_nil(last_l1_transaction)} do + Process.send(self(), :continue, []) + + {:noreply, + %{ + block_check_interval: block_check_interval, + messenger_contract: env[:messenger_contract], + json_rpc_named_arguments: json_rpc_named_arguments, + end_block: safe_block, + start_block: max(start_block, last_l1_block_number) + }} + else + {:start_block_undefined, true} -> + # the process shouldn't start if the start block is not defined + {:stop, :normal, %{}} + + {:reorg_monitor_started, false} -> + Logger.error("Cannot start this process as Indexer.Fetcher.RollupL1ReorgMonitor is not started.") + {:stop, :normal, %{}} + + {:rpc_undefined, true} -> + Logger.error("L1 RPC URL is not defined.") + {:stop, :normal, %{}} + + {:messenger_contract_address_is_valid, false} -> + Logger.error("L1ScrollMessenger contract address is invalid or not defined.") + {:stop, :normal, %{}} + + {:start_block_valid, false, last_l1_block_number, safe_block} -> + Logger.error("Invalid L1 Start Block value. Please, check the value and scroll_bridge table.") + Logger.error("last_l1_block_number = #{inspect(last_l1_block_number)}") + Logger.error("safe_block = #{inspect(safe_block)}") + {:stop, :normal, %{}} + + {:error, error_data} -> + Logger.error( + "Cannot get last L1 transaction from RPC by its hash, latest block, or block timestamp by its number due to RPC error: #{inspect(error_data)}" + ) + + {:stop, :normal, %{}} + + {:l1_transaction_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 scroll_bridge table." + ) + + {:stop, :normal, %{}} + + _ -> + Logger.error("L1 Start Block is invalid or zero.") + {:stop, :normal, %{}} + end + end + + # See the description of the `loop` function. + @impl GenServer + def handle_info(:continue, state) do + BridgeFetcher.loop(__MODULE__, state) + end + + @impl GenServer + def handle_info({ref, _result}, state) do + Process.demonitor(ref, [:flush]) + {:noreply, state} + end + + @doc """ + Handles L1 block reorg: removes all Deposit rows from the `scroll_bridge` table + created beginning from the reorged block. + + We only store block number for the initiating transaction of the L1->L2 or L2->L1 message, + so the `block_number` column doesn't contain L2 block numbers for messages initiated on L1 layer (i.e. for Deposits), + and that doesn't contain L1 block numbers for messages initiated on L2 layer (i.e. for Withdrawals). + This is the reason why we can only remove rows for Deposit operations from the `scroll_bridge` table + when a reorg happens on L1 layer. + + ## Parameters + - `reorg_block`: The block number where reorg has occurred. + + ## Returns + - Nothing. + """ + @spec reorg_handle(non_neg_integer()) :: any() + def reorg_handle(reorg_block) do + {deleted_count, _} = + Repo.delete_all(from(b in Bridge, where: b.type == :deposit and b.block_number >= ^reorg_block)) + + if deleted_count > 0 do + Logger.warning( + "As L1 reorg was detected, some deposits with block_number >= #{reorg_block} were removed from scroll_bridge table. Number of removed rows: #{deleted_count}." + ) + end + end + + @doc """ + Returns L1 RPC URL for this module. + Returns `nil` if not defined. + """ + @spec l1_rpc_url() :: binary() | nil + def l1_rpc_url do + ScrollHelper.l1_rpc_url() + end + + @doc """ + Determines if `Indexer.Fetcher.RollupL1ReorgMonitor` module must be up + for this module. + + ## Returns + - `true` if the reorg monitor must be active, `false` otherwise. + """ + @spec requires_l1_reorg_monitor?() :: boolean() + def requires_l1_reorg_monitor? do + module_config = Application.get_all_env(:indexer)[__MODULE__] + not is_nil(module_config[:start_block]) + end +end diff --git a/apps/indexer/lib/indexer/fetcher/scroll/bridge_l2.ex b/apps/indexer/lib/indexer/fetcher/scroll/bridge_l2.ex new file mode 100644 index 0000000..74504bb --- /dev/null +++ b/apps/indexer/lib/indexer/fetcher/scroll/bridge_l2.ex @@ -0,0 +1,155 @@ +defmodule Indexer.Fetcher.Scroll.BridgeL2 do + @moduledoc """ + The module for scanning Scroll RPC node on L2 for the message logs (events), parsing them, + and importing to the database (scroll_bridge table). + + The events discovery logic is located in the `Indexer.Fetcher.Scroll.Bridge` module whereas this module + only prepares required parameters for the discovery loop. + + The main function splits the whole block range by chunks and scans L2 Scroll Messenger contract + for the message logs (events) for each chunk. The found events are handled and then imported to the + `scroll_bridge` database table. + + After historical block range is covered, the process switches to realtime mode and + searches for the message events in every new block. Reorg blocks are taken into account. + """ + + use GenServer + use Indexer.Fetcher + + require Logger + + import Ecto.Query + + alias Explorer.Chain.RollupReorgMonitorQueue + alias Explorer.Chain.Scroll.{Bridge, Reader} + alias Explorer.Repo + alias Indexer.Fetcher.Scroll.Bridge, as: BridgeFetcher + alias Indexer.Helper + + @fetcher_name :scroll_bridge_l2 + + 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 + json_rpc_named_arguments = args[:json_rpc_named_arguments] + {:ok, %{}, {:continue, json_rpc_named_arguments}} + end + + @impl GenServer + def handle_continue(json_rpc_named_arguments, _state) do + Logger.metadata(fetcher: @fetcher_name) + # two seconds pause needed to avoid exceeding Supervisor restart intensity when DB issues + Process.send_after(self(), :init_with_delay, 2000) + {:noreply, %{json_rpc_named_arguments: json_rpc_named_arguments}} + end + + # Validates parameters and initiates searching of the events. + # + # When first launch, the events searching will start from the first block of the chain + # and end on the `latest` one. If this is not the first launch, the process will start + # from the block which was the last on the previous launch. + @impl GenServer + def handle_info(:init_with_delay, %{json_rpc_named_arguments: json_rpc_named_arguments} = state) do + env = Application.get_all_env(:indexer)[__MODULE__] + + with {:messenger_contract_address_is_valid, true} <- + {:messenger_contract_address_is_valid, Helper.address_correct?(env[:messenger_contract])}, + {last_l2_block_number, last_l2_transaction_hash} = Reader.last_l2_bridge_item(), + {:ok, block_check_interval, _} <- Helper.get_block_check_interval(json_rpc_named_arguments), + {:ok, latest_block} = Helper.get_block_number_by_tag("latest", json_rpc_named_arguments, 100_000_000), + {:ok, last_l2_transaction} <- + Helper.get_transaction_by_hash(last_l2_transaction_hash, json_rpc_named_arguments), + # here we check for the last known L2 transaction existence to make sure there wasn't reorg + # on L2 while the instance was down, and so we can use `last_l2_block_number` as the starting point + {:l2_transaction_not_found, false} <- + {:l2_transaction_not_found, !is_nil(last_l2_transaction_hash) && is_nil(last_l2_transaction)} do + Process.send(self(), :continue, []) + + {:noreply, + %{ + block_check_interval: block_check_interval, + messenger_contract: env[:messenger_contract], + json_rpc_named_arguments: json_rpc_named_arguments, + end_block: latest_block, + start_block: max(env[:start_block], last_l2_block_number) + }} + else + {:messenger_contract_address_is_valid, false} -> + Logger.error("L2ScrollMessenger contract address is invalid or not defined.") + {:stop, :normal, state} + + {:error, error_data} -> + Logger.error( + "Cannot get last L2 transaction from RPC by its hash, latest block, or block by number due to RPC error: #{inspect(error_data)}" + ) + + {:stop, :normal, state} + + {:l2_transaction_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 scroll_bridge table." + ) + + {:stop, :normal, state} + end + end + + # See the description of the `loop` function. + @impl GenServer + def handle_info(:continue, state) do + BridgeFetcher.loop(__MODULE__, state) + end + + @impl GenServer + def handle_info({ref, _result}, state) do + Process.demonitor(ref, [:flush]) + {:noreply, state} + end + + @doc """ + Handles L2 block reorg: removes all Withdrawal rows from the `scroll_bridge` table + created beginning from the reorged block. + + We only store block number for the initiating transaction of the L1->L2 or L2->L1 message, + so the `block_number` column doesn't contain L2 block numbers for messages initiated on L1 layer (i.e. for Deposits), + and that doesn't contain L1 block numbers for messages initiated on L2 layer (i.e. for Withdrawals). + This is the reason why we can only remove rows for Withdrawal operations from the `scroll_bridge` table + when a reorg happens on L2 layer. + + Also, the reorg block number is put into the reorg monitor queue to let the main loop function + (see `Indexer.Fetcher.Scroll.Bridge` module) use that block number and behave accordingly. + + ## Parameters + - `reorg_block`: The block number where reorg has occurred. + + ## Returns + - nothing + """ + def reorg_handle(reorg_block) do + {deleted_count, _} = + Repo.delete_all(from(b in Bridge, where: b.type == :withdrawal and b.block_number >= ^reorg_block)) + + if deleted_count > 0 do + Logger.warning( + "As L2 reorg was detected, some withdrawals with block_number >= #{reorg_block} were removed from scroll_bridge table. Number of removed rows: #{deleted_count}." + ) + end + + RollupReorgMonitorQueue.reorg_block_push(reorg_block, __MODULE__) + end +end diff --git a/apps/indexer/lib/indexer/fetcher/scroll/helper.ex b/apps/indexer/lib/indexer/fetcher/scroll/helper.ex new file mode 100644 index 0000000..574ff1a --- /dev/null +++ b/apps/indexer/lib/indexer/fetcher/scroll/helper.ex @@ -0,0 +1,14 @@ +defmodule Indexer.Fetcher.Scroll.Helper do + @moduledoc """ + A module to define common Scroll indexer functions. + """ + + @doc """ + Returns L1 RPC URL for Scroll modules. + Returns `nil` if not defined. + """ + @spec l1_rpc_url() :: binary() | nil + def l1_rpc_url do + Application.get_all_env(:indexer)[Indexer.Fetcher.Scroll][:rpc] + end +end diff --git a/apps/indexer/lib/indexer/fetcher/scroll/l1_fee_param.ex b/apps/indexer/lib/indexer/fetcher/scroll/l1_fee_param.ex new file mode 100644 index 0000000..c0b8d0c --- /dev/null +++ b/apps/indexer/lib/indexer/fetcher/scroll/l1_fee_param.ex @@ -0,0 +1,355 @@ +defmodule Indexer.Fetcher.Scroll.L1FeeParam do + @moduledoc """ + Fills scroll_l1_fee_params DB table. + + The table stores points in the chain (block number and transaction index within it) + when L1 Gas Oracle contract parameters were changed (and the new values of the changed + parameters). These points and values are then used by API to correctly display L1 fee + parameters of L2 transaction (such as `overhead`, `scalar`, etc.) + + This fetcher handles the events that were not handled by the realtime block fetcher + (namely `Indexer.Transform.Scroll.L1FeeParams` module). There are three possible cases when it happens: + 1. A Blockscout instance is deployed for a chain that already has blocks. + 2. A Blockscout instance is upgraded, and the functionality to discover fee parameter changes only becomes available after the upgrade. + 3. The block fetcher process (or entire instance) was halted for some time. + + Example of the parameter value change: + + Let's assume that the `scalar` parameter is initially set to 100. An owner decided + to change it to 200. It initiates a transaction that is included into block number 800 + under index 3. All transactions starting from block number 800 and index 4 will have + the scalar equal to 200. All transactions before index 3 of the block 800 (and preceding + blocks) will have the scalar equal to 100. + """ + + use GenServer + use Indexer.Fetcher + + require Logger + + import Ecto.Query + + import EthereumJSONRPC, only: [quantity_to_integer: 1] + import Explorer.Helper, only: [decode_data: 2] + + alias Explorer.{Chain, Repo} + alias Explorer.Chain.Data + alias Explorer.Chain.Scroll.L1FeeParam, as: ScrollL1FeeParam + alias Indexer.Helper + + @fetcher_name :scroll_l1_fee_params + + # 32-byte signature of the event OverheadUpdated(uint256 overhead) + @overhead_updated_event "0x32740b35c0ea213650f60d44366b4fb211c9033b50714e4a1d34e65d5beb9bb4" + + # 32-byte signature of the event ScalarUpdated(uint256 scalar) + @scalar_updated_event "0x3336cd9708eaf2769a0f0dc0679f30e80f15dcd88d1921b5a16858e8b85c591a" + + # 32-byte signature of the event CommitScalarUpdated(uint256 scalar) + @commit_scalar_updated_event "0x2ab3f5a4ebbcbf3c24f62f5454f52f10e1a8c9dcc5acac8f19199ce881a6a108" + + # 32-byte signature of the event BlobScalarUpdated(uint256 scalar) + @blob_scalar_updated_event "0x6b332a036d8c3ead57dcb06c87243bd7a2aed015ddf2d0528c2501dae56331aa" + + # 32-byte signature of the event L1BaseFeeUpdated(uint256 l1BaseFee) + @l1_base_fee_updated_event "0x351fb23757bb5ea0546c85b7996ddd7155f96b939ebaa5ff7bc49c75f27f2c44" + + # 32-byte signature of the event L1BlobBaseFeeUpdated(uint256 l1BlobBaseFee) + @l1_blob_base_fee_updated_event "0x9a14bfb5d18c4c3cf14cae19c23d7cf1bcede357ea40ca1f75cd49542c71c214" + + 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 + json_rpc_named_arguments = args[:json_rpc_named_arguments] + {:ok, %{}, {:continue, json_rpc_named_arguments}} + end + + # Validates L1 Gas Oracle contract address and initiates searching of gas oracle events. + # + # When first launch, the events searching will start from the first block of the chain + # and end on the `safe` block (or `latest` one if `safe` is not available). + # If this is not the first launch, the process will start from the block which was + # the last on the previous launch (plus one). The block from the previous launch + # is stored in the `last_fetched_counters` database table. + # + # ## Parameters + # - `json_rpc_named_arguments`: Configuration parameters for the JSON RPC connection on L2. + # - `state`: The current state of the fetcher. + # + # ## Returns + # - `{:noreply, new_state}` where the searching parameters are defined. + # - `{:stop, :normal, state}` in case of invalid L1 Gas Oracle contract address. + @impl GenServer + def handle_continue(json_rpc_named_arguments, state) do + Logger.metadata(fetcher: @fetcher_name) + + env = Application.get_all_env(:indexer)[__MODULE__] + + if Helper.address_correct?(env[:gas_oracle]) do + last_l2_block_number = ScrollL1FeeParam.last_l2_block_number() + + {safe_block, safe_block_is_latest} = Helper.get_safe_block(json_rpc_named_arguments) + + Process.send(self(), :find_events, []) + + {:noreply, + %{ + start_block: min(last_l2_block_number + 1, safe_block), + safe_block: safe_block, + safe_block_is_latest: safe_block_is_latest, + gas_oracle: env[:gas_oracle], + eth_get_logs_range_size: + Application.get_all_env(:indexer)[Indexer.Fetcher.Scroll][:l2_eth_get_logs_range_size], + json_rpc_named_arguments: json_rpc_named_arguments + }} + else + Logger.error("L1 Gas Oracle contract address is invalid or not defined.") + {:stop, :normal, state} + end + end + + # Scans the L1 Gas Oracle contract for the events and saves the found parameter changes + # into `scroll_l1_fee_params` database table. + # + # The scanning process starts from the `start_block` defined by `handle_continue` function + # and ends with the latest one. The `safe_block` can be the latest block if the `safe` one + # is not available on the chain (in this case `safe_block_is_latest` is true). So the process + # works in the following block ranges: `start_block..safe_block` and `(safe_block + 1)..latest_block` + # or `start_block..latest`. + # + # ## Parameters + # - `:find_events`: The message that triggers the event scanning process. + # - `state`: The current state of the fetcher containing the searching parameters. + # + # ## Returns + # - `{:stop, :normal, state}` as a signal for the fetcher to stop working after all blocks are handled. + @impl GenServer + def handle_info( + :find_events, + %{ + start_block: start_block, + safe_block: safe_block, + safe_block_is_latest: safe_block_is_latest, + gas_oracle: gas_oracle, + eth_get_logs_range_size: eth_get_logs_range_size, + 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) + scan_block_range(start_block, safe_block, gas_oracle, eth_get_logs_range_size, 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} = Helper.get_block_number_by_tag("latest", json_rpc_named_arguments) + scan_block_range(safe_block + 1, latest_block, gas_oracle, eth_get_logs_range_size, 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 + + @doc """ + Handles L2 block reorg: removes all rows from the `scroll_l1_fee_params` table + created beginning from the reorged block, and accordingly reduces the last + block number defined in the `last_fetched_counters` database table. + + ## Parameters + - `starting_block`: the block number where reorg has occurred. + + ## Returns + - nothing + """ + @spec handle_l2_reorg(non_neg_integer()) :: any() + def handle_l2_reorg(starting_block) do + Repo.delete_all(from(p in ScrollL1FeeParam, where: p.block_number >= ^starting_block)) + + if ScrollL1FeeParam.last_l2_block_number() >= starting_block do + ScrollL1FeeParam.set_last_l2_block_number(starting_block - 1) + end + end + + @doc """ + Converts event parameters and data into the map which can be + used to write a new row to the `scroll_l1_fee_params` table. + + ## Parameters + - `first_topic`: The 32-byte hash of an event signature (in the form of `0x` prefixed hex string). + - `data`: The event data containing a changed parameter. + - `block_number`: The number of the block when the event transaction appeared. + - `transaction_index`: The event transaction index withing the `block_number` block. + + ## Returns + - A map for one row for `Chain.import` function. + """ + @spec event_to_param(binary(), Data.t(), non_neg_integer(), non_neg_integer()) :: ScrollL1FeeParam.to_import() + def event_to_param(first_topic, data, block_number, transaction_index) + when first_topic in [ + @overhead_updated_event, + @scalar_updated_event, + @commit_scalar_updated_event, + @blob_scalar_updated_event, + @l1_base_fee_updated_event, + @l1_blob_base_fee_updated_event + ] do + name = + case first_topic do + @overhead_updated_event -> :overhead + @scalar_updated_event -> :scalar + @commit_scalar_updated_event -> :commit_scalar + @blob_scalar_updated_event -> :blob_scalar + @l1_base_fee_updated_event -> :l1_base_fee + @l1_blob_base_fee_updated_event -> :l1_blob_base_fee + end + + [value] = decode_data(data, [{:uint, 256}]) + + %{ + block_number: block_number, + transaction_index: transaction_index, + name: name, + value: value + } + end + + @doc """ + Returns a list of signatures of the events that can be emitted + by L1 Gas Oracle contract. + """ + @spec event_signatures() :: [binary()] + def event_signatures do + [ + @overhead_updated_event, + @scalar_updated_event, + @commit_scalar_updated_event, + @blob_scalar_updated_event, + @l1_base_fee_updated_event, + @l1_blob_base_fee_updated_event + ] + end + + # Scans the L1 Gas Oracle contract for the events and saves the found parameter changes + # into `scroll_l1_fee_params` database table for the given L2 block range. + # + # The scanning process starts from the `l2_block_start` and ends with the `l2_block_end`. + # The block range is divided by chunks to avoid RPC node overloading. + # + # ## Parameters + # - `l2_block_start`: The start L2 block of the range. + # - `l2_block_end`: The end L2 block of the range. + # - `gas_oracle`: The L1 Gas Oracle contract address. + # - `eth_get_logs_range_size`: Max size of the blocks chunk. + # - `json_rpc_named_arguments`: Configuration parameters for the JSON RPC connection. + # + # ## Returns + # - Nothing is returned. + @spec scan_block_range( + non_neg_integer(), + non_neg_integer(), + binary(), + non_neg_integer(), + EthereumJSONRPC.json_rpc_named_arguments() + ) :: any() + defp scan_block_range(l2_block_start, l2_block_end, gas_oracle, eth_get_logs_range_size, json_rpc_named_arguments) do + chunks_number = ceil((l2_block_end - l2_block_start + 1) / eth_get_logs_range_size) + 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 = min(chunk_start + eth_get_logs_range_size - 1, l2_block_end) + + Helper.log_blocks_chunk_handling(chunk_start, chunk_end, l2_block_start, l2_block_end, nil, :L2) + + count = + find_and_save_params( + gas_oracle, + chunk_start, + chunk_end, + json_rpc_named_arguments + ) + + Helper.log_blocks_chunk_handling( + chunk_start, + chunk_end, + l2_block_start, + l2_block_end, + "#{count} event(s) for parameters update", + :L2 + ) + + count_acc + count + end) + end + + # Scans the L1 Gas Oracle contract for the events and saves the found parameter changes + # into `scroll_l1_fee_params` database table for the given L2 block range. + # + # The scanning process starts from the `block_start` and ends with the `block_end`. + # The `block_end` block number is stored in the `last_fetched_counters` database table + # to be able to start from that point at the next launch of the fetcher. + # + # ## Parameters + # - `gas_oracle`: The L1 Gas Oracle contract address. + # - `block_start`: The start L2 block of the range. + # - `block_end`: The end L2 block of the range. + # - `json_rpc_named_arguments`: Configuration parameters for the JSON RPC connection. + # + # ## Returns + # - The number of found and saved items. + @spec find_and_save_params(binary(), non_neg_integer(), non_neg_integer(), EthereumJSONRPC.json_rpc_named_arguments()) :: + non_neg_integer() + defp find_and_save_params( + gas_oracle, + block_start, + block_end, + json_rpc_named_arguments + ) do + {:ok, result} = + Helper.get_logs( + block_start, + block_end, + gas_oracle, + [event_signatures()], + json_rpc_named_arguments + ) + + l1_fee_params = + Enum.map(result, fn event -> + event_to_param( + Enum.at(event["topics"], 0), + event["data"], + quantity_to_integer(event["blockNumber"]), + quantity_to_integer(event["transactionIndex"]) + ) + end) + + {:ok, _} = + Chain.import(%{ + scroll_l1_fee_params: %{params: l1_fee_params}, + timeout: :infinity + }) + + ScrollL1FeeParam.set_last_l2_block_number(block_end) + + Enum.count(l1_fee_params) + end +end diff --git a/apps/indexer/lib/indexer/fetcher/shibarium/l1.ex b/apps/indexer/lib/indexer/fetcher/shibarium/l1.ex index 4a3889d..aa3f7a6 100644 --- a/apps/indexer/lib/indexer/fetcher/shibarium/l1.ex +++ b/apps/indexer/lib/indexer/fetcher/shibarium/l1.ex @@ -23,6 +23,7 @@ defmodule Indexer.Fetcher.Shibarium.L1 do import Indexer.Fetcher.Shibarium.Helper, only: [calc_operation_hash: 5, prepare_insert_items: 2, recalculate_cached_count: 0] + alias Explorer.Chain.RollupReorgMonitorQueue alias Explorer.Chain.Shibarium.Bridge alias Explorer.{Chain, Repo} alias Indexer.Fetcher.RollupL1ReorgMonitor @@ -135,8 +136,10 @@ defmodule Indexer.Fetcher.Shibarium.L1 do {:start_block_valid, true} <- {:start_block_valid, start_block <= last_l1_block_number || last_l1_block_number == 0}, json_rpc_named_arguments = json_rpc_named_arguments(rpc), - {:ok, last_l1_tx} <- Helper.get_transaction_by_hash(last_l1_transaction_hash, json_rpc_named_arguments), - {:l1_tx_not_found, false} <- {:l1_tx_not_found, !is_nil(last_l1_transaction_hash) && is_nil(last_l1_tx)}, + {:ok, last_l1_transaction} <- + Helper.get_transaction_by_hash(last_l1_transaction_hash, json_rpc_named_arguments), + {:l1_transaction_not_found, false} <- + {:l1_transaction_not_found, !is_nil(last_l1_transaction_hash) && is_nil(last_l1_transaction)}, {:ok, block_check_interval, latest_block} <- get_block_check_interval(json_rpc_named_arguments), {:start_block_valid, true} <- {:start_block_valid, start_block <= latest_block} do recalculate_cached_count() @@ -204,7 +207,7 @@ defmodule Indexer.Fetcher.Shibarium.L1 do {:stop, :normal, %{}} - {:l1_tx_not_found, true} -> + {:l1_transaction_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 shibarium_bridge table." ) @@ -282,7 +285,7 @@ defmodule Indexer.Fetcher.Shibarium.L1 do ) end - reorg_block = RollupL1ReorgMonitor.reorg_block_pop(__MODULE__) + reorg_block = RollupReorgMonitorQueue.reorg_block_pop(__MODULE__) if !is_nil(reorg_block) && reorg_block > 0 do reorg_handle(reorg_block) @@ -316,6 +319,27 @@ defmodule Indexer.Fetcher.Shibarium.L1 do {:noreply, state} end + @doc """ + Returns L1 RPC URL for this module. + """ + @spec l1_rpc_url() :: binary() + def l1_rpc_url do + Application.get_all_env(:indexer)[__MODULE__][:rpc] + end + + @doc """ + Determines if `Indexer.Fetcher.RollupL1ReorgMonitor` module must be up + for this module. + + ## Returns + - `true` if the reorg monitor must be active, `false` otherwise. + """ + @spec requires_l1_reorg_monitor?() :: boolean() + def requires_l1_reorg_monitor? do + module_config = Application.get_all_env(:indexer)[__MODULE__] + not is_nil(module_config[:start_block]) + end + defp filter_deposit_events(events) do Enum.filter(events, fn event -> topic0 = Enum.at(event["topics"], 0) diff --git a/apps/indexer/lib/indexer/fetcher/shibarium/l2.ex b/apps/indexer/lib/indexer/fetcher/shibarium/l2.ex index b85e4b5..4007ba2 100644 --- a/apps/indexer/lib/indexer/fetcher/shibarium/l2.ex +++ b/apps/indexer/lib/indexer/fetcher/shibarium/l2.ex @@ -100,8 +100,10 @@ defmodule Indexer.Fetcher.Shibarium.L2 do {:start_block_valid, true} <- {:start_block_valid, (start_block <= last_l2_block_number || last_l2_block_number == 0) && start_block <= latest_block}, - {:ok, last_l2_tx} <- Helper.get_transaction_by_hash(last_l2_transaction_hash, json_rpc_named_arguments), - {:l2_tx_not_found, false} <- {:l2_tx_not_found, !is_nil(last_l2_transaction_hash) && is_nil(last_l2_tx)} do + {:ok, last_l2_transaction} <- + Helper.get_transaction_by_hash(last_l2_transaction_hash, json_rpc_named_arguments), + {:l2_transaction_not_found, false} <- + {:l2_transaction_not_found, !is_nil(last_l2_transaction_hash) && is_nil(last_l2_transaction)} do recalculate_cached_count() Process.send(self(), :continue, []) @@ -143,7 +145,7 @@ defmodule Indexer.Fetcher.Shibarium.L2 do {:stop, :normal, state} - {:l2_tx_not_found, true} -> + {:l2_transaction_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 shibarium_bridge table." ) @@ -445,9 +447,9 @@ defmodule Indexer.Fetcher.Shibarium.L2 do end end - defp get_receipt_logs(tx_hashes, json_rpc_named_arguments, retries) do + defp get_receipt_logs(transaction_hashes, json_rpc_named_arguments, retries) do reqs = - tx_hashes + transaction_hashes |> Enum.with_index() |> Enum.map(fn {hash, id} -> request(%{ diff --git a/apps/indexer/lib/indexer/fetcher/token_balance.ex b/apps/indexer/lib/indexer/fetcher/token_balance.ex index 643ed9f..5bfd3de 100644 --- a/apps/indexer/lib/indexer/fetcher/token_balance.ex +++ b/apps/indexer/lib/indexer/fetcher/token_balance.ex @@ -9,7 +9,7 @@ defmodule Indexer.Fetcher.TokenBalance do It behaves as a `BufferedTask`, so we can configure the `max_batch_size` and the `max_concurrency` to control how many token balances will be fetched at the same time. - Also, this module set a `retries_count` for each token balance and increment this number to avoid fetching the ones + Also, this module set a `refetch_after` for each token balance in case of failure to avoid fetching the ones that always raise errors interacting with the Smart Contract. """ @@ -32,8 +32,6 @@ defmodule Indexer.Fetcher.TokenBalance do @timeout :timer.minutes(10) - @max_retries 3 - @spec async_fetch( [ %{ @@ -93,7 +91,7 @@ defmodule Indexer.Fetcher.TokenBalance do @doc """ Fetches the given entries (token_balances) from the Smart Contract and import them in our database. - It also increments the `retries_count` to avoid fetching token balances that always raise errors + It also set the `refetch_after` in case of failure to avoid fetching token balances that always raise errors when reading their balance in the Smart Contract. """ @impl BufferedTask @@ -110,7 +108,6 @@ defmodule Indexer.Fetcher.TokenBalance do result = params |> MissingBalanceOfToken.filter_token_balances_params(true, missing_balance_of_tokens) - |> increase_retries_count() |> fetch_from_blockchain(missing_balance_of_tokens) |> import_token_balances() @@ -122,45 +119,22 @@ defmodule Indexer.Fetcher.TokenBalance do end def fetch_from_blockchain(params_list, missing_balance_of_tokens) do - retryable_params_list = - params_list - |> Enum.filter(&(&1.retries_count <= @max_retries)) - |> Enum.uniq_by(&Map.take(&1, [:token_contract_address_hash, :token_id, :address_hash, :block_number])) - - Logger.metadata(count: Enum.count(retryable_params_list)) - - %{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}} = - 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 - } - - handle_success_balances(fetched_token_balances, missing_balance_of_tokens) - - if Enum.empty?(failed_token_balances) do - {:halt, all_token_balances} - else - failed_token_balances = - failed_token_balances - |> handle_failed_balances() - |> increase_retries_count() - - token_balances_updated_retries_count = - all_token_balances - |> Map.put(:failed_token_balances, failed_token_balances) - - {:cont, token_balances_updated_retries_count} - end - end) + params_list = + Enum.uniq_by(params_list, &Map.take(&1, [:token_contract_address_hash, :token_id, :address_hash, :block_number])) + + Logger.metadata(count: Enum.count(params_list)) + + {:ok, %{fetched_token_balances: fetched_token_balances, failed_token_balances: failed_token_balances}} = + TokenBalances.fetch_token_balances_from_blockchain(params_list) + + handle_success_balances(fetched_token_balances, missing_balance_of_tokens) + failed_balances_to_keep = handle_failed_balances(failed_token_balances) - fetched_token_balances + fetched_token_balances ++ failed_balances_to_keep end + defp handle_success_balances([], _missing_balance_of_tokens), do: :ok + defp handle_success_balances(fetched_token_balances, missing_balance_of_tokens) do successful_token_hashes = fetched_token_balances @@ -178,9 +152,18 @@ defmodule Indexer.Fetcher.TokenBalance do |> MissingBalanceOfToken.mark_as_implemented() end + defp handle_failed_balances([]), do: [] + defp handle_failed_balances(failed_token_balances) do + failed_token_balances + |> handle_missing_balance_of_tokens() + |> handle_other_errors() + end + + defp handle_missing_balance_of_tokens(failed_token_balances) do {missing_balance_of_balances, other_failed_balances} = Enum.split_with(failed_token_balances, fn + %{error: :unable_to_decode} -> true %{error: error} when is_binary(error) -> error =~ "execution reverted" _ -> false end) @@ -200,9 +183,27 @@ defmodule Indexer.Fetcher.TokenBalance do other_failed_balances end - defp increase_retries_count(params_list) do - params_list - |> Enum.map(&Map.put(&1, :retries_count, &1.retries_count + 1)) + defp handle_other_errors(failed_token_balances) do + Enum.map(failed_token_balances, fn token_balance_params -> + new_retries_count = token_balance_params.retries_count + 1 + + Map.merge(token_balance_params, %{ + retries_count: new_retries_count, + refetch_after: define_refetch_after(new_retries_count) + }) + end) + end + + defp define_refetch_after(retries_count) do + config = Application.get_env(:indexer, __MODULE__) + + coef = config[:exp_timeout_coeff] + max_refetch_interval = config[:max_refetch_interval] + max_retries_count = :math.log(max_refetch_interval / 1000 / coef) + + value = floor(coef * :math.exp(min(retries_count, max_retries_count))) + + Timex.shift(Timex.now(), seconds: value) end def import_token_balances(token_balances_params) do @@ -258,17 +259,14 @@ defmodule Indexer.Fetcher.TokenBalance do end end - defp entry( - %{ - token_contract_address_hash: token_contract_address_hash, - address_hash: address_hash, - block_number: block_number, - token_type: token_type, - token_id: token_id - } = token_balance - ) do - retries_count = Map.get(token_balance, :retries_count, 0) - + defp entry(%{ + token_contract_address_hash: token_contract_address_hash, + address_hash: address_hash, + block_number: block_number, + token_type: token_type, + token_id: token_id, + retries_count: retries_count + }) do token_id_int = case token_id do %Decimal{} -> Decimal.to_integer(token_id) @@ -276,7 +274,7 @@ defmodule Indexer.Fetcher.TokenBalance do _ -> token_id end - {address_hash.bytes, token_contract_address_hash.bytes, block_number, token_type, token_id_int, retries_count} + {address_hash.bytes, token_contract_address_hash.bytes, block_number, token_type, token_id_int, retries_count || 0} end defp format_params( diff --git a/apps/indexer/lib/indexer/fetcher/token_instance/helper.ex b/apps/indexer/lib/indexer/fetcher/token_instance/helper.ex index 5067e60..0987055 100644 --- a/apps/indexer/lib/indexer/fetcher/token_instance/helper.ex +++ b/apps/indexer/lib/indexer/fetcher/token_instance/helper.ex @@ -239,13 +239,17 @@ defmodule Indexer.Fetcher.TokenInstance.Helper do end end - def prepare_request(_token_type, contract_address_hash_string, token_id, _retry) do - %{ + def prepare_request(_token_type, contract_address_hash_string, token_id, from_base_uri?) do + request = %{ contract_address: contract_address_hash_string, - method_id: @uri, - args: [token_id], block_number: nil } + + if from_base_uri? do + request |> Map.put(:method_id, @base_uri) |> Map.put(:args, []) + else + request |> Map.put(:method_id, @uri) |> Map.put(:args, [token_id]) + end end def normalize_token_id("ERC-721", _token_id), do: nil diff --git a/apps/indexer/lib/indexer/fetcher/transaction_action.ex b/apps/indexer/lib/indexer/fetcher/transaction_action.ex index 0e1a8a1..cb20400 100644 --- a/apps/indexer/lib/indexer/fetcher/transaction_action.ex +++ b/apps/indexer/lib/indexer/fetcher/transaction_action.ex @@ -19,9 +19,9 @@ defmodule Indexer.Fetcher.TransactionAction do alias Explorer.Chain.{Block, BlockNumberHelper, Log, TransactionAction} alias Indexer.Transform.{Addresses, TransactionActions} - @stage_first_block "tx_action_first_block" - @stage_next_block "tx_action_next_block" - @stage_last_block "tx_action_last_block" + @stage_first_block "transaction_action_first_block" + @stage_next_block "transaction_action_next_block" + @stage_last_block "transaction_action_last_block" defstruct first_block: nil, next_block: nil, last_block: nil, protocols: [], task: nil, pid: nil @@ -178,7 +178,7 @@ defmodule Indexer.Fetcher.TransactionAction do transaction_actions: transaction_actions }) - tx_actions = + transaction_actions_with_data = Enum.map(transaction_actions, fn action -> Map.put(action, :data, Map.delete(action.data, :block_number)) end) @@ -186,7 +186,7 @@ defmodule Indexer.Fetcher.TransactionAction do {:ok, _} = Chain.import(%{ addresses: %{params: addresses, on_conflict: :nothing}, - transaction_actions: %{params: tx_actions}, + transaction_actions: %{params: transaction_actions_with_data}, timeout: :infinity }) @@ -203,7 +203,7 @@ defmodule Indexer.Fetcher.TransactionAction do Logger.info( "Block #{block_number} handled successfully. Progress: #{progress_percentage}%. Initial block range: #{first_block}..#{last_block}." <> - " Actions found: #{Enum.count(tx_actions)}." <> + " Actions found: #{Enum.count(transaction_actions_with_data)}." <> if(next_block_new >= first_block, do: " Remaining block range: #{first_block}..#{next_block_new}", else: "") ) diff --git a/apps/indexer/lib/indexer/fetcher/zksync/discovery/batches_data.ex b/apps/indexer/lib/indexer/fetcher/zksync/discovery/batches_data.ex index 3c68a0f..36a8166 100644 --- a/apps/indexer/lib/indexer/fetcher/zksync/discovery/batches_data.ex +++ b/apps/indexer/lib/indexer/fetcher/zksync/discovery/batches_data.ex @@ -21,11 +21,11 @@ defmodule Indexer.Fetcher.ZkSync.Discovery.BatchesData do and `json_rpc_named_arguments` defining parameters for the RPC connection. ## Returns - - `{batches_to_import, l2_blocks_to_import, l2_txs_to_import}` + - `{batches_to_import, l2_blocks_to_import, l2_transactions_to_import}` where - `batches_to_import` is a map of batches data - `l2_blocks_to_import` is a list of blocks associated with batches by batch numbers - - `l2_txs_to_import` is a list of transactions associated with batches by batch numbers + - `l2_transactions_to_import` is a list of transactions associated with batches by batch numbers """ @spec extract_data_from_batches([integer()] | {integer(), integer()}, %{ :chunk_size => pos_integer(), @@ -55,10 +55,10 @@ defmodule Indexer.Fetcher.ZkSync.Discovery.BatchesData do batches_to_import = get_block_ranges(initial_batches_to_import, config) - {l2_blocks_to_import, l2_txs_to_import} = get_l2_blocks_and_transactions(batches_to_import, config) - log_info("Linked #{length(l2_blocks_to_import)} L2 blocks and #{length(l2_txs_to_import)} L2 transactions") + {l2_blocks_to_import, l2_transactions_to_import} = get_l2_blocks_and_transactions(batches_to_import, config) + log_info("Linked #{length(l2_blocks_to_import)} L2 blocks and #{length(l2_transactions_to_import)} L2 transactions") - {batches_to_import, l2_blocks_to_import, l2_txs_to_import} + {batches_to_import, l2_blocks_to_import, l2_transactions_to_import} end @doc """ @@ -70,40 +70,40 @@ defmodule Indexer.Fetcher.ZkSync.Discovery.BatchesData do ## Parameters - `batches`: A list of maps describing batches. Each map is expected to define the following - elements: `commit_tx_hash`, `commit_timestamp`, `prove_tx_hash`, `prove_timestamp`, - `executed_tx_hash`, `executed_timestamp`. + elements: `commit_transaction_hash`, `commit_timestamp`, `prove_transaction_hash`, `prove_timestamp`, + `executed_transaction_hash`, `executed_timestamp`. ## Returns - - `l1_txs`: A map where keys are L1 transaction hashes, and values are maps containing + - `l1_transactions`: A map where keys are L1 transaction hashes, and values are maps containing transaction hashes and timestamps. """ @spec collect_l1_transactions(list()) :: map() def collect_l1_transactions(batches) when is_list(batches) do - l1_txs = + l1_transactions = batches - |> Enum.reduce(%{}, fn batch, l1_txs -> + |> Enum.reduce(%{}, fn batch, l1_transactions -> [ - %{hash: batch.commit_tx_hash, timestamp: batch.commit_timestamp}, - %{hash: batch.prove_tx_hash, timestamp: batch.prove_timestamp}, - %{hash: batch.executed_tx_hash, timestamp: batch.executed_timestamp} + %{hash: batch.commit_transaction_hash, timestamp: batch.commit_timestamp}, + %{hash: batch.prove_transaction_hash, timestamp: batch.prove_timestamp}, + %{hash: batch.executed_transaction_hash, timestamp: batch.executed_timestamp} ] - |> Enum.reduce(l1_txs, fn l1_tx, acc -> - # checks if l1_tx is not empty and adds to acc - add_l1_tx_to_list(acc, l1_tx) + |> Enum.reduce(l1_transactions, fn l1_transaction, acc -> + # checks if l1_transaction is not empty and adds to acc + add_l1_transaction_to_list(acc, l1_transaction) end) end) - log_info("Collected #{length(Map.keys(l1_txs))} L1 hashes") + log_info("Collected #{length(Map.keys(l1_transactions))} L1 hashes") - l1_txs + l1_transactions end - defp add_l1_tx_to_list(l1_txs, l1_tx) do - if l1_tx.hash != Rpc.get_binary_zero_hash() do - Map.put(l1_txs, l1_tx.hash, l1_tx) + defp add_l1_transaction_to_list(l1_transactions, l1_transaction) do + if l1_transaction.hash != Rpc.get_binary_zero_hash() do + Map.put(l1_transactions, l1_transaction.hash, l1_transaction) else - l1_txs + l1_transactions end end @@ -260,10 +260,10 @@ defmodule Indexer.Fetcher.ZkSync.Discovery.BatchesData do # RPC connection. # # ## Returns - # - {l2_blocks_to_import, l2_txs_to_import}, where + # - {l2_blocks_to_import, l2_transactions_to_import}, where # - `l2_blocks_to_import` contains a list of all rollup blocks with their associations with # the provided batches. The association is a map with the block hash and the batch number. - # - `l2_txs_to_import` contains a list of all rollup transactions with their associations + # - `l2_transactions_to_import` contains a list of all rollup transactions with their associations # with the provided batches. The association is a map with the transaction hash and # the batch number. defp get_l2_blocks_and_transactions( @@ -297,12 +297,12 @@ defmodule Indexer.Fetcher.ZkSync.Discovery.BatchesData do # The chunks requests are sent to the RPC node and parsed to # extract rollup block hashes and rollup transactions. - {blocks_associations, l2_txs_to_import} = + {blocks_associations, l2_transactions_to_import} = finalized_chunked_requests - |> Enum.reduce({blocks_to_batches, []}, fn requests, {blocks, l2_txs} -> + |> Enum.reduce({blocks_to_batches, []}, fn requests, {blocks, l2_transactions} -> requests |> Rpc.fetch_blocks_details(json_rpc_named_arguments) - |> extract_block_hash_and_transactions_list(blocks, l2_txs) + |> extract_block_hash_and_transactions_list(blocks, l2_transactions) end) # Check that amount of received transactions for a batch is correct @@ -310,15 +310,15 @@ defmodule Indexer.Fetcher.ZkSync.Discovery.BatchesData do |> Map.keys() |> Enum.each(fn batch_number -> batch = Map.get(batches, batch_number) - txs_in_batch = batch.l1_tx_count + batch.l2_tx_count + transactions_in_batch = batch.l1_transaction_count + batch.l2_transaction_count - ^txs_in_batch = - Enum.count(l2_txs_to_import, fn tx -> - tx.batch_number == batch_number + ^transactions_in_batch = + Enum.count(l2_transactions_to_import, fn transaction -> + transaction.batch_number == batch_number end) end) - {Map.values(blocks_associations), l2_txs_to_import} + {Map.values(blocks_associations), l2_transactions_to_import} end # For a given list of rollup block numbers, this function extends: @@ -376,38 +376,38 @@ defmodule Indexer.Fetcher.ZkSync.Discovery.BatchesData do # ## Parameters # - `json_responses`: A list of responses to `eth_getBlockByNumber` calls. # - `l2_blocks`: A map of accumulated associations between rollup blocks and batches. - # - `l2_txs`: A list of accumulated associations between rollup transactions and batches. + # - `l2_transactions`: A list of accumulated associations between rollup transactions and batches. # # ## Returns - # - {l2_blocks, l2_txs}, where + # - {l2_blocks, l2_transactions}, where # - `l2_blocks`: Updated map of accumulated associations between rollup blocks and batches. - # - `l2_txs`: Updated list of accumulated associations between rollup transactions and batches. - defp extract_block_hash_and_transactions_list(json_responses, l2_blocks, l2_txs) do + # - `l2_transactions`: Updated list of accumulated associations between rollup transactions and batches. + defp extract_block_hash_and_transactions_list(json_responses, l2_blocks, l2_transactions) do json_responses - |> Enum.reduce({l2_blocks, l2_txs}, fn resp, {l2_blocks, l2_txs} -> + |> Enum.reduce({l2_blocks, l2_transactions}, fn resp, {l2_blocks, l2_transactions} -> {block, l2_blocks} = Map.get_and_update(l2_blocks, resp.id, fn block -> {block, Map.put(block, :hash, Map.get(resp.result, "hash"))} end) - l2_txs = + l2_transactions = case Map.get(resp.result, "transactions") do nil -> - l2_txs + l2_transactions - new_txs -> - Enum.reduce(new_txs, l2_txs, fn l2_tx_hash, l2_txs -> + new_transactions -> + Enum.reduce(new_transactions, l2_transactions, fn l2_transaction_hash, l2_transactions -> [ %{ batch_number: block.batch_number, - tx_hash: l2_tx_hash + transaction_hash: l2_transaction_hash } - | l2_txs + | l2_transactions ] end) end - {l2_blocks, l2_txs} + {l2_blocks, l2_transactions} end) end end diff --git a/apps/indexer/lib/indexer/fetcher/zksync/discovery/workers.ex b/apps/indexer/lib/indexer/fetcher/zksync/discovery/workers.ex index 43ad89b..327c9e5 100644 --- a/apps/indexer/lib/indexer/fetcher/zksync/discovery/workers.ex +++ b/apps/indexer/lib/indexer/fetcher/zksync/discovery/workers.ex @@ -40,7 +40,7 @@ defmodule Indexer.Fetcher.ZkSync.Discovery.Workers do is_integer(end_batch_number) and (is_map(config) and is_map_key(config, :json_rpc_named_arguments) and is_map_key(config, :chunk_size)) do - {batches_to_import, l2_blocks_to_import, l2_txs_to_import} = + {batches_to_import, l2_blocks_to_import, l2_transactions_to_import} = extract_data_from_batches({start_batch_number, end_batch_number}, config) batches_list_to_import = @@ -53,7 +53,7 @@ defmodule Indexer.Fetcher.ZkSync.Discovery.Workers do Db.import_to_db( batches_list_to_import, [], - l2_txs_to_import, + l2_transactions_to_import, l2_blocks_to_import ) @@ -82,10 +82,11 @@ defmodule Indexer.Fetcher.ZkSync.Discovery.Workers do (is_map(config) and is_map_key(config, :json_rpc_named_arguments) and is_map_key(config, :chunk_size)) do # Collect batches and linked L2 blocks and transaction - {batches_to_import, l2_blocks_to_import, l2_txs_to_import} = extract_data_from_batches(batches_numbers_list, config) + {batches_to_import, l2_blocks_to_import, l2_transactions_to_import} = + extract_data_from_batches(batches_numbers_list, config) # Collect L1 transactions associated with batches - l1_txs = + l1_transactions = batches_to_import |> Map.values() |> collect_l1_transactions() @@ -98,9 +99,9 @@ defmodule Indexer.Fetcher.ZkSync.Discovery.Workers do |> Enum.reduce([], fn batch, batches -> [ batch - |> Map.put(:commit_id, get_l1_tx_id_by_hash(l1_txs, batch.commit_tx_hash)) - |> Map.put(:prove_id, get_l1_tx_id_by_hash(l1_txs, batch.prove_tx_hash)) - |> Map.put(:execute_id, get_l1_tx_id_by_hash(l1_txs, batch.executed_tx_hash)) + |> Map.put(:commit_id, get_l1_transaction_id_by_hash(l1_transactions, batch.commit_transaction_hash)) + |> Map.put(:prove_id, get_l1_transaction_id_by_hash(l1_transactions, batch.prove_transaction_hash)) + |> Map.put(:execute_id, get_l1_transaction_id_by_hash(l1_transactions, batch.executed_transaction_hash)) |> Db.prune_json_batch() | batches ] @@ -108,8 +109,8 @@ defmodule Indexer.Fetcher.ZkSync.Discovery.Workers do Db.import_to_db( batches_list_to_import, - Map.values(l1_txs), - l2_txs_to_import, + Map.values(l1_transactions), + l2_transactions_to_import, l2_blocks_to_import ) @@ -154,8 +155,8 @@ defmodule Indexer.Fetcher.ZkSync.Discovery.Workers do :ok end - defp get_l1_tx_id_by_hash(l1_txs, hash) do - l1_txs + defp get_l1_transaction_id_by_hash(l1_transactions, hash) do + l1_transactions |> Map.get(hash) |> Kernel.||(%{id: nil}) |> Map.get(:id) diff --git a/apps/indexer/lib/indexer/fetcher/zksync/status_tracking/committed.ex b/apps/indexer/lib/indexer/fetcher/zksync/status_tracking/committed.ex index ed1a046..85ed1f9 100644 --- a/apps/indexer/lib/indexer/fetcher/zksync/status_tracking/committed.ex +++ b/apps/indexer/lib/indexer/fetcher/zksync/status_tracking/committed.ex @@ -52,8 +52,8 @@ defmodule Indexer.Fetcher.ZkSync.StatusTracking.Committed do expected_batch_number -> log_info("Checking if the batch #{expected_batch_number} was committed") - {next_action, tx_hash, l1_txs} = - check_if_batch_status_changed(expected_batch_number, :commit_tx, json_l2_rpc_named_arguments) + {next_action, transaction_hash, l1_transactions} = + check_if_batch_status_changed(expected_batch_number, :commit_transaction, json_l2_rpc_named_arguments) case next_action do :skip -> @@ -61,17 +61,25 @@ defmodule Indexer.Fetcher.ZkSync.StatusTracking.Committed do :look_for_batches -> log_info("The batch #{expected_batch_number} looks like committed") - commit_tx_receipt = Rpc.fetch_tx_receipt_by_hash(tx_hash, json_l1_rpc_named_arguments) - batches_numbers_from_rpc = get_committed_batches_from_logs(commit_tx_receipt["logs"]) - associate_and_import_or_prepare_for_recovery(batches_numbers_from_rpc, l1_txs, tx_hash, :commit_id) + commit_transaction_receipt = + Rpc.fetch_transaction_receipt_by_hash(transaction_hash, json_l1_rpc_named_arguments) + + batches_numbers_from_rpc = get_committed_batches_from_logs(commit_transaction_receipt["logs"]) + + associate_and_import_or_prepare_for_recovery( + batches_numbers_from_rpc, + l1_transactions, + transaction_hash, + :commit_id + ) end end end defp get_committed_batches_from_logs(logs) do committed_batches = Rpc.filter_logs_and_extract_topic_at(logs, @block_commit_event, 1) - log_info("Discovered #{length(committed_batches)} committed batches in the commitment tx") + log_info("Discovered #{length(committed_batches)} committed batches in the commitment transaction") committed_batches end diff --git a/apps/indexer/lib/indexer/fetcher/zksync/status_tracking/common.ex b/apps/indexer/lib/indexer/fetcher/zksync/status_tracking/common.ex index 0c8cccf..a5133d9 100644 --- a/apps/indexer/lib/indexer/fetcher/zksync/status_tracking/common.ex +++ b/apps/indexer/lib/indexer/fetcher/zksync/status_tracking/common.ex @@ -10,18 +10,18 @@ defmodule Indexer.Fetcher.ZkSync.StatusTracking.CommonUtils do @doc """ Fetches the details of the batch with the given number and checks if the representation of the same batch in the database refers to the same commitment, proving, or executing transaction - depending on `tx_type`. If the transaction state changes, the new transaction is prepared for + depending on `transaction_type`. If the transaction state changes, the new transaction is prepared for import to the database. ## Parameters - `batch_number`: the number of the batch to check L1 transaction state. - - `tx_type`: a type of the transaction to check, one of :commit_tx, :execute_tx, or :prove_tx. + - `transaction_type`: a type of the transaction to check, one of :commit_transaction, :execute_transaction, or :prove_transaction. - `json_l2_rpc_named_arguments`: parameters for the RPC connections. ## Returns - - `{:look_for_batches, l1_tx_hash, l1_txs}` where - - `l1_tx_hash` is the hash of the L1 transaction. - - `l1_txs` is a map containing the transaction hash as a key, and values are maps + - `{:look_for_batches, l1_transaction_hash, l1_transactions}` where + - `l1_transaction_hash` is the hash of the L1 transaction. + - `l1_transactions` is a map containing the transaction hash as a key, and values are maps with transaction hashes and transaction timestamps. - `{:skip, "", %{}}` means the batch is not found in the database or the state of the transaction in the batch representation is the same as the state of the transaction for the batch @@ -29,12 +29,12 @@ defmodule Indexer.Fetcher.ZkSync.StatusTracking.CommonUtils do """ @spec check_if_batch_status_changed( binary() | non_neg_integer(), - :commit_tx | :execute_tx | :prove_tx, + :commit_transaction | :execute_transaction | :prove_transaction, EthereumJSONRPC.json_rpc_named_arguments() ) :: {:look_for_batches, any(), any()} | {:skip, <<>>, %{}} - def check_if_batch_status_changed(batch_number, tx_type, json_l2_rpc_named_arguments) + def check_if_batch_status_changed(batch_number, transaction_type, json_l2_rpc_named_arguments) when (is_binary(batch_number) or is_integer(batch_number)) and - tx_type in [:commit_tx, :prove_tx, :execute_tx] and + transaction_type in [:commit_transaction, :prove_transaction, :execute_transaction] and is_list(json_l2_rpc_named_arguments) do batch_from_rpc = Rpc.fetch_batch_details_by_batch_number(batch_number, json_l2_rpc_named_arguments) @@ -42,62 +42,67 @@ defmodule Indexer.Fetcher.ZkSync.StatusTracking.CommonUtils do case Reader.batch( batch_number, necessity_by_association: %{ - get_association(tx_type) => :optional + get_association(transaction_type) => :optional } ) do - {:ok, batch_from_db} -> transactions_of_batch_changed?(batch_from_db, batch_from_rpc, tx_type) + {:ok, batch_from_db} -> transactions_of_batch_changed?(batch_from_db, batch_from_rpc, transaction_type) {:error, :not_found} -> :error end - l1_tx = get_l1_tx_from_batch(batch_from_rpc, tx_type) + l1_transaction = get_l1_transaction_from_batch(batch_from_rpc, transaction_type) - if l1_tx.hash != Rpc.get_binary_zero_hash() and status_changed_or_error in [true, :error] do - l1_txs = Db.get_indices_for_l1_transactions(%{l1_tx.hash => l1_tx}) + if l1_transaction.hash != Rpc.get_binary_zero_hash() and status_changed_or_error in [true, :error] do + l1_transactions = Db.get_indices_for_l1_transactions(%{l1_transaction.hash => l1_transaction}) - {:look_for_batches, l1_tx.hash, l1_txs} + {:look_for_batches, l1_transaction.hash, l1_transactions} else {:skip, "", %{}} end end - defp get_association(tx_type) do - case tx_type do - :commit_tx -> :commit_transaction - :prove_tx -> :prove_transaction - :execute_tx -> :execute_transaction + defp get_association(transaction_type) do + case transaction_type do + :commit_transaction -> :commit_transaction + :prove_transaction -> :prove_transaction + :execute_transaction -> :execute_transaction end end - defp transactions_of_batch_changed?(batch_db, batch_json, tx_type) do - tx_hash_json = - case tx_type do - :commit_tx -> batch_json.commit_tx_hash - :prove_tx -> batch_json.prove_tx_hash - :execute_tx -> batch_json.executed_tx_hash + defp transactions_of_batch_changed?(batch_db, batch_json, transaction_type) do + transaction_hash_json = + case transaction_type do + :commit_transaction -> batch_json.commit_transaction_hash + :prove_transaction -> batch_json.prove_transaction_hash + :execute_transaction -> batch_json.executed_transaction_hash end - tx_hash_db = - case tx_type do - :commit_tx -> batch_db.commit_transaction - :prove_tx -> batch_db.prove_transaction - :execute_tx -> batch_db.execute_transaction + transaction_hash_db = + case transaction_type do + :commit_transaction -> batch_db.commit_transaction + :prove_transaction -> batch_db.prove_transaction + :execute_transaction -> batch_db.execute_transaction end - tx_hash_db_bytes = - if is_nil(tx_hash_db) do + transaction_hash_db_bytes = + if is_nil(transaction_hash_db) do Rpc.get_binary_zero_hash() else - tx_hash_db.hash.bytes + transaction_hash_db.hash.bytes end - tx_hash_json != tx_hash_db_bytes + transaction_hash_json != transaction_hash_db_bytes end - defp get_l1_tx_from_batch(batch_from_rpc, tx_type) do - case tx_type do - :commit_tx -> %{hash: batch_from_rpc.commit_tx_hash, timestamp: batch_from_rpc.commit_timestamp} - :prove_tx -> %{hash: batch_from_rpc.prove_tx_hash, timestamp: batch_from_rpc.prove_timestamp} - :execute_tx -> %{hash: batch_from_rpc.executed_tx_hash, timestamp: batch_from_rpc.executed_timestamp} + defp get_l1_transaction_from_batch(batch_from_rpc, transaction_type) do + case transaction_type do + :commit_transaction -> + %{hash: batch_from_rpc.commit_transaction_hash, timestamp: batch_from_rpc.commit_timestamp} + + :prove_transaction -> + %{hash: batch_from_rpc.prove_transaction_hash, timestamp: batch_from_rpc.prove_timestamp} + + :execute_transaction -> + %{hash: batch_from_rpc.executed_transaction_hash, timestamp: batch_from_rpc.executed_timestamp} end end @@ -110,9 +115,9 @@ defmodule Indexer.Fetcher.ZkSync.StatusTracking.CommonUtils do ## Parameters - `batches_numbers`: the list of batch numbers that must be updated. - - `l1_txs`: a map containing transaction hashes as keys, and values are maps + - `l1_transactions`: a map containing transaction hashes as keys, and values are maps with transaction hashes and transaction timestamps of L1 transactions to import to the database. - - `tx_hash`: the hash of the L1 transaction to build an association with. + - `transaction_hash`: the hash of the L1 transaction to build an association with. - `association_key`: the field in the batch description to build an association with L1 transactions. @@ -123,15 +128,15 @@ defmodule Indexer.Fetcher.ZkSync.StatusTracking.CommonUtils do """ @spec associate_and_import_or_prepare_for_recovery([integer()], map(), binary(), :commit_id | :execute_id | :prove_id) :: :ok | {:recovery_required, [integer()]} - def associate_and_import_or_prepare_for_recovery(batches_numbers, l1_txs, tx_hash, association_key) - when is_list(batches_numbers) and is_map(l1_txs) and is_binary(tx_hash) and + def associate_and_import_or_prepare_for_recovery(batches_numbers, l1_transactions, transaction_hash, association_key) + when is_list(batches_numbers) and is_map(l1_transactions) and is_binary(transaction_hash) and association_key in [:commit_id, :prove_id, :execute_id] do - case prepare_batches_to_import(batches_numbers, %{association_key => l1_txs[tx_hash][:id]}) do + case prepare_batches_to_import(batches_numbers, %{association_key => l1_transactions[transaction_hash][:id]}) do {:error, batches_to_recover} -> {:recovery_required, batches_to_recover} {:ok, batches_to_import} -> - Db.import_to_db(batches_to_import, Map.values(l1_txs)) + Db.import_to_db(batches_to_import, Map.values(l1_transactions)) :ok end end diff --git a/apps/indexer/lib/indexer/fetcher/zksync/status_tracking/executed.ex b/apps/indexer/lib/indexer/fetcher/zksync/status_tracking/executed.ex index 38d7db9..03538bd 100644 --- a/apps/indexer/lib/indexer/fetcher/zksync/status_tracking/executed.ex +++ b/apps/indexer/lib/indexer/fetcher/zksync/status_tracking/executed.ex @@ -52,8 +52,8 @@ defmodule Indexer.Fetcher.ZkSync.StatusTracking.Executed do expected_batch_number -> log_info("Checking if the batch #{expected_batch_number} was executed") - {next_action, tx_hash, l1_txs} = - check_if_batch_status_changed(expected_batch_number, :execute_tx, json_l2_rpc_named_arguments) + {next_action, transaction_hash, l1_transactions} = + check_if_batch_status_changed(expected_batch_number, :execute_transaction, json_l2_rpc_named_arguments) case next_action do :skip -> @@ -61,17 +61,25 @@ defmodule Indexer.Fetcher.ZkSync.StatusTracking.Executed do :look_for_batches -> log_info("The batch #{expected_batch_number} looks like executed") - execute_tx_receipt = Rpc.fetch_tx_receipt_by_hash(tx_hash, json_l1_rpc_named_arguments) - batches_numbers_from_rpc = get_executed_batches_from_logs(execute_tx_receipt["logs"]) - associate_and_import_or_prepare_for_recovery(batches_numbers_from_rpc, l1_txs, tx_hash, :execute_id) + execute_transaction_receipt = + Rpc.fetch_transaction_receipt_by_hash(transaction_hash, json_l1_rpc_named_arguments) + + batches_numbers_from_rpc = get_executed_batches_from_logs(execute_transaction_receipt["logs"]) + + associate_and_import_or_prepare_for_recovery( + batches_numbers_from_rpc, + l1_transactions, + transaction_hash, + :execute_id + ) end end end defp get_executed_batches_from_logs(logs) do executed_batches = Rpc.filter_logs_and_extract_topic_at(logs, @block_execution_event, 1) - log_info("Discovered #{length(executed_batches)} executed batches in the executing tx") + log_info("Discovered #{length(executed_batches)} executed batches in the executing transaction") executed_batches end diff --git a/apps/indexer/lib/indexer/fetcher/zksync/status_tracking/proven.ex b/apps/indexer/lib/indexer/fetcher/zksync/status_tracking/proven.ex index ad2bb98..0fb3e21 100644 --- a/apps/indexer/lib/indexer/fetcher/zksync/status_tracking/proven.ex +++ b/apps/indexer/lib/indexer/fetcher/zksync/status_tracking/proven.ex @@ -50,8 +50,8 @@ defmodule Indexer.Fetcher.ZkSync.StatusTracking.Proven do expected_batch_number -> log_info("Checking if the batch #{expected_batch_number} was proven") - {next_action, tx_hash, l1_txs} = - check_if_batch_status_changed(expected_batch_number, :prove_tx, json_l2_rpc_named_arguments) + {next_action, transaction_hash, l1_transactions} = + check_if_batch_status_changed(expected_batch_number, :prove_transaction, json_l2_rpc_named_arguments) case next_action do :skip -> @@ -59,10 +59,15 @@ defmodule Indexer.Fetcher.ZkSync.StatusTracking.Proven do :look_for_batches -> log_info("The batch #{expected_batch_number} looks like proven") - prove_tx = Rpc.fetch_tx_by_hash(tx_hash, json_l1_rpc_named_arguments) - batches_numbers_from_rpc = get_proven_batches_from_calldata(prove_tx["input"]) - - associate_and_import_or_prepare_for_recovery(batches_numbers_from_rpc, l1_txs, tx_hash, :prove_id) + prove_transaction = Rpc.fetch_transaction_by_hash(transaction_hash, json_l1_rpc_named_arguments) + batches_numbers_from_rpc = get_proven_batches_from_calldata(prove_transaction["input"]) + + associate_and_import_or_prepare_for_recovery( + batches_numbers_from_rpc, + l1_transactions, + transaction_hash, + :prove_id + ) end end end @@ -176,7 +181,7 @@ defmodule Indexer.Fetcher.ZkSync.StatusTracking.Proven do [] end - log_info("Discovered #{length(proven_batches)} proven batches in the prove tx") + log_info("Discovered #{length(proven_batches)} proven batches in the prove transaction") proven_batches |> Enum.map(fn batch_info -> elem(batch_info, 0) end) diff --git a/apps/indexer/lib/indexer/fetcher/zksync/utils/db.ex b/apps/indexer/lib/indexer/fetcher/zksync/utils/db.ex index 64eedee..eee7f47 100644 --- a/apps/indexer/lib/indexer/fetcher/zksync/utils/db.ex +++ b/apps/indexer/lib/indexer/fetcher/zksync/utils/db.ex @@ -8,11 +8,11 @@ defmodule Indexer.Fetcher.ZkSync.Utils.Db do import Indexer.Fetcher.ZkSync.Utils.Logging, only: [log_warning: 1, log_info: 1] @json_batch_fields_absent_in_db_batch [ - :commit_tx_hash, + :commit_transaction_hash, :commit_timestamp, - :prove_tx_hash, + :prove_transaction_hash, :prove_timestamp, - :executed_tx_hash, + :executed_transaction_hash, :executed_timestamp ] @@ -126,57 +126,57 @@ defmodule Indexer.Fetcher.ZkSync.Utils.Db do the next available indices are assigned. ## Parameters - - `new_l1_txs`: A map of L1 transaction descriptions. The keys of the map are + - `new_l1_transactions`: A map of L1 transaction descriptions. The keys of the map are transaction hashes. ## Returns - - `l1_txs`: A map of L1 transaction descriptions. Each element is extended with + - `l1_transactions`: A map of L1 transaction descriptions. Each element is extended with the key `:id`, representing the index of the L1 transaction in the `zksync_lifecycle_l1_transactions` table. """ @spec get_indices_for_l1_transactions(map()) :: any() # TODO: consider a way to remove duplicate with Arbitrum.Utils.Db # credo:disable-for-next-line Credo.Check.Design.DuplicatedCode - def get_indices_for_l1_transactions(new_l1_txs) - when is_map(new_l1_txs) do + def get_indices_for_l1_transactions(new_l1_transactions) + when is_map(new_l1_transactions) do # Get indices for l1 transactions previously handled - l1_txs = - new_l1_txs + l1_transactions = + new_l1_transactions |> Map.keys() |> Reader.lifecycle_transactions() - |> Enum.reduce(new_l1_txs, fn {hash, id}, txs -> - {_, txs} = - Map.get_and_update!(txs, hash.bytes, fn l1_tx -> - {l1_tx, Map.put(l1_tx, :id, id)} + |> Enum.reduce(new_l1_transactions, fn {hash, id}, transactions -> + {_, transactions} = + Map.get_and_update!(transactions, hash.bytes, fn l1_transaction -> + {l1_transaction, Map.put(l1_transaction, :id, id)} end) - txs + transactions end) # Get the next index for the first new transaction based # on the indices existing in DB - l1_tx_next_id = Reader.next_id() + l1_transaction_next_id = Reader.next_id() # Assign new indices for the transactions which are not in # the l1 transactions table yet - {updated_l1_txs, _} = - l1_txs + {updated_l1_transactions, _} = + l1_transactions |> Map.keys() |> Enum.reduce( - {l1_txs, l1_tx_next_id}, - fn hash, {txs, next_id} -> - tx = txs[hash] - id = Map.get(tx, :id) + {l1_transactions, l1_transaction_next_id}, + fn hash, {transactions, next_id} -> + transaction = transactions[hash] + id = Map.get(transaction, :id) if is_nil(id) do - {Map.put(txs, hash, Map.put(tx, :id, next_id)), next_id + 1} + {Map.put(transactions, hash, Map.put(transaction, :id, next_id)), next_id + 1} else - {txs, next_id} + {transactions, next_id} end end ) - updated_l1_txs + updated_l1_transactions end @doc """ @@ -185,20 +185,20 @@ defmodule Indexer.Fetcher.ZkSync.Utils.Db do ## Parameters - `batches`: A list of maps with batch descriptions. - - `l1_txs`: A list of maps with L1 transaction descriptions. Optional. - - `l2_txs`: A list of maps with rollup transaction associations. Optional. + - `l1_transactions`: A list of maps with L1 transaction descriptions. Optional. + - `l2_transactions`: A list of maps with rollup transaction associations. Optional. - `l2_blocks`: A list of maps with rollup block associations. Optional. ## Returns n/a """ - def import_to_db(batches, l1_txs \\ [], l2_txs \\ [], l2_blocks \\ []) - when is_list(batches) and is_list(l1_txs) and is_list(l2_txs) and is_list(l2_blocks) do + def import_to_db(batches, l1_transactions \\ [], l2_transactions \\ [], l2_blocks \\ []) + when is_list(batches) and is_list(l1_transactions) and is_list(l2_transactions) and is_list(l2_blocks) do {:ok, _} = Chain.import(%{ - zksync_lifecycle_transactions: %{params: l1_txs}, + zksync_lifecycle_transactions: %{params: l1_transactions}, zksync_transaction_batches: %{params: batches}, - zksync_batch_transactions: %{params: l2_txs}, + zksync_batch_transactions: %{params: l2_transactions}, zksync_batch_blocks: %{params: l2_blocks}, timeout: :infinity }) diff --git a/apps/indexer/lib/indexer/fetcher/zksync/utils/rpc.ex b/apps/indexer/lib/indexer/fetcher/zksync/utils/rpc.ex index 71e594d..2323888 100644 --- a/apps/indexer/lib/indexer/fetcher/zksync/utils/rpc.ex +++ b/apps/indexer/lib/indexer/fetcher/zksync/utils/rpc.ex @@ -67,16 +67,16 @@ defmodule Indexer.Fetcher.ZkSync.Utils.Rpc do end end - defp json_tx_id_to_hash(hash) do + defp json_transaction_id_to_hash(hash) do case hash do - "0x" <> tx_hash -> tx_hash + "0x" <> transaction_hash -> transaction_hash nil -> @zero_hash end end defp string_hash_to_bytes_hash(hash) do hash - |> json_tx_id_to_hash() + |> json_transaction_id_to_hash() |> Base.decode16!(case: :mixed) end @@ -99,14 +99,14 @@ defmodule Indexer.Fetcher.ZkSync.Utils.Rpc do %{ "number" => {:number, :ok}, "timestamp" => {:timestamp, :ts_to_datetime}, - "l1TxCount" => {:l1_tx_count, :ok}, - "l2TxCount" => {:l2_tx_count, :ok}, + "l1TxCount" => {:l1_transaction_count, :ok}, + "l2TxCount" => {:l2_transaction_count, :ok}, "rootHash" => {:root_hash, :str_to_byteshash}, - "commitTxHash" => {:commit_tx_hash, :str_to_byteshash}, + "commitTxHash" => {:commit_transaction_hash, :str_to_byteshash}, "committedAt" => {:commit_timestamp, :iso8601_to_datetime}, - "proveTxHash" => {:prove_tx_hash, :str_to_byteshash}, + "proveTxHash" => {:prove_transaction_hash, :str_to_byteshash}, "provenAt" => {:prove_timestamp, :iso8601_to_datetime}, - "executeTxHash" => {:executed_tx_hash, :str_to_byteshash}, + "executeTxHash" => {:executed_transaction_hash, :str_to_byteshash}, "executedAt" => {:executed_timestamp, :iso8601_to_datetime}, "l1GasPrice" => {:l1_gas_price, :ok}, "l2FairGasPrice" => {:l2_fair_gas_price, :ok} @@ -122,7 +122,7 @@ defmodule Indexer.Fetcher.ZkSync.Utils.Rpc do case transform_type do :iso8601_to_datetime -> from_iso8601_to_datetime(value_in_json_response) :ts_to_datetime -> IndexerHelper.timestamp_to_datetime(value_in_json_response) - :str_to_txhash -> json_tx_id_to_hash(value_in_json_response) + :str_to_txhash -> json_transaction_id_to_hash(value_in_json_response) :str_to_byteshash -> string_hash_to_bytes_hash(value_in_json_response) _ -> value_in_json_response end @@ -146,8 +146,8 @@ defmodule Indexer.Fetcher.ZkSync.Utils.Rpc do %{ number: batch.number, timestamp: batch.timestamp, - l1_tx_count: batch.l1_tx_count, - l2_tx_count: batch.l2_tx_count, + l1_transaction_count: batch.l1_transaction_count, + l2_transaction_count: batch.l2_transaction_count, root_hash: batch.root_hash.bytes, l1_gas_price: batch.l1_gas_price, l2_fair_gas_price: batch.l2_fair_gas_price, @@ -200,10 +200,10 @@ defmodule Indexer.Fetcher.ZkSync.Utils.Rpc do ## Returns - A map containing details of the transaction. """ - @spec fetch_tx_by_hash(binary(), EthereumJSONRPC.json_rpc_named_arguments()) :: map() - def fetch_tx_by_hash(raw_hash, json_rpc_named_arguments) + @spec fetch_transaction_by_hash(binary(), EthereumJSONRPC.json_rpc_named_arguments()) :: map() + def fetch_transaction_by_hash(raw_hash, json_rpc_named_arguments) when is_binary(raw_hash) and is_list(json_rpc_named_arguments) do - hash = prepare_tx_hash(raw_hash) + hash = prepare_transaction_hash(raw_hash) req = EthereumJSONRPC.request(%{ @@ -231,10 +231,10 @@ defmodule Indexer.Fetcher.ZkSync.Utils.Rpc do ## Returns - A map containing the receipt details of the transaction. """ - @spec fetch_tx_receipt_by_hash(binary(), EthereumJSONRPC.json_rpc_named_arguments()) :: map() - def fetch_tx_receipt_by_hash(raw_hash, json_rpc_named_arguments) + @spec fetch_transaction_receipt_by_hash(binary(), EthereumJSONRPC.json_rpc_named_arguments()) :: map() + def fetch_transaction_receipt_by_hash(raw_hash, json_rpc_named_arguments) when is_binary(raw_hash) and is_list(json_rpc_named_arguments) do - hash = prepare_tx_hash(raw_hash) + hash = prepare_transaction_hash(raw_hash) req = EthereumJSONRPC.request(%{ @@ -377,8 +377,8 @@ defmodule Indexer.Fetcher.ZkSync.Utils.Rpc do end # Converts a transaction hash represented as binary to a hexadecimal string - @spec prepare_tx_hash(binary()) :: binary() - defp prepare_tx_hash(raw_hash) do + @spec prepare_transaction_hash(binary()) :: binary() + defp prepare_transaction_hash(raw_hash) do case raw_hash do "0x" <> <<_::binary-size(64)>> -> raw_hash _ -> "0x" <> Base.encode16(raw_hash, case: :lower) diff --git a/apps/indexer/lib/indexer/helper.ex b/apps/indexer/lib/indexer/helper.ex index b5a143e..d01e030 100644 --- a/apps/indexer/lib/indexer/helper.ex +++ b/apps/indexer/lib/indexer/helper.ex @@ -108,9 +108,9 @@ defmodule Indexer.Helper do 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), + get_block_timestamp_by_number(first_block, json_rpc_named_arguments, @infinite_retries_number), {:ok, last_safe_block_timestamp} <- - get_block_timestamp_by_number(last_safe_block, json_rpc_named_arguments, 100_000_000) do + get_block_timestamp_by_number(last_safe_block, json_rpc_named_arguments, @infinite_retries_number) do block_check_interval = ceil((last_safe_block_timestamp - first_block_timestamp) / (last_safe_block - first_block) * 1000 / 2) @@ -129,9 +129,9 @@ defmodule Indexer.Helper do - `json_rpc_named_arguments`: Configuration parameters for the JSON RPC connection. ## Returns - `{block_num, latest}`: A tuple where - - `block_num` is the safe or latest block number. - - `latest` is a boolean, where `true` indicates that `block_num` is the latest block number fetched using the tag `latest`. + `{block_number, latest}`: A tuple where + - `block_number` is the safe or latest block number. + - `latest` is a boolean, where `true` indicates that `block_number` is the latest block number fetched using the tag `latest`. """ @spec get_safe_block(EthereumJSONRPC.json_rpc_named_arguments()) :: {non_neg_integer(), boolean()} def get_safe_block(json_rpc_named_arguments) do @@ -139,8 +139,9 @@ defmodule Indexer.Helper 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) + {:error, _} -> + {:ok, latest_block} = get_block_number_by_tag("latest", json_rpc_named_arguments, @infinite_retries_number) + {latest_block, true} end end @@ -219,7 +220,7 @@ defmodule Indexer.Helper do [1..3, 4..6, 7..9, 10..10] """ @spec range_chunk_every(Range.t(), non_neg_integer()) :: Enum.t() - def range_chunk_every(from..to, chunk_size) do + def range_chunk_every(from..to//_, chunk_size) do chunks_number = floor((to - from + 1) / chunk_size) 0..chunks_number @@ -238,7 +239,10 @@ defmodule Indexer.Helper do - `from_block`: The starting block number (integer or hexadecimal string) for the log search. - `to_block`: The ending block number (integer or hexadecimal string) for the log search. - `address`: The address of the contract to filter logs from. - - `topics`: List of topics to filter the logs. + - `topics`: List of topics to filter the logs. The list represents each topic as follows: + [topic0, topic1, topic2, topic3]. The `topicN` can be either some value or + a list of possible values, e.g.: [[topic0_1, topic0_2], topic1, topic2, topic3]. + If a topic is omitted or `nil`, it doesn't take part in the logs filtering. - `json_rpc_named_arguments`: Configuration for the JSON-RPC call. - `id`: (optional) JSON-RPC request identifier, defaults to 0. - `retries`: (optional) Number of retry attempts if the request fails, defaults to 3. @@ -251,14 +255,14 @@ defmodule Indexer.Helper do non_neg_integer() | binary(), non_neg_integer() | binary(), binary(), - [binary()], + [binary()] | [list()], EthereumJSONRPC.json_rpc_named_arguments() ) :: {:error, atom() | binary() | map()} | {:ok, any()} @spec get_logs( non_neg_integer() | binary(), non_neg_integer() | binary(), binary(), - [binary()], + [binary()] | [list()], EthereumJSONRPC.json_rpc_named_arguments(), integer() ) :: {:error, atom() | binary() | map()} | {:ok, any()} @@ -266,7 +270,7 @@ defmodule Indexer.Helper do non_neg_integer() | binary(), non_neg_integer() | binary(), binary(), - [binary()], + [binary()] | [list()], EthereumJSONRPC.json_rpc_named_arguments(), integer(), non_neg_integer() @@ -548,11 +552,22 @@ defmodule Indexer.Helper do end @doc """ - Fetches blocks info from the given list of events (logs). - Performs a specified number of retries (up to) if the first attempt returns error. + Fetches blocks info from the given list of events (logs). + Performs a specified number of retries (up to) if the first attempt returns error. + + ## Parameters + - `events`: The list of events to retrieve block numbers from. + - `json_rpc_named_arguments`: Configuration parameters for the JSON RPC connection. + - `retries`: Number of retry attempts if the request fails. + - `transaction_details`: Whether to include transaction details into the resulting list of blocks. + + ## Returns + - The list of blocks. The list is empty if the HTTP response returns error. """ - @spec get_blocks_by_events(list(), list(), non_neg_integer()) :: list() - def get_blocks_by_events(events, json_rpc_named_arguments, retries) do + @spec get_blocks_by_events(list(), EthereumJSONRPC.json_rpc_named_arguments(), non_neg_integer(), boolean()) :: [ + %{String.t() => any()} + ] + def get_blocks_by_events(events, json_rpc_named_arguments, retries, transaction_details \\ false) do events |> Enum.reduce(%{}, fn event, acc -> block_number = Map.get(event, :block_number, event["blockNumber"]) @@ -561,7 +576,7 @@ defmodule Indexer.Helper do |> 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)) + |> Blocks.requests(&ByNumber.request(&1, transaction_details, false)) |> Enum.chunk_every(@block_by_number_chunk_size) |> Enum.reduce([], fn current_requests, results_acc -> error_message = diff --git a/apps/indexer/lib/indexer/memory/monitor.ex b/apps/indexer/lib/indexer/memory/monitor.ex index c907cda..225428a 100644 --- a/apps/indexer/lib/indexer/memory/monitor.ex +++ b/apps/indexer/lib/indexer/memory/monitor.ex @@ -7,9 +7,9 @@ defmodule Indexer.Memory.Monitor do `c:Indexer.Memory.Shrinkable.shrink/0`. """ - require Bitwise require Logger + import Bitwise import Indexer.Logger, only: [process: 1] alias Indexer.Memory.Shrinkable @@ -47,10 +47,16 @@ defmodule Indexer.Memory.Monitor do @impl GenServer def init(options) when is_map(options) do - state = struct!(__MODULE__, options) - {:ok, timer_reference} = :timer.send_interval(state.timer_interval, :check) + case Application.get_env(:explorer, :mode) do + :api -> + :ignore - {:ok, %__MODULE__{state | timer_reference: timer_reference}} + _other_mode -> + state = struct!(__MODULE__, Map.put_new(options, :limit, define_memory_limit())) + {:ok, timer_reference} = :timer.send_interval(state.timer_interval, :check) + + {:ok, %__MODULE__{state | timer_reference: timer_reference}} + end end @impl GenServer @@ -66,14 +72,14 @@ defmodule Indexer.Memory.Monitor do end @impl GenServer - def handle_info(:check, state) do + def handle_info(:check, %{limit: limit} = state) do total = :erlang.memory(:total) set_metrics(state) shrunk_state = - if memory_limit() < total do - log_memory(%{limit: memory_limit(), total: total}) + if limit < total do + log_memory(%{limit: limit, total: total}) shrink_or_log(state) %{state | shrunk?: true} else @@ -81,8 +87,8 @@ defmodule Indexer.Memory.Monitor do end final_state = - if state.shrunk? and total <= memory_limit() * @expandable_memory_coefficient do - log_expandable_memory(%{limit: memory_limit(), total: total}) + if state.shrunk? and total <= limit * @expandable_memory_coefficient do + log_expandable_memory(%{limit: limit, total: total}) expand(state) %{state | shrunk?: false} else @@ -94,6 +100,28 @@ defmodule Indexer.Memory.Monitor do {:noreply, final_state} end + defp define_memory_limit do + case Application.get_env(:indexer, :memory_limit) do + integer when is_integer(integer) -> integer + _not_set -> memory_limit_from_system() + end + end + + defp memory_limit_from_system do + default_limit = 1 <<< 30 + + percentage = + case Application.get_env(:explorer, :mode) do + :indexer -> 100 + :all -> Application.get_env(:indexer, :system_memory_percentage) + end + + case :memsup.get_system_memory_data()[:total_memory] do + nil -> default_limit + total_memory -> floor(total_memory * percentage / 100) + end + end + defp flush(message) do receive do ^message -> flush(message) @@ -250,8 +278,4 @@ 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/pending_transactions_sanitizer.ex b/apps/indexer/lib/indexer/pending_transactions_sanitizer.ex index e1ea489..11c0c26 100644 --- a/apps/indexer/lib/indexer/pending_transactions_sanitizer.ex +++ b/apps/indexer/lib/indexer/pending_transactions_sanitizer.ex @@ -75,17 +75,17 @@ defmodule Indexer.PendingTransactionsSanitizer do |> json_rpc(json_rpc_named_arguments) do Enum.each(responses, fn %{id: id, result: result} -> - pending_tx = Map.fetch!(id_to_params, id) + pending_transaction = Map.fetch!(id_to_params, id) if result do - fetch_block_and_invalidate_wrapper(pending_tx, to_string(pending_tx.hash), result) + fetch_block_and_invalidate_wrapper(pending_transaction, to_string(pending_transaction.hash), result) else Logger.debug( - "Transaction with hash #{pending_tx.hash} doesn't exist in the node anymore. We should remove it from Blockscout DB.", + "Transaction with hash #{pending_transaction.hash} doesn't exist in the node anymore. We should remove it from Blockscout DB.", fetcher: :pending_transactions_to_refetch ) - fetch_pending_transaction_and_delete(pending_tx) + fetch_pending_transaction_and_delete(pending_transaction) end error -> @@ -104,40 +104,40 @@ defmodule Indexer.PendingTransactionsSanitizer do end) end - defp fetch_block_and_invalidate_wrapper(pending_tx, pending_tx_hash_str, result) do + defp fetch_block_and_invalidate_wrapper(pending_transaction, pending_transaction_hash_string, result) do block_hash = Map.get(result, "blockHash") if block_hash do Logger.debug( - "Transaction with hash #{pending_tx_hash_str} already included into the block #{block_hash}. We should invalidate consensus for it in order to re-fetch transactions", + "Transaction with hash #{pending_transaction_hash_string} already included into the block #{block_hash}. We should invalidate consensus for it in order to re-fetch transactions", fetcher: :pending_transactions_to_refetch ) - fetch_block_and_invalidate(block_hash, pending_tx, result) + fetch_block_and_invalidate(block_hash, pending_transaction, result) else Logger.debug( - "Transaction with hash #{pending_tx_hash_str} is still pending. Do nothing.", + "Transaction with hash #{pending_transaction_hash_string} is still pending. Do nothing.", fetcher: :pending_transactions_to_refetch ) end end defp fetch_pending_transaction_and_delete(transaction) do - pending_tx_hash_str = "0x" <> Base.encode16(transaction.hash.bytes, case: :lower) + pending_transaction_hash_string = "0x" <> Base.encode16(transaction.hash.bytes, case: :lower) case transaction |> Changeset.change() |> Repo.delete() do {:ok, _transaction} -> Logger.debug( - "Transaction with hash #{pending_tx_hash_str} successfully deleted from Blockscout DB because it doesn't exist in the archive node anymore", + "Transaction with hash #{pending_transaction_hash_string} successfully deleted from Blockscout DB because it doesn't exist in the archive node anymore", fetcher: :pending_transactions_to_refetch ) {:error, changeset} -> Logger.debug( [ - "Deletion of pending transaction with hash #{pending_tx_hash_str} from Blockscout DB failed", + "Deletion of pending transaction with hash #{pending_transaction_hash_string} from Blockscout DB failed", inspect(changeset) ], fetcher: :pending_transactions_to_refetch @@ -145,7 +145,7 @@ defmodule Indexer.PendingTransactionsSanitizer do end end - defp fetch_block_and_invalidate(block_hash, pending_tx, tx) do + defp fetch_block_and_invalidate(block_hash, pending_transaction, transaction) do case Chain.fetch_block_by_hash(block_hash) do %{number: number, consensus: consensus} = block -> Logger.debug( @@ -153,7 +153,7 @@ defmodule Indexer.PendingTransactionsSanitizer do fetcher: :pending_transactions_to_refetch ) - invalidate_block(block, pending_tx, tx) + invalidate_block(block, pending_transaction, transaction) _ -> Logger.debug( @@ -163,18 +163,18 @@ defmodule Indexer.PendingTransactionsSanitizer do end end - defp invalidate_block(block, pending_tx, tx) do + defp invalidate_block(block, pending_transaction, transaction) do if block.consensus do Block.set_refetch_needed(block.number) else - tx_info = to_elixir(tx) + transaction_info = to_elixir(transaction) changeset = - pending_tx + pending_transaction |> Transaction.changeset() - |> Changeset.put_change(:cumulative_gas_used, tx_info["cumulativeGasUsed"]) - |> Changeset.put_change(:gas_used, tx_info["gasUsed"]) - |> Changeset.put_change(:index, tx_info["transactionIndex"]) + |> Changeset.put_change(:cumulative_gas_used, transaction_info["cumulativeGasUsed"]) + |> Changeset.put_change(:gas_used, transaction_info["gasUsed"]) + |> Changeset.put_change(:index, transaction_info["transactionIndex"]) |> Changeset.put_change(:block_number, block.number) |> Changeset.put_change(:block_hash, block.hash) |> Changeset.put_change(:block_timestamp, block.timestamp) @@ -183,7 +183,7 @@ defmodule Indexer.PendingTransactionsSanitizer do Repo.update(changeset) Logger.debug( - "Pending tx with hash #{"0x" <> Base.encode16(pending_tx.hash.bytes, case: :lower)} assigned to block ##{block.number} with hash #{block.hash}" + "Pending transaction with hash #{"0x" <> Base.encode16(pending_transaction.hash.bytes, case: :lower)} assigned to block ##{block.number} with hash #{block.hash}" ) end end diff --git a/apps/indexer/lib/indexer/prometheus/collector/filecoin_pending_address_operations_collector.ex b/apps/indexer/lib/indexer/prometheus/collector/filecoin_pending_address_operations_collector.ex new file mode 100644 index 0000000..207990c --- /dev/null +++ b/apps/indexer/lib/indexer/prometheus/collector/filecoin_pending_address_operations_collector.ex @@ -0,0 +1,31 @@ +if Application.compile_env(:explorer, :chain_type) == :filecoin do + defmodule Indexer.Prometheus.Collector.FilecoinPendingAddressOperations do + @moduledoc """ + Custom collector to count number of records in filecoin_pending_address_operations table. + """ + + use Prometheus.Collector + + alias Explorer.Chain.Filecoin.PendingAddressOperation + alias Explorer.Repo + alias Prometheus.Model + + def collect_mf(_registry, callback) do + callback.( + create_gauge( + :filecoin_pending_address_operations, + "Number of records in filecoin_pending_address_operations table", + Repo.aggregate(PendingAddressOperation, :count, timeout: :infinity) + ) + ) + end + + def collect_metrics(:filecoin_pending_address_operations, count) do + Model.gauge_metrics([{count}]) + end + + defp create_gauge(name, help, data) do + Model.create_mf(name, help, :gauge, __MODULE__, data) + end + end +end diff --git a/apps/indexer/lib/indexer/prometheus/pending_block_operations_collector.ex b/apps/indexer/lib/indexer/prometheus/collector/pending_block_operations_collector.ex similarity index 91% rename from apps/indexer/lib/indexer/prometheus/pending_block_operations_collector.ex rename to apps/indexer/lib/indexer/prometheus/collector/pending_block_operations_collector.ex index e1e836a..56f7f48 100644 --- a/apps/indexer/lib/indexer/prometheus/pending_block_operations_collector.ex +++ b/apps/indexer/lib/indexer/prometheus/collector/pending_block_operations_collector.ex @@ -1,4 +1,4 @@ -defmodule Indexer.Prometheus.PendingBlockOperationsCollector do +defmodule Indexer.Prometheus.Collector.PendingBlockOperations do @moduledoc """ Custom collector to count number of records in pending_block_operations table. """ diff --git a/apps/indexer/lib/indexer/supervisor.ex b/apps/indexer/lib/indexer/supervisor.ex index 641b4ac..857a799 100644 --- a/apps/indexer/lib/indexer/supervisor.ex +++ b/apps/indexer/lib/indexer/supervisor.ex @@ -18,6 +18,7 @@ defmodule Indexer.Supervisor do alias Indexer.Block.Catchup, as: BlockCatchup alias Indexer.Block.Realtime, as: BlockRealtime + alias Indexer.Fetcher.Blackfort.Validator, as: ValidatorBlackfort alias Indexer.Fetcher.CoinBalance.Catchup, as: CoinBalanceCatchup alias Indexer.Fetcher.CoinBalance.Realtime, as: CoinBalanceRealtime alias Indexer.Fetcher.Stability.Validator, as: ValidatorStability @@ -46,6 +47,7 @@ defmodule Indexer.Supervisor do Withdrawal } + alias Indexer.Fetcher.Arbitrum.MessagesToL2Matcher, as: ArbitrumMessagesToL2Matcher alias Indexer.Fetcher.Arbitrum.RollupMessagesCatchup, as: ArbitrumRollupMessagesCatchup alias Indexer.Fetcher.Arbitrum.TrackingBatchesStatuses, as: ArbitrumTrackingBatchesStatuses alias Indexer.Fetcher.Arbitrum.TrackingMessagesOnL1, as: ArbitrumTrackingMessagesOnL1 @@ -143,7 +145,7 @@ defmodule Indexer.Supervisor do {ReplacedTransaction.Supervisor, [[memory_monitor: memory_monitor]]}, {Indexer.Fetcher.RollupL1ReorgMonitor.Supervisor, [[memory_monitor: memory_monitor]]}, configure( - Indexer.Fetcher.Optimism.TxnBatch.Supervisor, + Indexer.Fetcher.Optimism.TransactionBatch.Supervisor, [[memory_monitor: memory_monitor, json_rpc_named_arguments: json_rpc_named_arguments]] ), configure(Indexer.Fetcher.Optimism.OutputRoot.Supervisor, [[memory_monitor: memory_monitor]]), @@ -166,6 +168,22 @@ defmodule Indexer.Supervisor do [json_rpc_named_arguments: json_rpc_named_arguments, memory_monitor: memory_monitor] ]), configure(Indexer.Fetcher.Shibarium.L1.Supervisor, [[memory_monitor: memory_monitor]]), + {Indexer.Fetcher.Scroll.BridgeL1.Supervisor, + [ + [memory_monitor: memory_monitor] + ]}, + {Indexer.Fetcher.Scroll.BridgeL2.Supervisor, + [ + [json_rpc_named_arguments: json_rpc_named_arguments, memory_monitor: memory_monitor] + ]}, + {Indexer.Fetcher.Scroll.L1FeeParam.Supervisor, + [ + [json_rpc_named_arguments: json_rpc_named_arguments, memory_monitor: memory_monitor] + ]}, + {Indexer.Fetcher.Scroll.Batch.Supervisor, + [ + [memory_monitor: memory_monitor] + ]}, configure(Indexer.Fetcher.PolygonZkevm.BridgeL1.Supervisor, [[memory_monitor: memory_monitor]]), configure(Indexer.Fetcher.PolygonZkevm.BridgeL1Tokens.Supervisor, [[memory_monitor: memory_monitor]]), configure(Indexer.Fetcher.PolygonZkevm.BridgeL2.Supervisor, [ @@ -189,12 +207,16 @@ defmodule Indexer.Supervisor do configure(ArbitrumRollupMessagesCatchup.Supervisor, [ [json_rpc_named_arguments: json_rpc_named_arguments, memory_monitor: memory_monitor] ]), + {ArbitrumMessagesToL2Matcher.Supervisor, [[memory_monitor: memory_monitor]]}, configure(Indexer.Fetcher.Celo.ValidatorGroupVotes.Supervisor, [ [json_rpc_named_arguments: json_rpc_named_arguments, memory_monitor: memory_monitor] ]), configure(Indexer.Fetcher.Celo.EpochBlockOperations.Supervisor, [ [json_rpc_named_arguments: json_rpc_named_arguments, memory_monitor: memory_monitor] ]), + configure(Indexer.Fetcher.Filecoin.AddressInfo.Supervisor, [ + [memory_monitor: memory_monitor] + ]), {Indexer.Fetcher.Beacon.Blob.Supervisor, [[memory_monitor: memory_monitor]]}, # Out-of-band fetchers @@ -282,6 +304,9 @@ defmodule Indexer.Supervisor do :stability -> [{ValidatorStability, []} | fetchers] + :blackfort -> + [{ValidatorBlackfort, []} | fetchers] + _ -> fetchers end diff --git a/apps/indexer/lib/indexer/token_balances.ex b/apps/indexer/lib/indexer/token_balances.ex index 5348bd2..78180c9 100644 --- a/apps/indexer/lib/indexer/token_balances.ex +++ b/apps/indexer/lib/indexer/token_balances.ex @@ -8,9 +8,7 @@ defmodule Indexer.TokenBalances do require Indexer.Tracer require Logger - alias Explorer.Chain alias Explorer.Token.BalanceReader - alias Indexer.Fetcher.TokenBalance alias Indexer.Tracer @nft_balance_function_abi [ @@ -85,7 +83,7 @@ defmodule Indexer.TokenBalances do requested_token_balances |> handle_killed_tasks(token_balances) |> unfetched_token_balances(fetched_token_balances) - |> schedule_token_balances + |> log_fetching_errors() failed_token_balances = requested_token_balances @@ -119,27 +117,6 @@ defmodule Indexer.TokenBalances do Map.merge(token_balance, %{value: nil, value_fetched_at: nil, error: error_message}) end - defp schedule_token_balances([]), do: nil - - defp schedule_token_balances(unfetched_token_balances) do - Logger.debug(fn -> "#{Enum.count(unfetched_token_balances)} token balances will be retried" end) - - log_fetching_errors(unfetched_token_balances) - - unfetched_token_balances - |> Enum.map(fn token_balance -> - {:ok, address_hash} = Chain.string_to_address_hash(token_balance.address_hash) - {:ok, token_hash} = Chain.string_to_address_hash(token_balance.token_contract_address_hash) - - Map.merge(token_balance, %{ - address_hash: address_hash, - token_contract_address_hash: token_hash, - block_number: token_balance.block_number - }) - end) - |> TokenBalance.async_fetch(false) - end - defp ignore_request_with_errors(%{value: nil, value_fetched_at: nil, error: _error}), do: false defp ignore_request_with_errors(_token_balance), do: true @@ -161,7 +138,7 @@ defmodule Indexer.TokenBalances do end) if Enum.any?(error_messages) do - Logger.debug( + Logger.error( [ "Errors while fetching TokenBalances through Contract interaction: \n", error_messages diff --git a/apps/indexer/lib/indexer/transform/address_coin_balances.ex b/apps/indexer/lib/indexer/transform/address_coin_balances.ex index e85cd1f..84d692c 100644 --- a/apps/indexer/lib/indexer/transform/address_coin_balances.ex +++ b/apps/indexer/lib/indexer/transform/address_coin_balances.ex @@ -67,20 +67,32 @@ defmodule Indexer.Transform.AddressCoinBalances do defp internal_transactions_params_reducer(%{block_number: block_number} = internal_transaction_params, acc) when is_integer(block_number) do case internal_transaction_params do - %{type: "call"} -> + %{type: "call"} = params -> acc + |> process_internal_transaction_field(params, :from_address_hash, block_number) + |> process_internal_transaction_field(params, :to_address_hash, block_number) %{type: "create", error: _} -> acc - %{type: "create", created_contract_address_hash: address_hash} when is_binary(address_hash) -> - MapSet.put(acc, %{address_hash: address_hash, block_number: block_number}) + %{type: "create"} = params -> + process_internal_transaction_field(acc, params, :created_contract_address_hash, block_number) %{type: "selfdestruct", from_address_hash: from_address_hash, to_address_hash: to_address_hash} when is_binary(from_address_hash) and is_binary(to_address_hash) -> acc |> MapSet.put(%{address_hash: from_address_hash, block_number: block_number}) |> MapSet.put(%{address_hash: to_address_hash, block_number: block_number}) + + _params -> + acc + end + end + + defp process_internal_transaction_field(acc, params, field, block_number) do + case Map.get(params, field) do + nil -> acc + address_hash -> MapSet.put(acc, %{address_hash: address_hash, block_number: block_number}) end end diff --git a/apps/indexer/lib/indexer/transform/addresses.ex b/apps/indexer/lib/indexer/transform/addresses.ex index 73c4837..59a9e0b 100644 --- a/apps/indexer/lib/indexer/transform/addresses.ex +++ b/apps/indexer/lib/indexer/transform/addresses.ex @@ -73,11 +73,18 @@ defmodule Indexer.Transform.Addresses do %{from: :block_number, to: :fetched_coin_balance_block_number}, %{from: :to_address_hash, to: :hash} ], - [ - %{from: :block_number, to: :fetched_coin_balance_block_number}, - %{from: :created_contract_address_hash, to: :hash}, - %{from: :created_contract_code, to: :contract_code} - ] + if Application.compile_env(:explorer, :chain_type) == :zksync do + [ + %{from: :block_number, to: :fetched_coin_balance_block_number}, + %{from: :created_contract_address_hash, to: :hash} + ] + else + [ + %{from: :block_number, to: :fetched_coin_balance_block_number}, + %{from: :created_contract_address_hash, to: :hash}, + %{from: :created_contract_code, to: :contract_code} + ] + end ], codes: [ [ @@ -501,18 +508,18 @@ defmodule Indexer.Transform.Addresses do (entity_items = Map.get(fetched_data, entity_key)) != nil, do: extract_addresses_from_collection(entity_items, entity_fields, state) - tx_actions_addresses = + transaction_actions_addresses = fetched_data |> Map.get(:transaction_actions, []) - |> Enum.map(fn tx_action -> - tx_action.data + |> Enum.map(fn transaction_action -> + transaction_action.data |> Map.get(:block_number) - |> find_tx_action_addresses(tx_action.data) + |> find_transaction_action_addresses(transaction_action.data) end) |> List.flatten() addresses - |> Enum.concat(tx_actions_addresses) + |> Enum.concat(transaction_actions_addresses) |> List.flatten() |> merge_addresses() end @@ -522,16 +529,16 @@ defmodule Indexer.Transform.Addresses do def extract_addresses_from_item(item, fields, state), do: Enum.flat_map(fields, &extract_fields(&1, item, state)) - defp find_tx_action_addresses(block_number, data, accumulator \\ []) + defp find_transaction_action_addresses(block_number, data, accumulator \\ []) - defp find_tx_action_addresses(block_number, data, accumulator) when is_map(data) or is_list(data) do + defp find_transaction_action_addresses(block_number, data, accumulator) when is_map(data) or is_list(data) do Enum.reduce(data, accumulator, fn - {_, value}, acc -> find_tx_action_addresses(block_number, value, acc) - value, acc -> find_tx_action_addresses(block_number, value, acc) + {_, value}, acc -> find_transaction_action_addresses(block_number, value, acc) + value, acc -> find_transaction_action_addresses(block_number, value, acc) end) end - defp find_tx_action_addresses(block_number, value, accumulator) when is_binary(value) do + defp find_transaction_action_addresses(block_number, value, accumulator) when is_binary(value) do if Helper.address_correct?(value) do [%{:fetched_coin_balance_block_number => block_number, :hash => value} | accumulator] else @@ -539,7 +546,7 @@ defmodule Indexer.Transform.Addresses do end end - defp find_tx_action_addresses(_block_number, _value, accumulator), do: accumulator + defp find_transaction_action_addresses(_block_number, _value, accumulator), do: accumulator def merge_addresses(addresses) when is_list(addresses) do addresses diff --git a/apps/indexer/lib/indexer/transform/arbitrum/messaging.ex b/apps/indexer/lib/indexer/transform/arbitrum/messaging.ex index f33c327..e8c328f 100644 --- a/apps/indexer/lib/indexer/transform/arbitrum/messaging.ex +++ b/apps/indexer/lib/indexer/transform/arbitrum/messaging.ex @@ -3,6 +3,7 @@ defmodule Indexer.Transform.Arbitrum.Messaging do Helper functions for transforming data for Arbitrum cross-chain messages. """ + alias Explorer.Chain.Arbitrum.Message alias Indexer.Fetcher.Arbitrum.Messaging, as: ArbitrumMessages require Logger @@ -11,25 +12,27 @@ defmodule Indexer.Transform.Arbitrum.Messaging do Parses and combines lists of rollup transactions and logs to identify and process both L1-to-L2 and L2-to-L1 messages. This function utilizes two filtering operations: one that identifies L1-to-L2 - message completions from a list of transactions and another that identifies - L2-to-L1 message initiations from a list of logs. Each filter constructs - a detailed message structure for the respective direction. The function then - combines these messages into a single list suitable for database import. + message completions from a list of transactions, as well as the transactions + suspected of containing messages but requiring additional handling due to + hashed message IDs; and another that identifies L2-to-L1 message initiations + from a list of logs. ## Parameters - `transactions`: A list of rollup transaction entries to filter for L1-to-L2 messages. - `logs`: A list of log entries to filter for L2-to-L1 messages. ## Returns + A tuple containing: - A combined list of detailed message maps from both L1-to-L2 completions and L2-to-L1 initiations, ready for database import. + - A list of transactions with hashed message IDs that require further processing. """ - @spec parse(list(), list()) :: list() + @spec parse([map()], [map()]) :: {[Message.to_import()], [map()]} def parse(transactions, logs) do prev_metadata = Logger.metadata() Logger.metadata(fetcher: :arbitrum_bridge_l2) - l1_to_l2_completion_ops = + {l1_to_l2_completion_ops, transactions_with_hashed_message_id} = transactions |> ArbitrumMessages.filter_l1_to_l2_messages() @@ -39,6 +42,6 @@ defmodule Indexer.Transform.Arbitrum.Messaging do Logger.reset_metadata(prev_metadata) - l1_to_l2_completion_ops ++ l2_to_l1_initiating_ops + {l1_to_l2_completion_ops ++ l2_to_l1_initiating_ops, transactions_with_hashed_message_id} end end diff --git a/apps/indexer/lib/indexer/transform/celo/transaction_token_transfers.ex b/apps/indexer/lib/indexer/transform/celo/transaction_token_transfers.ex index 2efa6b7..659a9c8 100644 --- a/apps/indexer/lib/indexer/transform/celo/transaction_token_transfers.ex +++ b/apps/indexer/lib/indexer/transform/celo/transaction_token_transfers.ex @@ -49,23 +49,25 @@ defmodule Indexer.Transform.Celo.TransactionTokenTransfers do token_transfers = if Application.get_env(:explorer, :chain_type) == :celo do transactions - |> Enum.filter(fn tx -> tx.value > 0 end) - |> Enum.map(fn tx -> - to_address_hash = Map.get(tx, :to_address_hash) || Map.get(tx, :created_contract_address_hash) - log_index = -1 * (tx.index + 1) * @transaction_buffer_size - {:ok, celo_token_address} = CeloCoreContracts.get_address(:celo_token, tx.block_number) + |> Enum.filter(fn transaction -> transaction.value > 0 end) + |> Enum.map(fn transaction -> + to_address_hash = + Map.get(transaction, :to_address_hash) || Map.get(transaction, :created_contract_address_hash) + + log_index = -1 * (transaction.index + 1) * @transaction_buffer_size + {:ok, celo_token_address} = CeloCoreContracts.get_address(:celo_token, transaction.block_number) %{ - amount: Decimal.new(tx.value), - block_hash: tx.block_hash, - block_number: tx.block_number, - from_address_hash: tx.from_address_hash, + amount: Decimal.new(transaction.value), + block_hash: transaction.block_hash, + block_number: transaction.block_number, + from_address_hash: transaction.from_address_hash, log_index: log_index, to_address_hash: to_address_hash, token_contract_address_hash: celo_token_address, token_ids: nil, token_type: @token_type, - transaction_hash: tx.hash + transaction_hash: transaction.hash } end) |> tap(&Logger.debug("Found #{length(&1)} Celo token transfers.")) @@ -86,28 +88,33 @@ defmodule Indexer.Transform.Celo.TransactionTokenTransfers do def parse_internal_transactions(internal_transactions, block_number_to_block_hash) do token_transfers = internal_transactions - |> Enum.filter(fn itx -> - itx.value > 0 && - itx.index > 0 && - not Map.has_key?(itx, :error) && - (not Map.has_key?(itx, :call_type) || itx.call_type != "delegatecall") + |> Enum.filter(fn internal_transaction -> + internal_transaction.value > 0 && + internal_transaction.index > 0 && + not Map.has_key?(internal_transaction, :error) && + (not Map.has_key?(internal_transaction, :call_type) || internal_transaction.call_type != "delegatecall") end) - |> Enum.map(fn itx -> - to_address_hash = Map.get(itx, :to_address_hash) || Map.get(itx, :created_contract_address_hash) - log_index = -1 * (itx.transaction_index * @transaction_buffer_size + itx.index) - {:ok, celo_token_address} = CeloCoreContracts.get_address(:celo_token, itx.block_number) + |> Enum.map(fn internal_transaction -> + to_address_hash = + Map.get(internal_transaction, :to_address_hash) || + Map.get(internal_transaction, :created_contract_address_hash) + + log_index = + -1 * (internal_transaction.transaction_index * @transaction_buffer_size + internal_transaction.index) + + {:ok, celo_token_address} = CeloCoreContracts.get_address(:celo_token, internal_transaction.block_number) %{ - amount: Decimal.new(itx.value), - block_hash: block_number_to_block_hash[itx.block_number], - block_number: itx.block_number, - from_address_hash: itx.from_address_hash, + amount: Decimal.new(internal_transaction.value), + block_hash: block_number_to_block_hash[internal_transaction.block_number], + block_number: internal_transaction.block_number, + from_address_hash: internal_transaction.from_address_hash, log_index: log_index, to_address_hash: to_address_hash, token_contract_address_hash: celo_token_address, token_ids: nil, token_type: @token_type, - transaction_hash: itx.transaction_hash + transaction_hash: internal_transaction.transaction_hash } end) diff --git a/apps/indexer/lib/indexer/transform/scroll/l1_fee_params.ex b/apps/indexer/lib/indexer/transform/scroll/l1_fee_params.ex new file mode 100644 index 0000000..1287525 --- /dev/null +++ b/apps/indexer/lib/indexer/transform/scroll/l1_fee_params.ex @@ -0,0 +1,69 @@ +defmodule Indexer.Transform.Scroll.L1FeeParams do + @moduledoc """ + Helper functions for transforming data for Scroll L1 fee parameters + in realtime block fetcher. + """ + + require Logger + + @doc """ + Takes logs from the realtime fetcher, filters them + by signatures (L1 Gas Oracle events), and prepares an output for + `Chain.import` function. It doesn't work if L1 Gas Oracle contract + address is not configured or the chain type is not :scroll. In this case + the returned value is an empty list. + + ## Parameters + - `logs`: A list of log entries to filter for L1 Gas Oracle events. + + ## Returns + - A list of items ready for database import. The list can be empty. + """ + @spec parse([map()]) :: [Explorer.Chain.Scroll.L1FeeParam.to_import()] + def parse(logs) + + if Application.compile_env(:explorer, :chain_type) == :scroll do + def parse(logs) do + prev_metadata = Logger.metadata() + Logger.metadata(fetcher: :scroll_l1_fee_params_realtime) + + # credo:disable-for-next-line Credo.Check.Design.AliasUsage + gas_oracle = Application.get_env(:indexer, Indexer.Fetcher.Scroll.L1FeeParam)[:gas_oracle] + + # credo:disable-for-lines:2 Credo.Check.Design.AliasUsage + items = + if Indexer.Helper.address_correct?(gas_oracle) do + gas_oracle = String.downcase(gas_oracle) + + logs + |> Enum.filter(&fee_param_update_event?(&1, gas_oracle)) + |> Enum.map(fn log -> + Logger.info("Event for parameter update found.") + # credo:disable-for-next-line Credo.Check.Design.AliasUsage + Indexer.Fetcher.Scroll.L1FeeParam.event_to_param( + log.first_topic, + log.data, + log.block_number, + log.transaction_index + ) + end) + else + Logger.error("L1 Gas Oracle contract address is incorrect. Cannot use #{__MODULE__} for parsing logs.") + [] + end + + Logger.reset_metadata(prev_metadata) + + items + end + + defp fee_param_update_event?(log, gas_oracle) do + # credo:disable-for-lines:3 Credo.Check.Design.AliasUsage + !is_nil(log.first_topic) && + String.downcase(log.first_topic) in Indexer.Fetcher.Scroll.L1FeeParam.event_signatures() && + String.downcase(Indexer.Helper.address_hash_to_string(log.address_hash)) == gas_oracle + end + else + def parse(_logs), do: [] + end +end diff --git a/apps/indexer/lib/indexer/transform/shibarium/bridge.ex b/apps/indexer/lib/indexer/transform/shibarium/bridge.ex index dc34881..6d867c5 100644 --- a/apps/indexer/lib/indexer/transform/shibarium/bridge.ex +++ b/apps/indexer/lib/indexer/transform/shibarium/bridge.ex @@ -43,8 +43,8 @@ defmodule Indexer.Transform.Shibarium.Bridge do deposit_transaction_hashes = transactions_with_receipts - |> Enum.filter(fn tx -> tx.from_address_hash == burn_address_hash_string() end) - |> Enum.map(fn tx -> tx.hash end) + |> Enum.filter(fn transaction -> transaction.from_address_hash == burn_address_hash_string() end) + |> Enum.map(fn transaction -> transaction.hash end) deposit_events = logs @@ -53,11 +53,11 @@ defmodule Indexer.Transform.Shibarium.Bridge do withdrawal_transaction_hashes = transactions_with_receipts - |> Enum.filter(fn tx -> + |> Enum.filter(fn transaction -> # filter by `withdraw(uint256 amount)` signature - String.downcase(String.slice(tx.input, 0..9)) == withdraw_method_signature() + String.downcase(String.slice(transaction.input, 0..9)) == withdraw_method_signature() end) - |> Enum.map(fn tx -> tx.hash end) + |> Enum.map(fn transaction -> transaction.hash end) withdrawal_events = logs diff --git a/apps/indexer/lib/indexer/transform/signed_authorizations.ex b/apps/indexer/lib/indexer/transform/signed_authorizations.ex new file mode 100644 index 0000000..b7e5f8e --- /dev/null +++ b/apps/indexer/lib/indexer/transform/signed_authorizations.ex @@ -0,0 +1,75 @@ +defmodule Indexer.Transform.SignedAuthorizations do + @moduledoc """ + Helper functions for extracting signed authorizations from EIP-7702 transactions. + """ + + alias Explorer.Chain.{Hash, SignedAuthorization} + + # The magic number used in EIP-7702 to prefix the message to be signed. + @eip7702_magic 0x5 + + @doc """ + Extracts signed authorizations from a list of transactions with receipts. + + This function parses the authorization tuples from EIP-7702 set code transactions, + recovers the authority address from the signature, and prepares the data for database import. + + ## Parameters + - `transactions_with_receipts`: A list of transactions with receipts. + + ## Returns + A list of signed authorizations ready for database import. + """ + @spec parse([ + %{optional(:authorization_list) => [EthereumJSONRPC.SignedAuthorization.params()], optional(any()) => any()} + ]) :: [SignedAuthorization.to_import()] + def parse(transactions_with_receipts) do + transactions_with_receipts + |> Enum.filter(&Map.has_key?(&1, :authorization_list)) + |> Enum.flat_map( + &(&1.authorization_list + |> Enum.with_index() + |> Enum.map(fn {authorization, index} -> + authorization + |> Map.merge(%{ + transaction_hash: &1.hash, + index: index, + authority: recover_authority(authorization) + }) + end)) + ) + end + + # This function recovers the signer address from the signed authorization data using this formula: + # authority = ecrecover(keccak(MAGIC || rlp([chain_id, address, nonce])), y_parity, r, s] + @spec recover_authority(EthereumJSONRPC.SignedAuthorization.params()) :: String.t() | nil + defp recover_authority(signed_authorization) do + {:ok, %{bytes: address}} = Hash.Address.cast(signed_authorization.address) + + signed_message = + ExKeccak.hash_256( + <<@eip7702_magic>> <> ExRLP.encode([signed_authorization.chain_id, address, signed_authorization.nonce]) + ) + + authority = + ec_recover(signed_message, signed_authorization.r, signed_authorization.s, signed_authorization.v) + + authority + end + + # This function uses elliptic curve recovery to get the address from the signed message and the signature. + @spec ec_recover(binary(), non_neg_integer(), non_neg_integer(), non_neg_integer()) :: EthereumJSONRPC.address() | nil + defp ec_recover(signed_message, r, s, v) do + r_bytes = <> + s_bytes = <> + + with {:ok, <<_compression::bytes-size(1), public_key::binary>>} <- + ExSecp256k1.recover(signed_message, r_bytes, s_bytes, v), + <<_::bytes-size(12), hash::binary>> <- ExKeccak.hash_256(public_key) do + address = Base.encode16(hash, case: :lower) + "0x" <> address + else + _ -> nil + end + end +end diff --git a/apps/indexer/lib/indexer/transform/transaction_actions.ex b/apps/indexer/lib/indexer/transform/transaction_actions.ex index e2b951f..be9c16d 100644 --- a/apps/indexer/lib/indexer/transform/transaction_actions.ex +++ b/apps/indexer/lib/indexer/transform/transaction_actions.ex @@ -133,7 +133,7 @@ defmodule Indexer.Transform.TransactionActions do if not is_nil(protocols_to_rewrite) do logs - |> logs_group_by_txs() + |> logs_group_by_transactions() |> clear_actions(protocols_to_rewrite) end @@ -157,7 +157,7 @@ defmodule Indexer.Transform.TransactionActions do Enum.member?(protocols_to_rewrite, "aave_v3")) do logs |> aave_filter_logs(String.downcase(aave_v3_pool)) - |> logs_group_by_txs() + |> logs_group_by_transactions() |> aave(actions, chain_id) else actions @@ -175,7 +175,7 @@ defmodule Indexer.Transform.TransactionActions do logs |> uniswap_filter_logs(uniswap_v3_positions_nft) - |> logs_group_by_txs() + |> logs_group_by_transactions() |> uniswap(actions, chain_id, uniswap_v3_positions_nft) else actions @@ -203,9 +203,9 @@ defmodule Indexer.Transform.TransactionActions do defp aave(logs_grouped, actions, chain_id) do # iterate for each transaction - Enum.reduce(logs_grouped, actions, fn {_tx_hash, tx_logs}, actions_acc -> + Enum.reduce(logs_grouped, actions, fn {_transaction_hash, transaction_logs}, actions_acc -> # go through actions - Enum.reduce(tx_logs, actions_acc, fn log, acc -> + Enum.reduce(transaction_logs, actions_acc, fn log, acc -> acc ++ aave_handle_action(log, chain_id) end) end) @@ -388,12 +388,13 @@ defmodule Indexer.Transform.TransactionActions do legitimate = uniswap_legitimate_pools(logs_grouped) # iterate for each transaction - Enum.reduce(logs_grouped, actions, fn {tx_hash, tx_logs}, actions_acc -> + Enum.reduce(logs_grouped, actions, fn {transaction_hash, transaction_logs}, actions_acc -> # trying to find `mint_nft` actions - actions_acc = uniswap_handle_mint_nft_actions(tx_hash, tx_logs, actions_acc, uniswap_v3_positions_nft) + actions_acc = + uniswap_handle_mint_nft_actions(transaction_hash, transaction_logs, actions_acc, uniswap_v3_positions_nft) # go through other actions - Enum.reduce(tx_logs, actions_acc, fn log, acc -> + Enum.reduce(transaction_logs, actions_acc, fn log, acc -> acc ++ uniswap_handle_action(log, legitimate, chain_id) end) end) @@ -455,11 +456,11 @@ defmodule Indexer.Transform.TransactionActions do end end - defp uniswap_handle_mint_nft_actions(tx_hash, tx_logs, actions_acc, uniswap_v3_positions_nft) do - first_log = Enum.at(tx_logs, 0) + defp uniswap_handle_mint_nft_actions(transaction_hash, transaction_logs, actions_acc, uniswap_v3_positions_nft) do + first_log = Enum.at(transaction_logs, 0) local_acc = - tx_logs + transaction_logs |> Enum.reduce(%{}, fn log, acc -> if sanitize_first_topic(log.first_topic) == @uniswap_v3_transfer_nft_event do # This is Transfer event for NFT @@ -495,7 +496,7 @@ defmodule Indexer.Transform.TransactionActions do end) |> Enum.reduce([], fn {to, %{ids: ids, log_index: log_index}}, acc -> action = %{ - hash: tx_hash, + hash: transaction_hash, protocol: "uniswap_v3", data: %{ name: "Uniswap V3: Positions NFT", @@ -554,7 +555,7 @@ defmodule Indexer.Transform.TransactionActions do true -> Logger.error( - "TransactionActions: Invalid Swap event in tx #{log.transaction_hash}. Log index: #{log.index}. amount0 = #{amount0}, amount1 = #{amount1}" + "TransactionActions: Invalid Swap event in transaction #{log.transaction_hash}. Log index: #{log.index}. amount0 = #{amount0}, amount1 = #{amount1}" ) {amount0, symbol0, address0, amount1, symbol1, address1, true} @@ -607,8 +608,8 @@ defmodule Indexer.Transform.TransactionActions do {pools_to_request, pools_cached} = logs_grouped - |> Enum.reduce(%{}, fn {_tx_hash, tx_logs}, addresses_acc -> - tx_logs + |> Enum.reduce(%{}, fn {_transaction_hash, transaction_logs}, addresses_acc -> + transaction_logs |> Enum.filter(fn log -> sanitize_first_topic(log.first_topic) != @uniswap_v3_transfer_nft_event end) @@ -778,12 +779,12 @@ defmodule Indexer.Transform.TransactionActions do defp clear_actions(logs_grouped, protocols_to_clear) do logs_grouped - |> Enum.each(fn {tx_hash, _} -> + |> Enum.each(fn {transaction_hash, _} -> query = if Enum.empty?(protocols_to_clear) do - from(ta in TransactionAction, where: ta.hash == ^tx_hash) + from(ta in TransactionAction, where: ta.hash == ^transaction_hash) else - from(ta in TransactionAction, where: ta.hash == ^tx_hash and ta.protocol in ^protocols_to_clear) + from(ta in TransactionAction, where: ta.hash == ^transaction_hash and ta.protocol in ^protocols_to_clear) end Repo.delete_all(query) @@ -968,7 +969,7 @@ defmodule Indexer.Transform.TransactionActions do {requests, responses} end - defp logs_group_by_txs(logs) do + defp logs_group_by_transactions(logs) do logs |> Enum.group_by(& &1.transaction_hash) end diff --git a/apps/indexer/mix.exs b/apps/indexer/mix.exs index f8fca0a..c84d313 100644 --- a/apps/indexer/mix.exs +++ b/apps/indexer/mix.exs @@ -14,13 +14,13 @@ defmodule Indexer.MixProject do elixirc_paths: elixirc_paths(Mix.env()), lockfile: "../../mix.lock", start_permanent: Mix.env() == :prod, - version: "6.8.0", + version: "6.9.2", xref: [ exclude: [ Explorer.Chain.Optimism.Deposit, Explorer.Chain.Optimism.FrameSequence, Explorer.Chain.Optimism.OutputRoot, - Explorer.Chain.Optimism.TxnBatch, + Explorer.Chain.Optimism.TransactionBatch, Explorer.Chain.Optimism.Withdrawal, Explorer.Chain.Optimism.WithdrawalEvent ] @@ -31,7 +31,7 @@ defmodule Indexer.MixProject do # Run "mix help compile.app" to learn about applications. def application do [ - extra_applications: [:logger], + extra_applications: [:logger, :os_mon], mod: {Indexer.Application, []} ] end diff --git a/apps/indexer/test/indexer/block/catchup/fetcher_test.exs b/apps/indexer/test/indexer/block/catchup/fetcher_test.exs index 292b481..86fac86 100644 --- a/apps/indexer/test/indexer/block/catchup/fetcher_test.exs +++ b/apps/indexer/test/indexer/block/catchup/fetcher_test.exs @@ -52,6 +52,7 @@ defmodule Indexer.Block.Catchup.FetcherTest do InternalTransaction.Supervisor.Case.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments) Token.Supervisor.Case.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments) TokenBalance.Supervisor.Case.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments) + Indexer.Fetcher.Filecoin.AddressInfo.Supervisor.Case.start_supervised!() MissingRangesCollector.start_link([]) MissingRangesManipulator.start_link([]) diff --git a/apps/indexer/test/indexer/block/fetcher_test.exs b/apps/indexer/test/indexer/block/fetcher_test.exs index ce5ff16..dc337b5 100644 --- a/apps/indexer/test/indexer/block/fetcher_test.exs +++ b/apps/indexer/test/indexer/block/fetcher_test.exs @@ -49,7 +49,11 @@ defmodule Indexer.Block.FetcherTest do describe "import_range/2" do setup %{json_rpc_named_arguments: json_rpc_named_arguments} do - CoinBalanceCatchup.Supervisor.Case.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments) + CoinBalanceCatchup.Supervisor.Case.start_supervised!( + json_rpc_named_arguments: json_rpc_named_arguments, + poll: false + ) + ContractCode.Supervisor.Case.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments) InternalTransaction.Supervisor.Case.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments) Token.Supervisor.Case.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments) @@ -275,6 +279,8 @@ defmodule Indexer.Block.FetcherTest do } do block_number = @first_full_block_number + Indexer.Fetcher.Filecoin.AddressInfo.Supervisor.Case.start_supervised!() + if json_rpc_named_arguments[:transport] == EthereumJSONRPC.Mox do case Keyword.fetch!(json_rpc_named_arguments, :variant) do EthereumJSONRPC.Nethermind -> @@ -682,6 +688,8 @@ defmodule Indexer.Block.FetcherTest do } do block_number = 7_374_455 + Indexer.Fetcher.Filecoin.AddressInfo.Supervisor.Case.start_supervised!() + if json_rpc_named_arguments[:transport] == EthereumJSONRPC.Mox do EthereumJSONRPC.Mox |> expect(:json_rpc, 2, fn diff --git a/apps/indexer/test/indexer/block/realtime/fetcher_test.exs b/apps/indexer/test/indexer/block/realtime/fetcher_test.exs index 12e164b..ed249cf 100644 --- a/apps/indexer/test/indexer/block/realtime/fetcher_test.exs +++ b/apps/indexer/test/indexer/block/realtime/fetcher_test.exs @@ -91,6 +91,8 @@ defmodule Indexer.Block.Realtime.FetcherTest do ReplacedTransaction.Supervisor.Case.start_supervised!() + Indexer.Fetcher.Filecoin.AddressInfo.Supervisor.Case.start_supervised!() + # In CELO network, there is a token duality feature where CELO can be used # as both a native chain currency and as an ERC-20 token (GoldToken). # Transactions that transfer CELO are also counted as token transfers, and @@ -596,6 +598,8 @@ defmodule Indexer.Block.Realtime.FetcherTest do block_fetcher: %Indexer.Block.Fetcher{json_rpc_named_arguments: json_rpc_named_arguments} ) + Indexer.Fetcher.Filecoin.AddressInfo.Supervisor.Case.start_supervised!() + ReplacedTransaction.Supervisor.Case.start_supervised!() # In CELO network, there is a token duality feature where CELO can be used diff --git a/apps/indexer/test/indexer/buffered_task_test.exs b/apps/indexer/test/indexer/buffered_task_test.exs index 9dc3a7d..09212ee 100644 --- a/apps/indexer/test/indexer/buffered_task_test.exs +++ b/apps/indexer/test/indexer/buffered_task_test.exs @@ -3,7 +3,8 @@ defmodule Indexer.BufferedTaskTest do import Mox - alias Indexer.{BoundQueue, BufferedTask} + alias Explorer.BoundQueue + alias Indexer.BufferedTask alias Indexer.BufferedTaskTest.{RetryableTask, ShrinkableTask} @max_batch_size 2 diff --git a/apps/indexer/test/indexer/fetcher/coin_balance/catchup_test.exs b/apps/indexer/test/indexer/fetcher/coin_balance/catchup_test.exs index 358598a..b330dcc 100644 --- a/apps/indexer/test/indexer/fetcher/coin_balance/catchup_test.exs +++ b/apps/indexer/test/indexer/fetcher/coin_balance/catchup_test.exs @@ -8,6 +8,7 @@ defmodule Indexer.Fetcher.CoinBalance.CatchupTest do import Mox alias Explorer.Chain.{Address, Hash, Wei} + alias Explorer.Chain.Cache.BlockNumber alias Indexer.Fetcher.CoinBalance.Catchup, as: CoinBalanceCatchup @moduletag :capture_log @@ -21,6 +22,13 @@ defmodule Indexer.Fetcher.CoinBalance.CatchupTest do setup do start_supervised!({Task.Supervisor, name: Indexer.TaskSupervisor}) + initial_config = Application.get_env(:explorer, Explorer.Chain.Cache.BlockNumber) + Application.put_env(:explorer, Explorer.Chain.Cache.BlockNumber, enabled: true) + + on_exit(fn -> + Application.put_env(:explorer, Explorer.Chain.Cache.BlockNumber, initial_config) + end) + :ok end @@ -225,6 +233,8 @@ defmodule Indexer.Fetcher.CoinBalance.CatchupTest do end) end + BlockNumber.set_max(block_number) + CoinBalanceCatchup.Supervisor.Case.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments) assert :ok = CoinBalanceCatchup.async_fetch_balances([%{address_hash: hash, block_number: block_number}]) @@ -318,6 +328,8 @@ defmodule Indexer.Fetcher.CoinBalance.CatchupTest do {:ok, [res2]} end) + BlockNumber.set_max(2) + case CoinBalanceCatchup.run(entries, json_rpc_named_arguments) do :ok -> balances = Repo.all(from(balance in Address.CoinBalance, where: balance.address_hash == ^hash_data)) @@ -373,6 +385,8 @@ defmodule Indexer.Fetcher.CoinBalance.CatchupTest do {:ok, [%{id: id, error: %{code: 1, message: "Bad"}}]} end) + BlockNumber.set_max(block_number()) + assert {:retry, ^entries} = CoinBalanceCatchup.run(entries, json_rpc_named_arguments) end @@ -401,6 +415,8 @@ defmodule Indexer.Fetcher.CoinBalance.CatchupTest do {:ok, [res]} end) + BlockNumber.set_max(block_number) + assert :ok = CoinBalanceCatchup.run(entries, json_rpc_named_arguments) end @@ -456,6 +472,8 @@ defmodule Indexer.Fetcher.CoinBalance.CatchupTest do {:ok, [res_good]} end) + BlockNumber.set_max(good_block_number) + assert {:retry, [{^address_hash_bytes, ^bad_block_number}]} = CoinBalanceCatchup.run( [{address_hash_bytes, good_block_number}, {address_hash_bytes, bad_block_number}], diff --git a/apps/indexer/test/indexer/fetcher/internal_transaction_test.exs b/apps/indexer/test/indexer/fetcher/internal_transaction_test.exs index 66de4d8..1a87a0a 100644 --- a/apps/indexer/test/indexer/fetcher/internal_transaction_test.exs +++ b/apps/indexer/test/indexer/fetcher/internal_transaction_test.exs @@ -604,14 +604,14 @@ defmodule Indexer.Fetcher.InternalTransactionTest do assert nil == Repo.get(PendingBlockOperation, block_hash) - int_txs = Repo.all(from(i in Chain.InternalTransaction, where: i.block_hash == ^block_hash)) + internal_transactions = Repo.all(from(i in Chain.InternalTransaction, where: i.block_hash == ^block_hash)) - assert Enum.count(int_txs) > 0 + assert Enum.count(internal_transactions) > 0 - last_int_tx = List.last(int_txs) + last_internal_transaction = List.last(internal_transactions) - assert last_int_tx.type == :call - assert last_int_tx.call_type == :invalid + assert last_internal_transaction.type == :call + assert last_internal_transaction.call_type == :invalid end end diff --git a/apps/indexer/test/indexer/fetcher/on_demand/token_instance_metadata_refetch_test.exs b/apps/indexer/test/indexer/fetcher/on_demand/token_instance_metadata_refetch_test.exs index d1203de..79d6f52 100644 --- a/apps/indexer/test/indexer/fetcher/on_demand/token_instance_metadata_refetch_test.exs +++ b/apps/indexer/test/indexer/fetcher/on_demand/token_instance_metadata_refetch_test.exs @@ -33,6 +33,12 @@ defmodule Indexer.Fetcher.OnDemand.TokenInstanceMetadataRefetchTest do setup do Subscriber.to(:fetched_token_instance_metadata, :on_demand) + Application.put_env(:explorer, :http_adapter, Explorer.Mox.HTTPoison) + + on_exit(fn -> + Application.put_env(:explorer, :http_adapter, HTTPoison) + end) + :ok end @@ -54,8 +60,6 @@ defmodule Indexer.Fetcher.OnDemand.TokenInstanceMetadataRefetchTest do TestHelper.fetch_token_uri_mock(url, token_contract_address_hash_string) - Application.put_env(:explorer, :http_adapter, Explorer.Mox.HTTPoison) - Explorer.Mox.HTTPoison |> expect(:get, fn ^url, _headers, _options -> {:ok, %HTTPoison.Response{status_code: 200, body: Jason.encode!(metadata)}} @@ -83,11 +87,9 @@ defmodule Indexer.Fetcher.OnDemand.TokenInstanceMetadataRefetchTest do {:chain_event, :fetched_token_instance_metadata, :on_demand, [^token_contract_address_hash_string, ^token_id, ^metadata]} ) - - Application.put_env(:explorer, :http_adapter, HTTPoison) end - test "don't run the update on the token instance with no metadata fetched initially" do + test "run the update on the token instance with no metadata fetched initially" do token = insert(:token, name: "Super Token", type: "ERC-721") token_id = 1 @@ -100,9 +102,17 @@ defmodule Indexer.Fetcher.OnDemand.TokenInstanceMetadataRefetchTest do |> Repo.preload(:token) metadata = %{"name" => "Super Token"} + url = "http://metadata.endpoint.com" token_contract_address_hash_string = to_string(token.contract_address_hash) - assert TokenInstanceMetadataRefetchOnDemand.trigger_refetch(token_instance) == nil + TestHelper.fetch_token_uri_mock(url, token_contract_address_hash_string) + + Explorer.Mox.HTTPoison + |> expect(:get, fn ^url, _headers, _options -> + {:ok, %HTTPoison.Response{status_code: 200, body: Jason.encode!(metadata)}} + end) + + assert TokenInstanceMetadataRefetchOnDemand.trigger_refetch(token_instance) == :ok :timer.sleep(100) @@ -110,7 +120,7 @@ defmodule Indexer.Fetcher.OnDemand.TokenInstanceMetadataRefetchTest do Repo.get_by(TokenInstance, token_id: token_id, token_contract_address_hash: token.contract_address_hash) assert(token_instance_from_db) - assert is_nil(token_instance_from_db.metadata) + assert token_instance_from_db.metadata == metadata assert is_nil( Repo.get_by(TokenInstanceMetadataRefetchAttempt, @@ -119,9 +129,9 @@ defmodule Indexer.Fetcher.OnDemand.TokenInstanceMetadataRefetchTest do ) ) - refute_receive( + assert_receive( {:chain_event, :fetched_token_instance_metadata, :on_demand, - [^token_contract_address_hash_string, ^token_id, %{metadata: ^metadata}]} + [^token_contract_address_hash_string, ^token_id, ^metadata]} ) end @@ -143,8 +153,6 @@ defmodule Indexer.Fetcher.OnDemand.TokenInstanceMetadataRefetchTest do TestHelper.fetch_token_uri_mock(url, token_contract_address_hash_string) - Application.put_env(:explorer, :http_adapter, Explorer.Mox.HTTPoison) - Explorer.Mox.HTTPoison |> expect(:get, fn ^url, _headers, _options -> {:ok, %HTTPoison.Response{status_code: 200, body: nil}} @@ -174,8 +182,6 @@ defmodule Indexer.Fetcher.OnDemand.TokenInstanceMetadataRefetchTest do {:chain_event, :fetched_token_instance_metadata, :on_demand, [^token_contract_address_hash_string, ^token_id, %{metadata: ^metadata}]} ) - - Application.put_env(:explorer, :http_adapter, HTTPoison) end end end diff --git a/apps/indexer/test/indexer/fetcher/token_balance_test.exs b/apps/indexer/test/indexer/fetcher/token_balance_test.exs index e39cc6e..0c4fb0b 100644 --- a/apps/indexer/test/indexer/fetcher/token_balance_test.exs +++ b/apps/indexer/test/indexer/fetcher/token_balance_test.exs @@ -28,6 +28,22 @@ defmodule Indexer.Fetcher.TokenBalanceTest do {address_hash_bytes, token_contract_address_hash_bytes, 1000, "ERC-20", nil, 0} ] end + + test "omits failed balances with refetch_after in future" do + %Address.TokenBalance{ + address_hash: %Hash{bytes: address_hash_bytes}, + token_contract_address_hash: %Hash{bytes: token_contract_address_hash_bytes}, + block_number: block_number + } = insert(:token_balance, value_fetched_at: nil) + + insert(:token_balance, value_fetched_at: DateTime.utc_now()) + + insert(:token_balance, refetch_after: Timex.shift(Timex.now(), hours: 1)) + + assert TokenBalance.init([], &[&1 | &2], nil) == [ + {address_hash_bytes, token_contract_address_hash_bytes, block_number, "ERC-20", nil, 0} + ] + end end describe "run/3" do @@ -70,86 +86,6 @@ defmodule Indexer.Fetcher.TokenBalanceTest do assert token_balance_updated.value_fetched_at != nil end - test "imports the given token balances from 2nd retry" do - %Address.TokenBalance{ - address_hash: %Hash{bytes: address_hash_bytes} = address_hash, - token_contract_address_hash: %Hash{bytes: token_contract_address_hash_bytes}, - block_number: block_number - } = insert(:token_balance, value_fetched_at: nil, value: nil) - - expect( - EthereumJSONRPC.Mox, - :json_rpc, - fn [%{id: id, method: "eth_call", params: [%{data: _, to: _}, _]}], _options -> - {:ok, - [ - %{ - id: id, - jsonrpc: "2.0", - error: %{code: -32015, message: "VM execution error.", data: ""} - } - ]} - end - ) - - expect( - EthereumJSONRPC.Mox, - :json_rpc, - fn [%{id: id, method: "eth_call", params: [%{data: _, to: _}, _]}], _options -> - {:ok, - [ - %{ - id: id, - jsonrpc: "2.0", - result: "0x00000000000000000000000000000000000000000000d3c21bcecceda1000000" - } - ]} - end - ) - - assert TokenBalance.run( - [{address_hash_bytes, token_contract_address_hash_bytes, block_number, "ERC-20", nil, 0}], - nil - ) == :ok - - token_balance_updated = Repo.get_by(Address.TokenBalance, address_hash: address_hash) - - assert token_balance_updated.value == Decimal.new(1_000_000_000_000_000_000_000_000) - assert token_balance_updated.value_fetched_at != nil - end - - test "does not try to fetch the token balance again if the retry is over" do - max_retries = 3 - - Application.put_env(:indexer, :token_balance_max_retries, max_retries) - - token_balance_a = insert(:token_balance, value_fetched_at: nil, value: nil) - token_balance_b = insert(:token_balance, value_fetched_at: nil, value: nil) - - token_balances = [ - { - token_balance_a.address_hash.bytes, - token_balance_a.token_contract_address_hash.bytes, - "ERC-20", - nil, - token_balance_a.block_number, - # this token balance must be ignored - max_retries - }, - { - token_balance_b.address_hash.bytes, - token_balance_b.token_contract_address_hash.bytes, - "ERC-20", - nil, - token_balance_b.block_number, - # this token balance still have to be retried - max_retries - 2 - } - ] - - assert TokenBalance.run(token_balances, nil) == :ok - end - test "fetches duplicate params only once" do %Address.TokenBalance{ address_hash: %Hash{bytes: address_hash_bytes} = address_hash, @@ -233,7 +169,7 @@ defmodule Indexer.Fetcher.TokenBalanceTest do assert %{currently_implemented: true} = Repo.one(MissingBalanceOfToken) end - test "in case of error deletes token balance placeholders below the given number and inserts new missing balanceOf tokens" do + test "in case of execution reverted error deletes token balance placeholders below the given number and inserts new missing balanceOf tokens" do address = insert(:address) %{contract_address_hash: token_contract_address_hash} = insert(:token) @@ -272,6 +208,46 @@ defmodule Indexer.Fetcher.TokenBalanceTest do assert Repo.all(Address.TokenBalance) == [] end + + test "in case of other error updates the refetch_after and retries_count of token balance" do + address = insert(:address) + %{contract_address_hash: token_contract_address_hash} = insert(:token) + + insert(:token_balance, + token_contract_address_hash: token_contract_address_hash, + address: address, + block_number: 1, + value_fetched_at: nil, + value: nil, + refetch_after: nil, + retries_count: nil + ) + + expect( + EthereumJSONRPC.Mox, + :json_rpc, + fn [%{id: id, method: "eth_call", params: [%{data: _, to: _}, _]}], _options -> + {:ok, + [ + %{ + id: id, + jsonrpc: "2.0", + error: %{code: "-32000", message: "other error"} + } + ]} + end + ) + + assert TokenBalance.run( + [ + {address.hash.bytes, token_contract_address_hash.bytes, 1, "ERC-20", nil, 0} + ], + nil + ) == :ok + + assert %{retries_count: 1, refetch_after: refetch_after} = Repo.one(Address.TokenBalance) + refute is_nil(refetch_after) + end end describe "import_token_balances/1" do diff --git a/apps/indexer/test/indexer/fetcher/token_instance/helper_test.exs b/apps/indexer/test/indexer/fetcher/token_instance/helper_test.exs index 552ea31..03e6f27 100644 --- a/apps/indexer/test/indexer/fetcher/token_instance/helper_test.exs +++ b/apps/indexer/test/indexer/fetcher/token_instance/helper_test.exs @@ -25,7 +25,7 @@ defmodule Indexer.Fetcher.TokenInstance.HelperTest 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\":[]}" + "{\"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_transaction_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 -> diff --git a/apps/indexer/test/indexer/fetcher/token_instance/sanitize_erc721_test.exs b/apps/indexer/test/indexer/fetcher/token_instance/sanitize_erc721_test.exs index 5568b8d..79b27ad 100644 --- a/apps/indexer/test/indexer/fetcher/token_instance/sanitize_erc721_test.exs +++ b/apps/indexer/test/indexer/fetcher/token_instance/sanitize_erc721_test.exs @@ -10,14 +10,14 @@ defmodule Indexer.Fetcher.TokenInstance.SanitizeERC721Test do for x <- 0..3 do erc_721_token = insert(:token, type: "ERC-721") - tx = insert(:transaction, input: "0xabcd010203040506") |> with_block() + transaction = insert(:transaction, input: "0xabcd010203040506") |> with_block() address = insert(:address) insert(:token_transfer, - transaction: tx, - block: tx.block, - block_number: tx.block_number, + transaction: transaction, + block: transaction.block, + block_number: transaction.block_number, from_address: address, token_contract_address: erc_721_token.contract_address, token_ids: [x] 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 ae2889b..a230b43 100644 --- a/apps/indexer/test/indexer/transform/address_coin_balances_test.exs +++ b/apps/indexer/test/indexer/transform/address_coin_balances_test.exs @@ -27,16 +27,24 @@ defmodule Indexer.Transform.AddressCoinBalancesTest do assert MapSet.size(params_set) == 0 end - test "with call internal transaction extracts nothing" do + test "with call internal transaction extracts from_address_hash and to_address_hash" do + block_number = 1 + from_address_hash = to_string(Factory.address_hash()) + to_address_hash = to_string(Factory.address_hash()) + internal_transaction_params = :internal_transaction |> Factory.params_for() - |> Map.update!(:type, &to_string/1) - |> Map.put(:block_number, 1) + |> Map.put(:type, "call") + |> Map.put(:block_number, block_number) + |> Map.put(:from_address_hash, from_address_hash) + |> Map.put(:to_address_hash, to_address_hash) params_set = AddressCoinBalances.params_set(%{internal_transactions_params: [internal_transaction_params]}) - assert MapSet.size(params_set) == 0 + assert MapSet.size(params_set) == 2 + assert MapSet.member?(params_set, %{address_hash: from_address_hash, block_number: block_number}) + assert MapSet.member?(params_set, %{address_hash: to_address_hash, block_number: block_number}) end test "with create internal transaction with error extracts nothing" do @@ -69,7 +77,7 @@ defmodule Indexer.Transform.AddressCoinBalancesTest do params_set = AddressCoinBalances.params_set(%{internal_transactions_params: [internal_transaction_params]}) assert MapSet.size(params_set) == 1 - assert %{address_hash: created_contract_address_hash, block_number: block_number} + assert MapSet.member?(params_set, %{address_hash: created_contract_address_hash, block_number: block_number}) end test "with self-destruct internal transaction extracts from_address_hash and to_address_hash" do @@ -94,8 +102,8 @@ defmodule Indexer.Transform.AddressCoinBalancesTest do params_set = AddressCoinBalances.params_set(%{internal_transactions_params: [internal_transaction_params]}) assert MapSet.size(params_set) == 2 - assert %{address_hash: from_address_hash, block_number: block_number} - assert %{address_hash: to_address_hash, block_number: block_number} + assert MapSet.member?(params_set, %{address_hash: from_address_hash, block_number: block_number}) + assert MapSet.member?(params_set, %{address_hash: to_address_hash, block_number: block_number}) end test "with log extracts address_hash" do @@ -162,7 +170,7 @@ defmodule Indexer.Transform.AddressCoinBalancesTest do params_set = AddressCoinBalances.params_set(%{transactions_params: [transaction_params]}) assert MapSet.size(params_set) == 1 - assert %{address_hash: from_address_hash, block_number: block_number} + assert MapSet.member?(params_set, %{address_hash: from_address_hash, block_number: block_number}) end test "with transaction with to_address_hash extracts from_address_hash and to_address_hash" do @@ -186,8 +194,8 @@ defmodule Indexer.Transform.AddressCoinBalancesTest do params_set = AddressCoinBalances.params_set(%{transactions_params: [transaction_params]}) assert MapSet.size(params_set) == 2 - assert %{address_hash: from_address_hash, block_number: block_number} - assert %{address_hash: to_address_hash, block_number: block_number} + assert MapSet.member?(params_set, %{address_hash: from_address_hash, block_number: block_number}) + assert MapSet.member?(params_set, %{address_hash: to_address_hash, block_number: block_number}) end end end diff --git a/apps/indexer/test/support/indexer/fetcher/coin_balance_catchup_supervisor_case.ex b/apps/indexer/test/support/indexer/fetcher/coin_balance_catchup_supervisor_case.ex index 1783109..69ec782 100644 --- a/apps/indexer/test/support/indexer/fetcher/coin_balance_catchup_supervisor_case.ex +++ b/apps/indexer/test/support/indexer/fetcher/coin_balance_catchup_supervisor_case.ex @@ -4,10 +4,12 @@ defmodule Indexer.Fetcher.CoinBalance.Catchup.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 + [ + flush_interval: 50, + max_batch_size: 1, + max_concurrency: 1 + ], + fetcher_arguments ) [merged_fetcher_arguments] diff --git a/apps/indexer/test/support/indexer/fetcher/filecoin_native_address_supervisor_case.ex b/apps/indexer/test/support/indexer/fetcher/filecoin_native_address_supervisor_case.ex new file mode 100644 index 0000000..6f46db1 --- /dev/null +++ b/apps/indexer/test/support/indexer/fetcher/filecoin_native_address_supervisor_case.ex @@ -0,0 +1,17 @@ +defmodule Indexer.Fetcher.Filecoin.AddressInfo.Supervisor.Case do + alias Indexer.Fetcher.Filecoin.AddressInfo + + 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 + ) + + [merged_fetcher_arguments] + |> AddressInfo.Supervisor.child_spec() + |> ExUnit.Callbacks.start_supervised!() + end +end diff --git a/bin/version_bump.sh b/bin/version_bump.sh new file mode 100755 index 0000000..97583f4 --- /dev/null +++ b/bin/version_bump.sh @@ -0,0 +1,79 @@ +#!/bin/bash + +# Path to the mix.exs file +MIX_FILES=( + "$(pwd)/mix.exs" + "$(pwd)/apps/block_scout_web/mix.exs" + "$(pwd)/apps/explorer/mix.exs" + "$(pwd)/apps/indexer/mix.exs" + "$(pwd)/apps/ethereum_jsonrpc/mix.exs" +) +CONFIG_FILE="$(pwd)/rel/config.exs" +DOCKER_COMPOSE_FILE="$(pwd)/docker-compose/docker-compose.yml" +MAKE_FILE="$(pwd)/docker/Makefile" +WORKFLOW_FILES=($(find "$(pwd)/.github/workflows" -type f \( -name "pre-release*" -o -name "release*" -o -name "publish-regular-docker-image-on-demand*" -o -name "publish-docker-image-*" \))) +METADATA_RETRIEVER_FILE="$(pwd)/apps/explorer/lib/explorer/token/metadata_retriever.ex" + +# Function to bump version +bump_version() { + local type=$1 + local custom_version=$2 + + # Extract the current version + MIX_FILE="${MIX_FILES[0]}" + current_version=$(grep -o 'version: "[0-9]\+\.[0-9]\+\.[0-9]\+"' "$MIX_FILE" | grep -o '[0-9]\+\.[0-9]\+\.[0-9]\+') + echo "Current version: $current_version" + + # Split the version into its components + IFS='.' read -r -a version_parts <<< "$current_version" + + # Check if the --patch flag is provided + if [[ "$type" == "--patch" ]]; then + # Increment the patch version + version_parts[2]=$((version_parts[2] + 1)) + elif [[ "$type" == "--minor" ]]; then + # Increment the minor version and reset the patch version + version_parts[1]=$((version_parts[1] + 1)) + version_parts[2]=0 + elif [[ "$type" == "--major" ]]; then + # Increment the major version and reset the minor and patch versions + version_parts[0]=$((version_parts[0] + 1)) + version_parts[1]=0 + version_parts[2]=0 + elif [[ "$type" == "--update-to-version" ]]; then + # Apply the version from the 3rd argument + if [[ -z "$2" ]]; then + echo "Error: No version specified for --update-to-version." + exit 1 + fi + new_version="$custom_version" + IFS='.' read -r -a version_parts <<< "$new_version" + else + echo "No --patch flag provided. Exiting." + exit 1 + fi + + # Join the version parts back together + new_version="${version_parts[0]}.${version_parts[1]}.${version_parts[2]}" + + # Replace the old version with the new version in the mix.exs files + for MIX_FILE in "${MIX_FILES[@]}"; do + sed -i '' "s/version: \"$current_version\"/version: \"$new_version\"/" "$MIX_FILE" + done + + sed -i '' "s/version: \"$current_version/version: \"$new_version/" "$CONFIG_FILE" + sed -i '' "s/RELEASE_VERSION: $current_version/RELEASE_VERSION: $new_version/" "$DOCKER_COMPOSE_FILE" + sed -i '' "s/RELEASE_VERSION ?= '$current_version'/RELEASE_VERSION ?= '$new_version'/" "$MAKE_FILE" + + # Replace the old version with the new version in the GitHub workflows files + for WORKFLOW_FILE in "${WORKFLOW_FILES[@]}"; do + sed -i '' "s/RELEASE_VERSION: $current_version/RELEASE_VERSION: $new_version/" "$WORKFLOW_FILE" + done + + sed -i '' "s/\"blockscout-$current_version\"/\"blockscout-$new_version\"/" "$METADATA_RETRIEVER_FILE" + + echo "Version bumped from $current_version to $new_version" +} + +# Call the function +bump_version "$1" "$2" \ No newline at end of file diff --git a/config/config_helper.exs b/config/config_helper.exs index ffaf0ad..715cfbb 100644 --- a/config/config_helper.exs +++ b/config/config_helper.exs @@ -16,6 +16,7 @@ defmodule ConfigHelper do :polygon_edge -> base_repos ++ [Explorer.Repo.PolygonEdge] :polygon_zkevm -> base_repos ++ [Explorer.Repo.PolygonZkevm] :rsk -> base_repos ++ [Explorer.Repo.RSK] + :scroll -> base_repos ++ [Explorer.Repo.Scroll] :shibarium -> base_repos ++ [Explorer.Repo.Shibarium] :suave -> base_repos ++ [Explorer.Repo.Suave] :filecoin -> base_repos ++ [Explorer.Repo.Filecoin] @@ -23,6 +24,7 @@ defmodule ConfigHelper do :zksync -> base_repos ++ [Explorer.Repo.ZkSync] :celo -> base_repos ++ [Explorer.Repo.Celo] :arbitrum -> base_repos ++ [Explorer.Repo.Arbitrum] + :blackfort -> base_repos ++ [Explorer.Repo.Blackfort] _ -> base_repos end @@ -159,16 +161,14 @@ defmodule ConfigHelper do @spec indexer_memory_limit() :: integer() def indexer_memory_limit do - indexer_memory_limit_default = 1 - "INDEXER_MEMORY_LIMIT" - |> safe_get_env(to_string(indexer_memory_limit_default)) + |> safe_get_env(nil) |> String.downcase() |> Integer.parse() |> case do {integer, g} when g in ["g", "gb", ""] -> integer <<< 30 {integer, m} when m in ["m", "mb"] -> integer <<< 20 - _ -> indexer_memory_limit_default <<< 30 + _ -> nil end end @@ -311,17 +311,24 @@ defmodule ConfigHelper do "polygon_edge", "polygon_zkevm", "rsk", + "scroll", "shibarium", "stability", "suave", "zetachain", "zksync", - "celo" + "celo", + "blackfort" ] @spec chain_type() :: atom() | nil def chain_type, do: parse_catalog_value("CHAIN_TYPE", @supported_chain_types, true, "default") + @supported_modes ["all", "indexer", "api"] + + @spec mode :: atom() + def mode, do: parse_catalog_value("APPLICATION_MODE", @supported_modes, true, "all") + @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 diff --git a/config/runtime.exs b/config/runtime.exs index 4bbb936..2ad5409 100644 --- a/config/runtime.exs +++ b/config/runtime.exs @@ -33,6 +33,7 @@ config :block_scout_web, permanent_light_mode_enabled: ConfigHelper.parse_bool_env_var("PERMANENT_LIGHT_MODE_ENABLED"), display_token_icons: ConfigHelper.parse_bool_env_var("DISPLAY_TOKEN_ICONS"), hide_block_miner: ConfigHelper.parse_bool_env_var("HIDE_BLOCK_MINER"), + hide_scam_addresses: ConfigHelper.parse_bool_env_var("HIDE_SCAM_ADDRESSES"), show_tenderly_link: ConfigHelper.parse_bool_env_var("SHOW_TENDERLY_LINK"), sensitive_endpoints_api_key: System.get_env("API_SENSITIVE_ENDPOINTS_KEY"), disable_api?: disable_api? @@ -42,7 +43,9 @@ config :block_scout_web, :recaptcha, v2_secret_key: System.get_env("RE_CAPTCHA_SECRET_KEY"), v3_client_key: System.get_env("RE_CAPTCHA_V3_CLIENT_KEY"), v3_secret_key: System.get_env("RE_CAPTCHA_V3_SECRET_KEY"), - is_disabled: ConfigHelper.parse_bool_env_var("RE_CAPTCHA_DISABLED") + is_disabled: ConfigHelper.parse_bool_env_var("RE_CAPTCHA_DISABLED"), + check_hostname?: ConfigHelper.parse_bool_env_var("RE_CAPTCHA_CHECK_HOSTNAME", "true"), + score_threshold: ConfigHelper.parse_float_env_var("RE_CAPTCHA_SCORE_THRESHOLD", "0.5") network_path = "NETWORK_PATH" @@ -140,7 +143,7 @@ price_chart_config = price_chart_legend_enabled? = ConfigHelper.parse_bool_env_var("SHOW_PRICE_CHART") || ConfigHelper.parse_bool_env_var("SHOW_PRICE_CHART_LEGEND") -tx_chart_config = +transaction_chart_config = if ConfigHelper.parse_bool_env_var("SHOW_TXS_CHART", "true") do %{transactions: [:transactions_per_day]} else @@ -148,7 +151,7 @@ tx_chart_config = end config :block_scout_web, :chart, - chart_config: Map.merge(price_chart_config, tx_chart_config), + chart_config: Map.merge(price_chart_config, transaction_chart_config), price_chart_legend_enabled?: price_chart_legend_enabled? config :block_scout_web, BlockScoutWeb.Chain.Address.CoinBalance, @@ -218,6 +221,7 @@ checksum_function = System.get_env("CHECKSUM_FUNCTION") exchange_rates_coin = System.get_env("EXCHANGE_RATES_COIN") config :explorer, + mode: ConfigHelper.mode(), coin: System.get_env("COIN") || exchange_rates_coin || "ETH", coin_name: System.get_env("COIN_NAME") || exchange_rates_coin || "ETH", allowed_solidity_evm_versions: @@ -496,6 +500,10 @@ config :explorer, Explorer.ThirdPartyIntegrations.Zerion, service_url: System.get_env("ZERION_BASE_API_URL", "https://api.zerion.io/v1"), api_key: System.get_env("ZERION_API_TOKEN") +config :explorer, Explorer.ThirdPartyIntegrations.Xname, + service_url: System.get_env("XNAME_BASE_API_URL", "https://gateway.xname.app"), + api_key: System.get_env("XNAME_API_TOKEN") + enabled? = ConfigHelper.parse_bool_env_var("MICROSERVICE_SC_VERIFIER_ENABLED", "true") # or "eth_bytecode_db" type = System.get_env("MICROSERVICE_SC_VERIFIER_TYPE", "sc_verifier") @@ -551,11 +559,14 @@ 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"), + verification_email_resend_interval: + ConfigHelper.parse_time_env_var("ACCOUNT_VERIFICATION_EMAIL_RESEND_INTERVAL", "5m"), + otp_resend_interval: ConfigHelper.parse_time_env_var("ACCOUNT_OTP_RESEND_INTERVAL", "1m"), 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) + ConfigHelper.parse_integer_env_var("ACCOUNT_WATCHLIST_NOTIFICATIONS_LIMIT_FOR_30_DAYS", 1000), + siwe_message: System.get_env("ACCOUNT_SIWE_MESSAGE", "Sign in to Blockscout Account V2") config :explorer, :token_id_migration, first_block: ConfigHelper.parse_integer_env_var("TOKEN_ID_MIGRATION_FIRST_BLOCK", 0), @@ -614,10 +625,18 @@ config :explorer, Explorer.Migrator.RestoreOmittedWETHTransfers, batch_size: ConfigHelper.parse_integer_env_var("MIGRATION_RESTORE_OMITTED_WETH_TOKEN_TRANSFERS_BATCH_SIZE", 50), timeout: ConfigHelper.parse_time_env_var("MIGRATION_RESTORE_OMITTED_WETH_TOKEN_TRANSFERS_TIMEOUT", "250ms") +config :explorer, Explorer.Migrator.SanitizeDuplicatedLogIndexLogs, + concurrency: ConfigHelper.parse_integer_env_var("MIGRATION_SANITIZE_DUPLICATED_LOG_INDEX_LOGS_CONCURRENCY", 10), + batch_size: ConfigHelper.parse_integer_env_var("MIGRATION_SANITIZE_DUPLICATED_LOG_INDEX_LOGS_BATCH_SIZE", 500) + +config :explorer, Explorer.Migrator.RefetchContractCodes, + concurrency: ConfigHelper.parse_integer_env_var("MIGRATION_REFETCH_CONTRACT_CODES_CONCURRENCY", 5), + batch_size: ConfigHelper.parse_integer_env_var("MIGRATION_REFETCH_CONTRACT_CODES_BATCH_SIZE", 100) + config :explorer, Explorer.Migrator.ShrinkInternalTransactions, enabled: ConfigHelper.parse_bool_env_var("SHRINK_INTERNAL_TRANSACTIONS_ENABLED"), - batch_size: ConfigHelper.parse_integer_env_var("SHRINK_INTERNAL_TRANSACTIONS_BATCH_SIZE", 1000), - concurrency: ConfigHelper.parse_integer_env_var("SHRINK_INTERNAL_TRANSACTIONS_CONCURRENCY", 1) + batch_size: ConfigHelper.parse_integer_env_var("SHRINK_INTERNAL_TRANSACTIONS_BATCH_SIZE", 100), + concurrency: ConfigHelper.parse_integer_env_var("SHRINK_INTERNAL_TRANSACTIONS_CONCURRENCY", 10) config :explorer, Explorer.Chain.BridgedToken, eth_omni_bridge_mediator: System.get_env("BRIDGED_TOKENS_ETH_OMNI_BRIDGE_MEDIATOR"), @@ -637,10 +656,22 @@ config :explorer, Explorer.Chain.Metrics, enabled: ConfigHelper.parse_bool_env_var("PUBLIC_METRICS_ENABLED", "false"), update_period_hours: ConfigHelper.parse_integer_env_var("PUBLIC_METRICS_UPDATE_PERIOD_HOURS", 24) +config :explorer, Explorer.Chain.Filecoin.NativeAddress, + network_prefix: ConfigHelper.parse_catalog_value("FILECOIN_NETWORK_PREFIX", ["f", "t"], true, "f") + +config :explorer, Explorer.Migrator.FilecoinPendingAddressOperations, + enabled: ConfigHelper.chain_type() == :filecoin, + batch_size: ConfigHelper.parse_integer_env_var("FILECOIN_PENDING_ADDRESS_OPERATIONS_MIGRATION_BATCH_SIZE", 100), + concurrency: ConfigHelper.parse_integer_env_var("FILECOIN_PENDING_ADDRESS_OPERATIONS_MIGRATION_CONCURRENCY", 1) + +config :explorer, Explorer.Chain.Blackfort.Validator, api_url: System.get_env("BLACKFORT_VALIDATOR_API_URL") + ############### ### Indexer ### ############### +first_block = ConfigHelper.parse_integer_env_var("FIRST_BLOCK", 0) + 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") @@ -654,13 +685,14 @@ 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: ConfigHelper.parse_integer_env_var("FIRST_BLOCK", 0), + first_block: first_block, 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(), + system_memory_percentage: ConfigHelper.parse_integer_env_var("INDEXER_SYSTEM_MEMORY_PERCENTAGE", 60), 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"), @@ -669,7 +701,9 @@ config :indexer, 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), - graceful_shutdown_period: ConfigHelper.parse_time_env_var("INDEXER_GRACEFUL_SHUTDOWN_PERIOD", "5m") + graceful_shutdown_period: ConfigHelper.parse_time_env_var("INDEXER_GRACEFUL_SHUTDOWN_PERIOD", "5m"), + internal_transactions_fetch_order: + ConfigHelper.parse_catalog_value("INDEXER_INTERNAL_TRANSACTIONS_FETCH_ORDER", ["asc", "desc"], true, "asc") config :indexer, :ipfs, gateway_url: System.get_env("IPFS_GATEWAY_URL", "https://ipfs.io/ipfs"), @@ -707,7 +741,9 @@ config :indexer, Indexer.Fetcher.Token, concurrency: ConfigHelper.parse_integer_ config :indexer, Indexer.Fetcher.TokenBalance, batch_size: ConfigHelper.parse_integer_env_var("INDEXER_TOKEN_BALANCES_BATCH_SIZE", 100), - concurrency: ConfigHelper.parse_integer_env_var("INDEXER_TOKEN_BALANCES_CONCURRENCY", 10) + concurrency: ConfigHelper.parse_integer_env_var("INDEXER_TOKEN_BALANCES_CONCURRENCY", 10), + max_refetch_interval: ConfigHelper.parse_time_env_var("INDEXER_TOKEN_BALANCES_MAX_REFETCH_INTERVAL", "168h"), + exp_timeout_coeff: ConfigHelper.parse_integer_env_var("INDEXER_TOKEN_BALANCES_EXPONENTIAL_TIMEOUT_COEFF", 100) config :indexer, Indexer.Fetcher.OnDemand.TokenBalance, threshold: ConfigHelper.parse_time_env_var("TOKEN_BALANCE_ON_DEMAND_FETCHER_THRESHOLD", "1h"), @@ -836,7 +872,7 @@ config :indexer, Indexer.Fetcher.CoinBalance.Realtime, batch_size: coin_balances_batch_size, concurrency: coin_balances_concurrency -config :indexer, Indexer.Fetcher.Optimism.TxnBatch.Supervisor, enabled: ConfigHelper.chain_type() == :optimism +config :indexer, Indexer.Fetcher.Optimism.TransactionBatch.Supervisor, enabled: ConfigHelper.chain_type() == :optimism config :indexer, Indexer.Fetcher.Optimism.OutputRoot.Supervisor, enabled: ConfigHelper.chain_type() == :optimism config :indexer, Indexer.Fetcher.Optimism.DisputeGame.Supervisor, enabled: ConfigHelper.chain_type() == :optimism config :indexer, Indexer.Fetcher.Optimism.Deposit.Supervisor, enabled: ConfigHelper.chain_type() == :optimism @@ -847,7 +883,9 @@ config :indexer, Indexer.Fetcher.Optimism, optimism_l1_rpc: System.get_env("INDEXER_OPTIMISM_L1_RPC"), optimism_l1_system_config: System.get_env("INDEXER_OPTIMISM_L1_SYSTEM_CONFIG_CONTRACT") -config :indexer, Indexer.Fetcher.Optimism.Deposit, batch_size: System.get_env("INDEXER_OPTIMISM_L1_DEPOSITS_BATCH_SIZE") +config :indexer, Indexer.Fetcher.Optimism.Deposit, + batch_size: System.get_env("INDEXER_OPTIMISM_L1_DEPOSITS_BATCH_SIZE"), + transaction_type: ConfigHelper.parse_integer_env_var("INDEXER_OPTIMISM_L1_DEPOSITS_TRANSACTION_TYPE", 126) config :indexer, Indexer.Fetcher.Optimism.OutputRoot, output_oracle: System.get_env("INDEXER_OPTIMISM_L1_OUTPUT_ORACLE_CONTRACT") @@ -857,7 +895,7 @@ config :indexer, Indexer.Fetcher.Optimism.Withdrawal, message_passer: System.get_env("INDEXER_OPTIMISM_L2_MESSAGE_PASSER_CONTRACT", "0x4200000000000000000000000000000000000016") -config :indexer, Indexer.Fetcher.Optimism.TxnBatch, +config :indexer, Indexer.Fetcher.Optimism.TransactionBatch, blocks_chunk_size: System.get_env("INDEXER_OPTIMISM_L1_BATCH_BLOCKS_CHUNK_SIZE", "4"), eip4844_blobs_api_url: System.get_env("INDEXER_OPTIMISM_L1_BATCH_BLOCKSCOUT_BLOBS_API_URL", ""), celestia_blobs_api_url: System.get_env("INDEXER_OPTIMISM_L1_BATCH_CELESTIA_BLOBS_API_URL", ""), @@ -936,7 +974,7 @@ config :indexer, Indexer.Fetcher.Arbitrum.TrackingMessagesOnL1.Supervisor, config :indexer, Indexer.Fetcher.Arbitrum.TrackingBatchesStatuses, recheck_interval: ConfigHelper.parse_time_env_var("INDEXER_ARBITRUM_BATCHES_TRACKING_RECHECK_INTERVAL", "20s"), - track_l1_tx_finalization: + track_l1_transaction_finalization: ConfigHelper.parse_bool_env_var("INDEXER_ARBITRUM_BATCHES_TRACKING_L1_FINALIZATION_CHECK_ENABLED", "false"), messages_to_blocks_shift: ConfigHelper.parse_integer_env_var("INDEXER_ARBITRUM_BATCHES_TRACKING_MESSAGES_TO_BLOCKS_SHIFT", 0), @@ -957,6 +995,9 @@ config :indexer, Indexer.Fetcher.Arbitrum.RollupMessagesCatchup, config :indexer, Indexer.Fetcher.Arbitrum.RollupMessagesCatchup.Supervisor, enabled: ConfigHelper.parse_bool_env_var("INDEXER_ARBITRUM_BRIDGE_MESSAGES_TRACKING_ENABLED") +config :indexer, Indexer.Fetcher.Arbitrum.MessagesToL2Matcher.Supervisor, + disabled?: not ConfigHelper.parse_bool_env_var("INDEXER_ARBITRUM_BRIDGE_MESSAGES_TRACKING_ENABLED") + config :indexer, Indexer.Fetcher.RootstockData.Supervisor, disabled?: ConfigHelper.chain_type() != :rsk || ConfigHelper.parse_bool_env_var("INDEXER_DISABLE_ROOTSTOCK_DATA_FETCHER") @@ -1036,16 +1077,70 @@ config :indexer, Indexer.Fetcher.PolygonZkevm.TransactionBatch.Supervisor, config :indexer, Indexer.Fetcher.Celo.ValidatorGroupVotes, batch_size: ConfigHelper.parse_integer_env_var("INDEXER_CELO_VALIDATOR_GROUP_VOTES_BATCH_SIZE", 200_000) +config :indexer, Indexer.Fetcher.Celo.ValidatorGroupVotes.Supervisor, + enabled: + ConfigHelper.chain_type() == :celo and + not ConfigHelper.parse_bool_env_var("INDEXER_DISABLE_CELO_VALIDATOR_GROUP_VOTES_FETCHER") + celo_epoch_fetchers_enabled? = ConfigHelper.chain_type() == :celo and not ConfigHelper.parse_bool_env_var("INDEXER_DISABLE_CELO_EPOCH_FETCHER") -config :indexer, Indexer.Fetcher.Celo.ValidatorGroupVotes.Supervisor, enabled: celo_epoch_fetchers_enabled? - config :indexer, Indexer.Fetcher.Celo.EpochBlockOperations.Supervisor, enabled: celo_epoch_fetchers_enabled?, disabled?: not celo_epoch_fetchers_enabled? +config :indexer, Indexer.Fetcher.Filecoin.BeryxAPI, + base_url: ConfigHelper.safe_get_env("BERYX_API_BASE_URL", "https://api.zondax.ch/fil/data/v3/mainnet"), + api_token: System.get_env("BERYX_API_TOKEN") + +filecoin_native_address_fetcher_enabled? = + ConfigHelper.chain_type() == :filecoin and + not ConfigHelper.parse_bool_env_var("INDEXER_DISABLE_FILECOIN_ADDRESS_INFO_FETCHER") + +config :indexer, Indexer.Fetcher.Filecoin.AddressInfo.Supervisor, + enabled: filecoin_native_address_fetcher_enabled?, + disabled?: not filecoin_native_address_fetcher_enabled? + +config :indexer, Indexer.Fetcher.Filecoin.AddressInfo, + concurrency: ConfigHelper.parse_integer_env_var("INDEXER_FILECOIN_ADDRESS_INFO_CONCURRENCY", 1) + +config :indexer, Indexer.Fetcher.Scroll, + l1_eth_get_logs_range_size: ConfigHelper.parse_integer_env_var("INDEXER_SCROLL_L1_ETH_GET_LOGS_RANGE_SIZE", 250), + l2_eth_get_logs_range_size: ConfigHelper.parse_integer_env_var("INDEXER_SCROLL_L2_ETH_GET_LOGS_RANGE_SIZE", 1000), + rpc: System.get_env("INDEXER_SCROLL_L1_RPC") + +config :indexer, Indexer.Fetcher.Scroll.L1FeeParam, gas_oracle: System.get_env("INDEXER_SCROLL_L2_GAS_ORACLE_CONTRACT") + +config :explorer, Explorer.Chain.Scroll.L1FeeParam, + curie_upgrade_block: ConfigHelper.parse_integer_env_var("SCROLL_L2_CURIE_UPGRADE_BLOCK", 0), + scalar_init: ConfigHelper.parse_integer_env_var("SCROLL_L1_SCALAR_INIT", 0), + overhead_init: ConfigHelper.parse_integer_env_var("SCROLL_L1_OVERHEAD_INIT", 0), + commit_scalar_init: ConfigHelper.parse_integer_env_var("SCROLL_L1_COMMIT_SCALAR_INIT", 0), + blob_scalar_init: ConfigHelper.parse_integer_env_var("SCROLL_L1_BLOB_SCALAR_INIT", 0), + l1_base_fee_init: ConfigHelper.parse_integer_env_var("SCROLL_L1_BASE_FEE_INIT", 0), + l1_blob_base_fee_init: ConfigHelper.parse_integer_env_var("SCROLL_L1_BLOB_BASE_FEE_INIT", 0) + +config :indexer, Indexer.Fetcher.Scroll.L1FeeParam.Supervisor, disabled?: ConfigHelper.chain_type() != :scroll + +config :indexer, Indexer.Fetcher.Scroll.BridgeL1, + messenger_contract: System.get_env("INDEXER_SCROLL_L1_MESSENGER_CONTRACT"), + start_block: ConfigHelper.parse_integer_or_nil_env_var("INDEXER_SCROLL_L1_MESSENGER_START_BLOCK") + +config :indexer, Indexer.Fetcher.Scroll.BridgeL2, + messenger_contract: System.get_env("INDEXER_SCROLL_L2_MESSENGER_CONTRACT"), + start_block: ConfigHelper.parse_integer_env_var("INDEXER_SCROLL_L2_MESSENGER_START_BLOCK", first_block) + +config :indexer, Indexer.Fetcher.Scroll.Batch, + scroll_chain_contract: System.get_env("INDEXER_SCROLL_L1_CHAIN_CONTRACT"), + start_block: ConfigHelper.parse_integer_or_nil_env_var("INDEXER_SCROLL_L1_BATCH_START_BLOCK") + +config :indexer, Indexer.Fetcher.Scroll.BridgeL1.Supervisor, disabled?: ConfigHelper.chain_type() != :scroll + +config :indexer, Indexer.Fetcher.Scroll.BridgeL2.Supervisor, disabled?: ConfigHelper.chain_type() != :scroll + +config :indexer, Indexer.Fetcher.Scroll.Batch.Supervisor, disabled?: ConfigHelper.chain_type() != :scroll + 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 cdd35cc..198d0ef 100644 --- a/config/runtime/dev.exs +++ b/config/runtime/dev.exs @@ -117,6 +117,13 @@ config :explorer, Explorer.Repo.PolygonZkevm, # 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 Scroll database +config :explorer, Explorer.Repo.Scroll, + database: database, + hostname: hostname, + url: System.get_env("DATABASE_URL"), + pool_size: 1 + # Configure ZkSync database config :explorer, Explorer.Repo.ZkSync, database: database, @@ -199,6 +206,13 @@ config :explorer, Explorer.Repo.ShrunkInternalTransactions, url: System.get_env("DATABASE_URL"), pool_size: 1 +# Configures Blackfort database +config :explorer, Explorer.Repo.Blackfort, + database: database, + hostname: hostname, + url: System.get_env("DATABASE_URL"), + pool_size: 1 + variant = Variant.get() Code.require_file("#{variant}.exs", "apps/explorer/config/dev") diff --git a/config/runtime/prod.exs b/config/runtime/prod.exs index 2b10f79..fbb3a50 100644 --- a/config/runtime/prod.exs +++ b/config/runtime/prod.exs @@ -88,6 +88,12 @@ config :explorer, Explorer.Repo.PolygonZkevm, pool_size: 1, ssl: ExplorerConfigHelper.ssl_enabled?() +# Configures Scroll database +config :explorer, Explorer.Repo.Scroll, + url: System.get_env("DATABASE_URL"), + pool_size: 1, + ssl: ExplorerConfigHelper.ssl_enabled?() + # Configures ZkSync database config :explorer, Explorer.Repo.ZkSync, url: System.get_env("DATABASE_URL"), @@ -159,6 +165,12 @@ config :explorer, Explorer.Repo.ShrunkInternalTransactions, pool_size: 1, ssl: ExplorerConfigHelper.ssl_enabled?() +# Configures Blackfort database +config :explorer, Explorer.Repo.Blackfort, + url: System.get_env("DATABASE_URL"), + pool_size: 1, + ssl: ExplorerConfigHelper.ssl_enabled?() + variant = Variant.get() Code.require_file("#{variant}.exs", "apps/explorer/config/prod") diff --git a/cspell.json b/cspell.json index 7eda2f9..55edd9b 100644 --- a/cspell.json +++ b/cspell.json @@ -18,8 +18,8 @@ "AION", "AIRTABLE", "Aiubo", - "alloc", "alfajores", + "alloc", "amzootyukbugmx", "anytrust", "apikey", @@ -41,17 +41,20 @@ "badhash", "badnumber", "badpassword", + "bafybe", "bafybeid", "bafybeig", "bafybeihxuj", "balancemulti", "benchee", + "beryx", "besu", "bignumber", "bigserial", "binwrite", "bitmask", "bizbuz", + "Blackfort", "Blockchair", "blockheight", "blockless", @@ -132,6 +135,7 @@ "Cyclomatic", "cypherpunk", "czilladx", + "datacap", "datapoint", "datepicker", "DATETIME", @@ -157,6 +161,7 @@ "describedby", "differenceby", "discordapp", + "disksup", "dropzone", "dxgd", "dyntsrohg", @@ -176,6 +181,7 @@ "errora", "errorb", "erts", + "ethaccount", "Ethash", "etherchain", "ethprice", @@ -196,9 +202,9 @@ "falala", "feelin", "FEVM", + "filecoin", "Filecoin", "Filesize", - "filecoin", "fixidity", "fkey", "Floki", @@ -266,6 +272,7 @@ "ipos", "itxs", "johnnny", + "joken", "jsons", "juon", "Karnaugh", @@ -307,6 +314,7 @@ "mdef", "MDWW", "meer", + "memsup", "Mendonça", "Menlo", "mergeable", @@ -376,7 +384,9 @@ "opos", "outcoming", "overengineering", + "passwordless", "pawesome", + "paych", "pbcopy", "peeker", "peekers", @@ -408,6 +418,7 @@ "prederived", "progressbar", "proxiable", + "proxying", "psql", "pubkey", "pubkeys", @@ -455,6 +466,7 @@ "rollups", "RPC's", "RPCs", + "sabm", "safelow", "savechives", "Secon", @@ -471,6 +483,7 @@ "shibarium", "shortdoc", "shortify", + "siwe", "SJONRPC", "smallint", "smth", @@ -552,12 +565,14 @@ "Txns", "txpool", "txreceipt", + "typname", "ueberauth", "ufixed", "uncatalog", "unclosable", "unfetched", "unfinalized", + "Unichain", "unindexed", "Unitarion", "Unitorius", @@ -579,6 +594,7 @@ "upserts", "urijs", "urlset", + "userinfo", "Utqn", "UUPS", "valign", @@ -586,6 +602,7 @@ "valuemin", "valuenow", "varint", + "verifreg", "verifyproxycontract", "verifysourcecode", "viewerjs", @@ -606,6 +623,7 @@ "xakgj", "xbaddress", "xdai", + "Xname", "xffff", "xlevel", "xlink", @@ -625,6 +643,7 @@ "zkatana", "zkbob", "zkevm", + "zksolc", "zksync" ], "enableFiletypes": [ diff --git a/docker-compose/docker-compose.yml b/docker-compose/docker-compose.yml index 988662f..4e1e510 100644 --- a/docker-compose/docker-compose.yml +++ b/docker-compose/docker-compose.yml @@ -26,6 +26,7 @@ services: extends: file: ./services/backend.yml service: backend + image: blockscout-backend:v6.9.2-beta build: context: .. dockerfile: ./docker/Dockerfile @@ -37,27 +38,20 @@ services: CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED: "" CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL: "" ADMIN_PANEL_ENABLED: "" - RELEASE_VERSION: 6.8.0 + RELEASE_VERSION: 6.9.2 links: - db:database - 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' + # 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/visualizer.yml service: visualizer - sc-verifier: - extends: - file: ./services/smart-contract-verifier.yml - service: smart-contract-verifier - ports: - - 8082:8050 - sig-provider: extends: file: ./services/sig-provider.yml @@ -104,7 +98,6 @@ services: - backend - frontend - stats - - sc-verifier extends: file: ./services/nginx.yml service: proxy diff --git a/docker-compose/envs/common-blockscout.env b/docker-compose/envs/common-blockscout.env index df233cd..c948d2e 100644 --- a/docker-compose/envs/common-blockscout.env +++ b/docker-compose/envs/common-blockscout.env @@ -65,12 +65,12 @@ EXCHANGE_RATES_COIN= # EXCHANGE_RATES_CRYPTORANK_COIN_ID= # EXCHANGE_RATES_CRYPTORANK_LIMIT= # TOKEN_EXCHANGE_RATES_SOURCE= -POOL_SIZE=80 # EXCHANGE_RATES_COINGECKO_PLATFORM_ID= # TOKEN_EXCHANGE_RATE_INTERVAL= # TOKEN_EXCHANGE_RATE_REFETCH_INTERVAL= # TOKEN_EXCHANGE_RATE_MAX_BATCH_SIZE= # DISABLE_TOKEN_EXCHANGE_RATE= +POOL_SIZE=80 POOL_SIZE_API=10 ECTO_USE_SSL=false # DATADOG_HOST= @@ -204,6 +204,8 @@ INDEXER_DISABLE_INTERNAL_TRANSACTIONS_FETCHER=false # INDEXER_TOKEN_CONCURRENCY= # INDEXER_TOKEN_BALANCES_BATCH_SIZE= # INDEXER_TOKEN_BALANCES_CONCURRENCY= +# INDEXER_TOKEN_BALANCES_MAX_REFETCH_INTERVAL= +# INDEXER_TOKEN_BALANCES_EXPONENTIAL_TIMEOUT_COEFF= # INDEXER_TX_ACTIONS_ENABLE= # INDEXER_TX_ACTIONS_MAX_TOKEN_CACHE_SIZE= # INDEXER_TX_ACTIONS_REINDEX_FIRST_BLOCK= @@ -260,15 +262,25 @@ INDEXER_DISABLE_INTERNAL_TRANSACTIONS_FETCHER=false # INDEXER_ARBITRUM_BRIDGE_MESSAGES_TRACKING_ENABLED= # INDEXER_ARBITRUM_TRACKING_MESSAGES_ON_L1_RECHECK_INTERVAL= # INDEXER_ARBITRUM_MISSED_MESSAGES_RECHECK_INTERVAL= +# INDEXER_ARBITRUM_MISSED_MESSAGES_BLOCKS_DEPTH= # CELO_CORE_CONTRACTS= # INDEXER_CELO_VALIDATOR_GROUP_VOTES_BATCH_SIZE=200000 # INDEXER_DISABLE_CELO_EPOCH_FETCHER=false -# INDEXER_ARBITRUM_MISSED_MESSAGES_BLOCKS_DEPTH= +# INDEXER_DISABLE_CELO_VALIDATOR_GROUP_VOTES_FETCHER=false +# BERYX_API_TOKEN= +# BERYX_API_BASE_URL= +# FILECOIN_NETWORK_PREFIX=f +# FILECOIN_PENDING_ADDRESS_OPERATIONS_MIGRATION_BATCH_SIZE= +# FILECOIN_PENDING_ADDRESS_OPERATIONS_MIGRATION_CONCURRENCY= +# INDEXER_DISABLE_FILECOIN_ADDRESS_INFO_FETCHER=false +# INDEXER_FILECOIN_ADDRESS_INFO_CONCURRENCY=1 # 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_GRACEFUL_SHUTDOWN_PERIOD= +# INDEXER_INTERNAL_TRANSACTIONS_FETCH_ORDER= +# INDEXER_SYSTEM_MEMORY_PERCENTAGE= # WITHDRAWALS_FIRST_BLOCK= # INDEXER_OPTIMISM_L1_RPC= # INDEXER_OPTIMISM_L1_SYSTEM_CONFIG_CONTRACT= @@ -278,6 +290,24 @@ INDEXER_DISABLE_INTERNAL_TRANSACTIONS_FETCHER=false # INDEXER_OPTIMISM_L2_WITHDRAWALS_START_BLOCK= # INDEXER_OPTIMISM_L2_MESSAGE_PASSER_CONTRACT= # INDEXER_OPTIMISM_L1_DEPOSITS_BATCH_SIZE= +# INDEXER_OPTIMISM_L1_DEPOSITS_TRANSACTION_TYPE= +# INDEXER_SCROLL_L1_RPC= +# INDEXER_SCROLL_L1_MESSENGER_CONTRACT= +# INDEXER_SCROLL_L1_MESSENGER_START_BLOCK= +# INDEXER_SCROLL_L1_CHAIN_CONTRACT= +# INDEXER_SCROLL_L1_BATCH_START_BLOCK= +# INDEXER_SCROLL_L2_MESSENGER_CONTRACT= +# INDEXER_SCROLL_L2_MESSENGER_START_BLOCK= +# INDEXER_SCROLL_L2_GAS_ORACLE_CONTRACT= +# INDEXER_SCROLL_L1_ETH_GET_LOGS_RANGE_SIZE= +# INDEXER_SCROLL_L2_ETH_GET_LOGS_RANGE_SIZE= +# SCROLL_L2_CURIE_UPGRADE_BLOCK= +# SCROLL_L1_SCALAR_INIT= +# SCROLL_L1_OVERHEAD_INIT= +# SCROLL_L1_COMMIT_SCALAR_INIT= +# SCROLL_L1_BLOB_SCALAR_INIT= +# SCROLL_L1_BASE_FEE_INIT= +# SCROLL_L1_BLOB_BASE_FEE_INIT= # ROOTSTOCK_REMASC_ADDRESS= # ROOTSTOCK_BRIDGE_ADDRESS= # ROOTSTOCK_LOCKED_BTC_CACHE_PERIOD= @@ -331,12 +361,15 @@ MAINTENANCE_ALERT_MESSAGE= CHAIN_ID= MAX_SIZE_UNLESS_HIDE_ARRAY=50 HIDE_BLOCK_MINER=false +# HIDE_SCAM_ADDRESSES= DISPLAY_TOKEN_ICONS=false RE_CAPTCHA_SECRET_KEY= RE_CAPTCHA_CLIENT_KEY= RE_CAPTCHA_V3_SECRET_KEY= RE_CAPTCHA_V3_CLIENT_KEY= RE_CAPTCHA_DISABLED=false +# RE_CAPTCHA_CHECK_HOSTNAME +# RE_CAPTCHA_SCORE_THRESHOLD JSON_RPC= # API_RATE_LIMIT_HAMMER_REDIS_URL=redis://redis-db:6379/1 # API_RATE_LIMIT_IS_BLOCKSCOUT_BEHIND_PROXY=false @@ -372,8 +405,10 @@ DECODE_NOT_A_CONTRACT_CALLS=true # ACCOUNT_SENDGRID_SENDER= # ACCOUNT_SENDGRID_TEMPLATE= # ACCOUNT_VERIFICATION_EMAIL_RESEND_INTERVAL= +# ACCOUNT_OTP_RESEND_INTERVAL= # ACCOUNT_PRIVATE_TAGS_LIMIT=2000 # ACCOUNT_WATCHLIST_ADDRESSES_LIMIT=15 +# ACCOUNT_SIWE_MESSAGE= ACCOUNT_CLOAK_KEY= ACCOUNT_ENABLED=false ACCOUNT_REDIS_URL=redis://redis-db:6379 @@ -397,6 +432,10 @@ EIP_1559_ELASTICITY_MULTIPLIER=2 # MIGRATION_RESTORE_OMITTED_WETH_TOKEN_TRANSFERS_CONCURRENCY= # MIGRATION_RESTORE_OMITTED_WETH_TOKEN_TRANSFERS_BATCH_SIZE= # MIGRATION_RESTORE_OMITTED_WETH_TOKEN_TRANSFERS_TIMEOUT= +# MIGRATION_SANITIZE_DUPLICATED_LOG_INDEX_LOGS_CONCURRENCY= +# MIGRATION_SANITIZE_DUPLICATED_LOG_INDEX_LOGS_BATCH_SIZE= +# MIGRATION_REFETCH_CONTRACT_CODES_BATCH_SIZE= +# MIGRATION_REFETCH_CONTRACT_CODES_CONCURRENCY= SOURCIFY_INTEGRATION_ENABLED=false SOURCIFY_SERVER_URL= SOURCIFY_REPO_URL= @@ -410,6 +449,8 @@ TENDERLY_CHAIN_PATH= # NOVES_FI_API_TOKEN= # ZERION_BASE_API_URL= # ZERION_API_TOKEN= +# XNAME_BASE_API_URL= +# XNAME_API_TOKEN= # BRIDGED_TOKENS_ENABLED= # BRIDGED_TOKENS_ETH_OMNI_BRIDGE_MEDIATOR= # BRIDGED_TOKENS_BSC_OMNI_BRIDGE_MEDIATOR= @@ -429,3 +470,5 @@ TENDERLY_CHAIN_PATH= # SHRINK_INTERNAL_TRANSACTIONS_ENABLED= # SHRINK_INTERNAL_TRANSACTIONS_BATCH_SIZE= # SHRINK_INTERNAL_TRANSACTIONS_CONCURRENCY= +# OP_NODE_TO_ADDRESS= +# OP_NODE_FROM_ADDRESS= \ No newline at end of file diff --git a/docker-compose/external-frontend.yml b/docker-compose/external-frontend.yml index a696896..075234f 100644 --- a/docker-compose/external-frontend.yml +++ b/docker-compose/external-frontend.yml @@ -6,26 +6,13 @@ services: file: ./services/redis.yml service: redis-db - db-init: - extends: - file: ./services/db.yml - service: db-init - - db: - depends_on: - db-init: - condition: service_completed_successfully - extends: - file: ./services/db.yml - service: db - backend: depends_on: - - db - redis-db extends: file: ./services/backend.yml service: backend + image: blockscout-backend:v6.9.2-beta build: context: .. dockerfile: ./docker/Dockerfile @@ -37,21 +24,13 @@ services: CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED: "" CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL: "" ADMIN_PANEL_ENABLED: "" - RELEASE_VERSION: 6.7.2 - 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' - + RELEASE_VERSION: 6.9.2 + visualizer: extends: file: ./services/visualizer.yml service: visualizer - + sc-verifier: extends: file: ./services/smart-contract-verifier.yml @@ -85,14 +64,6 @@ services: file: ./services/stats.yml service: stats - user-ops-indexer: - depends_on: - - db - - backend - extends: - file: ./services/user-ops-indexer.yml - service: user-ops-indexer - proxy: depends_on: - backend diff --git a/docker-compose/services/backend.yml b/docker-compose/services/backend.yml index 4cbbe88..1f6d9fa 100644 --- a/docker-compose/services/backend.yml +++ b/docker-compose/services/backend.yml @@ -2,6 +2,7 @@ version: '3.9' services: backend: + image: blockscout-backend:v6.9.2-beta pull_policy: always restart: always stop_grace_period: 5m diff --git a/docker-compose/services/db.yml b/docker-compose/services/db.yml index 060052f..430409b 100644 --- a/docker-compose/services/db.yml +++ b/docker-compose/services/db.yml @@ -21,7 +21,7 @@ services: environment: POSTGRES_DB: 'blockscout' POSTGRES_USER: 'blockscout' - POSTGRES_PASSWORD: 'dummy_password' + POSTGRES_PASSWORD: 'ceWb1MeLBEeOIfk65gU8EjF8' ports: - target: 5432 published: 7432 diff --git a/docker-compose/services/stats.yml b/docker-compose/services/stats.yml index 4f61edb..1e94e5b 100644 --- a/docker-compose/services/stats.yml +++ b/docker-compose/services/stats.yml @@ -21,7 +21,7 @@ services: environment: POSTGRES_DB: 'stats' POSTGRES_USER: 'stats' - POSTGRES_PASSWORD: 'dummy_password' + POSTGRES_PASSWORD: 'n0uejXPl61ci6ldCuE2gQU5Y' ports: - target: 5432 published: 7433 @@ -50,3 +50,4 @@ services: - STATS__CREATE_DATABASE=${STATS__CREATE_DATABASE:-true} - STATS__RUN_MIGRATIONS=${STATS__RUN_MIGRATIONS:-true} - STATS_CHARTS__TEMPLATE_VALUES__NATIVE_COIN_SYMBOL=OAS + - STATS__IGNORE_BLOCKSCOUT_API_ABSENCE=true diff --git a/docker/Dockerfile b/docker/Dockerfile index 6e49e61..8d1f4cd 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,4 +1,4 @@ -FROM hexpm/elixir:1.16.3-erlang-26.2.5.1-alpine-3.18.7 AS builder +FROM hexpm/elixir:1.17.3-erlang-27.1-alpine-3.20.3 AS builder WORKDIR /app @@ -29,6 +29,8 @@ ARG MUD_INDEXER_ENABLED ENV MUD_INDEXER_ENABLED=${MUD_INDEXER_ENABLED} ARG SHRINK_INTERNAL_TRANSACTIONS_ENABLED ENV SHRINK_INTERNAL_TRANSACTIONS_ENABLED=${SHRINK_INTERNAL_TRANSACTIONS_ENABLED} +ARG API_GRAPHQL_MAX_COMPLEXITY +ENV API_GRAPHQL_MAX_COMPLEXITY=${API_GRAPHQL_MAX_COMPLEXITY} # Cache elixir deps ADD mix.exs mix.lock ./ @@ -46,31 +48,17 @@ ADD config ./config ADD rel ./rel ADD *.exs ./ -RUN apk add --update nodejs npm - -# Run forderground build and phoenix digest -RUN mix compile && npm install npm@latest - -# Add blockscout npm deps -RUN cd apps/block_scout_web/assets/ && \ - npm install && \ - npm run deploy && \ - cd /app/apps/explorer/ && \ - npm install && \ - apk update && \ - apk del --force-broken-world alpine-sdk gmp-dev automake libtool inotify-tools autoconf python3 - +# Run backend compilation +RUN mix compile RUN apk add --update git make -RUN mix phx.digest - RUN mkdir -p /opt/release \ && mix release blockscout \ && mv _build/${MIX_ENV}/rel/blockscout /opt/release ############################################################## -FROM hexpm/elixir:1.16.3-erlang-26.2.5.1-alpine-3.18.7 +FROM hexpm/elixir:1.17.3-erlang-27.1-alpine-3.20.3 ARG RELEASE_VERSION ENV RELEASE_VERSION=${RELEASE_VERSION} @@ -82,13 +70,22 @@ ARG SHRINK_INTERNAL_TRANSACTIONS_ENABLED ENV SHRINK_INTERNAL_TRANSACTIONS_ENABLED=${SHRINK_INTERNAL_TRANSACTIONS_ENABLED} ARG BLOCKSCOUT_VERSION ENV BLOCKSCOUT_VERSION=${BLOCKSCOUT_VERSION} +ARG BLOCKSCOUT_USER=blockscout +ARG BLOCKSCOUT_GROUP=blockscout +ARG BLOCKSCOUT_UID=10001 +ARG BLOCKSCOUT_GID=10001 -RUN apk --no-cache --update add jq curl +RUN apk --no-cache --update add jq curl && \ + addgroup --system --gid ${BLOCKSCOUT_GID} ${BLOCKSCOUT_GROUP} && \ + adduser --system --uid ${BLOCKSCOUT_UID} --ingroup ${BLOCKSCOUT_GROUP} --disabled-password ${BLOCKSCOUT_USER} WORKDIR /app -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 -COPY --from=builder /app/config/assets/precompiles-arbitrum.json ./config/assets/precompiles-arbitrum.json +COPY --from=builder --chown=${BLOCKSCOUT_USER}:${BLOCKSCOUT_GROUP} /opt/release/blockscout . +COPY --from=builder --chown=${BLOCKSCOUT_USER}:${BLOCKSCOUT_GROUP} /app/config/config_helper.exs ./config/config_helper.exs +COPY --from=builder --chown=${BLOCKSCOUT_USER}:${BLOCKSCOUT_GROUP} /app/config/config_helper.exs /app/releases/${RELEASE_VERSION}/config_helper.exs +COPY --from=builder --chown=${BLOCKSCOUT_USER}:${BLOCKSCOUT_GROUP} /app/config/assets/precompiles-arbitrum.json ./config/assets/precompiles-arbitrum.json + +RUN chown -R ${BLOCKSCOUT_USER}:${BLOCKSCOUT_GROUP} /app + +USER ${BLOCKSCOUT_USER}:${BLOCKSCOUT_GROUP} diff --git a/docker/Makefile b/docker/Makefile index 9661699..9b79726 100644 --- a/docker/Makefile +++ b/docker/Makefile @@ -10,7 +10,7 @@ STATS_CONTAINER_NAME := stats STATS_DB_CONTAINER_NAME := stats-db PROXY_CONTAINER_NAME := proxy PG_CONTAINER_NAME := postgres -RELEASE_VERSION ?= '6.8.0' +RELEASE_VERSION ?= '6.9.2' TAG := $(RELEASE_VERSION)-commit-$(shell git log -1 --pretty=format:"%h") STABLE_TAG := $(RELEASE_VERSION) diff --git a/docker/oldUI.Dockerfile b/docker/oldUI.Dockerfile new file mode 100644 index 0000000..1a2b1da --- /dev/null +++ b/docker/oldUI.Dockerfile @@ -0,0 +1,104 @@ +FROM hexpm/elixir:1.17.3-erlang-27.1-alpine-3.20.3 AS builder + +WORKDIR /app + +ENV MIX_ENV="prod" + +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 gcompat + +ARG CACHE_EXCHANGE_RATES_PERIOD +ARG API_V1_READ_METHODS_DISABLED +ARG DISABLE_WEBAPP +ARG API_V1_WRITE_METHODS_DISABLED +ARG CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED +ARG ADMIN_PANEL_ENABLED +ARG CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL +ARG SESSION_COOKIE_DOMAIN +ARG MIXPANEL_TOKEN +ARG MIXPANEL_URL +ARG AMPLITUDE_API_KEY +ARG AMPLITUDE_URL +ARG CHAIN_TYPE +ENV CHAIN_TYPE=${CHAIN_TYPE} +ARG BRIDGED_TOKENS_ENABLED +ENV BRIDGED_TOKENS_ENABLED=${BRIDGED_TOKENS_ENABLED} +ARG MUD_INDEXER_ENABLED +ENV MUD_INDEXER_ENABLED=${MUD_INDEXER_ENABLED} +ARG SHRINK_INTERNAL_TRANSACTIONS_ENABLED +ENV SHRINK_INTERNAL_TRANSACTIONS_ENABLED=${SHRINK_INTERNAL_TRANSACTIONS_ENABLED} + +# Cache elixir deps +ADD mix.exs mix.lock ./ +ADD apps/block_scout_web/mix.exs ./apps/block_scout_web/ +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 apps ./apps +ADD config ./config +ADD rel ./rel +ADD *.exs ./ + +RUN apk add --update nodejs npm + +# Run backend compilation and install latest npm +RUN mix compile && npm install npm@latest + +# Add blockscout npm deps +RUN cd apps/block_scout_web/assets/ && \ + npm install && \ + npm run deploy && \ + cd /app/apps/explorer/ && \ + npm install && \ + 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 \ + && mix release blockscout \ + && mv _build/${MIX_ENV}/rel/blockscout /opt/release + +############################################################## +FROM hexpm/elixir:1.17.3-erlang-27.1-alpine-3.20.3 + +ARG RELEASE_VERSION +ENV RELEASE_VERSION=${RELEASE_VERSION} +ARG CHAIN_TYPE +ENV CHAIN_TYPE=${CHAIN_TYPE} +ARG BRIDGED_TOKENS_ENABLED +ENV BRIDGED_TOKENS_ENABLED=${BRIDGED_TOKENS_ENABLED} +ARG SHRINK_INTERNAL_TRANSACTIONS_ENABLED +ENV SHRINK_INTERNAL_TRANSACTIONS_ENABLED=${SHRINK_INTERNAL_TRANSACTIONS_ENABLED} +ARG BLOCKSCOUT_VERSION +ENV BLOCKSCOUT_VERSION=${BLOCKSCOUT_VERSION} +ARG BLOCKSCOUT_USER=blockscout +ARG BLOCKSCOUT_GROUP=blockscout +ARG BLOCKSCOUT_UID=10001 +ARG BLOCKSCOUT_GID=10001 + +RUN apk --no-cache --update add jq curl && \ + addgroup --system --gid ${BLOCKSCOUT_GID} ${BLOCKSCOUT_GROUP} && \ + adduser --system --uid ${BLOCKSCOUT_UID} --ingroup ${BLOCKSCOUT_GROUP} --disabled-password ${BLOCKSCOUT_USER} + +WORKDIR /app + +COPY --from=builder --chown=${BLOCKSCOUT_USER}:${BLOCKSCOUT_GROUP} /opt/release/blockscout . +COPY --from=builder --chown=${BLOCKSCOUT_USER}:${BLOCKSCOUT_GROUP} /app/apps/explorer/node_modules ./node_modules +COPY --from=builder --chown=${BLOCKSCOUT_USER}:${BLOCKSCOUT_GROUP} /app/config/config_helper.exs ./config/config_helper.exs +COPY --from=builder --chown=${BLOCKSCOUT_USER}:${BLOCKSCOUT_GROUP} /app/config/config_helper.exs /app/releases/${RELEASE_VERSION}/config_helper.exs +COPY --from=builder --chown=${BLOCKSCOUT_USER}:${BLOCKSCOUT_GROUP} /app/config/assets/precompiles-arbitrum.json ./config/assets/precompiles-arbitrum.json + +RUN chown -R ${BLOCKSCOUT_USER}:${BLOCKSCOUT_GROUP} /app + +USER ${BLOCKSCOUT_USER}:${BLOCKSCOUT_GROUP} diff --git a/mix.exs b/mix.exs index 37b0f7c..aeceec2 100644 --- a/mix.exs +++ b/mix.exs @@ -7,7 +7,7 @@ defmodule BlockScout.Mixfile do [ # app: :block_scout, # aliases: aliases(config_env()), - version: "6.8.0", + version: "6.9.2", apps_path: "apps", deps: deps(), dialyzer: dialyzer(), diff --git a/mix.lock b/mix.lock index f4841a9..fe3eb84 100644 --- a/mix.lock +++ b/mix.lock @@ -4,33 +4,34 @@ "absinthe_plug": {:git, "https://github.com/blockscout/absinthe_plug.git", "90a8188e94e2650f13259fb16462075a87f98e18", [tag: "1.5.8"]}, "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.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"}, + "bamboo": {:hex, :bamboo, "2.3.1", "85029339f01c3dd59071cfd2b7b18e826aa7fc172cc324d75603bb99d95b6544", [: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", "895b2993ed195b2b0fa79c0d5a1d36aa529e817b6df257e4a10745459048d505"}, + "bcrypt_elixir": {:hex, :bcrypt_elixir, "3.2.0", "feab711974beba4cb348147170346fe097eea2e840db4e012a145e180ed4ab75", [:make, :mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "563e92a6c77d667b19c5f4ba17ab6d440a085696bdf4c68b9b0f5b30bc5422b8"}, "benchee": {:hex, :benchee, "1.3.1", "c786e6a76321121a44229dde3988fc772bca73ea75170a73fd5f4ddf1af95ccf", [: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", "76224c58ea1d0391c8309a8ecbfe27d71062878f59bd41a390266bf4ac1cc56d"}, "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"}, + "blake2": {:hex, :blake2, "1.0.4", "8263c69a191142922bc2510f1ffc0de0ae96e8c3bd5e2ad3fac7e87aed94c8b1", [:mix], [], "hexpm", "e9f4120d163ba14d86304195e50745fa18483e6ad2be94c864ae449bbdd6a189"}, "briefly": {:git, "https://github.com/CargoSense/briefly.git", "4836ba322ffb504a102a15cc6e35d928ef97120e", []}, "brotli": {:hex, :brotli, "0.3.2", "59cf45a399098516f1d34f70d8e010e5c9bf326659d3ef34c7cc56793339002b", [:rebar3], [], "hexpm", "9ec3ef9c753f80d0c657b4905193c55e5198f169fa1d1c044d8601d4d931a2ad"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "bureaucrat": {:hex, :bureaucrat, "0.2.10", "b0de157dad540e40007b663b683f716ced21f85ff0591093aadb209ad0d967e1", [: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 or ~> 5.0", [hex: :poison, repo: "hexpm", optional: true]}], "hexpm", "bc7e5162b911c29c8ebefee87a2c16fbf13821a58f448a8fd024eb6c17fae15c"}, "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.8", "dedcf20ea746694647f883590b82d9e96014057aff1d44d03ec90f36a5c0dc6e", [:mix], [], "hexpm", "0b2b66d2ee742cb1d9cb8c8be3b43c3a70ee8651f37b75a8b982e036752983f1"}, + "castore": {:hex, :castore, "1.0.9", "5cc77474afadf02c7c017823f460a17daa7908e991b0cc917febc90e466a375c", [:mix], [], "hexpm", "5ea956504f1ba6f2b4eb707061d8e17870de2bee95fb59d512872c2ef06925e7"}, "cbor": {:hex, :cbor, "1.0.1", "39511158e8ea5a57c1fcb9639aaa7efde67129678fee49ebbda780f6f24959b0", [:mix], [], "hexpm", "5431acbe7a7908f17f6a9cd43311002836a34a8ab01876918d8cfb709cd8b6a2"}, "certifi": {:hex, :certifi, "2.12.0", "2d1cca2ec95f59643862af91f001478c9863c2ac9cb6e2f89780bfd8de987329", [:rebar3], [], "hexpm", "ee68d85df22e554040cdb4be100f33873ac6051387baf6a8f6ce82272340ff1c"}, - "cldr_utils": {:hex, :cldr_utils, "2.28.0", "ce309d11b79fc13e1f22f808b5e3c1647102b01b11734ca8cb0296ca6d406fe4", [: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", "e7ac4bcea0fdbc11b5295ef30dd7b18d0922512399361af06a97198e57d23742"}, + "cldr_utils": {:hex, :cldr_utils, "2.28.2", "f500667164a9043369071e4f9dcef31f88b8589b2e2c07a1eb9f9fa53cb1dce9", [: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", "c506eb1a170ba7cdca59b304ba02a56795ed119856662f6b1a420af80ec42551"}, "cloak": {:hex, :cloak, "1.1.4", "aba387b22ea4d80d92d38ab1890cc528b06e0e7ef2a4581d71c3fdad59e997e7", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "92b20527b9aba3d939fab0dd32ce592ff86361547cfdc87d74edce6f980eb3d7"}, "cloak_ecto": {:hex, :cloak_ecto, "1.3.0", "0de127c857d7452ba3c3367f53fb814b0410ff9c680a8d20fbe8b9a3c57a1118", [:mix], [{:cloak, "~> 1.1.1", [hex: :cloak, repo: "hexpm", optional: false]}, {:ecto, "~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}], "hexpm", "314beb0c123b8a800418ca1d51065b27ba3b15f085977e65c0f7b2adab2de1cc"}, "coerce": {:hex, :coerce, "1.0.1", "211c27386315dc2894ac11bc1f413a0e38505d808153367bd5c6e75a4003d096", [:mix], [], "hexpm", "b44a691700f7a1a15b4b7e2ff1fa30bebd669929ac8aa43cffe9e2f8bf051cf1"}, "combine": {:hex, :combine, "0.10.0", "eff8224eeb56498a2af13011d142c5e7997a80c8f5b97c499f84c841032e429f", [:mix], [], "hexpm", "1b1dbc1790073076580d0d1d64e42eae2366583e7aecd455d1215b0d16f2451b"}, - "comeonin": {:hex, :comeonin, "5.4.0", "246a56ca3f41d404380fc6465650ddaa532c7f98be4bda1b4656b3a37cc13abe", [:mix], [], "hexpm", "796393a9e50d01999d56b7b8420ab0481a7538d0caf80919da493b4a6e51faf1"}, - "con_cache": {:hex, :con_cache, "1.1.0", "45c7c6cd6dc216e47636232e8c683734b7fe293221fccd9454fa1757bc685044", [:mix], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "8655f2ae13a1e56c8aef304d250814c7ed929c12810f126fc423ecc8e871593b"}, + "comeonin": {:hex, :comeonin, "5.5.0", "364d00df52545c44a139bad919d7eacb55abf39e86565878e17cebb787977368", [:mix], [], "hexpm", "6287fc3ba0aad34883cbe3f7949fc1d1e738e5ccdce77165bc99490aa69f47fb"}, + "con_cache": {:hex, :con_cache, "1.1.1", "9f47a68dfef5ac3bbff8ce2c499869dbc5ba889dadde6ac4aff8eb78ddaf6d82", [:mix], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "1def4d1bec296564c75b5bbc60a19f2b5649d81bfa345a2febcc6ae380e8ae15"}, "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.12.0", "f276d521a1ff88b2b9b4c54d0e753da6c66dd7be6c9fca3d9418b561828a3731", [:make, :rebar3], [{:cowlib, "2.13.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "8a7abe6d183372ceb21caa2709bec928ab2b72e18a3911aa1771639bef82651e"}, "cowboy_telemetry": {:hex, :cowboy_telemetry, "0.4.0", "f239f68b588efa7707abce16a84d0d2acf3a0f50571f8bb7f56a15865aae820c", [:rebar3], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7d98bac1ee4565d31b62d59f8823dfd8356a169e7fcbb83831b8a5397404c9de"}, "cowlib": {:hex, :cowlib, "2.13.0", "db8f7505d8332d98ef50a3ef34b34c1afddec7506e4ee4dd4a3a266285d282ca", [:make, :rebar3], [], "hexpm", "e1e1284dc3fc030a64b1ad0d8382ae7e99da46c3246b815318a4b848873800a4"}, - "credo": {:hex, :credo, "1.7.7", "771445037228f763f9b2afd612b6aa2fd8e28432a95dbbc60d8e03ce71ba4446", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "8bc87496c9aaacdc3f90f01b7b0582467b69b4bd2441fe8aae3109d843cc2f2e"}, + "credo": {:hex, :credo, "1.7.8", "9722ba1681e973025908d542ec3d95db5f9c549251ba5b028e251ad8c24ab8c5", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "cb9e87cc64f152f3ed1c6e325e7b894dea8f5ef2e41123bd864e3cd5ceb44968"}, "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, "2.0.0", "49b42d60b9bb06d761a71d7b034c4b34787957e713d4fae15387a25fcd639112", [:mix], [{:ecto, ">= 3.4.3 and < 4.0.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:opentelemetry_process_propagator, "~> 0.2.1", [hex: :opentelemetry_process_propagator, repo: "hexpm", optional: true]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "09d61781b76ce216e395cdbc883ff00d00f46a503e215c22722dba82507dfef0"}, + "dataloader": {:hex, :dataloader, "2.0.1", "fa06b057b432b993203003fbff5ff040b7f6483a77e732b7dfc18f34ded2634f", [:mix], [{:ecto, ">= 3.4.3 and < 4.0.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:opentelemetry_process_propagator, "~> 0.2.1 or ~> 0.3", [hex: :opentelemetry_process_propagator, repo: "hexpm", optional: true]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "da7ff00890e1b14f7457419b9508605a8e66ae2cc2d08c5db6a9f344550efa11"}, "db_connection": {:hex, :db_connection, "2.7.0", "b99faa9291bb09892c7da373bb82cba59aefa9b36300f6145c5f201c7adf48ec", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "dcf08f31b2701f857dfc787fbad78223d61a32204f217f15e881dd93e4bdd3ff"}, "decimal": {:hex, :decimal, "2.1.1", "5611dca5d4b2c3dd497dec8f68751f1f1a54755e8ed2a966c2633cf885973ad6", [:mix], [], "hexpm", "53cfe5f497ed0e7771ae1a475575603d77425099ba5faef9394932b35020ffcc"}, "decorator": {:hex, :decorator, "1.4.0", "a57ac32c823ea7e4e67f5af56412d12b33274661bb7640ec7fc882f8d23ac419", [:mix], [], "hexpm", "0a07cedd9083da875c7418dea95b78361197cf2bf3211d743f6f7ce39656597f"}, @@ -38,16 +39,16 @@ "dialyxir": {:hex, :dialyxir, "1.4.3", "edd0124f358f0b9e95bfe53a9fcf806d615d8f838e2202a9f430d59566b6b53b", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "bf2cfb75cd5c5006bec30141b131663299c661a864ec7fbbc72dfa557487a986"}, "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.41", "ab34711c9dc6212dda44fcd20ecb87ac3f3fce6f0ca2f28d4a00e4154f8cd599", [:mix], [], "hexpm", "a81a04c7e34b6617c2792e291b5a2e57ab316365c2644ddc553bb9ed863ebefa"}, - "ecto": {:hex, :ecto, "3.11.2", "e1d26be989db350a633667c5cda9c3d115ae779b66da567c68c80cfb26a8c9ee", [: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", "3c38bca2c6f8d8023f2145326cc8a80100c3ffe4dcbd9842ff867f7fc6156c65"}, - "ecto_sql": {:hex, :ecto_sql, "3.11.3", "4eb7348ff8101fbc4e6bbc5a4404a24fecbe73a3372d16569526b0cf34ebc195", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.11.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16 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", "e5f36e3d736b99c7fee3e631333b8394ade4bafe9d96d35669fca2d81c2be928"}, - "elixir_make": {:hex, :elixir_make, "0.7.7", "7128c60c2476019ed978210c245badf08b03dbec4f24d05790ef791da11aa17c", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}], "hexpm", "5bc19fff950fad52bbe5f211b12db9ec82c6b34a9647da0c2224b8b8464c7e6c"}, + "ecto": {:hex, :ecto, "3.12.4", "267c94d9f2969e6acc4dd5e3e3af5b05cdae89a4d549925f3008b2b7eb0b93c3", [: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", "ef04e4101688a67d061e1b10d7bc1fbf00d1d13c17eef08b71d070ff9188f747"}, + "ecto_sql": {:hex, :ecto_sql, "3.12.1", "c0d0d60e85d9ff4631f12bafa454bc392ce8b9ec83531a412c12a0d415a3a4d0", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.12", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.7", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.19 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", "aff5b958a899762c5f09028c847569f7dfb9cc9d63bdb8133bff8a5546de6bf5"}, + "elixir_make": {:hex, :elixir_make, "0.8.4", "4960a03ce79081dee8fe119d80ad372c4e7badb84c493cc75983f9d3bc8bde0f", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:certifi, "~> 2.0", [hex: :certifi, repo: "hexpm", optional: true]}], "hexpm", "6e7f1d619b5f61dfabd0a20aa268e575572b542ac31723293a4c1a567d5ef040"}, "erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"}, - "ex_abi": {:hex, :ex_abi, "0.8.0", "bb08827bd8d71dbb311c69ac55a008669dfabe2ce5b58d65f97c08c0aba60ec6", [:mix], [{:ex_keccak, "~> 0.7.5", [hex: :ex_keccak, repo: "hexpm", optional: true]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "bbdae12c186aeeb4c53dd7c7c57f457923602db315aa1f66d7427467c8ad77af"}, - "ex_cldr": {:hex, :ex_cldr, "2.40.0", "624717778dbf0a8cd307f1576eabbd44470c16190172abf293fed24150440a5a", [:mix], [{:cldr_utils, "~> 2.28", [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", "113394b6dd23aaf7912da583aab103d9cf082b9821bc4a6e287543a895af7cb4"}, + "ex_abi": {:hex, :ex_abi, "0.8.1", "451fa960ddc4dfbb350e13509f3dd64ca586b8484a77aad9f7d778161b5eab79", [:mix], [{:ex_keccak, "~> 0.7.5", [hex: :ex_keccak, repo: "hexpm", optional: true]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "abcf53d556c2948e5c1241340afd4a72cdf93ab6daef16fc200c16ca1183cdca"}, + "ex_cldr": {:hex, :ex_cldr, "2.40.1", "c1fcb0cd9d2a70d28f4540a99f32127e7f1813e0db109d65ab29dea5337ae266", [:mix], [{:cldr_utils, "~> 2.28", [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", "509810702e8e81991851d9426ffe6b34b48b7b9baa12922e7b3fb8f6368606f3"}, "ex_cldr_currencies": {:hex, :ex_cldr_currencies, "2.16.2", "670d96cc4fb18cfebd82488ed687742683be2d0725d66ec051578d4b13539aa8", [:mix], [{:ex_cldr, "~> 2.38", [hex: :ex_cldr, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "2ccfac2838f4df8c8e5424dbc68eb2f3ac9eeb45e10365050901f7ac7a914ce1"}, - "ex_cldr_lists": {:hex, :ex_cldr_lists, "2.11.0", "1d39e75f0e493ccc95adfc85c55b4ca34f0771626350ce326d9ab8813d91444e", [: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", "8132b30a5506ae8a09e5c9a21c23fd60c8837ce6c3a1de9966d813eb78951695"}, - "ex_cldr_numbers": {:hex, :ex_cldr_numbers, "2.33.2", "c5587a8d84214d9cc42e7827e4c3bed2aa9e52505a55b10540020725954ded2c", [: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.38", [hex: :ex_cldr, repo: "hexpm", optional: false]}, {:ex_cldr_currencies, "~> 2.16", [hex: :ex_cldr_currencies, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "49f1dbaddc1ad6e3f496a97fa425d25b3ae89e8178ce0416d9909deaf2e5ad80"}, - "ex_cldr_units": {:hex, :ex_cldr_units, "3.17.1", "f03c7a138113511af903d0d2205b5cc01df1e599c28839ca2e1e78b7ca0bf2a2", [:mix], [{:cldr_utils, "~> 2.25", [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.33.0", [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", "7de1bf7ff7599cf4da9dd0f1a7b6e19ca8db83b883301f5dcd0f565aa5eaec8c"}, + "ex_cldr_lists": {:hex, :ex_cldr_lists, "2.11.1", "ad18f861d7c5ca82aac6d173469c6a2339645c96790172ab0aa255b64fb7303b", [: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", "00161c04510ccb3f18b19a6b8562e50c21f1e9c15b8ff4c934bea5aad0b4ade2"}, + "ex_cldr_numbers": {:hex, :ex_cldr_numbers, "2.33.3", "9fedcf279a17d19abdf8872738472326e82378d90ec2dd9756a0c84558c86b36", [: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.38", [hex: :ex_cldr, repo: "hexpm", optional: false]}, {:ex_cldr_currencies, "~> 2.16", [hex: :ex_cldr_currencies, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "4a0d90d06710c1499528d5f536c539379a73a68d4679c55375198a798d138442"}, + "ex_cldr_units": {:hex, :ex_cldr_units, "3.17.2", "b0483d5c61c6c8649aafdcafc7372dd71a7a30f52dd4c9b072576467bf721454", [:mix], [{:cldr_utils, "~> 2.25", [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.33.0", [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", "457d76c6e3b548bd7aba3c7b5d157213be2842d1162c2283abf81d9e2f1e1fc7"}, "ex_doc": {:hex, :ex_doc, "0.34.2", "13eedf3844ccdce25cfd837b99bea9ad92c4e511233199440488d217c92571e8", [:mix], [{:earmark_parser, "~> 1.4.39", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "5ce5f16b41208a50106afed3de6a2ed34f4acfd65715b82a0b84b49d995f95c1"}, "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.5", "f3b733173510d48ae9a1ea1de415e694b2651f35c787e63f33b5ed0013fbfd35", [:mix], [{:rustler, ">= 0.0.0", [hex: :rustler, repo: "hexpm", optional: true]}, {:rustler_precompiled, "~> 0.7", [hex: :rustler_precompiled, repo: "hexpm", optional: false]}], "hexpm", "8a5e1cb7f96fff5e480ff6a121477b90c4fd8c150984086dffd98819f5d83763"}, @@ -57,22 +58,26 @@ "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, "1.0.0", "647639267e088717232f4d4451526e7a9de31a3402af7fcbda09b27e9a10395a", [:mix], [], "hexpm", "18d2093d344d97678e8a331ca0391e85d29816f9664a25653fd7e6166827827c"}, - "exvcr": {:hex, :exvcr, "0.15.1", "772db4d065f5136c6a984c302799a79e4ade3e52701c95425fa2229dd6426886", [: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", "de4fc18b1d672d9b72bc7468735e19779aa50ea963a1f859ef82cd9e294b13e3"}, + "expo": {:hex, :expo, "1.0.1", "f9e2f984f5b8d195815d52d0ba264798c12c8d2f2606f76fa4c60e8ebe39474d", [:mix], [], "hexpm", "f250b33274e3e56513644858c116f255d35c767c2b8e96a512fe7839ef9306a1"}, + "exvcr": {:hex, :exvcr, "0.15.2", "2216c8605b5c3e300160c2a5bd896b4928fa51fc3fb3420d3e792ad833ac89ba", [: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", "2bd4125889bd3953d7fbb7b388c34190c31e292f12896da56ecf0743d40439ed"}, "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"}, + "finch": {:hex, :finch, "0.18.0", "944ac7d34d0bd2ac8998f79f7a811b21d87d911e77a786bc5810adb75632ada4", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: false]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.3", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 0.2.6 or ~> 1.0", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "69f5045b042e531e53edc2574f15e25e735b522c37e2ddb766e15b979e03aa65"}, "floki": {:hex, :floki, "0.36.2", "a7da0193538c93f937714a6704369711998a51a6164a222d710ebd54020aa7a3", [:mix], [], "hexpm", "a8766c0bc92f074e5cb36c4f9961982eda84c5d2b8e979ca67f5c268ec8ed580"}, "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.25.0", "98a95a862a94e2d55d24520dd79256a15c87ea75b49673a2e2f206e6ebc42e5d", [:mix], [{:expo, "~> 0.5.1 or ~> 1.0", [hex: :expo, repo: "hexpm", optional: false]}], "hexpm", "38e5d754e66af37980a94fb93bb20dcde1d2361f664b0a19f01e87296634051f"}, + "gettext": {:hex, :gettext, "0.26.1", "38e14ea5dcf962d1fc9f361b63ea07c0ce715a8ef1f9e82d3dfb8e67e0416715", [:mix], [{:expo, "~> 0.5.1 or ~> 1.0", [hex: :expo, repo: "hexpm", optional: false]}], "hexpm", "01ce56f188b9dc28780a52783d6529ad2bc7124f9744e571e1ee4ea88bf08734"}, "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.2.1", "5ae9c33e3dceaeb42de0db46bf505bd9c35f259c8defb03390cd7556fea67ee2", [:mix], [{:poolboy, "~> 1.5", [hex: :poolboy, repo: "hexpm", optional: false]}], "hexpm", "b9476d0c13883d2dc0cc72e786bac6ac28911fba7cc2e04b70ce6a6d9c4b2bdc"}, "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"}, + "hpax": {:hex, :hpax, "1.0.0", "28dcf54509fe2152a3d040e4e3df5b265dcb6cb532029ecbacf4ce52caea3fd2", [:mix], [], "hexpm", "7f1314731d711e2ca5fdc7fd361296593fc2542570b3105595bb0bc6d0fad601"}, "html_entities": {:hex, :html_entities, "0.5.2", "9e47e70598da7de2a9ff6af8758399251db6dbb7eebe2b013f2bbd2515895c3c", [:mix], [], "hexpm", "c53ba390403485615623b9531e97696f076ed415e8d8058b1dbaa28181f4fdcc"}, "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.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, + "joken": {:hex, :joken, "2.6.2", "5daaf82259ca603af4f0b065475099ada1b2b849ff140ccd37f4b6828ca6892a", [:mix], [{:jose, "~> 1.11.10", [hex: :jose, repo: "hexpm", optional: false]}], "hexpm", "5134b5b0a6e37494e46dbf9e4dad53808e5e787904b7c73972651b51cce3d72b"}, + "jose": {:hex, :jose, "1.11.10", "a903f5227417bd2a08c8a00a0cbcc458118be84480955e8d251297a425723f83", [:mix, :rebar3], [], "hexpm", "0d6cd36ff8ba174db29148fc112b5842186b68a90ce9fc2b3ec3afe76593e614"}, "jsx": {:hex, :jsx, "2.8.3", "a05252d381885240744d955fbe3cf810504eb2567164824e19303ea59eef62cf", [:mix, :rebar3], [], "hexpm", "fc3499fed7a726995aa659143a248534adc754ebd16ccd437cd93b649a95091f"}, "junit_formatter": {:hex, :junit_formatter, "3.4.0", "d0e8db6c34dab6d3c4154c3b46b21540db1109ae709d6cf99ba7e7a2ce4b1ac2", [:mix], [], "hexpm", "bb36e2ae83f1ced6ab931c4ce51dd3dbef1ef61bb4932412e173b0cfa259dacd"}, "logger_file_backend": {:hex, :logger_file_backend, "0.0.14", "774bb661f1c3fed51b624d2859180c01e386eb1273dc22de4f4a155ef749a602", [:mix], [], "hexpm", "071354a18196468f3904ef09413af20971d55164267427f6257b52cfba03f9e6"}, @@ -87,13 +92,15 @@ "mime": {:hex, :mime, "2.0.6", "8f18486773d9b15f95f4f4f1e39b710045fa1de891fada4516559967276e4dc2", [:mix], [], "hexpm", "c9945363a6b26d747389aac3643f8e0e09d30499a138ad64fe8fd1d13d9b153e"}, "mimerl": {:hex, :mimerl, "1.3.0", "d0cd9fc04b9061f82490f6581e0128379830e78535e017f7780f37fea7545726", [:rebar3], [], "hexpm", "a1e15a50d1887217de95f0b9b0793e32853f7c258a5cd227650889b38839fe9d"}, "mimetype_parser": {:hex, :mimetype_parser, "0.1.3", "628ac9fe56aa7edcedb534d68397dd66674ab82493c8ebe39acb9a19b666099d", [:mix], [], "hexpm", "7d8f80c567807ce78cd93c938e7f4b0a20b1aaaaab914bf286f68457d9f7a852"}, + "mint": {:hex, :mint, "1.6.2", "af6d97a4051eee4f05b5500671d47c3a67dac7386045d87a904126fd4bbcea2e", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1 or ~> 0.2.0 or ~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "5ee441dffc1892f1ae59127f74afe8fd82fda6587794278d924e4d90ea3d63f9"}, "mix_erlang_tasks": {:hex, :mix_erlang_tasks, "0.1.0", "36819fec60b80689eb1380938675af215565a89320a9e29c72c70d97512e4649", [:mix], [], "hexpm", "95d2839c422c482a70c08a8702da8242f86b773f8ab6e8602a4eb72da8da04ed"}, "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.1.0", "3b31a57ede9cb1502071fade751ab0c7b8dbe75a9a4c2b5bbb0943a690b63172", [:mix], [], "hexpm", "8bbbb3941af3ca9acc7835f5655ea062111c9c27bcac53e004460dfd19008a99"}, + "nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"}, "nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"}, + "nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"}, "number": {:hex, :number, "1.0.5", "d92136f9b9382aeb50145782f116112078b3465b7be58df1f85952b8bb399b0f", [:mix], [{:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}], "hexpm", "c0733a0a90773a66582b9e92a3f01290987f395c972cb7d685f51dd927cd5169"}, "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"}, @@ -106,11 +113,11 @@ "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.3", "3168d78ba41835aecad272d5e8cd51aa87a7ac9eb836eabc42f6e57538e3731d", [:mix], [], "hexpm", "bba06bc1dcfd8cb086759f0edc94a8ba2bc8896d5331a1e2c2902bf8e36ee502"}, "plug": {:hex, :plug, "1.16.1", "40c74619c12f82736d2214557dedec2e9762029b2438d6d175c5074c933edc9d", [: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", "a13ff6b9006b03d7e33874945b2755253841b238c34071ed85b0e86057f8cddc"}, - "plug_cowboy": {:hex, :plug_cowboy, "2.7.1", "87677ffe3b765bc96a89be7960f81703223fe2e21efa42c125fcd0127dd9d6b2", [: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", "02dbd5f9ab571b864ae39418db7811618506256f6d13b4a45037e5fe78dc5de3"}, + "plug_cowboy": {:hex, :plug_cowboy, "2.7.2", "fdadb973799ae691bf9ecad99125b16625b1c6039999da5fe544d99218e662e4", [: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", "245d8a11ee2306094840c000e8816f0cbed69a23fc0ac2bcf8d7835ae019bb2f"}, "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.19.0", "f7d50e50cb42e0a185f5b9a6095125a9ab7e4abccfbe2ab820ab9aa92b71dbab", [: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", "dba2d2a0a8637defbf2307e8629cb2526388ba7348f67d04ec77a5d6a72ecfae"}, + "postgrex": {:hex, :postgrex, "0.19.1", "73b498508b69aded53907fe48a1fee811be34cc720e69ef4ccd568c8715495ea", [: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", "8bac7885a18f381e091ec6caf41bda7bb8c77912bb0e9285212829afe5d8a8f8"}, "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"]}, @@ -121,11 +128,12 @@ "quantile_estimator": {:hex, :quantile_estimator, "0.2.1", "ef50a361f11b5f26b5f16d0696e46a9e4661756492c981f7b2229ef42ff1cd15", [:rebar3], [], "hexpm", "282a8a323ca2a845c9e6f787d166348f776c1d4a41ede63046d72d422e3da946"}, "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"}, - "recon": {:hex, :recon, "2.5.5", "c108a4c406fa301a529151a3bb53158cadc4064ec0c5f99b03ddb8c0e4281bdf", [:mix, :rebar3], [], "hexpm", "632a6f447df7ccc1a4a10bdcfce71514412b16660fe59deca0fcf0aa3c054404"}, - "redix": {:hex, :redix, "1.5.1", "a2386971e69bf23630fb3a215a831b5478d2ee7dc9ea7ac811ed89186ab5d7b7", [: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", "85224eb2b683c516b80d472eb89b76067d5866913bf0be59d646f550de71f5c4"}, + "recon": {:hex, :recon, "2.5.6", "9052588e83bfedfd9b72e1034532aee2a5369d9d9343b61aeb7fbce761010741", [:mix, :rebar3], [], "hexpm", "96c6799792d735cc0f0fd0f86267e9d351e63339cbe03df9d162010cefc26bb0"}, + "redix": {:hex, :redix, "1.5.2", "ab854435a663f01ce7b7847f42f5da067eea7a3a10c0a9d560fa52038fd7ab48", [: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", "78538d184231a5d6912f20567d76a49d1be7d3fca0e1aaaa20f4df8e1142dcb8"}, "remote_ip": {:hex, :remote_ip, "1.2.0", "fb078e12a44414f4cef5a75963c33008fe169b806572ccd17257c208a7bc760f", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "2ff91de19c48149ce19ed230a81d377186e4412552a597d6a5137373e5877cb7"}, - "rustler_precompiled": {:hex, :rustler_precompiled, "0.7.1", "ecadf02cc59a0eccbaed6c1937303a5827fbcf60010c541595e6d3747d3d0f9f", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: false]}, {:rustler, "~> 0.23", [hex: :rustler, repo: "hexpm", optional: true]}], "hexpm", "b9e4657b99a1483ea31502e1d58c464bedebe9028808eda45c3a429af4550c66"}, + "req": {:hex, :req, "0.5.6", "8fe1eead4a085510fe3d51ad854ca8f20a622aae46e97b302f499dfb84f726ac", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.17", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 2.0.6 or ~> 2.1", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "cfaa8e720945d46654853de39d368f40362c2641c4b2153c886418914b372185"}, + "rustler_precompiled": {:hex, :rustler_precompiled, "0.8.1", "8afe0b6f3a9a677ada046cdd23e3f4c6399618b91a6122289324774961281e1e", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: false]}, {:rustler, "~> 0.23", [hex: :rustler, repo: "hexpm", optional: true]}], "hexpm", "90b8c2297bf7959cfa1c927b2881faad7bb0707183124955369991b76177a166"}, + "siwe": {:git, "https://github.com/royal-markets/siwe-ex.git", "51c9c08240eb7eea3c35693011f8d260cd9bb3be", [ref: "51c9c08240eb7eea3c35693011f8d260cd9bb3be"]}, "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.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"}, @@ -133,8 +141,8 @@ "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.7", "354c321cf377240c7b8716899e182ce4890c5938111a1296add3ec74cf1715df", [:make, :mix, :rebar3], [], "hexpm", "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"}, "statistex": {:hex, :statistex, "1.0.0", "f3dc93f3c0c6c92e5f291704cf62b99b553253d7969e9a5fa713e5481cd858a5", [:mix], [], "hexpm", "ff9d8bee7035028ab4742ff52fc80a2aa35cece833cf5319009b52f1b5a86c27"}, - "telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"}, - "tesla": {:hex, :tesla, "1.12.1", "fe2bf4250868ee72e5d8b8dfa408d13a00747c41b7237b6aa3b9a24057346681", [: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", "2391efc6243d37ead43afd0327b520314c7b38232091d4a440c1212626fdd6e7"}, + "telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"}, + "tesla": {:hex, :tesla, "1.12.2", "1399c2dada208e381fec752e8fc21d6aa646c0a1f7cec8276484324bcba11b1e", [: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", "36bbea8b6e94fa9d089118f31afc622cf4139e3bcd035773b85ca0c6e96e94c1"}, "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"}, "typed_ecto_schema": {:hex, :typed_ecto_schema, "0.4.1", "a373ca6f693f4de84cde474a67467a9cb9051a8a7f3f615f1e23dc74b75237fa", [:mix], [{:ecto, "~> 3.5", [hex: :ecto, repo: "hexpm", optional: false]}], "hexpm", "85c6962f79d35bf543dd5659c6adc340fd2480cacc6f25d2cc2933ea6e8fcb3b"}, diff --git a/rel/config.exs b/rel/config.exs index 7cebe1d..5f7d8e9 100644 --- a/rel/config.exs +++ b/rel/config.exs @@ -71,7 +71,7 @@ end # will be used by default release :blockscout do - set version: "6.8.0-beta" + set version: "6.9.2-beta" set applications: [ :runtime_tools, block_scout_web: :permanent,