Merge pull request #8908 from blockscout/vb-solidityscan-support

Solidityscan report API endpoint
pull/8789/head
Victor Baranov 11 months ago committed by GitHub
commit 355c3c34b8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      .dialyzer-ignore
  2. 26
      .github/workflows/config.yml
  3. 4
      CHANGELOG.md
  4. 24
      apps/block_scout_web/lib/block_scout_web/controllers/api/v2/fallback_controller.ex
  5. 26
      apps/block_scout_web/lib/block_scout_web/controllers/api/v2/smart_contract_controller.ex
  6. 1
      apps/block_scout_web/lib/block_scout_web/smart_contracts_api_v2_router.ex
  7. 7
      apps/block_scout_web/lib/block_scout_web/views/api/v2/helper.ex
  8. 4
      apps/block_scout_web/lib/block_scout_web/views/api/v2/transaction_view.ex
  9. 22
      apps/explorer/lib/explorer/account/notifier/forbidden_address.ex
  10. 10
      apps/explorer/lib/explorer/chain/address.ex
  11. 28
      apps/explorer/lib/explorer/exchange_rates/source.ex
  12. 23
      apps/explorer/lib/explorer/helper.ex
  13. 33
      apps/explorer/lib/explorer/smart_contract/compiler_version.ex
  14. 40
      apps/explorer/lib/explorer/third_party_integrations/solidityscan.ex
  15. 34
      apps/explorer/lib/explorer/third_party_integrations/sourcify.ex
  16. 15
      apps/indexer/lib/indexer/fetcher/token_instance/metadata_retriever.ex
  17. 4
      config/runtime.exs
  18. 13
      cspell.json

@ -13,8 +13,6 @@ lib/block_scout_web/schema/types.ex:31
lib/phoenix/router.ex:324 lib/phoenix/router.ex:324
lib/phoenix/router.ex:402 lib/phoenix/router.ex:402
lib/explorer/smart_contract/reader.ex:435 lib/explorer/smart_contract/reader.ex:435
lib/explorer/exchange_rates/source.ex:139
lib/explorer/exchange_rates/source.ex:142
lib/indexer/fetcher/polygon_edge.ex:737 lib/indexer/fetcher/polygon_edge.ex:737
lib/indexer/fetcher/polygon_edge/deposit_execute.ex:140 lib/indexer/fetcher/polygon_edge/deposit_execute.ex:140
lib/indexer/fetcher/polygon_edge/deposit_execute.ex:184 lib/indexer/fetcher/polygon_edge/deposit_execute.ex:184

@ -72,7 +72,7 @@ jobs:
path: | path: |
deps deps
_build _build
key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_27-${{ hashFiles('mix.lock') }} key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_28-${{ hashFiles('mix.lock') }}
restore-keys: | restore-keys: |
${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps- ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-
@ -130,7 +130,7 @@ jobs:
path: | path: |
deps deps
_build _build
key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_27-${{ hashFiles('mix.lock') }} key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_28-${{ hashFiles('mix.lock') }}
restore-keys: | restore-keys: |
${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-"
@ -154,7 +154,7 @@ jobs:
path: | path: |
deps deps
_build _build
key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_27-${{ hashFiles('mix.lock') }} key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_28-${{ hashFiles('mix.lock') }}
restore-keys: | restore-keys: |
${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-"
@ -183,7 +183,7 @@ jobs:
path: | path: |
deps deps
_build _build
key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_27-${{ hashFiles('mix.lock') }} key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_28-${{ hashFiles('mix.lock') }}
restore-keys: | restore-keys: |
${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-"
@ -227,7 +227,7 @@ jobs:
path: | path: |
deps deps
_build _build
key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_27-${{ hashFiles('mix.lock') }} key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_28-${{ hashFiles('mix.lock') }}
restore-keys: | restore-keys: |
${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-"
@ -253,7 +253,7 @@ jobs:
path: | path: |
deps deps
_build _build
key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_27-${{ hashFiles('mix.lock') }} key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_28-${{ hashFiles('mix.lock') }}
restore-keys: | restore-keys: |
${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-"
@ -282,7 +282,7 @@ jobs:
path: | path: |
deps deps
_build _build
key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_27-${{ hashFiles('mix.lock') }} key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_28-${{ hashFiles('mix.lock') }}
restore-keys: | restore-keys: |
${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-"
@ -330,7 +330,7 @@ jobs:
path: | path: |
deps deps
_build _build
key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_27-${{ hashFiles('mix.lock') }} key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_28-${{ hashFiles('mix.lock') }}
restore-keys: | restore-keys: |
${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-"
@ -376,7 +376,7 @@ jobs:
path: | path: |
deps deps
_build _build
key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_27-${{ hashFiles('mix.lock') }} key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_28-${{ hashFiles('mix.lock') }}
restore-keys: | restore-keys: |
${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-"
@ -438,7 +438,7 @@ jobs:
path: | path: |
deps deps
_build _build
key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_27-${{ hashFiles('mix.lock') }} key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_28-${{ hashFiles('mix.lock') }}
restore-keys: | restore-keys: |
${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-"
@ -498,7 +498,7 @@ jobs:
path: | path: |
deps deps
_build _build
key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_27-${{ hashFiles('mix.lock') }} key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_28-${{ hashFiles('mix.lock') }}
restore-keys: | restore-keys: |
${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-"
@ -569,7 +569,7 @@ jobs:
path: | path: |
deps deps
_build _build
key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_27-${{ hashFiles('mix.lock') }} key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_28-${{ hashFiles('mix.lock') }}
restore-keys: | restore-keys: |
${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-"
@ -637,7 +637,7 @@ jobs:
path: | path: |
deps deps
_build _build
key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_27-${{ hashFiles('mix.lock') }} key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_28-${{ hashFiles('mix.lock') }}
restore-keys: | restore-keys: |
${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-"

@ -2,15 +2,15 @@
## Current ## Current
- [#8924](https://github.com/blockscout/blockscout/pull/8924) - Delete invalid current token balances in OnDemand fetcher
### Features ### Features
- [#8908](https://github.com/blockscout/blockscout/pull/8908) - Solidityscan report API endpoint
- [#8900](https://github.com/blockscout/blockscout/pull/8900) - Add Compound proxy contract pattern - [#8900](https://github.com/blockscout/blockscout/pull/8900) - Add Compound proxy contract pattern
- [#8611](https://github.com/blockscout/blockscout/pull/8611) - Implement sorting of smart contracts, address transactions - [#8611](https://github.com/blockscout/blockscout/pull/8611) - Implement sorting of smart contracts, address transactions
### Fixes ### Fixes
- [#8924](https://github.com/blockscout/blockscout/pull/8924) - Delete invalid current token balances in OnDemand fetcher
- [#8922](https://github.com/blockscout/blockscout/pull/8922) - Allow call type to be in lowercase - [#8922](https://github.com/blockscout/blockscout/pull/8922) - Allow call type to be in lowercase
- [#8917](https://github.com/blockscout/blockscout/pull/8917) - Proxy detection hotfix in API v2 - [#8917](https://github.com/blockscout/blockscout/pull/8917) - Proxy detection hotfix in API v2
- [#8915](https://github.com/blockscout/blockscout/pull/8915) - smart-contract: delete embeds_many relation on replace - [#8915](https://github.com/blockscout/blockscout/pull/8915) - smart-contract: delete embeds_many relation on replace

@ -23,6 +23,9 @@ defmodule BlockScoutWeb.API.V2.FallbackController do
@unauthorized "Unauthorized" @unauthorized "Unauthorized"
@not_configured_api_key "API key not configured on the server" @not_configured_api_key "API key not configured on the server"
@wrong_api_key "Wrong API key" @wrong_api_key "Wrong API key"
@address_not_found "Address not found"
@address_is_not_smart_contract "Address is not smart-contract"
@empty_response "Empty response"
def call(conn, {:format, _params}) do def call(conn, {:format, _params}) do
Logger.error(fn -> Logger.error(fn ->
@ -232,4 +235,25 @@ defmodule BlockScoutWeb.API.V2.FallbackController do
|> put_view(ApiView) |> put_view(ApiView)
|> render(:message, %{message: @wrong_api_key}) |> render(:message, %{message: @wrong_api_key})
end end
def call(conn, {:address, {:error, :not_found}}) do
conn
|> put_status(:not_found)
|> put_view(ApiView)
|> render(:message, %{message: @address_not_found})
end
def call(conn, {:is_smart_contract, result}) when is_nil(result) or result == false do
conn
|> put_status(:not_found)
|> put_view(ApiView)
|> render(:message, %{message: @address_is_not_smart_contract})
end
def call(conn, {:is_empty_response, true}) do
conn
|> put_status(500)
|> put_view(ApiView)
|> render(:message, %{message: @empty_response})
end
end end

@ -12,9 +12,10 @@ defmodule BlockScoutWeb.API.V2.SmartContractController do
alias BlockScoutWeb.{AccessHelper, AddressView} alias BlockScoutWeb.{AccessHelper, AddressView}
alias Ecto.Association.NotLoaded alias Ecto.Association.NotLoaded
alias Explorer.Chain alias Explorer.Chain
alias Explorer.Chain.SmartContract alias Explorer.Chain.{Address, SmartContract}
alias Explorer.SmartContract.{Reader, Writer} alias Explorer.SmartContract.{Reader, Writer}
alias Explorer.SmartContract.Solidity.PublishHelper alias Explorer.SmartContract.Solidity.PublishHelper
alias Explorer.ThirdPartyIntegrations.SolidityScan
@smart_contract_address_options [ @smart_contract_address_options [
necessity_by_association: %{ necessity_by_association: %{
@ -190,6 +191,29 @@ defmodule BlockScoutWeb.API.V2.SmartContractController do
end end
end end
@doc """
/api/v2/smart-contracts/${address_hash_string}/solidityscan-report logic
"""
@spec solidityscan_report(Plug.Conn.t(), map()) ::
{:address, {:error, :not_found}}
| {:format_address, :error}
| {:is_empty_response, true}
| {:is_smart_contract, false | nil}
| {:restricted_access, true}
| Plug.Conn.t()
def solidityscan_report(conn, %{"address_hash" => address_hash_string} = params) do
with {:format_address, {:ok, address_hash}} <- {:format_address, Chain.string_to_address_hash(address_hash_string)},
{:ok, false} <- AccessHelper.restricted_access?(address_hash_string, params),
{:address, {:ok, address}} <- {:address, Chain.hash_to_address(address_hash)},
{:is_smart_contract, true} <- {:is_smart_contract, Address.is_smart_contract(address)},
response = SolidityScan.solidityscan_request(address_hash_string),
{:is_empty_response, false} <- {:is_empty_response, is_nil(response)} do
conn
|> put_status(200)
|> json(response)
end
end
def smart_contracts_list(conn, params) do def smart_contracts_list(conn, params) do
full_options = full_options =
[necessity_by_association: %{[address: :token] => :optional, [address: :names] => :optional, address: :required}] [necessity_by_association: %{[address: :token] => :optional, [address: :names] => :optional, address: :required}]

@ -27,6 +27,7 @@ defmodule BlockScoutWeb.SmartContractsApiV2Router do
get("/:address_hash/methods-read-proxy", V2.SmartContractController, :methods_read_proxy) get("/:address_hash/methods-read-proxy", V2.SmartContractController, :methods_read_proxy)
get("/:address_hash/methods-write-proxy", V2.SmartContractController, :methods_write_proxy) get("/:address_hash/methods-write-proxy", V2.SmartContractController, :methods_write_proxy)
post("/:address_hash/query-read-method", V2.SmartContractController, :query_read_method) post("/:address_hash/query-read-method", V2.SmartContractController, :query_read_method)
get("/:address_hash/solidityscan-report", V2.SmartContractController, :solidityscan_report)
get("/verification/config", V2.VerificationController, :config) get("/verification/config", V2.VerificationController, :config)

@ -51,7 +51,7 @@ defmodule BlockScoutWeb.API.V2.Helper do
defp address_with_info(%Address{} = address, _address_hash) do defp address_with_info(%Address{} = address, _address_hash) do
%{ %{
"hash" => Address.checksum(address), "hash" => Address.checksum(address),
"is_contract" => is_smart_contract(address), "is_contract" => Address.is_smart_contract(address),
"name" => address_name(address), "name" => address_name(address),
"implementation_name" => implementation_name(address), "implementation_name" => implementation_name(address),
"is_verified" => is_verified(address) "is_verified" => is_verified(address)
@ -94,11 +94,6 @@ defmodule BlockScoutWeb.API.V2.Helper do
def implementation_name(_), do: nil def implementation_name(_), do: nil
def is_smart_contract(%Address{contract_code: nil}), do: false
def is_smart_contract(%Address{contract_code: _}), do: true
def is_smart_contract(%NotLoaded{}), do: nil
def is_smart_contract(_), do: false
def is_verified(%Address{smart_contract: nil}), do: false def is_verified(%Address{smart_contract: nil}), do: false
def is_verified(%Address{smart_contract: %{metadata_from_verified_twin: true}}), do: false def is_verified(%Address{smart_contract: %{metadata_from_verified_twin: true}}), do: false
def is_verified(%Address{smart_contract: %NotLoaded{}}), do: nil def is_verified(%Address{smart_contract: %NotLoaded{}}), do: nil

@ -707,7 +707,7 @@ defmodule BlockScoutWeb.API.V2.TransactionView do
_, _,
skip_sc_check? skip_sc_check?
) do ) do
if skip_sc_check? || Helper.is_smart_contract(to_address) do if skip_sc_check? || Address.is_smart_contract(to_address) do
"0x" <> Base.encode16(method_id, case: :lower) "0x" <> Base.encode16(method_id, case: :lower)
else else
nil nil
@ -760,7 +760,7 @@ defmodule BlockScoutWeb.API.V2.TransactionView do
defp tx_types(%Transaction{to_address: to_address} = tx, types, :contract_call) do defp tx_types(%Transaction{to_address: to_address} = tx, types, :contract_call) do
types = types =
if Helper.is_smart_contract(to_address) do if Address.is_smart_contract(to_address) do
[:contract_call | types] [:contract_call | types]
else else
types types

@ -5,16 +5,16 @@ defmodule Explorer.Account.Notifier.ForbiddenAddress do
import Explorer.Chain.SmartContract, only: [burn_address_hash_string: 0] import Explorer.Chain.SmartContract, only: [burn_address_hash_string: 0]
alias Explorer.Chain.Address
@blacklist [ @blacklist [
burn_address_hash_string(), burn_address_hash_string(),
"0x000000000000000000000000000000000000dEaD" "0x000000000000000000000000000000000000dEaD"
] ]
alias Explorer.{AccessHelper, Repo} alias Explorer.AccessHelper
alias Explorer.Chain.Token
import Ecto.Query, only: [from: 2] import Explorer.Chain, only: [string_to_address_hash: 1, hash_to_address: 1]
import Explorer.Chain, only: [string_to_address_hash: 1]
def check(address_string) when is_bitstring(address_string) do def check(address_string) when is_bitstring(address_string) do
case format_address(address_string) do case format_address(address_string) do
@ -32,7 +32,7 @@ defmodule Explorer.Account.Notifier.ForbiddenAddress do
{:error, "This address is blacklisted"} {:error, "This address is blacklisted"}
is_contract(address_hash) -> is_contract(address_hash) ->
{:error, "This address isn't personal"} {:error, "This address isn't EOA"}
match?({:restricted_access, true}, AccessHelper.restricted_access?(to_string(address_hash), %{})) -> match?({:restricted_access, true}, AccessHelper.restricted_access?(to_string(address_hash), %{})) ->
{:error, "This address has restricted access"} {:error, "This address has restricted access"}
@ -43,14 +43,10 @@ defmodule Explorer.Account.Notifier.ForbiddenAddress do
end end
defp is_contract(%Explorer.Chain.Hash{} = address_hash) do defp is_contract(%Explorer.Chain.Hash{} = address_hash) do
query = case hash_to_address(address_hash) do
from( {:error, :not_found} -> false
token in Token, {:ok, address} -> Address.is_smart_contract(address)
where: token.contract_address_hash == ^address_hash end
)
contract_addresses = Repo.all(query)
List.first(contract_addresses)
end end
defp format_address(address_hash_string) do defp format_address(address_hash_string) do

@ -7,6 +7,7 @@ defmodule Explorer.Chain.Address do
use Explorer.Schema use Explorer.Schema
alias Ecto.Association.NotLoaded
alias Ecto.Changeset alias Ecto.Changeset
alias Explorer.{Chain, PagingOptions} alias Explorer.{Chain, PagingOptions}
@ -332,6 +333,15 @@ defmodule Explorer.Chain.Address do
end end
end end
@doc """
Checks if given address is smart-contract
"""
@spec is_smart_contract(any()) :: boolean() | nil
def is_smart_contract(%__MODULE__{contract_code: nil}), do: false
def is_smart_contract(%__MODULE__{contract_code: _}), do: true
def is_smart_contract(%NotLoaded{}), do: nil
def is_smart_contract(_), do: false
defp get_addresses(options) do defp get_addresses(options) do
accounts_with_n = fetch_top_addresses(options) accounts_with_n = fetch_top_addresses(options)

@ -6,6 +6,7 @@ defmodule Explorer.ExchangeRates.Source do
alias Explorer.Chain.Hash alias Explorer.Chain.Hash
alias Explorer.ExchangeRates.Source.CoinGecko alias Explorer.ExchangeRates.Source.CoinGecko
alias Explorer.ExchangeRates.Token alias Explorer.ExchangeRates.Token
alias Explorer.Helper
alias HTTPoison.{Error, Response} alias HTTPoison.{Error, Response}
@doc """ @doc """
@ -91,12 +92,6 @@ defmodule Explorer.ExchangeRates.Source do
[{"Content-Type", "application/json"}] [{"Content-Type", "application/json"}]
end end
def decode_json(data) do
Jason.decode!(data)
rescue
_ -> data
end
def to_decimal(nil), do: nil def to_decimal(nil), do: nil
def to_decimal(%Decimal{} = value), do: value def to_decimal(%Decimal{} = value), do: value
@ -135,32 +130,17 @@ defmodule Explorer.ExchangeRates.Source do
{:error, %Error{reason: reason}} -> {:error, %Error{reason: reason}} ->
{:error, reason} {:error, reason}
{:error, :nxdomain} ->
{:error, "Source is not responsive"}
{:error, _} ->
{:error, "Source unknown response"}
end end
end end
defp parse_http_success_response(body) do defp parse_http_success_response(body) do
body_json = decode_json(body) body_json = Helper.decode_json(body)
cond do {:ok, body_json}
is_map(body_json) ->
{:ok, body_json}
is_list(body_json) ->
{:ok, body_json}
true ->
{:ok, body}
end
end end
defp parse_http_error_response(body) do defp parse_http_error_response(body) do
body_json = decode_json(body) body_json = Helper.decode_json(body)
if is_map(body_json) do if is_map(body_json) do
{:error, body_json["error"]} {:error, body_json["error"]}

@ -58,4 +58,27 @@ defmodule Explorer.Helper do
Enum.map(list, fn el -> Map.put(el, preload_field, associated_elements[el[foreign_key_field]]) end) Enum.map(list, fn el -> Map.put(el, preload_field, associated_elements[el[foreign_key_field]]) end)
end end
@doc """
Decode json
"""
@spec decode_json(any()) :: map() | list() | nil
def decode_json(nil), do: nil
def decode_json(data) do
if String.valid?(data) do
safe_decode_json(data)
else
data
|> :unicode.characters_to_binary(:latin1)
|> safe_decode_json()
end
end
defp safe_decode_json(data) do
case Jason.decode(data) do
{:ok, decoded} -> decoded
_ -> %{error: data}
end
end
end end

@ -31,36 +31,25 @@ defmodule Explorer.SmartContract.CompilerVersion do
end end
defp fetch_solc_versions do defp fetch_solc_versions do
if RustVerifierInterface.enabled?() do fetch_compiler_versions(&RustVerifierInterface.get_versions_list/0, :solc)
RustVerifierInterface.get_versions_list()
else
headers = [{"Content-Type", "application/json"}]
case HTTPoison.get(source_url(:solc), headers) do
{:ok, %{status_code: 200, body: body}} ->
{:ok, format_data(body, :solc)}
{:ok, %{status_code: _status_code, body: body}} ->
{:error, decode_json(body)["error"]}
{:error, %{reason: reason}} ->
{:error, reason}
end
end
end end
defp fetch_vyper_versions do defp fetch_vyper_versions do
fetch_compiler_versions(&RustVerifierInterface.vyper_get_versions_list/0, :vyper)
end
defp fetch_compiler_versions(compiler_list_fn, compiler_type) do
if RustVerifierInterface.enabled?() do if RustVerifierInterface.enabled?() do
RustVerifierInterface.vyper_get_versions_list() compiler_list_fn.()
else else
headers = [{"Content-Type", "application/json"}] headers = [{"Content-Type", "application/json"}]
case HTTPoison.get(source_url(:vyper), headers) do case HTTPoison.get(source_url(compiler_type), headers) do
{:ok, %{status_code: 200, body: body}} -> {:ok, %{status_code: 200, body: body}} ->
{:ok, format_data(body, :vyper)} {:ok, format_data(body, compiler_type)}
{:ok, %{status_code: _status_code, body: body}} -> {:ok, %{status_code: _status_code, body: body}} ->
{:error, decode_json(body)["error"]} {:error, Helper.decode_json(body)["error"]}
{:error, %{reason: reason}} -> {:error, %{reason: reason}} ->
{:error, reason} {:error, reason}
@ -140,10 +129,6 @@ defmodule Explorer.SmartContract.CompilerVersion do
end end
end end
defp decode_json(json) do
Jason.decode!(json)
end
@spec source_url(:solc | :vyper) :: String.t() @spec source_url(:solc | :vyper) :: String.t()
defp source_url(compiler) do defp source_url(compiler) do
case compiler do case compiler do

@ -0,0 +1,40 @@
defmodule Explorer.ThirdPartyIntegrations.SolidityScan do
@moduledoc """
Module for SolidityScan integration https://apidoc.solidityscan.com/solidityscan-security-api/solidityscan-other-apis/quickscan-api-v1
"""
alias Explorer.Helper
@blockscout_platform_id "16"
@recv_timeout 60_000
@doc """
Proxy request to solidityscan API endpoint for the given smart-contract
"""
@spec solidityscan_request(String.t()) :: any()
def solidityscan_request(address_hash_string) do
headers = [{"Authorization", "Token #{api_key()}"}]
url = base_url(address_hash_string)
case HTTPoison.get(url, headers, recv_timeout: @recv_timeout) do
{:ok, %HTTPoison.Response{status_code: 200, body: body}} ->
Helper.decode_json(body)
_ ->
nil
end
end
defp base_url(address_hash_string) do
"https://api.solidityscan.com/api/v1/quickscan/#{@blockscout_platform_id}/#{chain_id()}/#{address_hash_string}"
end
defp chain_id do
Application.get_env(:explorer, __MODULE__)[:chain_id]
end
defp api_key do
Application.get_env(:explorer, __MODULE__)[:api_key]
end
end

@ -4,6 +4,7 @@ defmodule Explorer.ThirdPartyIntegrations.Sourcify do
""" """
use Tesla use Tesla
alias Explorer.Helper, as: ExplorerHelper
alias Explorer.SmartContract.{Helper, RustVerifierInterface} alias Explorer.SmartContract.{Helper, RustVerifierInterface}
alias HTTPoison.{Error, Response} alias HTTPoison.{Error, Response}
alias Tesla.Multipart alias Tesla.Multipart
@ -223,7 +224,7 @@ defmodule Explorer.ThirdPartyIntegrations.Sourcify do
end end
defp parse_verify_http_response(body) do defp parse_verify_http_response(body) do
body_json = decode_json(body) body_json = ExplorerHelper.decode_json(body)
case body_json do case body_json do
# Success status from native Sourcify server # Success status from native Sourcify server
@ -246,7 +247,7 @@ defmodule Explorer.ThirdPartyIntegrations.Sourcify do
end end
defp parse_check_by_address_http_response(body) do defp parse_check_by_address_http_response(body) do
body_json = decode_json(body) body_json = ExplorerHelper.decode_json(body)
case body_json do case body_json do
[%{"status" => "perfect"}] -> [%{"status" => "perfect"}] ->
@ -264,11 +265,11 @@ defmodule Explorer.ThirdPartyIntegrations.Sourcify do
end end
defp parse_get_metadata_http_response(body) do defp parse_get_metadata_http_response(body) do
body_json = decode_json(body) body_json = ExplorerHelper.decode_json(body)
case body_json do case body_json do
%{"message" => message, "errors" => errors} -> %{"message" => message, "errors" => errors} ->
{:error, "#{message}: #{decode_json(errors)}"} {:error, "#{message}: #{ExplorerHelper.decode_json(errors)}"}
metadata -> metadata ->
{:ok, metadata} {:ok, metadata}
@ -276,11 +277,11 @@ defmodule Explorer.ThirdPartyIntegrations.Sourcify do
end end
defp parse_get_metadata_any_http_response(body) do defp parse_get_metadata_any_http_response(body) do
body_json = decode_json(body) body_json = ExplorerHelper.decode_json(body)
case body_json do case body_json do
%{"message" => message, "errors" => errors} -> %{"message" => message, "errors" => errors} ->
{:error, "#{message}: #{decode_json(errors)}"} {:error, "#{message}: #{ExplorerHelper.decode_json(errors)}"}
%{"status" => status, "files" => metadata} -> %{"status" => status, "files" => metadata} ->
{:ok, status, metadata} {:ok, status, metadata}
@ -290,16 +291,23 @@ defmodule Explorer.ThirdPartyIntegrations.Sourcify do
end end
end end
@invalid_json_response "invalid http error json response"
defp parse_http_error_response(body) do defp parse_http_error_response(body) do
body_json = decode_json(body) body_json = ExplorerHelper.decode_json(body)
if is_map(body_json) do if is_map(body_json) do
{:error, body_json["error"]} error = body_json["error"]
parse_http_error_response_internal(error)
else else
{:error, body} parse_http_error_response_internal(body)
end end
end end
defp parse_http_error_response_internal(nil), do: {:error, @invalid_json_response}
defp parse_http_error_response_internal(data), do: {:error, data}
def parse_params_from_sourcify(address_hash_string, verification_metadata) do def parse_params_from_sourcify(address_hash_string, verification_metadata) do
filtered_files = filtered_files =
verification_metadata verification_metadata
@ -350,7 +358,7 @@ defmodule Explorer.ThirdPartyIntegrations.Sourcify do
defp parse_json_from_sourcify_for_insertion(verification_metadata_json) do defp parse_json_from_sourcify_for_insertion(verification_metadata_json) do
%{"name" => _, "content" => content} = verification_metadata_json %{"name" => _, "content" => content} = verification_metadata_json
content_json = decode_json(content) content_json = ExplorerHelper.decode_json(content)
compiler_version = "v" <> (content_json |> Map.get("compiler") |> Map.get("version")) compiler_version = "v" <> (content_json |> Map.get("compiler") |> Map.get("version"))
abi = content_json |> Map.get("output") |> Map.get("abi") abi = content_json |> Map.get("output") |> Map.get("abi")
settings = Map.get(content_json, "settings") settings = Map.get(content_json, "settings")
@ -401,12 +409,6 @@ defmodule Explorer.ThirdPartyIntegrations.Sourcify do
|> Map.put("contract_source_code", content) |> Map.put("contract_source_code", content)
end end
def decode_json(data) do
Jason.decode!(data)
rescue
_ -> data
end
defp config(module, key) do defp config(module, key) do
:explorer :explorer
|> Application.get_env(module) |> Application.get_env(module)

@ -5,6 +5,7 @@ defmodule Indexer.Fetcher.TokenInstance.MetadataRetriever do
require Logger require Logger
alias Explorer.Helper, as: ExplorerHelper
alias Explorer.SmartContract.Reader alias Explorer.SmartContract.Reader
alias HTTPoison.{Error, Response} alias HTTPoison.{Error, Response}
@ -130,7 +131,7 @@ defmodule Indexer.Fetcher.TokenInstance.MetadataRetriever do
end end
defp fetch_json_from_uri({:ok, [json]}, hex_token_id) do defp fetch_json_from_uri({:ok, [json]}, hex_token_id) do
{:ok, json} = decode_json(json) json = ExplorerHelper.decode_json(json)
check_type(json, hex_token_id) check_type(json, hex_token_id)
rescue rescue
@ -222,7 +223,7 @@ defmodule Indexer.Fetcher.TokenInstance.MetadataRetriever do
check_type(json, nil) check_type(json, nil)
else else
{:ok, json} = decode_json(body) json = ExplorerHelper.decode_json(body)
check_type(json, hex_token_id) check_type(json, hex_token_id)
end end
@ -245,16 +246,6 @@ defmodule Indexer.Fetcher.TokenInstance.MetadataRetriever do
content_type && String.starts_with?(content_type, "video/") content_type && String.starts_with?(content_type, "video/")
end end
defp decode_json(body) do
if String.valid?(body) do
Jason.decode(body)
else
body
|> :unicode.characters_to_binary(:latin1)
|> Jason.decode()
end
end
defp check_type(json, nil) when is_map(json) do defp check_type(json, nil) when is_map(json) do
{:ok, %{metadata: json}} {:ok, %{metadata: json}}
end end

@ -368,6 +368,10 @@ config :explorer, Explorer.ThirdPartyIntegrations.Sourcify,
chain_id: System.get_env("CHAIN_ID"), chain_id: System.get_env("CHAIN_ID"),
repo_url: System.get_env("SOURCIFY_REPO_URL") || "https://repo.sourcify.dev/contracts" repo_url: System.get_env("SOURCIFY_REPO_URL") || "https://repo.sourcify.dev/contracts"
config :explorer, Explorer.ThirdPartyIntegrations.SolidityScan,
chain_id: System.get_env("SOLIDITYSCAN_CHAIN_ID"),
api_key: System.get_env("SOLIDITYSCAN_API_TOKEN")
enabled? = ConfigHelper.parse_bool_env_var("MICROSERVICE_SC_VERIFIER_ENABLED") enabled? = ConfigHelper.parse_bool_env_var("MICROSERVICE_SC_VERIFIER_ENABLED")
# or "eth_bytecode_db" # or "eth_bytecode_db"
type = System.get_env("MICROSERVICE_SC_VERIFIER_TYPE", "sc_verifier") type = System.get_env("MICROSERVICE_SC_VERIFIER_TYPE", "sc_verifier")

@ -534,7 +534,18 @@
"zindex", "zindex",
"zipcode", "zipcode",
"zkbob", "zkbob",
"zkevm" "zkevm",
"erts",
"Asfpp",
"Nerg",
"secp",
"qwertyuioiuytrewertyuioiuytrertyuio",
"urlset",
"lastmod",
"qitmeer",
"meer",
"DefiLlama",
"SOLIDITYSCAN"
], ],
"enableFiletypes": [ "enableFiletypes": [
"dotenv", "dotenv",

Loading…
Cancel
Save