Smart-contract proxy detection refactoring

mf-8807-example
Viktor Baranov 12 months ago
parent d9d7ef9cf7
commit 95b3101483
  1. 2
      .dialyzer-ignore
  2. 26
      .github/workflows/config.yml
  3. 1
      CHANGELOG.md
  4. 5
      apps/block_scout_web/lib/block_scout_web/controllers/address_contract_verification_controller.ex
  5. 3
      apps/block_scout_web/lib/block_scout_web/controllers/address_contract_verification_via_flattened_code_controller.ex
  6. 3
      apps/block_scout_web/lib/block_scout_web/controllers/address_contract_verification_via_json_controller.ex
  7. 3
      apps/block_scout_web/lib/block_scout_web/controllers/address_contract_verification_via_multi_part_files_controller.ex
  8. 3
      apps/block_scout_web/lib/block_scout_web/controllers/address_contract_verification_via_standard_json_input_controller.ex
  9. 3
      apps/block_scout_web/lib/block_scout_web/controllers/address_contract_verification_vyper_controller.ex
  10. 6
      apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/contract_controller.ex
  11. 3
      apps/block_scout_web/lib/block_scout_web/controllers/api/v1/verified_smart_contract_controller.ex
  12. 6
      apps/block_scout_web/lib/block_scout_web/controllers/api/v2/import_controller.ex
  13. 4
      apps/block_scout_web/lib/block_scout_web/controllers/api/v2/smart_contract_controller.ex
  14. 3
      apps/block_scout_web/lib/block_scout_web/controllers/api/v2/verification_controller.ex
  15. 2
      apps/block_scout_web/lib/block_scout_web/controllers/smart_contract_controller.ex
  16. 4
      apps/block_scout_web/lib/block_scout_web/controllers/tokens/contract_controller.ex
  17. 14
      apps/block_scout_web/lib/block_scout_web/templates/address_contract/index.html.eex
  18. 2
      apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex
  19. 2
      apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex
  20. 4
      apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification_via_standard_json_input/new.html.eex
  21. 2
      apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification_vyper/new.html.eex
  22. 22
      apps/block_scout_web/lib/block_scout_web/templates/smart_contract/_functions.html.eex
  23. 4
      apps/block_scout_web/lib/block_scout_web/templates/tokens/overview/_tabs.html.eex
  24. 1
      apps/block_scout_web/lib/block_scout_web/views/address_contract_verification_via_flattened_code_view.ex
  25. 1
      apps/block_scout_web/lib/block_scout_web/views/address_contract_verification_via_multi_part_files_view.ex
  26. 1
      apps/block_scout_web/lib/block_scout_web/views/address_contract_verification_via_standard_json_input_view.ex
  27. 1
      apps/block_scout_web/lib/block_scout_web/views/address_contract_verification_vyper_view.ex
  28. 2
      apps/block_scout_web/lib/block_scout_web/views/address_contract_view.ex
  29. 9
      apps/block_scout_web/lib/block_scout_web/views/address_view.ex
  30. 3
      apps/block_scout_web/lib/block_scout_web/views/api/rpc/contract_view.ex
  31. 7
      apps/block_scout_web/lib/block_scout_web/views/api/v2/smart_contract_view.ex
  32. 4
      apps/block_scout_web/lib/block_scout_web/views/smart_contract_view.ex
  33. 9
      apps/block_scout_web/lib/block_scout_web/views/tokens/overview_view.ex
  34. 48
      apps/block_scout_web/priv/gettext/default.pot
  35. 48
      apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po
  36. 16
      apps/block_scout_web/test/block_scout_web/controllers/address_read_contract_controller_test.exs
  37. 16
      apps/block_scout_web/test/block_scout_web/controllers/address_read_proxy_controller_test.exs
  38. 16
      apps/block_scout_web/test/block_scout_web/controllers/address_write_contract_controller_test.exs
  39. 16
      apps/block_scout_web/test/block_scout_web/controllers/address_write_proxy_controller_test.exs
  40. 20
      apps/block_scout_web/test/block_scout_web/controllers/api/rpc/contract_controller_test.exs
  41. 40
      apps/block_scout_web/test/block_scout_web/controllers/api/v2/smart_contract_controller_test.exs
  42. 4
      apps/block_scout_web/test/block_scout_web/controllers/api/v2/transaction_controller_test.exs
  43. 54
      apps/block_scout_web/test/block_scout_web/controllers/smart_contract_controller_test.exs
  44. 16
      apps/block_scout_web/test/block_scout_web/controllers/tokens/read_contract_controller_test.exs
  45. 12
      apps/block_scout_web/test/block_scout_web/controllers/transaction_token_transfer_controller_test.exs
  46. 655
      apps/explorer/lib/explorer/chain.ex
  47. 44
      apps/explorer/lib/explorer/chain/address/name.ex
  48. 8
      apps/explorer/lib/explorer/chain/block/reward.ex
  49. 2
      apps/explorer/lib/explorer/chain/hash/full.ex
  50. 3
      apps/explorer/lib/explorer/chain/log.ex
  51. 963
      apps/explorer/lib/explorer/chain/smart_contract.ex
  52. 230
      apps/explorer/lib/explorer/chain/smart_contract/proxy.ex
  53. 44
      apps/explorer/lib/explorer/chain/smart_contract/proxy/basic.ex
  54. 54
      apps/explorer/lib/explorer/chain/smart_contract/proxy/eip_1167.ex
  55. 32
      apps/explorer/lib/explorer/chain/smart_contract/proxy/eip_1822.ex
  56. 101
      apps/explorer/lib/explorer/chain/smart_contract/proxy/eip_1967.ex
  57. 31
      apps/explorer/lib/explorer/chain/smart_contract/proxy/eip_930.ex
  58. 43
      apps/explorer/lib/explorer/chain/smart_contract/proxy/master_copy.ex
  59. 21
      apps/explorer/lib/explorer/chain/smart_contract_additional_sources.ex
  60. 3
      apps/explorer/lib/explorer/chain/transaction.ex
  61. 10
      apps/explorer/lib/explorer/etherscan/contracts.ex
  62. 12
      apps/explorer/lib/explorer/smart_contract/reader.ex
  63. 7
      apps/explorer/lib/explorer/smart_contract/solidity/publisher.ex
  64. 3
      apps/explorer/lib/explorer/smart_contract/vyper/publisher.ex
  65. 3
      apps/explorer/lib/explorer/smart_contract/writer.ex
  66. 16
      apps/explorer/test/explorer/chain/log_test.exs
  67. 384
      apps/explorer/test/explorer/chain/smart_contract/proxy_test.exs
  68. 660
      apps/explorer/test/explorer/chain/smart_contract_test.exs
  69. 19
      apps/explorer/test/explorer/chain/transaction_test.exs
  70. 643
      apps/explorer/test/explorer/chain_test.exs
  71. 1
      apps/indexer/lib/indexer/block/fetcher.ex
  72. 2
      cspell.json

@ -25,4 +25,4 @@ lib/indexer/fetcher/zkevm/transaction_batch.ex:156
lib/indexer/fetcher/zkevm/transaction_batch.ex:252
lib/block_scout_web/views/api/v2/transaction_view.ex:431
lib/block_scout_web/views/api/v2/transaction_view.ex:472
lib/explorer/chain/transaction.ex:166
lib/explorer/chain/transaction.ex:167

@ -55,7 +55,7 @@ jobs:
path: |
deps
_build
key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_25-${{ hashFiles('mix.lock') }}
key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_26-${{ hashFiles('mix.lock') }}
restore-keys: |
${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-
@ -113,7 +113,7 @@ jobs:
path: |
deps
_build
key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_25-${{ hashFiles('mix.lock') }}
key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_26-${{ hashFiles('mix.lock') }}
restore-keys: |
${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-"
@ -137,7 +137,7 @@ jobs:
path: |
deps
_build
key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_25-${{ hashFiles('mix.lock') }}
key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_26-${{ hashFiles('mix.lock') }}
restore-keys: |
${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-"
@ -160,7 +160,7 @@ jobs:
path: |
deps
_build
key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_25-${{ hashFiles('mix.lock') }}
key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_26-${{ hashFiles('mix.lock') }}
restore-keys: |
${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-"
@ -200,7 +200,7 @@ jobs:
path: |
deps
_build
key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_25-${{ hashFiles('mix.lock') }}
key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_26-${{ hashFiles('mix.lock') }}
restore-keys: |
${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-"
@ -226,7 +226,7 @@ jobs:
path: |
deps
_build
key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_25-${{ hashFiles('mix.lock') }}
key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_26-${{ hashFiles('mix.lock') }}
restore-keys: |
${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-"
@ -255,7 +255,7 @@ jobs:
path: |
deps
_build
key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_25-${{ hashFiles('mix.lock') }}
key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_26-${{ hashFiles('mix.lock') }}
restore-keys: |
${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-"
@ -303,7 +303,7 @@ jobs:
path: |
deps
_build
key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_25-${{ hashFiles('mix.lock') }}
key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_26-${{ hashFiles('mix.lock') }}
restore-keys: |
${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-"
@ -349,7 +349,7 @@ jobs:
path: |
deps
_build
key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_25-${{ hashFiles('mix.lock') }}
key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_26-${{ hashFiles('mix.lock') }}
restore-keys: |
${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-"
@ -406,7 +406,7 @@ jobs:
path: |
deps
_build
key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_25-${{ hashFiles('mix.lock') }}
key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_26-${{ hashFiles('mix.lock') }}
restore-keys: |
${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-"
@ -460,7 +460,7 @@ jobs:
path: |
deps
_build
key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_25-${{ hashFiles('mix.lock') }}
key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_26-${{ hashFiles('mix.lock') }}
restore-keys: |
${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-"
@ -525,7 +525,7 @@ jobs:
path: |
deps
_build
key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_25-${{ hashFiles('mix.lock') }}
key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_26-${{ hashFiles('mix.lock') }}
restore-keys: |
${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-"
@ -589,7 +589,7 @@ jobs:
path: |
deps
_build
key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_25-${{ hashFiles('mix.lock') }}
key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_26-${{ hashFiles('mix.lock') }}
restore-keys: |
${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-"

@ -26,6 +26,7 @@
### Chore
- [#8832](https://github.com/blockscout/blockscout/pull/8832) - Log more details in regards 413 error
- [#8807](https://github.com/blockscout/blockscout/pull/8807) - Smart-contract proxy detection refactoring
- [#8802](https://github.com/blockscout/blockscout/pull/8802) - Enable API v2 by default
- [#8728](https://github.com/blockscout/blockscout/pull/8728) - Remove repos_list (default value for ecto repos) from Explorer.ReleaseTasks

@ -2,7 +2,6 @@ defmodule BlockScoutWeb.AddressContractVerificationController do
use BlockScoutWeb, :controller
alias BlockScoutWeb.Controller
alias Explorer.Chain
alias Explorer.Chain.Events.Publisher, as: EventsPublisher
alias Explorer.Chain.SmartContract
alias Explorer.SmartContract.{CompilerVersion, Solidity.CodeCompiler}
@ -12,7 +11,7 @@ defmodule BlockScoutWeb.AddressContractVerificationController do
alias Explorer.ThirdPartyIntegrations.Sourcify
def new(conn, %{"address_id" => address_hash_string}) do
if Chain.smart_contract_fully_verified?(address_hash_string) do
if SmartContract.verified_with_full_match?(address_hash_string) do
address_contract_path =
conn
|> address_contract_path(:index, address_hash_string)
@ -122,7 +121,7 @@ defmodule BlockScoutWeb.AddressContractVerificationController do
json_file = PublishHelper.get_one_json(files_array)
if json_file do
if Chain.smart_contract_fully_verified?(address_hash_string) do
if SmartContract.verified_with_full_match?(address_hash_string) do
EventsPublisher.broadcast(
PublishHelper.prepare_verification_error(
"This contract already verified in Blockscout.",

@ -2,12 +2,11 @@ defmodule BlockScoutWeb.AddressContractVerificationViaFlattenedCodeController do
use BlockScoutWeb, :controller
alias BlockScoutWeb.Controller
alias Explorer.Chain
alias Explorer.Chain.SmartContract
alias Explorer.SmartContract.{CompilerVersion, Solidity.CodeCompiler, Solidity.PublisherWorker}
def new(conn, %{"address_id" => address_hash_string}) do
if Chain.smart_contract_fully_verified?(address_hash_string) do
if SmartContract.verified_with_full_match?(address_hash_string) do
address_contract_path =
conn
|> address_contract_path(:index, address_hash_string)

@ -2,7 +2,6 @@ defmodule BlockScoutWeb.AddressContractVerificationViaJsonController do
use BlockScoutWeb, :controller
alias BlockScoutWeb.Controller
alias Explorer.Chain
alias Explorer.Chain.SmartContract
alias Explorer.SmartContract.Solidity.PublishHelper
alias Explorer.ThirdPartyIntegrations.Sourcify
@ -13,7 +12,7 @@ defmodule BlockScoutWeb.AddressContractVerificationViaJsonController do
|> address_contract_path(:index, address_hash_string)
|> Controller.full_path()
if Chain.smart_contract_fully_verified?(address_hash_string) do
if SmartContract.verified_with_full_match?(address_hash_string) do
redirect(conn, to: address_contract_path)
else
case Sourcify.check_by_address(address_hash_string) do

@ -2,12 +2,11 @@ defmodule BlockScoutWeb.AddressContractVerificationViaMultiPartFilesController d
use BlockScoutWeb, :controller
alias BlockScoutWeb.Controller
alias Explorer.Chain
alias Explorer.Chain.SmartContract
alias Explorer.SmartContract.{CompilerVersion, Solidity.CodeCompiler}
def new(conn, %{"address_id" => address_hash_string}) do
if Chain.smart_contract_fully_verified?(address_hash_string) do
if SmartContract.verified_with_full_match?(address_hash_string) do
address_contract_path =
conn
|> address_contract_path(:index, address_hash_string)

@ -2,12 +2,11 @@ defmodule BlockScoutWeb.AddressContractVerificationViaStandardJsonInputControlle
use BlockScoutWeb, :controller
alias BlockScoutWeb.Controller
alias Explorer.Chain
alias Explorer.Chain.SmartContract
alias Explorer.SmartContract.CompilerVersion
def new(conn, %{"address_id" => address_hash_string}) do
if Chain.smart_contract_fully_verified?(address_hash_string) do
if SmartContract.verified_with_full_match?(address_hash_string) do
address_contract_path =
conn
|> address_contract_path(:index, address_hash_string)

@ -2,12 +2,11 @@ defmodule BlockScoutWeb.AddressContractVerificationVyperController do
use BlockScoutWeb, :controller
alias BlockScoutWeb.Controller
alias Explorer.Chain
alias Explorer.Chain.SmartContract
alias Explorer.SmartContract.{CompilerVersion, Vyper.PublisherWorker}
def new(conn, %{"address_id" => address_hash_string}) do
if Chain.smart_contract_fully_verified?(address_hash_string) do
if SmartContract.verified_with_full_match?(address_hash_string) do
address_contract_path =
conn
|> address_contract_path(:index, address_hash_string)

@ -83,7 +83,7 @@ defmodule BlockScoutWeb.API.RPC.ContractController do
[]
end
if Chain.smart_contract_fully_verified?(address_hash) do
if SmartContract.verified_with_full_match?(address_hash) do
render(conn, :error, error: @verified)
else
case Sourcify.check_by_address(address_hash) do
@ -114,7 +114,7 @@ defmodule BlockScoutWeb.API.RPC.ContractController do
} = params
) do
with {:check_verified_status, false} <-
{:check_verified_status, Chain.smart_contract_fully_verified?(address_hash)},
{:check_verified_status, SmartContract.verified_with_full_match?(address_hash)},
{:format, {:ok, _casted_address_hash}} <- to_address_hash(address_hash),
{:params, {:ok, fetched_params}} <- {:params, fetch_verifysourcecode_params(params)},
uid <- VerificationStatus.generate_uid(address_hash) do
@ -457,7 +457,7 @@ defmodule BlockScoutWeb.API.RPC.ContractController do
_ = PublishHelper.check_and_verify(Hash.to_string(address_hash))
result =
case Chain.address_hash_to_smart_contract(address_hash) do
case SmartContract.address_hash_to_smart_contract(address_hash) do
nil ->
:not_found

@ -3,12 +3,13 @@ defmodule BlockScoutWeb.API.V1.VerifiedSmartContractController do
alias Explorer.Chain
alias Explorer.Chain.Hash.Address
alias Explorer.Chain.SmartContract
alias Explorer.SmartContract.Solidity.Publisher
def create(conn, params) do
with {:ok, hash} <- validate_address_hash(params["address_hash"]),
:ok <- Chain.check_address_exists(hash),
{:contract, :not_found} <- {:contract, Chain.check_verified_smart_contract_exists(hash)} do
{:contract, :not_found} <- {:contract, SmartContract.check_verified_smart_contract_exists(hash)} do
external_libraries = fetch_external_libraries(params)
case Publisher.publish(hash, params, external_libraries) do

@ -3,7 +3,7 @@ defmodule BlockScoutWeb.API.V2.ImportController do
alias BlockScoutWeb.API.V2.ApiView
alias Explorer.{Chain, Repo}
alias Explorer.Chain.{Data, Token}
alias Explorer.Chain.{Data, SmartContract, Token}
alias Explorer.Chain.Fetcher.LookUpSmartContractSourcesOnDemand
alias Explorer.SmartContract.EthBytecodeDBInterface
@ -60,7 +60,7 @@ defmodule BlockScoutWeb.API.V2.ImportController do
Protected by `x-api-key` header.
"""
@spec try_to_search_contract(Plug.Conn.t(), map()) ::
{:already_verified, nil | Explorer.Chain.SmartContract.t()}
{:already_verified, nil | SmartContract.t()}
| {:api_key, nil | binary()}
| {:format, :error}
| {:not_found, {:error, :not_found}}
@ -73,7 +73,7 @@ defmodule BlockScoutWeb.API.V2.ImportController do
{:format, {:ok, address_hash}} <- {:format, Chain.string_to_address_hash(address_hash_string)},
{:not_found, {:ok, address}} <- {:not_found, Chain.hash_to_address(address_hash, @api_true, false)},
{:already_verified, smart_contract} when is_nil(smart_contract) <-
{:already_verified, Chain.address_hash_to_smart_contract_without_twin(address_hash, @api_true)} do
{:already_verified, SmartContract.address_hash_to_smart_contract_without_twin(address_hash, @api_true)} do
creation_tx_input = contract_creation_input(address.hash)
with {:ok, %{"sourceType" => type} = source} <-

@ -60,7 +60,7 @@ defmodule BlockScoutWeb.API.V2.SmartContractController do
def methods_read(conn, %{"address_hash" => address_hash_string} = params) do
with {:format, {:ok, address_hash}} <- {:format, Chain.string_to_address_hash(address_hash_string)},
{:ok, false} <- AccessHelper.restricted_access?(address_hash_string, params),
smart_contract <- Chain.address_hash_to_smart_contract(address_hash, @api_true),
smart_contract <- SmartContract.address_hash_to_smart_contract(address_hash, @api_true),
{:not_found, false} <- {:not_found, is_nil(smart_contract)} do
read_only_functions_from_abi = Reader.read_only_functions(smart_contract, address_hash, params["from"])
@ -90,7 +90,7 @@ defmodule BlockScoutWeb.API.V2.SmartContractController do
{:contract_interaction_disabled, AddressView.contract_interaction_disabled?()},
{:format, {:ok, address_hash}} <- {:format, Chain.string_to_address_hash(address_hash_string)},
{:ok, false} <- AccessHelper.restricted_access?(address_hash_string, params),
smart_contract <- Chain.address_hash_to_smart_contract(address_hash, @api_true),
smart_contract <- SmartContract.address_hash_to_smart_contract(address_hash, @api_true),
{:not_found, false} <- {:not_found, is_nil(smart_contract)} do
conn
|> put_status(200)

@ -8,6 +8,7 @@ defmodule BlockScoutWeb.API.V2.VerificationController do
alias BlockScoutWeb.AccessHelper
alias BlockScoutWeb.API.V2.ApiView
alias Explorer.Chain
alias Explorer.Chain.SmartContract
alias Explorer.SmartContract.Solidity.PublisherWorker, as: SolidityPublisherWorker
alias Explorer.SmartContract.Solidity.PublishHelper
alias Explorer.SmartContract.Vyper.PublisherWorker, as: VyperPublisherWorker
@ -282,7 +283,7 @@ defmodule BlockScoutWeb.API.V2.VerificationController do
with {:format, {:ok, address_hash}} <- {:format, Chain.string_to_address_hash(address_hash_string)},
{:ok, false} <- AccessHelper.restricted_access?(address_hash_string, params),
{:already_verified, false} <-
{:already_verified, Chain.smart_contract_fully_verified?(address_hash, @api_true)} do
{:already_verified, SmartContract.verified_with_full_match?(address_hash, @api_true)} do
:validated
end
end

@ -65,7 +65,7 @@ defmodule BlockScoutWeb.SmartContractController do
implementation_abi =
if contract_type == "proxy" do
implementation_address_hash_string
|> Chain.get_implementation_abi()
|> SmartContract.get_smart_contract_abi()
|> Poison.encode!()
else
[]

@ -6,13 +6,13 @@ defmodule BlockScoutWeb.Tokens.ContractController do
alias BlockScoutWeb.{AccessHelper, TabHelper}
alias Explorer.Chain
alias Explorer.Chain.Address
alias Explorer.Chain.{Address, SmartContract}
def index(conn, %{"token_id" => address_hash_string} = params) do
options = [necessity_by_association: %{[contract_address: :smart_contract] => :optional}]
with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string),
:ok <- Chain.check_verified_smart_contract_exists(address_hash),
:ok <- SmartContract.check_verified_smart_contract_exists(address_hash),
{:ok, token} <- Chain.token_from_address_hash(address_hash, options),
{:ok, false} <- AccessHelper.restricted_access?(address_hash_string, params) do
%{type: type, action: action} =

@ -1,9 +1,9 @@
<% contract_creation_code = contract_creation_code(@address) %>
<% minimal_proxy_template = Chain.get_minimal_proxy_template(@address.hash) %>
<% metadata_for_verification = minimal_proxy_template || Chain.get_address_verified_twin_contract(@address.hash).verified_contract %>
<% minimal_proxy_template = EIP1167.get_implementation_address(@address.hash) %>
<% metadata_for_verification = minimal_proxy_template || SmartContract.get_address_verified_twin_contract(@address.hash).verified_contract %>
<% smart_contract_verified = BlockScoutWeb.AddressView.smart_contract_verified?(@address) %>
<% additional_sources_from_twin = Chain.get_address_verified_twin_contract(@address.hash).additional_sources %>
<% fully_verified = Chain.smart_contract_fully_verified?(@address.hash)%>
<% additional_sources_from_twin = SmartContract.get_address_verified_twin_contract(@address.hash).additional_sources %>
<% fully_verified = SmartContract.verified_with_full_match?(@address.hash)%>
<% additional_sources = if smart_contract_verified, do: @address.smart_contract_additional_sources, else: additional_sources_from_twin %>
<% visualize_sol2uml_enabled = Explorer.Visualize.Sol2uml.enabled?() %>
<section class="container">
@ -43,16 +43,16 @@
<% end %>
<%= if smart_contract_verified || (!smart_contract_verified && metadata_for_verification) do %>
<% target_contract = if smart_contract_verified, do: @address.smart_contract, else: metadata_for_verification %>
<%= if @address.smart_contract.verified_via_sourcify && @address.smart_contract.partially_verified && smart_contract_verified do %>
<%= if @address.smart_contract && @address.smart_contract.verified_via_sourcify && @address.smart_contract.partially_verified && smart_contract_verified do %>
<div class="mb-4">
<i style="color: #f7b32b;" class="fa fa-info-circle"></i><span> <%= gettext("This contract has been partially verified via Sourcify.") %>
<% else %>
<%= if @address.smart_contract.verified_via_sourcify && smart_contract_verified do %>
<%= if @address.smart_contract && @address.smart_contract.verified_via_sourcify && smart_contract_verified do %>
<div class="mb-4">
<i style="color: #f7b32b;" class="fa fa-info-circle"></i><span> <%= gettext("This contract has been verified via Sourcify.") %>
<% end %>
<% end %>
<%= if @address.smart_contract.verified_via_sourcify && smart_contract_verified do %>
<%= if @address.smart_contract && @address.smart_contract.verified_via_sourcify && smart_contract_verified do %>
<a data-test="external_url" href=<%= sourcify_repo_url(@address.hash, @address.smart_contract.partially_verified) %> target="_blank">
View contract in Sourcify repository <span class="external-token-icon"><%= render BlockScoutWeb.IconsView, "_external_link.html" %></span>
</a>

@ -1,4 +1,4 @@
<% metadata_for_verification = if assigns[:retrying], do: nil, else: Chain.get_address_verified_twin_contract(@address_hash).verified_contract %>
<% metadata_for_verification = if assigns[:retrying], do: nil, else: SmartContract.get_address_verified_twin_contract(@address_hash).verified_contract %>
<% changeset = (if assigns[:retrying], do: @changeset, else: SmartContract.merge_twin_contract_with_changeset(metadata_for_verification, @changeset)) |> SmartContract.address_to_checksum_address() %>
<% fetch_constructor_arguments_automatically = if metadata_for_verification, do: true, else: changeset.changes[:autodetect_constructor_args] || true %>
<% display_constructor_arguments_text_area = if fetch_constructor_arguments_automatically, do: "none", else: "block" %>

@ -1,4 +1,4 @@
<% metadata_for_verification = if assigns[:retrying], do: nil, else: Chain.get_address_verified_twin_contract(@address_hash).verified_contract %>
<% metadata_for_verification = if assigns[:retrying], do: nil, else: SmartContract.get_address_verified_twin_contract(@address_hash).verified_contract %>
<% changeset = (if assigns[:retrying], do: @changeset, else: SmartContract.merge_twin_contract_with_changeset(metadata_for_verification, @changeset)) |> SmartContract.address_to_checksum_address() %>
<section data-page="contract-verification" class="container new-smart-contract-container">
<%= render BlockScoutWeb.CommonComponentsView, "_channel_disconnected_message.html", text: gettext("Connection Lost") %>

@ -1,4 +1,4 @@
<% metadata_for_verification = Chain.get_address_verified_twin_contract(@address_hash).verified_contract %>
<% metadata_for_verification = SmartContract.get_address_verified_twin_contract(@address_hash).verified_contract %>
<% changeset = (if assigns[:retrying], do: @changeset, else: SmartContract.merge_twin_contract_with_changeset(metadata_for_verification, @changeset)) |> SmartContract.address_to_checksum_address() %>
<% fetch_constructor_arguments_automatically = if metadata_for_verification, do: true, else: changeset.changes[:autodetect_constructor_args] || true %>
<% display_constructor_arguments_text_area = if fetch_constructor_arguments_automatically, do: "none", else: "block" %>
@ -10,7 +10,7 @@
<%= form_for changeset,
address_contract_verification_path(@conn, :create),
[id: "standard-json-dropzone-form"],
fn f -> %>
fn f -> %>
<%= render BlockScoutWeb.AddressContractVerificationCommonFieldsView, "_contract_address_field.html", f: f %>
<%= render BlockScoutWeb.AddressContractVerificationCommonFieldsView, "_contract_name_field.html", f: f, tooltip: "Must match the name specified in the code. For example, in <span class=\"tooltip-quote\">contract MyContract {..}</span> <strong>MyContract</strong> is the contract name. Also contract name could be: <span class=\"tooltip-quote\"><strong>path/to/file.sol:MyContract</strong></span>" %>

@ -1,4 +1,4 @@
<% metadata_for_verification = Chain.get_address_verified_twin_contract(@address_hash).verified_contract %>
<% metadata_for_verification = SmartContract.get_address_verified_twin_contract(@address_hash).verified_contract %>
<% changeset = (if assigns[:retrying], do: @changeset, else: SmartContract.merge_twin_vyper_contract_with_changeset(metadata_for_verification, @changeset)) |> SmartContract.address_to_checksum_address() %>
<section data-page="contract-verification" class="container new-smart-contract-container">
<%= render BlockScoutWeb.CommonComponentsView, "_channel_disconnected_message.html", text: gettext("Connection Lost") %>

@ -1,5 +1,5 @@
<% minimal_proxy_template = if assigns[:custom_abi], do: nil, else: Chain.get_minimal_proxy_template(@address.hash) %>
<% metadata_for_verification = if assigns[:custom_abi], do: nil, else: minimal_proxy_template || Chain.get_address_verified_twin_contract(@address.hash).verified_contract %>
<% minimal_proxy_template = if assigns[:custom_abi], do: nil, else: EIP1167.get_implementation_address(@address.hash) %>
<% metadata_for_verification = if assigns[:custom_abi], do: nil, else: minimal_proxy_template || SmartContract.get_address_verified_twin_contract(@address.hash).verified_contract %>
<% smart_contract_verified = if assigns[:custom_abi], do: false, else: BlockScoutWeb.AddressView.smart_contract_verified?(@address) %>
<%= unless smart_contract_verified do %>
<%= if metadata_for_verification do %>
@ -52,8 +52,8 @@
<%= if queryable?(function["inputs"]) || writable?(function) || Helper.read_with_wallet_method?(function) do %>
<div style="width: 100%; overflow: hidden;">
<% function_abi =
case Jason.encode([function]) do
<% function_abi =
case Jason.encode([function]) do
{:ok, abi_string} ->
abi_string
_ ->
@ -61,7 +61,7 @@
@implementation_abi
else
@contract_abi
end
end
end %>
<form class="form-inline" data-function-form data-action="<%= if @action == "write", do: :write, else: :read %>" data-type="<%= @contract_type %>" data-url="<%= smart_contract_path(@conn, :show, Address.checksum(@address.hash)) %>" data-contract-address="<%= @address.hash %>" data-contract-abi="<%= function_abi %>" data-implementation-abi="<%= function_abi %>" data-chain-id="<%= Application.get_env(:block_scout_web, :chain_id) %>" data-custom-abi="<%= if assigns[:custom_abi], do: true, else: false %>">
<input type="hidden" name="function_name" value='<%= function["name"] %>' />
@ -78,17 +78,17 @@
<span class="button btn-line button-xs contract-plus-btn-container ml-1">
<i class="fa fa-plus contract-plus-btn"></i>
</span>
<div class="dropdown-menu exponention-dropdown">
<div class="dropdown-menu exponention-dropdown">
<div class="dropdown-item contract-exponentiation-btn" data-power=6>10<sup>6</sup></div>
<div class="dropdown-item contract-exponentiation-btn" data-power=8>10<sup>8</sup></div>
<div class="dropdown-item contract-exponentiation-btn" data-power=18>10<sup>18</sup></div>
<div class="dropdown-item contract-exponentiation-btn" data-power><input type="number" name="custom_power" class="form-control form-control-sm address-input-sm ml-1 custom-power-input" />10</div>
<div class="dropdown-item contract-exponentiation-btn" data-power><input type="number" name="custom_power" class="form-control form-control-sm address-input-sm ml-1 custom-power-input" />10</div>
</div>
</span>
<% else %>
<input type="text" name="function_input" class="form-control form-control-sm address-input-sm"
placeholder='<%= input["name"] %>(<%= input["type"] %>)'
<input type="text" name="function_input" class="form-control form-control-sm address-input-sm"
placeholder='<%= input["name"] %>(<%= input["type"] %>)'
size="<%= String.length(input["name"]) + String.length(input["type"]) + 2 %>" />
<% end %>
</div>
@ -143,7 +143,7 @@
<span class="d-none" data-conversion-text-eth><%= Explorer.coin_name() %></span>
</span>
</div>
</div>
</div>
<% else %>
<div class="align-self-center function-output word-break-all <%= if not_last_element?(length, index), do: "mb-1" %>"><%= raw(values_with_type(output["value"], output["type"], fetch_name(function["names"], index), 0)) %></div>
<% end %>
@ -158,4 +158,4 @@
<% end %>
<% end %>
</div>
<% end %>
<% end %>

@ -1,5 +1,5 @@
<% address_hash = Address.checksum(@token.contract_address_hash) %>
<% is_proxy = BlockScoutWeb.Tokens.OverviewView.smart_contract_is_proxy?(@token) %>
<% is_proxy = BlockScoutWeb.Tokens.OverviewView.token_smart_contract_is_proxy?(@token) %>
<div class="card-tabs js-card-tabs">
<%= link(
gettext("Token Transfers"),
@ -50,4 +50,4 @@
class: "card-tab #{tab_status("write-proxy", @conn.request_path)}")
%>
<% end %>
</div>
</div>

@ -1,7 +1,6 @@
defmodule BlockScoutWeb.AddressContractVerificationViaFlattenedCodeView do
use BlockScoutWeb, :view
alias Explorer.Chain
alias Explorer.Chain.SmartContract
alias Explorer.SmartContract.RustVerifierInterface
end

@ -1,7 +1,6 @@
defmodule BlockScoutWeb.AddressContractVerificationViaMultiPartFilesView do
use BlockScoutWeb, :view
alias Explorer.Chain
alias Explorer.Chain.SmartContract
alias Explorer.SmartContract.RustVerifierInterface
end

@ -1,6 +1,5 @@
defmodule BlockScoutWeb.AddressContractVerificationViaStandardJsonInputView do
use BlockScoutWeb, :view
alias Explorer.Chain
alias Explorer.Chain.SmartContract
end

@ -1,6 +1,5 @@
defmodule BlockScoutWeb.AddressContractVerificationVyperView do
use BlockScoutWeb, :view
alias Explorer.Chain
alias Explorer.Chain.SmartContract
end

@ -6,6 +6,8 @@ defmodule BlockScoutWeb.AddressContractView do
alias ABI.FunctionSelector
alias Explorer.Chain
alias Explorer.Chain.{Address, Data, InternalTransaction, Transaction}
alias Explorer.Chain.SmartContract
alias Explorer.Chain.SmartContract.Proxy.EIP1167
def render("scripts.html", %{conn: conn}) do
render_scripts(conn, "address_contract/code_highlighting.js")

@ -9,6 +9,7 @@ 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.ExchangeRates.Token, as: TokenExchangeRate
alias Explorer.SmartContract.{Helper, Writer}
@ -199,7 +200,7 @@ defmodule BlockScoutWeb.AddressView do
def primary_name(%Address{names: _} = address) do
with false <- is_nil(address.contract_code),
twin <- Chain.get_verified_twin_contract(address),
twin <- SmartContract.get_verified_twin_contract(address),
false <- is_nil(twin) do
twin.name
else
@ -264,8 +265,8 @@ defmodule BlockScoutWeb.AddressView do
def smart_contract_is_proxy?(address, options \\ [])
def smart_contract_is_proxy?(%Address{smart_contract: %SmartContract{} = smart_contract}, options) do
SmartContract.proxy_contract?(smart_contract, options)
def smart_contract_is_proxy?(%Address{smart_contract: %SmartContract{} = smart_contract}, _options) do
Proxy.proxy_contract?(smart_contract)
end
def smart_contract_is_proxy?(%Address{smart_contract: _}, _), do: false
@ -456,7 +457,7 @@ defmodule BlockScoutWeb.AddressView do
end
def smart_contract_is_gnosis_safe_proxy?(%Address{smart_contract: %SmartContract{}} = address) do
address.smart_contract.name == "GnosisSafeProxy" && Chain.gnosis_safe_contract?(address.smart_contract.abi)
address.smart_contract.name == "GnosisSafeProxy" && Proxy.gnosis_safe_contract?(address.smart_contract.abi)
end
def smart_contract_is_gnosis_safe_proxy?(_address), do: false

@ -4,7 +4,6 @@ defmodule BlockScoutWeb.API.RPC.ContractView do
alias BlockScoutWeb.AddressView
alias BlockScoutWeb.API.RPC.RPCView
alias Ecto.Association.NotLoaded
alias Explorer.Chain
alias Explorer.Chain.{Address, DecompiledSmartContract, SmartContract}
defguardp is_empty_string(input) when input == "" or input == nil
@ -168,7 +167,7 @@ defmodule BlockScoutWeb.API.RPC.ContractView do
end
defp insert_additional_sources(output, address) do
additional_sources_from_twin = Chain.get_address_verified_twin_contract(address.hash).additional_sources
additional_sources_from_twin = SmartContract.get_address_verified_twin_contract(address.hash).additional_sources
additional_sources =
if AddressView.smart_contract_verified?(address),

@ -11,6 +11,7 @@ defmodule BlockScoutWeb.API.V2.SmartContractView do
alias Ecto.Changeset
alias Explorer.Chain
alias Explorer.Chain.{Address, SmartContract}
alias Explorer.Chain.SmartContract.Proxy.EIP1167
alias Explorer.Visualize.Sol2uml
require Logger
@ -134,12 +135,12 @@ defmodule BlockScoutWeb.API.V2.SmartContractView do
# credo:disable-for-next-line
def prepare_smart_contract(%Address{smart_contract: %SmartContract{} = smart_contract} = address) do
minimal_proxy_template = Chain.get_minimal_proxy_template(address.hash, @api_true)
twin = Chain.get_address_verified_twin_contract(address.hash, @api_true)
minimal_proxy_template = EIP1167.get_implementation_address(address.hash, @api_true)
twin = SmartContract.get_address_verified_twin_contract(address.hash, @api_true)
metadata_for_verification = minimal_proxy_template || twin.verified_contract
smart_contract_verified = AddressView.smart_contract_verified?(address)
additional_sources_from_twin = twin.additional_sources
fully_verified = Chain.smart_contract_fully_verified?(address.hash, @api_true)
fully_verified = SmartContract.verified_with_full_match?(address.hash, @api_true)
additional_sources =
if smart_contract_verified, do: address.smart_contract_additional_sources, else: additional_sources_from_twin

@ -6,6 +6,8 @@ defmodule BlockScoutWeb.SmartContractView do
alias Explorer.Chain
alias Explorer.Chain.{Address, Transaction}
alias Explorer.Chain.Hash.Address, as: HashAddress
alias Explorer.Chain.SmartContract
alias Explorer.Chain.SmartContract.Proxy.EIP1167
alias Explorer.SmartContract.Helper
require Logger
@ -210,7 +212,7 @@ defmodule BlockScoutWeb.SmartContractView do
end
def decode_revert_reason(to_address, revert_reason, options \\ []) do
smart_contract = Chain.address_hash_to_smart_contract(to_address, options)
smart_contract = SmartContract.address_hash_to_smart_contract(to_address, options)
Transaction.decoded_revert_reason(
%Transaction{to_address: %{smart_contract: smart_contract}, hash: to_address},

@ -3,6 +3,7 @@ defmodule BlockScoutWeb.Tokens.OverviewView do
alias Explorer.{Chain, CustomContractsHelper}
alias Explorer.Chain.{Address, SmartContract, Token}
alias Explorer.Chain.SmartContract.Proxy
alias Explorer.SmartContract.{Helper, Writer}
alias BlockScoutWeb.{AccessHelper, CurrencyHelper, LayoutView}
@ -53,11 +54,13 @@ defmodule BlockScoutWeb.Tokens.OverviewView do
def smart_contract_with_read_only_functions?(%Token{contract_address: %Address{smart_contract: nil}}), do: false
def smart_contract_is_proxy?(%Token{contract_address: %Address{smart_contract: %SmartContract{} = smart_contract}}) do
SmartContract.proxy_contract?(smart_contract)
def token_smart_contract_is_proxy?(%Token{
contract_address: %Address{smart_contract: %SmartContract{} = smart_contract}
}) do
Proxy.proxy_contract?(smart_contract)
end
def smart_contract_is_proxy?(%Token{contract_address: %Address{smart_contract: nil}}), do: false
def token_smart_contract_is_proxy?(%Token{contract_address: %Address{smart_contract: nil}}), do: false
def smart_contract_with_write_functions?(%Token{
contract_address: %Address{smart_contract: %SmartContract{}} = address

@ -265,7 +265,7 @@ msgstr ""
#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:20
#: lib/block_scout_web/templates/transaction_state/index.html.eex:34
#: lib/block_scout_web/templates/verified_contracts/index.html.eex:60
#: lib/block_scout_web/views/address_view.ex:108
#: lib/block_scout_web/views/address_view.ex:109
#, elixir-autogen, elixir-format
msgid "Address"
msgstr ""
@ -556,7 +556,7 @@ msgstr ""
#: lib/block_scout_web/templates/address/_tabs.html.eex:56
#: lib/block_scout_web/templates/address/overview.html.eex:275
#: lib/block_scout_web/templates/address_validation/index.html.eex:11
#: lib/block_scout_web/views/address_view.ex:385
#: lib/block_scout_web/views/address_view.ex:386
#, elixir-autogen, elixir-format
msgid "Blocks Validated"
msgstr ""
@ -656,13 +656,13 @@ msgstr ""
#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:187
#: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:126
#: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:149
#: lib/block_scout_web/views/address_view.ex:378
#: lib/block_scout_web/views/address_view.ex:379
#, elixir-autogen, elixir-format
msgid "Code"
msgstr ""
#: lib/block_scout_web/templates/address/_tabs.html.eex:42
#: lib/block_scout_web/views/address_view.ex:384
#: lib/block_scout_web/views/address_view.ex:385
#, elixir-autogen, elixir-format
msgid "Coin Balance History"
msgstr ""
@ -771,14 +771,14 @@ msgstr ""
#: lib/block_scout_web/templates/account/custom_abi/form.html.eex:18
#: lib/block_scout_web/templates/account/custom_abi/index.html.eex:29
#: lib/block_scout_web/templates/address_contract_verification_common_fields/_contract_address_field.html.eex:3
#: lib/block_scout_web/views/address_view.ex:106
#: lib/block_scout_web/views/address_view.ex:107
#, elixir-autogen, elixir-format
msgid "Contract Address"
msgstr ""
#: lib/block_scout_web/templates/transaction/_pending_tile.html.eex:16
#: lib/block_scout_web/views/address_view.ex:46
#: lib/block_scout_web/views/address_view.ex:80
#: lib/block_scout_web/views/address_view.ex:47
#: lib/block_scout_web/views/address_view.ex:81
#, elixir-autogen, elixir-format
msgid "Contract Address Pending"
msgstr ""
@ -1084,7 +1084,7 @@ msgstr ""
msgid "Decoded"
msgstr ""
#: lib/block_scout_web/views/address_view.ex:379
#: lib/block_scout_web/views/address_view.ex:380
#, elixir-autogen, elixir-format
msgid "Decompiled Code"
msgstr ""
@ -1601,7 +1601,7 @@ msgstr ""
#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:17
#: lib/block_scout_web/templates/transaction/_tabs.html.eex:11
#: lib/block_scout_web/templates/transaction_internal_transaction/index.html.eex:6
#: lib/block_scout_web/views/address_view.ex:375
#: lib/block_scout_web/views/address_view.ex:376
#: lib/block_scout_web/views/transaction_view.ex:533
#, elixir-autogen, elixir-format
msgid "Internal Transactions"
@ -1614,7 +1614,7 @@ msgstr ""
#: lib/block_scout_web/templates/tokens/inventory/index.html.eex:16
#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:19
#: lib/block_scout_web/views/tokens/overview_view.ex:42
#: lib/block_scout_web/views/tokens/overview_view.ex:43
#, elixir-autogen, elixir-format
msgid "Inventory"
msgstr ""
@ -1718,7 +1718,7 @@ msgstr ""
#: lib/block_scout_web/templates/address_logs/index.html.eex:10
#: lib/block_scout_web/templates/transaction/_tabs.html.eex:17
#: lib/block_scout_web/templates/transaction_log/index.html.eex:8
#: lib/block_scout_web/views/address_view.ex:386
#: lib/block_scout_web/views/address_view.ex:387
#: lib/block_scout_web/views/transaction_view.ex:534
#, elixir-autogen, elixir-format
msgid "Logs"
@ -1732,7 +1732,7 @@ msgstr ""
#: lib/block_scout_web/templates/chain/show.html.eex:53
#: lib/block_scout_web/templates/layout/app.html.eex:50
#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:85
#: lib/block_scout_web/views/address_view.ex:146
#: lib/block_scout_web/views/address_view.ex:147
#, elixir-autogen, elixir-format
msgid "Market Cap"
msgstr ""
@ -2208,15 +2208,15 @@ msgstr ""
#: lib/block_scout_web/templates/address/_tabs.html.eex:89
#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:27
#: lib/block_scout_web/views/address_view.ex:380
#: lib/block_scout_web/views/tokens/overview_view.ex:41
#: lib/block_scout_web/views/address_view.ex:381
#: lib/block_scout_web/views/tokens/overview_view.ex:42
#, elixir-autogen, elixir-format
msgid "Read Contract"
msgstr ""
#: lib/block_scout_web/templates/address/_tabs.html.eex:96
#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:41
#: lib/block_scout_web/views/address_view.ex:381
#: lib/block_scout_web/views/address_view.ex:382
#, elixir-autogen, elixir-format
msgid "Read Proxy"
msgstr ""
@ -2870,7 +2870,7 @@ msgstr ""
#: lib/block_scout_web/templates/tokens/instance/holder/index.html.eex:16
#: lib/block_scout_web/templates/tokens/instance/overview/_tabs.html.eex:17
#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:11
#: lib/block_scout_web/views/tokens/overview_view.ex:40
#: lib/block_scout_web/views/tokens/overview_view.ex:41
#, elixir-autogen, elixir-format
msgid "Token Holders"
msgstr ""
@ -2903,9 +2903,9 @@ msgstr ""
#: lib/block_scout_web/templates/tokens/transfer/index.html.eex:15
#: lib/block_scout_web/templates/transaction/_tabs.html.eex:4
#: lib/block_scout_web/templates/transaction_token_transfer/index.html.eex:7
#: lib/block_scout_web/views/address_view.ex:377
#: lib/block_scout_web/views/address_view.ex:378
#: lib/block_scout_web/views/tokens/instance/overview_view.ex:114
#: lib/block_scout_web/views/tokens/overview_view.ex:39
#: lib/block_scout_web/views/tokens/overview_view.ex:40
#: lib/block_scout_web/views/transaction_view.ex:532
#, elixir-autogen, elixir-format
msgid "Token Transfers"
@ -2927,7 +2927,7 @@ msgstr ""
#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:13
#: lib/block_scout_web/templates/layout/_topnav.html.eex:84
#: lib/block_scout_web/templates/tokens/index.html.eex:10
#: lib/block_scout_web/views/address_view.ex:374
#: lib/block_scout_web/views/address_view.ex:375
#, elixir-autogen, elixir-format
msgid "Tokens"
msgstr ""
@ -3099,7 +3099,7 @@ msgstr ""
#: lib/block_scout_web/templates/block/overview.html.eex:80
#: lib/block_scout_web/templates/chain/show.html.eex:214
#: lib/block_scout_web/templates/layout/_topnav.html.eex:49
#: lib/block_scout_web/views/address_view.ex:376
#: lib/block_scout_web/views/address_view.ex:377
#, elixir-autogen, elixir-format
msgid "Transactions"
msgstr ""
@ -3469,14 +3469,14 @@ msgstr ""
#: lib/block_scout_web/templates/address/_tabs.html.eex:103
#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:34
#: lib/block_scout_web/views/address_view.ex:382
#: lib/block_scout_web/views/address_view.ex:383
#, elixir-autogen, elixir-format
msgid "Write Contract"
msgstr ""
#: lib/block_scout_web/templates/address/_tabs.html.eex:110
#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:48
#: lib/block_scout_web/views/address_view.ex:383
#: lib/block_scout_web/views/address_view.ex:384
#, elixir-autogen, elixir-format
msgid "Write Proxy"
msgstr ""
@ -3585,7 +3585,7 @@ msgstr ""
msgid "fallback"
msgstr ""
#: lib/block_scout_web/views/address_contract_view.ex:26
#: lib/block_scout_web/views/address_contract_view.ex:28
#, elixir-autogen, elixir-format
msgid "false"
msgstr ""
@ -3641,7 +3641,7 @@ msgstr ""
msgid "string"
msgstr ""
#: lib/block_scout_web/views/address_contract_view.ex:25
#: lib/block_scout_web/views/address_contract_view.ex:27
#, elixir-autogen, elixir-format
msgid "true"
msgstr ""

@ -265,7 +265,7 @@ msgstr ""
#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:20
#: lib/block_scout_web/templates/transaction_state/index.html.eex:34
#: lib/block_scout_web/templates/verified_contracts/index.html.eex:60
#: lib/block_scout_web/views/address_view.ex:108
#: lib/block_scout_web/views/address_view.ex:109
#, elixir-autogen, elixir-format
msgid "Address"
msgstr ""
@ -556,7 +556,7 @@ msgstr ""
#: lib/block_scout_web/templates/address/_tabs.html.eex:56
#: lib/block_scout_web/templates/address/overview.html.eex:275
#: lib/block_scout_web/templates/address_validation/index.html.eex:11
#: lib/block_scout_web/views/address_view.ex:385
#: lib/block_scout_web/views/address_view.ex:386
#, elixir-autogen, elixir-format
msgid "Blocks Validated"
msgstr ""
@ -656,13 +656,13 @@ msgstr ""
#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:187
#: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:126
#: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:149
#: lib/block_scout_web/views/address_view.ex:378
#: lib/block_scout_web/views/address_view.ex:379
#, elixir-autogen, elixir-format
msgid "Code"
msgstr ""
#: lib/block_scout_web/templates/address/_tabs.html.eex:42
#: lib/block_scout_web/views/address_view.ex:384
#: lib/block_scout_web/views/address_view.ex:385
#, elixir-autogen, elixir-format
msgid "Coin Balance History"
msgstr ""
@ -771,14 +771,14 @@ msgstr ""
#: lib/block_scout_web/templates/account/custom_abi/form.html.eex:18
#: lib/block_scout_web/templates/account/custom_abi/index.html.eex:29
#: lib/block_scout_web/templates/address_contract_verification_common_fields/_contract_address_field.html.eex:3
#: lib/block_scout_web/views/address_view.ex:106
#: lib/block_scout_web/views/address_view.ex:107
#, elixir-autogen, elixir-format
msgid "Contract Address"
msgstr ""
#: lib/block_scout_web/templates/transaction/_pending_tile.html.eex:16
#: lib/block_scout_web/views/address_view.ex:46
#: lib/block_scout_web/views/address_view.ex:80
#: lib/block_scout_web/views/address_view.ex:47
#: lib/block_scout_web/views/address_view.ex:81
#, elixir-autogen, elixir-format
msgid "Contract Address Pending"
msgstr ""
@ -1084,7 +1084,7 @@ msgstr ""
msgid "Decoded"
msgstr ""
#: lib/block_scout_web/views/address_view.ex:379
#: lib/block_scout_web/views/address_view.ex:380
#, elixir-autogen, elixir-format
msgid "Decompiled Code"
msgstr ""
@ -1601,7 +1601,7 @@ msgstr ""
#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:17
#: lib/block_scout_web/templates/transaction/_tabs.html.eex:11
#: lib/block_scout_web/templates/transaction_internal_transaction/index.html.eex:6
#: lib/block_scout_web/views/address_view.ex:375
#: lib/block_scout_web/views/address_view.ex:376
#: lib/block_scout_web/views/transaction_view.ex:533
#, elixir-autogen, elixir-format
msgid "Internal Transactions"
@ -1614,7 +1614,7 @@ msgstr ""
#: lib/block_scout_web/templates/tokens/inventory/index.html.eex:16
#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:19
#: lib/block_scout_web/views/tokens/overview_view.ex:42
#: lib/block_scout_web/views/tokens/overview_view.ex:43
#, elixir-autogen, elixir-format
msgid "Inventory"
msgstr ""
@ -1718,7 +1718,7 @@ msgstr ""
#: lib/block_scout_web/templates/address_logs/index.html.eex:10
#: lib/block_scout_web/templates/transaction/_tabs.html.eex:17
#: lib/block_scout_web/templates/transaction_log/index.html.eex:8
#: lib/block_scout_web/views/address_view.ex:386
#: lib/block_scout_web/views/address_view.ex:387
#: lib/block_scout_web/views/transaction_view.ex:534
#, elixir-autogen, elixir-format
msgid "Logs"
@ -1732,7 +1732,7 @@ msgstr ""
#: lib/block_scout_web/templates/chain/show.html.eex:53
#: lib/block_scout_web/templates/layout/app.html.eex:50
#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:85
#: lib/block_scout_web/views/address_view.ex:146
#: lib/block_scout_web/views/address_view.ex:147
#, elixir-autogen, elixir-format
msgid "Market Cap"
msgstr ""
@ -2208,15 +2208,15 @@ msgstr ""
#: lib/block_scout_web/templates/address/_tabs.html.eex:89
#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:27
#: lib/block_scout_web/views/address_view.ex:380
#: lib/block_scout_web/views/tokens/overview_view.ex:41
#: lib/block_scout_web/views/address_view.ex:381
#: lib/block_scout_web/views/tokens/overview_view.ex:42
#, elixir-autogen, elixir-format
msgid "Read Contract"
msgstr ""
#: lib/block_scout_web/templates/address/_tabs.html.eex:96
#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:41
#: lib/block_scout_web/views/address_view.ex:381
#: lib/block_scout_web/views/address_view.ex:382
#, elixir-autogen, elixir-format
msgid "Read Proxy"
msgstr ""
@ -2870,7 +2870,7 @@ msgstr ""
#: lib/block_scout_web/templates/tokens/instance/holder/index.html.eex:16
#: lib/block_scout_web/templates/tokens/instance/overview/_tabs.html.eex:17
#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:11
#: lib/block_scout_web/views/tokens/overview_view.ex:40
#: lib/block_scout_web/views/tokens/overview_view.ex:41
#, elixir-autogen, elixir-format
msgid "Token Holders"
msgstr ""
@ -2903,9 +2903,9 @@ msgstr ""
#: lib/block_scout_web/templates/tokens/transfer/index.html.eex:15
#: lib/block_scout_web/templates/transaction/_tabs.html.eex:4
#: lib/block_scout_web/templates/transaction_token_transfer/index.html.eex:7
#: lib/block_scout_web/views/address_view.ex:377
#: lib/block_scout_web/views/address_view.ex:378
#: lib/block_scout_web/views/tokens/instance/overview_view.ex:114
#: lib/block_scout_web/views/tokens/overview_view.ex:39
#: lib/block_scout_web/views/tokens/overview_view.ex:40
#: lib/block_scout_web/views/transaction_view.ex:532
#, elixir-autogen, elixir-format
msgid "Token Transfers"
@ -2927,7 +2927,7 @@ msgstr ""
#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:13
#: lib/block_scout_web/templates/layout/_topnav.html.eex:84
#: lib/block_scout_web/templates/tokens/index.html.eex:10
#: lib/block_scout_web/views/address_view.ex:374
#: lib/block_scout_web/views/address_view.ex:375
#, elixir-autogen, elixir-format
msgid "Tokens"
msgstr ""
@ -3099,7 +3099,7 @@ msgstr ""
#: lib/block_scout_web/templates/block/overview.html.eex:80
#: lib/block_scout_web/templates/chain/show.html.eex:214
#: lib/block_scout_web/templates/layout/_topnav.html.eex:49
#: lib/block_scout_web/views/address_view.ex:376
#: lib/block_scout_web/views/address_view.ex:377
#, elixir-autogen, elixir-format
msgid "Transactions"
msgstr ""
@ -3469,14 +3469,14 @@ msgstr ""
#: lib/block_scout_web/templates/address/_tabs.html.eex:103
#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:34
#: lib/block_scout_web/views/address_view.ex:382
#: lib/block_scout_web/views/address_view.ex:383
#, elixir-autogen, elixir-format
msgid "Write Contract"
msgstr ""
#: lib/block_scout_web/templates/address/_tabs.html.eex:110
#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:48
#: lib/block_scout_web/views/address_view.ex:383
#: lib/block_scout_web/views/address_view.ex:384
#, elixir-autogen, elixir-format
msgid "Write Proxy"
msgstr ""
@ -3585,7 +3585,7 @@ msgstr ""
msgid "fallback"
msgstr ""
#: lib/block_scout_web/views/address_contract_view.ex:26
#: lib/block_scout_web/views/address_contract_view.ex:28
#, elixir-autogen, elixir-format
msgid "false"
msgstr ""
@ -3641,7 +3641,7 @@ msgstr ""
msgid "string"
msgstr ""
#: lib/block_scout_web/views/address_contract_view.ex:25
#: lib/block_scout_web/views/address_contract_view.ex:27
#, elixir-autogen, elixir-format
msgid "true"
msgstr ""

@ -51,7 +51,7 @@ defmodule BlockScoutWeb.AddressReadContractControllerTest do
insert(:smart_contract, address_hash: contract_address.hash, contract_code_md5: "123")
get_eip1967_implementation()
request_zero_implementations()
conn =
get(conn, address_read_contract_path(BlockScoutWeb.Endpoint, :index, Address.checksum(contract_address.hash)))
@ -82,7 +82,7 @@ defmodule BlockScoutWeb.AddressReadContractControllerTest do
end
end
def get_eip1967_implementation do
def request_zero_implementations do
EthereumJSONRPC.Mox
|> expect(:json_rpc, fn %{
id: 0,
@ -120,5 +120,17 @@ defmodule BlockScoutWeb.AddressReadContractControllerTest do
_options ->
{:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"}
end)
|> expect(:json_rpc, fn %{
id: 0,
method: "eth_getStorageAt",
params: [
_,
"0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7",
"latest"
]
},
_options ->
{:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"}
end)
end
end

@ -51,7 +51,7 @@ defmodule BlockScoutWeb.AddressReadProxyControllerTest do
insert(:smart_contract, address_hash: contract_address.hash, contract_code_md5: "123")
get_eip1967_implementation()
request_zero_implementations()
conn = get(conn, address_read_proxy_path(BlockScoutWeb.Endpoint, :index, Address.checksum(contract_address.hash)))
@ -80,7 +80,7 @@ defmodule BlockScoutWeb.AddressReadProxyControllerTest do
end
end
def get_eip1967_implementation do
def request_zero_implementations do
EthereumJSONRPC.Mox
|> expect(:json_rpc, fn %{
id: 0,
@ -118,5 +118,17 @@ defmodule BlockScoutWeb.AddressReadProxyControllerTest do
_options ->
{:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"}
end)
|> expect(:json_rpc, fn %{
id: 0,
method: "eth_getStorageAt",
params: [
_,
"0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7",
"latest"
]
},
_options ->
{:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"}
end)
end
end

@ -53,7 +53,7 @@ defmodule BlockScoutWeb.AddressWriteContractControllerTest do
insert(:smart_contract, address_hash: contract_address.hash, contract_code_md5: "123")
get_eip1967_implementation()
request_zero_implementations()
conn =
get(conn, address_write_contract_path(BlockScoutWeb.Endpoint, :index, Address.checksum(contract_address.hash)))
@ -84,7 +84,7 @@ defmodule BlockScoutWeb.AddressWriteContractControllerTest do
end
end
def get_eip1967_implementation do
def request_zero_implementations do
EthereumJSONRPC.Mox
|> expect(:json_rpc, fn %{
id: 0,
@ -122,5 +122,17 @@ defmodule BlockScoutWeb.AddressWriteContractControllerTest do
_options ->
{:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"}
end)
|> expect(:json_rpc, fn %{
id: 0,
method: "eth_getStorageAt",
params: [
_,
"0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7",
"latest"
]
},
_options ->
{:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"}
end)
end
end

@ -51,7 +51,7 @@ defmodule BlockScoutWeb.AddressWriteProxyControllerTest do
insert(:smart_contract, address_hash: contract_address.hash, contract_code_md5: "123")
get_eip1967_implementation()
request_zero_implementations()
conn =
get(conn, address_write_proxy_path(BlockScoutWeb.Endpoint, :index, Address.checksum(contract_address.hash)))
@ -82,7 +82,7 @@ defmodule BlockScoutWeb.AddressWriteProxyControllerTest do
end
end
def get_eip1967_implementation do
def request_zero_implementations do
EthereumJSONRPC.Mox
|> expect(:json_rpc, fn %{
id: 0,
@ -120,5 +120,17 @@ defmodule BlockScoutWeb.AddressWriteProxyControllerTest do
_options ->
{:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"}
end)
|> expect(:json_rpc, fn %{
id: 0,
method: "eth_getStorageAt",
params: [
_,
"0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7",
"latest"
]
},
_options ->
{:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"}
end)
end
end

@ -1,8 +1,6 @@
defmodule BlockScoutWeb.API.RPC.ContractControllerTest do
use BlockScoutWeb.ConnCase
alias Explorer.Chain.SmartContract
alias Explorer.Chain
# alias Explorer.{Chain, Factory}
import Mox
@ -649,7 +647,7 @@ defmodule BlockScoutWeb.API.RPC.ContractControllerTest do
%SmartContract.ExternalLibrary{:address_hash => "0x283539e1b1daf24cdd58a3e934d55062ea663c3f", :name => "Test2"}
]
{:ok, %SmartContract{} = contract} = Chain.create_smart_contract(valid_attrs, external_libraries)
{:ok, %SmartContract{} = contract} = SmartContract.create_smart_contract(valid_attrs, external_libraries)
params = %{
"module" => "contract",
@ -771,7 +769,7 @@ defmodule BlockScoutWeb.API.RPC.ContractControllerTest do
# |> get("/api", params)
# |> json_response(200)
# verified_contract = Chain.address_hash_to_smart_contract(contract_address.hash)
# verified_contract = SmartContract.address_hash_to_smart_contract(contract_address.hash)
# expected_result = %{
# "Address" => to_string(contract_address.hash),
@ -844,7 +842,7 @@ defmodule BlockScoutWeb.API.RPC.ContractControllerTest do
# result = response["result"]
# verified_contract = Chain.address_hash_to_smart_contract(contract_address.hash)
# verified_contract = SmartContract.address_hash_to_smart_contract(contract_address.hash)
# assert result["Address"] == to_string(contract_address.hash)
@ -967,5 +965,17 @@ defmodule BlockScoutWeb.API.RPC.ContractControllerTest do
_options ->
{:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"}
end)
|> expect(:json_rpc, fn %{
id: 0,
method: "eth_getStorageAt",
params: [
_,
"0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7",
"latest"
]
},
_options ->
{:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"}
end)
end
end

@ -1920,6 +1920,8 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do
{:ok, "0x000000000000000000000000#{target_contract.address_hash |> to_string() |> String.replace("0x", "")}"}
end)
request_zero_implementations()
expect(
EthereumJSONRPC.Mox,
:json_rpc,
@ -2030,6 +2032,8 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do
{:ok, "0x000000000000000000000000#{target_contract.address_hash |> to_string() |> String.replace("0x", "")}"}
end)
request_zero_implementations()
expect(
EthereumJSONRPC.Mox,
:json_rpc,
@ -2115,6 +2119,8 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do
{:ok, "0x000000000000000000000000#{target_contract.address_hash |> to_string() |> String.replace("0x", "")}"}
end)
request_zero_implementations()
expect(
EthereumJSONRPC.Mox,
:json_rpc,
@ -2184,6 +2190,8 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do
{:ok, "0x000000000000000000000000#{target_contract.address_hash |> to_string() |> String.replace("0x", "")}"}
end)
request_zero_implementations()
expect(
EthereumJSONRPC.Mox,
:json_rpc,
@ -2252,6 +2260,8 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do
{:ok, "0x000000000000000000000000#{target_contract.address_hash |> to_string() |> String.replace("0x", "")}"}
end)
request_zero_implementations()
expect(
EthereumJSONRPC.Mox,
:json_rpc,
@ -2344,6 +2354,8 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do
{:ok, "0x000000000000000000000000#{target_contract.address_hash |> to_string() |> String.replace("0x", "")}"}
end)
request_zero_implementations()
contract = insert(:smart_contract)
request = get(conn, "/api/v2/smart-contracts/#{contract.address_hash}/methods-write-proxy")
@ -2460,4 +2472,32 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do
"solidity"
end
end
defp request_zero_implementations do
EthereumJSONRPC.Mox
|> expect(:json_rpc, fn %{
id: 0,
method: "eth_getStorageAt",
params: [
_,
"0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50",
"latest"
]
},
_options ->
{:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"}
end)
|> expect(:json_rpc, fn %{
id: 0,
method: "eth_getStorageAt",
params: [
_,
"0x7050c9e0f4ca769c69bd3a8ef740bc37934f8e2c036e5a723fd8ee048ed3f8c3",
"latest"
]
},
_options ->
{:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"}
end)
end
end

@ -970,7 +970,7 @@ defmodule BlockScoutWeb.API.V2.TransactionControllerTest do
test "check stability fees", %{conn: conn} do
tx = insert(:transaction) |> with_block()
log =
_log =
insert(:log,
transaction: tx,
index: 1,
@ -1033,7 +1033,7 @@ defmodule BlockScoutWeb.API.V2.TransactionControllerTest do
test "check stability if token absent in DB", %{conn: conn} do
tx = insert(:transaction) |> with_block()
log =
_log =
insert(:log,
transaction: tx,
index: 1,

@ -122,6 +122,8 @@ defmodule BlockScoutWeb.SmartContractControllerTest do
)
blockchain_get_implementation_mock()
blockchain_get_implementation_mock_empty()
blockchain_get_implementation_mock_empty()
path =
smart_contract_path(BlockScoutWeb.Endpoint, :index,
@ -159,6 +161,8 @@ defmodule BlockScoutWeb.SmartContractControllerTest do
)
blockchain_get_implementation_mock_2()
blockchain_get_implementation_mock_empty()
blockchain_get_implementation_mock_empty()
path =
smart_contract_path(BlockScoutWeb.Endpoint, :index,
@ -285,53 +289,23 @@ defmodule BlockScoutWeb.SmartContractControllerTest do
)
end
defp blockchain_get_implementation_mock_2 do
defp blockchain_get_implementation_mock_empty do
expect(
EthereumJSONRPC.Mox,
:json_rpc,
fn %{id: _, method: _, params: [_, _, _]}, _options ->
{:ok, "0x000000000000000000000000cebb2CCCFe291F0c442841cBE9C1D06EED61Ca02"}
{:ok, "0x"}
end
)
end
def get_eip1967_implementation do
EthereumJSONRPC.Mox
|> expect(:json_rpc, fn %{
id: 0,
method: "eth_getStorageAt",
params: [
_,
"0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc",
"latest"
]
},
_options ->
{:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"}
end)
|> expect(:json_rpc, fn %{
id: 0,
method: "eth_getStorageAt",
params: [
_,
"0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50",
"latest"
]
},
_options ->
{:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"}
end)
|> expect(:json_rpc, fn %{
id: 0,
method: "eth_getStorageAt",
params: [
_,
"0x7050c9e0f4ca769c69bd3a8ef740bc37934f8e2c036e5a723fd8ee048ed3f8c3",
"latest"
]
},
_options ->
{:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"}
end)
defp blockchain_get_implementation_mock_2 do
expect(
EthereumJSONRPC.Mox,
:json_rpc,
fn %{id: _, method: _, params: [_, _, _]}, _options ->
{:ok, "0x000000000000000000000000cebb2CCCFe291F0c442841cBE9C1D06EED61Ca02"}
end
)
end
end

@ -53,7 +53,7 @@ defmodule BlockScoutWeb.Tokens.ContractControllerTest do
token: token
)
get_eip1967_implementation()
request_zero_implementations()
conn = get(conn, token_read_contract_path(BlockScoutWeb.Endpoint, :index, token.contract_address_hash))
@ -62,7 +62,7 @@ defmodule BlockScoutWeb.Tokens.ContractControllerTest do
end
end
def get_eip1967_implementation do
def request_zero_implementations do
EthereumJSONRPC.Mox
|> expect(:json_rpc, fn %{
id: 0,
@ -100,5 +100,17 @@ defmodule BlockScoutWeb.Tokens.ContractControllerTest do
_options ->
{:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"}
end)
|> expect(:json_rpc, fn %{
id: 0,
method: "eth_getStorageAt",
params: [
_,
"0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7",
"latest"
]
},
_options ->
{:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"}
end)
end
end

@ -201,6 +201,18 @@ defmodule BlockScoutWeb.TransactionTokenTransferControllerTest do
_options ->
{:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"}
end)
|> expect(:json_rpc, fn %{
id: 0,
method: "eth_getStorageAt",
params: [
_,
"0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7",
"latest"
]
},
_options ->
{:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"}
end)
|> expect(:json_rpc, fn %{id: _id, method: "net_version", params: []}, _options ->
{:ok, "100"}
end)

@ -26,7 +26,6 @@ defmodule Explorer.Chain do
]
import EthereumJSONRPC, only: [integer_to_quantity: 1, fetch_block_internal_transactions: 2]
import Explorer.Chain.SmartContract, only: [burn_address_hash_string: 0]
require Logger
@ -58,7 +57,6 @@ defmodule Explorer.Chain do
PendingBlockOperation,
Search,
SmartContract,
SmartContractAdditionalSource,
Token,
Token.Instance,
TokenTransfer,
@ -87,11 +85,10 @@ defmodule Explorer.Chain do
alias Explorer.Chain.Fetcher.{CheckBytecodeMatchingOnDemand, LookUpSmartContractSourcesOnDemand}
alias Explorer.Chain.Import.Runner
alias Explorer.Chain.InternalTransaction.{CallType, Type}
alias Explorer.Chain.SmartContract.Proxy.EIP1167
alias Explorer.Market.MarketHistoryCache
alias Explorer.{PagingOptions, Repo}
alias Explorer.SmartContract.Helper
alias Explorer.SmartContract.Solidity.Verifier
alias Dataloader.Ecto, as: DataloaderEcto
@ -1065,6 +1062,33 @@ defmodule Explorer.Chain do
end
end
defp set_address_decompiled(repo, address_hash) do
query =
from(
address in Address,
where: address.hash == ^address_hash
)
case repo.update_all(query, set: [decompiled: true]) do
{1, _} -> {:ok, []}
_ -> {:error, "There was an error annotating that the address has been decompiled."}
end
end
@spec verified_contracts_top(non_neg_integer()) :: [Hash.Address.t()]
def verified_contracts_top(limit) do
query =
from(contract in SmartContract,
inner_join: address in Address,
on: contract.address_hash == address.hash,
order_by: [desc: address.transactions_count],
limit: ^limit,
select: contract.address_hash
)
Repo.all(query)
end
@doc """
Converts the `Explorer.Chain.Data.t:t/0` to `iodata` representation that can be written to users efficiently.
@ -1289,7 +1313,7 @@ defmodule Explorer.Chain do
if smart_contract do
address_result
else
compose_smart_contract(address_result, hash, options)
SmartContract.compose_smart_contract(address_result, hash, options)
end
_ ->
@ -1303,27 +1327,6 @@ defmodule Explorer.Chain do
end
end
defp compose_smart_contract(address_result, hash, options) do
address_verified_twin_contract =
get_minimal_proxy_template(hash, options) ||
get_address_verified_twin_contract(hash, options).verified_contract
if address_verified_twin_contract do
address_verified_twin_contract_updated =
address_verified_twin_contract
|> Map.put(:address_hash, hash)
|> Map.put(:metadata_from_verified_twin, true)
|> Map.put(:implementation_address_hash, nil)
|> Map.put(:implementation_name, nil)
|> Map.put(:implementation_fetched_at, nil)
address_result
|> Map.put(:smart_contract, address_verified_twin_contract_updated)
else
address_result
end
end
def decompiled_code(address_hash, version) do
query =
from(contract in DecompiledSmartContract,
@ -1481,15 +1484,15 @@ defmodule Explorer.Chain do
if smart_contract do
CheckBytecodeMatchingOnDemand.trigger_check(address_result, smart_contract)
LookUpSmartContractSourcesOnDemand.trigger_fetch(address_result, smart_contract)
check_and_update_constructor_args(address_result)
SmartContract.check_and_update_constructor_args(address_result)
else
LookUpSmartContractSourcesOnDemand.trigger_fetch(address_result, nil)
address_verified_twin_contract =
get_minimal_proxy_template(hash, options) ||
get_address_verified_twin_contract(hash, options).verified_contract
EIP1167.get_implementation_address(hash, options) ||
SmartContract.get_address_verified_twin_contract(hash, options).verified_contract
add_twin_info_to_contract(address_result, address_verified_twin_contract, hash)
SmartContract.add_twin_info_to_contract(address_result, address_verified_twin_contract, hash)
end
_ ->
@ -1504,53 +1507,6 @@ defmodule Explorer.Chain do
end
end
defp check_and_update_constructor_args(
%SmartContract{address_hash: address_hash, constructor_arguments: nil, verified_via_sourcify: true} =
smart_contract
) do
if args = Verifier.parse_constructor_arguments_for_sourcify_contract(address_hash, smart_contract.abi) do
smart_contract |> SmartContract.changeset(%{constructor_arguments: args}) |> Repo.update()
%SmartContract{smart_contract | constructor_arguments: args}
else
smart_contract
end
end
defp check_and_update_constructor_args(
%Address{
hash: address_hash,
contract_code: deployed_bytecode,
smart_contract: %SmartContract{constructor_arguments: nil, verified_via_sourcify: true} = smart_contract
} = address
) do
if args =
Verifier.parse_constructor_arguments_for_sourcify_contract(address_hash, smart_contract.abi, deployed_bytecode) do
smart_contract |> SmartContract.changeset(%{constructor_arguments: args}) |> Repo.update()
%Address{address | smart_contract: %SmartContract{smart_contract | constructor_arguments: args}}
else
address
end
end
defp check_and_update_constructor_args(other), do: other
defp add_twin_info_to_contract(address_result, address_verified_twin_contract, _hash)
when is_nil(address_verified_twin_contract),
do: address_result
defp add_twin_info_to_contract(address_result, address_verified_twin_contract, hash) do
address_verified_twin_contract_updated =
address_verified_twin_contract
|> Map.put(:address_hash, hash)
|> Map.put(:metadata_from_verified_twin, true)
|> Map.put(:implementation_address_hash, nil)
|> Map.put(:implementation_name, nil)
|> Map.put(:implementation_fetched_at, nil)
address_result
|> Map.put(:smart_contract, address_verified_twin_contract_updated)
end
@spec find_decompiled_contract_address(Hash.Address.t()) :: {:ok, Address.t()} | {:error, :not_found}
def find_decompiled_contract_address(%Hash{byte_count: unquote(Hash.Address.byte_count())} = hash) do
query =
@ -3569,412 +3525,6 @@ defmodule Explorer.Chain do
end
end
@doc """
Inserts a `t:SmartContract.t/0`.
As part of inserting a new smart contract, an additional record is inserted for
naming the address for reference.
"""
@spec create_smart_contract(map()) :: {:ok, SmartContract.t()} | {:error, Ecto.Changeset.t()}
def create_smart_contract(attrs \\ %{}, external_libraries \\ [], secondary_sources \\ []) do
new_contract = %SmartContract{}
attrs =
attrs
|> Helper.add_contract_code_md5()
smart_contract_changeset =
new_contract
|> SmartContract.changeset(attrs)
|> Changeset.put_change(:external_libraries, external_libraries)
new_contract_additional_source = %SmartContractAdditionalSource{}
smart_contract_additional_sources_changesets =
if secondary_sources do
secondary_sources
|> Enum.map(fn changeset ->
new_contract_additional_source
|> SmartContractAdditionalSource.changeset(changeset)
end)
else
[]
end
address_hash = Changeset.get_field(smart_contract_changeset, :address_hash)
# Enforce ShareLocks tables order (see docs: sharelocks.md)
insert_contract_query =
Multi.new()
|> Multi.run(:set_address_verified, fn repo, _ -> set_address_verified(repo, address_hash) end)
|> Multi.run(:clear_primary_address_names, fn repo, _ -> clear_primary_address_names(repo, address_hash) end)
|> Multi.insert(:smart_contract, smart_contract_changeset)
insert_contract_query_with_additional_sources =
smart_contract_additional_sources_changesets
|> Enum.with_index()
|> Enum.reduce(insert_contract_query, fn {changeset, index}, multi ->
Multi.insert(multi, "smart_contract_additional_source_#{Integer.to_string(index)}", changeset)
end)
insert_result =
insert_contract_query_with_additional_sources
|> Repo.transaction()
create_address_name(Repo, Changeset.get_field(smart_contract_changeset, :name), address_hash)
case insert_result do
{:ok, %{smart_contract: smart_contract}} ->
{:ok, smart_contract}
{:error, :smart_contract, changeset, _} ->
{:error, changeset}
{:error, :set_address_verified, message, _} ->
{:error, message}
end
end
@doc """
Updates a `t:SmartContract.t/0`.
Has the similar logic as create_smart_contract/1.
Used in cases when you need to update row in DB contains SmartContract, e.g. in case of changing
status `partially verified` to `fully verified` (re-verify).
"""
@spec update_smart_contract(map()) :: {:ok, SmartContract.t()} | {:error, Ecto.Changeset.t()}
def update_smart_contract(attrs \\ %{}, external_libraries \\ [], secondary_sources \\ []) do
address_hash = Map.get(attrs, :address_hash)
query =
from(
smart_contract in SmartContract,
where: smart_contract.address_hash == ^address_hash
)
query_sources =
from(
source in SmartContractAdditionalSource,
where: source.address_hash == ^address_hash
)
_delete_sources = Repo.delete_all(query_sources)
smart_contract = Repo.one(query)
smart_contract_changeset =
smart_contract
|> SmartContract.changeset(attrs)
|> Changeset.put_change(:external_libraries, external_libraries)
new_contract_additional_source = %SmartContractAdditionalSource{}
smart_contract_additional_sources_changesets =
if secondary_sources do
secondary_sources
|> Enum.map(fn changeset ->
new_contract_additional_source
|> SmartContractAdditionalSource.changeset(changeset)
end)
else
[]
end
# Enforce ShareLocks tables order (see docs: sharelocks.md)
insert_contract_query =
Multi.new()
|> Multi.run(:clear_primary_address_names, fn repo, _ -> clear_primary_address_names(repo, address_hash) end)
|> Multi.update(:smart_contract, smart_contract_changeset)
insert_contract_query_with_additional_sources =
smart_contract_additional_sources_changesets
|> Enum.with_index()
|> Enum.reduce(insert_contract_query, fn {changeset, index}, multi ->
Multi.insert(multi, "smart_contract_additional_source_#{Integer.to_string(index)}", changeset)
end)
insert_result =
insert_contract_query_with_additional_sources
|> Repo.transaction()
create_address_name(Repo, Changeset.get_field(smart_contract_changeset, :name), address_hash)
case insert_result do
{:ok, %{smart_contract: smart_contract}} ->
{:ok, smart_contract}
{:error, :smart_contract, changeset, _} ->
{:error, changeset}
{:error, :set_address_verified, message, _} ->
{:error, message}
end
end
defp set_address_verified(repo, address_hash) do
query =
from(
address in Address,
where: address.hash == ^address_hash
)
case repo.update_all(query, set: [verified: true]) do
{1, _} -> {:ok, []}
_ -> {:error, "There was an error annotating that the address has been verified."}
end
end
defp set_address_decompiled(repo, address_hash) do
query =
from(
address in Address,
where: address.hash == ^address_hash
)
case repo.update_all(query, set: [decompiled: true]) do
{1, _} -> {:ok, []}
_ -> {:error, "There was an error annotating that the address has been decompiled."}
end
end
defp clear_primary_address_names(repo, address_hash) do
query =
from(
address_name in Address.Name,
where: address_name.address_hash == ^address_hash,
# Enforce Name ShareLocks order (see docs: sharelocks.md)
order_by: [asc: :address_hash, asc: :name],
lock: "FOR NO KEY UPDATE"
)
repo.update_all(
from(n in Address.Name, join: s in subquery(query), on: n.address_hash == s.address_hash and n.name == s.name),
set: [primary: false]
)
{:ok, []}
end
defp create_address_name(repo, name, address_hash) do
params = %{
address_hash: address_hash,
name: name,
primary: true
}
%Address.Name{}
|> Address.Name.changeset(params)
|> repo.insert(on_conflict: :nothing, conflict_target: [:address_hash, :name])
end
def get_verified_twin_contract(%Explorer.Chain.Address{} = target_address, options \\ []) do
case target_address do
%{contract_code: %Chain.Data{bytes: contract_code_bytes}} ->
target_address_hash = target_address.hash
contract_code_md5 = Helper.contract_code_md5(contract_code_bytes)
verified_contract_twin_query =
from(
smart_contract in SmartContract,
where: smart_contract.contract_code_md5 == ^contract_code_md5,
where: smart_contract.address_hash != ^target_address_hash,
select: smart_contract,
limit: 1
)
verified_contract_twin_query
|> select_repo(options).one(timeout: 10_000)
_ ->
nil
end
end
@doc """
Finds metadata for verification of a contract from verified twins: contracts with the same bytecode
which were verified previously, returns a single t:SmartContract.t/0
"""
def get_address_verified_twin_contract(hash, options \\ [])
def get_address_verified_twin_contract(hash, options) when is_binary(hash) do
case string_to_address_hash(hash) do
{:ok, address_hash} -> get_address_verified_twin_contract(address_hash, options)
_ -> %{:verified_contract => nil, :additional_sources => nil}
end
end
def get_address_verified_twin_contract(%Explorer.Chain.Hash{} = address_hash, options) do
with target_address <- select_repo(options).get(Address, address_hash),
false <- is_nil(target_address) do
verified_contract_twin = get_verified_twin_contract(target_address, options)
verified_contract_twin_additional_sources = get_contract_additional_sources(verified_contract_twin, options)
%{
:verified_contract => check_and_update_constructor_args(verified_contract_twin),
:additional_sources => verified_contract_twin_additional_sources
}
else
_ ->
%{:verified_contract => nil, :additional_sources => nil}
end
end
def get_minimal_proxy_template(address_hash, options \\ []) do
minimal_proxy_template =
case select_repo(options).get(Address, address_hash) do
nil ->
nil
target_address ->
contract_code = target_address.contract_code
case contract_code do
%Chain.Data{bytes: contract_code_bytes} ->
contract_bytecode = Base.encode16(contract_code_bytes, case: :lower)
get_minimal_proxy_from_template_code(contract_bytecode, options)
_ ->
nil
end
end
minimal_proxy_template
end
defp get_minimal_proxy_from_template_code(contract_bytecode, options) do
case contract_bytecode do
"363d3d373d3d3d363d73" <> <<template_address::binary-size(40)>> <> _ ->
template_address = "0x" <> template_address
query =
from(
smart_contract in SmartContract,
where: smart_contract.address_hash == ^template_address,
select: smart_contract
)
template =
query
|> select_repo(options).one(timeout: 10_000)
template
_ ->
nil
end
end
defp get_contract_additional_sources(verified_contract_twin, options) do
if verified_contract_twin do
verified_contract_twin_additional_sources_query =
from(
s in SmartContractAdditionalSource,
where: s.address_hash == ^verified_contract_twin.address_hash
)
verified_contract_twin_additional_sources_query
|> select_repo(options).all()
else
[]
end
end
@spec address_hash_to_smart_contract(Hash.Address.t(), [api?]) :: SmartContract.t() | nil
def address_hash_to_smart_contract(address_hash, options \\ []) do
query =
from(
smart_contract in SmartContract,
where: smart_contract.address_hash == ^address_hash
)
current_smart_contract = select_repo(options).one(query)
if current_smart_contract do
current_smart_contract
else
address_verified_twin_contract =
get_minimal_proxy_template(address_hash, options) ||
get_address_verified_twin_contract(address_hash, options).verified_contract
if address_verified_twin_contract do
address_verified_twin_contract
|> Map.put(:address_hash, address_hash)
|> Map.put(:metadata_from_verified_twin, true)
|> Map.put(:implementation_address_hash, nil)
|> Map.put(:implementation_name, nil)
|> Map.put(:implementation_fetched_at, nil)
else
current_smart_contract
end
end
end
@spec address_hash_to_smart_contract_without_twin(Hash.Address.t(), [api?]) :: SmartContract.t() | nil
def address_hash_to_smart_contract_without_twin(address_hash, options) do
query =
from(
smart_contract in SmartContract,
where: smart_contract.address_hash == ^address_hash
)
select_repo(options).one(query)
end
def smart_contract_fully_verified?(address_hash, options \\ [])
def smart_contract_fully_verified?(address_hash_str, options) when is_binary(address_hash_str) do
case string_to_address_hash(address_hash_str) do
{:ok, address_hash} ->
check_fully_verified(address_hash, options)
_ ->
false
end
end
def smart_contract_fully_verified?(address_hash, options) do
check_fully_verified(address_hash, options)
end
defp check_fully_verified(address_hash, options) do
query =
from(
smart_contract in SmartContract,
where: smart_contract.address_hash == ^address_hash
)
result = select_repo(options).one(query)
if result, do: !result.partially_verified, else: false
end
def smart_contract_verified?(address_hash_str) when is_binary(address_hash_str) do
case string_to_address_hash(address_hash_str) do
{:ok, address_hash} ->
check_verified(address_hash)
_ ->
false
end
end
def smart_contract_verified?(address_hash) do
check_verified(address_hash)
end
defp check_verified(address_hash) do
query =
from(
smart_contract in SmartContract,
where: smart_contract.address_hash == ^address_hash
)
if Repo.one(query), do: true, else: false
end
defp fetch_transactions(paging_options \\ nil, from_block \\ nil, to_block \\ nil, with_pending? \\ false) do
Transaction
|> order_for_transactions(with_pending?)
@ -5457,36 +5007,6 @@ defmodule Explorer.Chain do
Repo.exists?(query)
end
@doc """
Checks if it exists a verified `t:Explorer.Chain.SmartContract.t/0` for the
`t:Explorer.Chain.Address.t/0` with the provided `hash`.
Returns `:ok` if found and `:not_found` otherwise.
"""
@spec check_verified_smart_contract_exists(Hash.Address.t()) :: :ok | :not_found
def check_verified_smart_contract_exists(address_hash) do
address_hash
|> verified_smart_contract_exists?()
|> boolean_to_check_result()
end
@doc """
Checks if it exists a verified `t:Explorer.Chain.SmartContract.t/0` for the
`t:Explorer.Chain.Address.t/0` with the provided `hash`.
Returns `true` if found and `false` otherwise.
"""
@spec verified_smart_contract_exists?(Hash.Address.t()) :: boolean()
def verified_smart_contract_exists?(address_hash) do
query =
from(
smart_contract in SmartContract,
where: smart_contract.address_hash == ^address_hash
)
Repo.exists?(query)
end
@doc """
Checks if a `t:Explorer.Chain.Transaction.t/0` with the given `hash` exists.
@ -5647,9 +5167,9 @@ defmodule Explorer.Chain do
Repo.exists?(query)
end
defp boolean_to_check_result(true), do: :ok
def boolean_to_check_result(true), do: :ok
defp boolean_to_check_result(false), do: :not_found
def boolean_to_check_result(false), do: :not_found
@doc """
Fetches the first trace from the Nethermind trace URL.
@ -5667,95 +5187,6 @@ defmodule Explorer.Chain do
end
end
def combine_proxy_implementation_abi(smart_contract, options \\ [])
def combine_proxy_implementation_abi(%SmartContract{abi: abi} = smart_contract, options) when not is_nil(abi) do
implementation_abi = get_implementation_abi_from_proxy(smart_contract, options)
if Enum.empty?(implementation_abi), do: abi, else: implementation_abi ++ abi
end
def combine_proxy_implementation_abi(_, _) do
[]
end
def gnosis_safe_contract?(abi) when not is_nil(abi) do
implementation_method_abi =
abi
|> Enum.find(fn method ->
master_copy_pattern?(method)
end)
if implementation_method_abi, do: true, else: false
end
def gnosis_safe_contract?(abi) when is_nil(abi), do: false
def master_copy_pattern?(method) do
Map.get(method, "type") == "constructor" &&
method
|> Enum.find(fn item ->
case item do
{"inputs", inputs} ->
master_copy_input?(inputs) || singleton_input?(inputs)
_ ->
false
end
end)
end
defp master_copy_input?(inputs) do
inputs
|> Enum.find(fn input ->
Map.get(input, "name") == "_masterCopy"
end)
end
defp singleton_input?(inputs) do
inputs
|> Enum.find(fn input ->
Map.get(input, "name") == "_singleton"
end)
end
def get_implementation_abi(implementation_address_hash_string, options \\ [])
def get_implementation_abi(implementation_address_hash_string, options)
when not is_nil(implementation_address_hash_string) do
case Chain.string_to_address_hash(implementation_address_hash_string) do
{:ok, implementation_address_hash} ->
implementation_smart_contract =
implementation_address_hash
|> address_hash_to_smart_contract(options)
if implementation_smart_contract do
implementation_smart_contract
|> Map.get(:abi)
else
[]
end
_ ->
[]
end
end
def get_implementation_abi(implementation_address_hash_string, _) when is_nil(implementation_address_hash_string) do
[]
end
def get_implementation_abi_from_proxy(
%SmartContract{address_hash: proxy_address_hash, abi: abi} = smart_contract,
options
)
when not is_nil(proxy_address_hash) and not is_nil(abi) do
{implementation_address_hash_string, _name} = SmartContract.get_implementation_address_hash(smart_contract, options)
get_implementation_abi(implementation_address_hash_string)
end
def get_implementation_abi_from_proxy(_, _), do: []
defp format_tx_first_trace(first_trace, block_hash, json_rpc_named_arguments) do
{:ok, to_address_hash} =
if Map.has_key?(first_trace, :to_address_hash) do
@ -5882,7 +5313,7 @@ defmodule Explorer.Chain do
@spec get_token_transfer_type(TokenTransfer.t()) ::
:token_burning | :token_minting | :token_spawning | :token_transfer
def get_token_transfer_type(transfer) do
{:ok, burn_address_hash} = Chain.string_to_address_hash(burn_address_hash_string())
{:ok, burn_address_hash} = Chain.string_to_address_hash(SmartContract.burn_address_hash_string())
cond do
transfer.to_address_hash == burn_address_hash && transfer.from_address_hash !== burn_address_hash ->
@ -6415,20 +5846,6 @@ defmodule Explorer.Chain do
)
end
@spec verified_contracts_top(non_neg_integer()) :: [Hash.Address.t()]
def verified_contracts_top(limit) do
query =
from(contract in SmartContract,
inner_join: address in Address,
on: contract.address_hash == address.hash,
order_by: [desc: address.transactions_count],
limit: ^limit,
select: contract.address_hash
)
Repo.all(query)
end
@spec default_paging_options() :: map()
def default_paging_options do
@default_paging_options

@ -7,9 +7,11 @@ defmodule Explorer.Chain.Address.Name do
import Ecto.Changeset
alias Ecto.Changeset
alias Ecto.{Changeset, Repo}
alias Explorer.Chain.{Address, Hash}
import Ecto.Query, only: [from: 2]
@typedoc """
* `address` - the `t:Explorer.Chain.Address.t/0` with `value` at end of `block_number`.
* `address_hash` - foreign key for `address`.
@ -46,6 +48,46 @@ defmodule Explorer.Chain.Address.Name do
|> foreign_key_constraint(:address_hash)
end
@doc """
Sets primary false for all primary names for the given address hash
"""
@spec clear_primary_address_names(Repo.t(), Hash.Address.t()) :: {:ok, []}
def clear_primary_address_names(repo, address_hash) do
query =
from(
address_name in __MODULE__,
where: address_name.address_hash == ^address_hash,
where: address_name.primary == true,
# Enforce Name ShareLocks order (see docs: sharelocks.md)
order_by: [asc: :address_hash, asc: :name],
lock: "FOR NO KEY UPDATE"
)
repo.update_all(
from(n in __MODULE__, join: s in subquery(query), on: n.address_hash == s.address_hash and n.name == s.name),
set: [primary: false]
)
{:ok, []}
end
@doc """
Creates primary address name for the given address hash
"""
@spec create_primary_address_name(Repo.t(), String.t(), Hash.Address.t()) ::
{:ok, [__MODULE__.t()]} | {:error, [Changeset.t()]}
def create_primary_address_name(repo, name, address_hash) do
params = %{
address_hash: address_hash,
name: name,
primary: true
}
%__MODULE__{}
|> __MODULE__.changeset(params)
|> repo.insert(on_conflict: :nothing, conflict_target: [:address_hash, :name])
end
defp trim_name(%Changeset{valid?: false} = changeset), do: changeset
defp trim_name(%Changeset{valid?: true} = changeset) do

@ -5,8 +5,6 @@ defmodule Explorer.Chain.Block.Reward do
use Explorer.Schema
import Explorer.Chain.SmartContract, only: [burn_address_hash_string: 0]
alias Explorer.Application.Constants
alias Explorer.{Chain, PagingOptions}
alias Explorer.Chain.Block.Reward.AddressType
@ -16,6 +14,8 @@ defmodule Explorer.Chain.Block.Reward do
@required_attrs ~w(address_hash address_type block_hash reward)a
@burn_address_hash_string "0x0000000000000000000000000000000000000000"
@get_payout_by_mining_abi %{
"type" => "function",
"stateMutability" => "view",
@ -252,6 +252,10 @@ defmodule Explorer.Chain.Block.Reward do
end
end
defp burn_address_hash_string do
@burn_address_hash_string
end
defp join_associations(query) do
query
|> preload(:address)

@ -90,7 +90,7 @@ defmodule Explorer.Chain.Hash.Full do
...> )
{:ok, <<0x9fc76417374aa880d4449a1f7f31ec597f00b1f6f3dd2d66f4c9c6c445836d8b :: big-integer-size(32)-unit(8)>>}
If the field from the struct is an incorrect format such as `t:Explorer.Chain.Address.Hash.t/0`, `:error` is returned.
If the field from the struct is an incorrect format such as `t:Explorer.Chain.Hash.Address.t/0`, `:error` is returned.
iex> Explorer.Chain.Hash.Full.dump(
...> %Explorer.Chain.Hash{

@ -8,6 +8,7 @@ defmodule Explorer.Chain.Log do
alias ABI.{Event, FunctionSelector}
alias Explorer.Chain
alias Explorer.Chain.{Address, Block, ContractMethod, Data, Hash, Transaction}
alias Explorer.Chain.SmartContract.Proxy
alias Explorer.SmartContract.SigProviderInterface
@required_attrs ~w(address_hash data block_hash index transaction_hash)a
@ -165,7 +166,7 @@ defmodule Explorer.Chain.Log do
else
case Chain.find_contract_address(address_hash, address_options, false) do
{:ok, %{smart_contract: smart_contract}} ->
full_abi = Chain.combine_proxy_implementation_abi(smart_contract, options)
full_abi = Proxy.combine_proxy_implementation_abi(smart_contract, options)
{full_abi, Map.put(acc, address_hash, full_abi)}
_ ->

File diff suppressed because it is too large Load Diff

@ -0,0 +1,230 @@
defmodule Explorer.Chain.SmartContract.Proxy do
@moduledoc """
Module for proxy smart-contract implementation detection
"""
alias EthereumJSONRPC.Contract
alias Explorer.Chain.{Hash, SmartContract}
alias Explorer.Chain.SmartContract.Proxy
alias Explorer.Chain.SmartContract.Proxy.{Basic, EIP1167, EIP1822, EIP1967, EIP930, MasterCopy}
import Explorer.Chain,
only: [
string_to_address_hash: 1
]
# supported signatures:
# 5c60da1b = keccak256(implementation())
@implementation_signature "5c60da1b"
# aaf10f42 = keccak256(getImplementation())
@get_implementation_signature "aaf10f42"
# aaf10f42 = keccak256(getAddress(bytes32))
@get_address_signature "21f8a721"
@burn_address_hash_string_32 "0x0000000000000000000000000000000000000000000000000000000000000000"
@typep api? :: {:api?, true | false}
defguard is_burn_signature(term) when term in ["0x", "0x0", @burn_address_hash_string_32]
defguard is_burn_signature_or_nil(term) when is_burn_signature(term) or term == nil
@doc """
Fetches into DB proxy contract implementation's address and name from different proxy patterns
"""
@spec fetch_implementation_address_hash(Hash.Address.t(), list(), boolean() | nil, [api?]) ::
{String.t() | nil, String.t() | nil}
def fetch_implementation_address_hash(proxy_address_hash, proxy_abi, metadata_from_verified_twin, options)
when not is_nil(proxy_address_hash) and not is_nil(proxy_abi) do
implementation_address_hash_string = get_implementation_address_hash_string(proxy_address_hash, proxy_abi)
{:ok, burn_address_hash} = string_to_address_hash(SmartContract.burn_address_hash_string())
with false <- is_nil(implementation_address_hash_string),
{:ok, implementation_address_hash} <- string_to_address_hash(implementation_address_hash_string),
false <- implementation_address_hash.bytes == burn_address_hash.bytes do
SmartContract.save_implementation_data(
implementation_address_hash_string,
proxy_address_hash,
metadata_from_verified_twin,
options
)
else
_ ->
{nil, nil}
end
end
def fetch_implementation_address_hash(_, _, _, _) do
{nil, nil}
end
@doc """
Checks if smart-contract is proxy. Returns true/false.
"""
@spec proxy_contract?(SmartContract.t()) :: boolean()
def proxy_contract?(smart_contract) do
{:ok, burn_address_hash} = string_to_address_hash(SmartContract.burn_address_hash_string())
if smart_contract.implementation_address_hash &&
smart_contract.implementation_address_hash.bytes !== burn_address_hash.bytes do
true
else
{implementation_address_hash_string, _} = SmartContract.get_implementation_address_hash(smart_contract, [])
with false <- is_nil(implementation_address_hash_string),
{:ok, implementation_address_hash} <- string_to_address_hash(implementation_address_hash_string),
false <- implementation_address_hash.bytes == burn_address_hash.bytes do
true
else
_ ->
false
end
end
end
@doc """
Decodes address output into 20 bytes address hash
"""
@spec abi_decode_address_output(any()) :: nil | binary()
def abi_decode_address_output(nil), do: nil
def abi_decode_address_output("0x"), do: SmartContract.burn_address_hash_string()
def abi_decode_address_output(address) when is_binary(address) do
if String.length(address) > 42 do
"0x" <> String.slice(address, -40, 40)
else
address
end
end
def abi_decode_address_output(_), do: nil
@doc """
Gets implementation ABI for given proxy smart-contract
"""
@spec get_implementation_abi_from_proxy(any(), any()) :: [map()]
def get_implementation_abi_from_proxy(
%SmartContract{address_hash: proxy_address_hash, abi: abi} = smart_contract,
options
)
when not is_nil(proxy_address_hash) and not is_nil(abi) do
{implementation_address_hash_string, _name} = SmartContract.get_implementation_address_hash(smart_contract, options)
SmartContract.get_smart_contract_abi(implementation_address_hash_string)
end
def get_implementation_abi_from_proxy(_, _), do: []
@doc """
Checks if the ABI of the smart-contract follows GnosisSafe proxy pattern
"""
@spec gnosis_safe_contract?([map()]) :: boolean()
def gnosis_safe_contract?(abi) when not is_nil(abi) do
if get_master_copy_pattern(abi), do: true, else: false
end
def gnosis_safe_contract?(abi) when is_nil(abi), do: false
@doc """
Checks if the input of the smart-contract follows master-copy proxy pattern
"""
@spec master_copy_pattern?(map()) :: any()
def master_copy_pattern?(method) do
Map.get(method, "type") == "constructor" &&
method
|> Enum.find(fn item ->
case item do
{"inputs", inputs} ->
find_input_by_name(inputs, "_masterCopy") || find_input_by_name(inputs, "_singleton")
_ ->
false
end
end)
end
@doc """
Gets implementation from proxy contract's specific storage
"""
@spec get_implementation_from_storage(Hash.Address.t(), String.t(), any()) :: String.t() | nil
def get_implementation_from_storage(proxy_address_hash, storage_slot, json_rpc_named_arguments) do
case Contract.eth_get_storage_at_request(
proxy_address_hash,
storage_slot,
nil,
json_rpc_named_arguments
) do
{:ok, empty_address_hash_string}
when is_burn_signature_or_nil(empty_address_hash_string) ->
nil
{:ok, implementation_logic_address_hash_string} ->
implementation_logic_address_hash_string
_ ->
nil
end
end
defp get_implementation_address_hash_string(proxy_address_hash, proxy_abi) do
implementation_method_abi = get_naive_implementation_abi(proxy_abi, "implementation")
get_implementation_method_abi = get_naive_implementation_abi(proxy_abi, "getImplementation")
master_copy_method_abi = get_master_copy_pattern(proxy_abi)
get_address_method_abi = get_naive_implementation_abi(proxy_abi, "getAddress")
cond do
implementation_method_abi ->
Basic.get_implementation_address(@implementation_signature, proxy_address_hash, proxy_abi)
get_implementation_method_abi ->
Basic.get_implementation_address(@get_implementation_signature, proxy_address_hash, proxy_abi)
master_copy_method_abi ->
MasterCopy.get_implementation_address(proxy_address_hash)
get_address_method_abi ->
EIP930.get_implementation_address(@get_address_signature, proxy_address_hash, proxy_abi)
true ->
EIP1967.get_implementation_address(proxy_address_hash) ||
EIP1167.get_implementation_address(proxy_address_hash) ||
EIP1822.get_implementation_address(proxy_address_hash)
end
end
defp get_naive_implementation_abi(abi, getter_name) do
abi
|> Enum.find(fn method ->
Map.get(method, "name") == getter_name && Map.get(method, "stateMutability") == "view"
end)
end
defp get_master_copy_pattern(abi) do
abi
|> Enum.find(fn method ->
master_copy_pattern?(method)
end)
end
def combine_proxy_implementation_abi(smart_contract, options \\ [])
def combine_proxy_implementation_abi(%SmartContract{abi: abi} = smart_contract, options) when not is_nil(abi) do
implementation_abi = Proxy.get_implementation_abi_from_proxy(smart_contract, options)
if Enum.empty?(implementation_abi), do: abi, else: implementation_abi ++ abi
end
def combine_proxy_implementation_abi(_, _) do
[]
end
defp find_input_by_name(inputs, name) do
inputs
|> Enum.find(fn input ->
Map.get(input, "name") == name
end)
end
end

@ -0,0 +1,44 @@
defmodule Explorer.Chain.SmartContract.Proxy.Basic do
@moduledoc """
Module for fetching proxy implementation from specific smart-contract getter
"""
alias Explorer.SmartContract.Reader
@doc """
Gets implementation if proxy contract from getter.
"""
@spec get_implementation_address(any(), binary(), any()) :: nil | binary()
def get_implementation_address(signature, proxy_address_hash, abi) do
implementation_address =
case Reader.query_contract(
proxy_address_hash,
abi,
%{
"#{signature}" => []
},
false
) do
%{^signature => {:ok, [result]}} -> result
_ -> nil
end
adds_0x_to_address(implementation_address)
end
@doc """
Adds 0x to address at the beginning
"""
@spec adds_0x_to_address(nil | binary()) :: nil | binary()
def adds_0x_to_address(nil), do: nil
def adds_0x_to_address(address) do
if address do
if String.starts_with?(address, "0x") do
address
else
"0x" <> Base.encode16(address, case: :lower)
end
end
end
end

@ -0,0 +1,54 @@
defmodule Explorer.Chain.SmartContract.Proxy.EIP1167 do
@moduledoc """
Module for fetching proxy implementation from https://eips.ethereum.org/EIPS/eip-1167 (Minimal Proxy Contract)
"""
alias Explorer.Chain
alias Explorer.Chain.{Address, Hash, SmartContract}
import Ecto.Query, only: [from: 2]
@doc """
Get implementation address following EIP-1167
"""
@spec get_implementation_address(Hash.Address.t(), Keyword.t()) :: SmartContract.t() | nil
def get_implementation_address(address_hash, options \\ []) do
case Chain.select_repo(options).get(Address, address_hash) do
nil ->
nil
target_address ->
contract_code = target_address.contract_code
case contract_code do
%Chain.Data{bytes: contract_code_bytes} ->
contract_bytecode = Base.encode16(contract_code_bytes, case: :lower)
get_proxy_eip_1167(contract_bytecode, options)
_ ->
nil
end
end
end
defp get_proxy_eip_1167(contract_bytecode, options) do
case contract_bytecode do
"363d3d373d3d3d363d73" <> <<template_address::binary-size(40)>> <> _ ->
template_address = "0x" <> template_address
query =
from(
smart_contract in SmartContract,
where: smart_contract.address_hash == ^template_address,
select: smart_contract
)
query
|> Chain.select_repo(options).one(timeout: 10_000)
_ ->
nil
end
end
end

@ -0,0 +1,32 @@
defmodule Explorer.Chain.SmartContract.Proxy.EIP1822 do
@moduledoc """
Module for fetching proxy implementation from https://eips.ethereum.org/EIPS/eip-1822 Universal Upgradeable Proxy Standard (UUPS)
"""
alias Explorer.Chain.{Hash, SmartContract}
alias Explorer.Chain.SmartContract.Proxy
@burn_address_hash_string_32 "0x0000000000000000000000000000000000000000000000000000000000000000"
defguard is_burn_signature(term) when term in ["0x", "0x0", @burn_address_hash_string_32]
defguard is_burn_signature_or_nil(term) when is_burn_signature(term) or term == nil
# keccak256("PROXIABLE")
@storage_slot_proxiable "0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7"
@doc """
Get implementation address following EIP-1967
"""
@spec get_implementation_address(Hash.Address.t()) :: SmartContract.t() | nil
def get_implementation_address(proxy_address_hash) do
json_rpc_named_arguments = Application.get_env(:explorer, :json_rpc_named_arguments)
proxiable_contract_address_hash_string =
Proxy.get_implementation_from_storage(
proxy_address_hash,
@storage_slot_proxiable,
json_rpc_named_arguments
)
Proxy.abi_decode_address_output(proxiable_contract_address_hash_string)
end
end

@ -0,0 +1,101 @@
defmodule Explorer.Chain.SmartContract.Proxy.EIP1967 do
@moduledoc """
Module for fetching proxy implementation from https://eips.ethereum.org/EIPS/eip-1967 (Proxy Storage Slots)
"""
alias EthereumJSONRPC.Contract
alias Explorer.Chain.{Hash, SmartContract}
alias Explorer.Chain.SmartContract.Proxy
alias Explorer.Chain.SmartContract.Proxy.Basic
# supported signatures:
# 5c60da1b = keccak256(implementation())
@implementation_signature "5c60da1b"
@burn_address_hash_string_32 "0x0000000000000000000000000000000000000000000000000000000000000000"
defguard is_burn_signature(term) when term in ["0x", "0x0", @burn_address_hash_string_32]
defguard is_burn_signature_or_nil(term) when is_burn_signature(term) or term == nil
@storage_slot_logic_contract_address "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc"
# to be precise, it is not the part of the EIP-1967 standard, but still uses the same pattern
# changes requested by https://github.com/blockscout/blockscout/issues/5292
# This is the keccak-256 hash of "org.zeppelinos.proxy.implementation"
@storage_slot_openzeppelin_contract_address "0x7050c9e0f4ca769c69bd3a8ef740bc37934f8e2c036e5a723fd8ee048ed3f8c3"
@doc """
Get implementation address following EIP-1967
"""
@spec get_implementation_address(Hash.Address.t()) :: SmartContract.t() | nil
def get_implementation_address(proxy_address_hash) do
json_rpc_named_arguments = Application.get_env(:explorer, :json_rpc_named_arguments)
logic_contract_address_hash_string =
Proxy.get_implementation_from_storage(
proxy_address_hash,
@storage_slot_logic_contract_address,
json_rpc_named_arguments
)
beacon_contract_address_hash_string =
fetch_beacon_proxy_implementation(proxy_address_hash, json_rpc_named_arguments)
openzeppelin_style_contract_address_hash_string =
Proxy.get_implementation_from_storage(
proxy_address_hash,
@storage_slot_openzeppelin_contract_address,
json_rpc_named_arguments
)
implementation_address_hash_string =
logic_contract_address_hash_string || beacon_contract_address_hash_string ||
openzeppelin_style_contract_address_hash_string
Proxy.abi_decode_address_output(implementation_address_hash_string)
end
# changes requested by https://github.com/blockscout/blockscout/issues/4770
# for support BeaconProxy pattern
defp fetch_beacon_proxy_implementation(proxy_address_hash, json_rpc_named_arguments) do
# https://eips.ethereum.org/EIPS/eip-1967
storage_slot_beacon_contract_address = "0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50"
implementation_method_abi = [
%{
"type" => "function",
"stateMutability" => "view",
"outputs" => [%{"type" => "address", "name" => "", "internalType" => "address"}],
"name" => "implementation",
"inputs" => []
}
]
case Contract.eth_get_storage_at_request(
proxy_address_hash,
storage_slot_beacon_contract_address,
nil,
json_rpc_named_arguments
) do
{:ok, empty_address}
when is_burn_signature_or_nil(empty_address) ->
nil
{:ok, beacon_contract_address} ->
case beacon_contract_address
|> Proxy.abi_decode_address_output()
|> Basic.get_implementation_address(
@implementation_signature,
implementation_method_abi
) do
<<implementation_address::binary-size(42)>> ->
implementation_address
_ ->
beacon_contract_address
end
_ ->
nil
end
end
end

@ -0,0 +1,31 @@
defmodule Explorer.Chain.SmartContract.Proxy.EIP930 do
@moduledoc """
Module for fetching proxy implementation from smart-contract getter following https://github.com/ethereum/EIPs/issues/930
"""
alias Explorer.Chain.SmartContract.Proxy.Basic
alias Explorer.SmartContract.Reader
@storage_slot_logic_contract_address "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc"
@doc """
Gets implementation if proxy contract from getter.
"""
@spec get_implementation_address(any(), binary(), any()) :: nil | binary()
def get_implementation_address(signature, proxy_address_hash, abi) do
implementation_address =
case Reader.query_contract(
proxy_address_hash,
abi,
%{
"#{signature}" => [@storage_slot_logic_contract_address]
},
false
) do
%{^signature => {:ok, [result]}} -> result
_ -> nil
end
Basic.adds_0x_to_address(implementation_address)
end
end

@ -0,0 +1,43 @@
defmodule Explorer.Chain.SmartContract.Proxy.MasterCopy do
@moduledoc """
Module for fetching master-copy proxy implementation
"""
alias EthereumJSONRPC.Contract
alias Explorer.Chain.{Hash, SmartContract}
alias Explorer.Chain.SmartContract.Proxy
@burn_address_hash_string_32 "0x0000000000000000000000000000000000000000000000000000000000000000"
defguard is_burn_signature(term) when term in ["0x", "0x0", @burn_address_hash_string_32]
@doc """
Gets implementation address for proxy contract from master-copy pattern
"""
@spec get_implementation_address(Hash.Address.t()) :: SmartContract.t() | nil
def get_implementation_address(proxy_address_hash) do
json_rpc_named_arguments = Application.get_env(:explorer, :json_rpc_named_arguments)
master_copy_storage_pointer = "0x0"
{:ok, implementation_address} =
case Contract.eth_get_storage_at_request(
proxy_address_hash,
master_copy_storage_pointer,
nil,
json_rpc_named_arguments
) do
{:ok, empty_address}
when is_burn_signature(empty_address) ->
{:ok, "0x"}
{:ok, logic_contract_address} ->
{:ok, logic_contract_address}
_ ->
{:ok, nil}
end
Proxy.abi_decode_address_output(implementation_address)
end
end

@ -8,6 +8,8 @@ defmodule Explorer.Chain.SmartContractAdditionalSource do
use Explorer.Schema
import Explorer.Chain, only: [select_repo: 1]
alias Explorer.Chain.{Hash, SmartContract}
@typedoc """
@ -59,5 +61,24 @@ defmodule Explorer.Chain.SmartContractAdditionalSource do
add_error(validated, :contract_source_code, error_message(error))
end
@doc """
Returns all additional sources for the given smart-contract address hash
"""
@spec get_contract_additional_sources(SmartContract.t(), Keyword.t()) :: [__MODULE__.t()]
def get_contract_additional_sources(smart_contract, options) do
if smart_contract do
all_additional_sources_query =
from(
s in __MODULE__,
where: s.address_hash == ^smart_contract.address_hash
)
all_additional_sources_query
|> select_repo(options).all()
else
[]
end
end
defp error_message(_), do: "There was an error validating your contract, please try again."
end

@ -31,6 +31,7 @@ defmodule Explorer.Chain.Transaction do
Wei
}
alias Explorer.Chain.SmartContract.Proxy
alias Explorer.Chain.Transaction.{Fork, Status}
alias Explorer.Chain.Zkevm.BatchTransaction
alias Explorer.SmartContract.SigProviderInterface
@ -792,7 +793,7 @@ defmodule Explorer.Chain.Transaction 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 = Chain.combine_proxy_implementation_abi(smart_contract, options)
full_abi = Proxy.combine_proxy_implementation_abi(smart_contract, options)
{full_abi, Map.put(full_abi_acc, address_hash, full_abi)}
end

@ -11,8 +11,10 @@ defmodule Explorer.Etherscan.Contracts do
where: 3
]
alias Explorer.{Chain, Repo}
alias Explorer.Repo
alias Explorer.Chain.{Address, Hash, SmartContract}
alias Explorer.Chain.SmartContract.Proxy
alias Explorer.Chain.SmartContract.Proxy.EIP1167
@spec address_hash_to_address_with_source_code(Hash.Address.t()) :: Address.t() | nil
def address_hash_to_address_with_source_code(address_hash) do
@ -38,8 +40,8 @@ defmodule Explorer.Etherscan.Contracts do
}
else
address_verified_twin_contract =
Chain.get_minimal_proxy_template(address_hash) ||
Chain.get_address_verified_twin_contract(address_hash).verified_contract
EIP1167.get_implementation_address(address_hash) ||
SmartContract.get_address_verified_twin_contract(address_hash).verified_contract
compose_address_with_smart_contract(
address_with_smart_contract,
@ -67,7 +69,7 @@ defmodule Explorer.Etherscan.Contracts do
def append_proxy_info(%Address{smart_contract: smart_contract} = address) when not is_nil(smart_contract) do
updated_smart_contract =
if SmartContract.proxy_contract?(smart_contract) do
if Proxy.proxy_contract?(smart_contract) do
smart_contract
|> Map.put(:is_proxy, true)
|> Map.put(

@ -7,8 +7,8 @@ defmodule Explorer.SmartContract.Reader do
"""
alias EthereumJSONRPC.{Contract, Encoder}
alias Explorer.Chain
alias Explorer.Chain.{Hash, SmartContract}
alias Explorer.Chain.SmartContract.Proxy
alias Explorer.SmartContract.Helper
@typedoc """
@ -92,7 +92,7 @@ defmodule Explorer.SmartContract.Reader do
defp prepare_abi(nil, address_hash) do
address_hash
|> Chain.address_hash_to_smart_contract()
|> SmartContract.address_hash_to_smart_contract()
|> Map.get(:abi)
end
@ -222,7 +222,7 @@ defmodule Explorer.SmartContract.Reader do
def read_only_functions(nil, _, _), do: []
def read_only_functions_proxy(contract_address_hash, implementation_address_hash_string, from, options \\ []) do
implementation_abi = Chain.get_implementation_abi(implementation_address_hash_string, options)
implementation_abi = SmartContract.get_smart_contract_abi(implementation_address_hash_string, options)
case implementation_abi do
nil ->
@ -238,7 +238,7 @@ defmodule Explorer.SmartContract.Reader do
"""
@spec read_functions_required_wallet_proxy(String.t()) :: [%{}]
def read_functions_required_wallet_proxy(implementation_address_hash_string) do
implementation_abi = Chain.get_implementation_abi(implementation_address_hash_string)
implementation_abi = SmartContract.get_smart_contract_abi(implementation_address_hash_string)
case implementation_abi do
nil ->
@ -572,10 +572,10 @@ defmodule Explorer.SmartContract.Reader do
end
defp get_abi(contract_address_hash, type, options) do
contract = Chain.address_hash_to_smart_contract(contract_address_hash, options)
contract = SmartContract.address_hash_to_smart_contract(contract_address_hash, options)
if type == :proxy do
Chain.get_implementation_abi_from_proxy(contract, options)
Proxy.get_implementation_abi_from_proxy(contract, options)
else
contract.abi
end

@ -7,7 +7,6 @@ defmodule Explorer.SmartContract.Solidity.Publisher do
import Explorer.SmartContract.Helper, only: [cast_libraries: 1]
alias Explorer.Chain
alias Explorer.Chain.SmartContract
alias Explorer.SmartContract.{CompilerVersion, Helper}
alias Explorer.SmartContract.Solidity.Verifier
@ -200,10 +199,10 @@ defmodule Explorer.SmartContract.Solidity.Publisher do
defp create_or_update_smart_contract(address_hash, attrs) do
Logger.info("Publish successfully verified Solidity smart-contract #{address_hash} into the DB")
if Chain.smart_contract_verified?(address_hash) do
Chain.update_smart_contract(attrs, attrs.external_libraries, attrs.secondary_sources)
if SmartContract.verified?(address_hash) do
SmartContract.update_smart_contract(attrs, attrs.external_libraries, attrs.secondary_sources)
else
Chain.create_smart_contract(attrs, attrs.external_libraries, attrs.secondary_sources)
SmartContract.create_smart_contract(attrs, attrs.external_libraries, attrs.secondary_sources)
end
end

@ -7,7 +7,6 @@ defmodule Explorer.SmartContract.Vyper.Publisher do
require Logger
alias Explorer.Chain
alias Explorer.Chain.SmartContract
alias Explorer.SmartContract.CompilerVersion
alias Explorer.SmartContract.Vyper.Verifier
@ -124,7 +123,7 @@ defmodule Explorer.SmartContract.Vyper.Publisher do
Logger.info("Publish successfully verified Vyper smart-contract #{address_hash} into the DB")
attrs = address_hash |> attributes(params, abi)
Chain.create_smart_contract(attrs, attrs.external_libraries, attrs.secondary_sources)
SmartContract.create_smart_contract(attrs, attrs.external_libraries, attrs.secondary_sources)
end
defp unverified_smart_contract(address_hash, params, error, error_message, verification_with_files? \\ false) do

@ -3,7 +3,6 @@ defmodule Explorer.SmartContract.Writer do
Generates smart-contract transactions
"""
alias Explorer.Chain
alias Explorer.Chain.{Hash, SmartContract}
alias Explorer.SmartContract.Helper
@ -21,7 +20,7 @@ defmodule Explorer.SmartContract.Writer do
@spec write_functions_proxy(Hash.t() | String.t()) :: [%{}]
def write_functions_proxy(implementation_address_hash_string, options \\ []) do
implementation_abi = Chain.get_implementation_abi(implementation_address_hash_string, options)
implementation_abi = SmartContract.get_smart_contract_abi(implementation_address_hash_string, options)
case implementation_abi do
nil ->

@ -104,7 +104,7 @@ defmodule Explorer.Chain.LogTest do
data: data
)
get_eip1967_implementation()
request_zero_implementations()
assert {{:ok, "eb9b3c4c", "WantsPets(string indexed _from_human, uint256 _number, bool indexed _belly)",
[
@ -173,7 +173,7 @@ defmodule Explorer.Chain.LogTest do
end
end
def get_eip1967_implementation do
def request_zero_implementations do
EthereumJSONRPC.Mox
|> expect(:json_rpc, fn %{
id: 0,
@ -211,5 +211,17 @@ defmodule Explorer.Chain.LogTest do
_options ->
{:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"}
end)
|> expect(:json_rpc, fn %{
id: 0,
method: "eth_getStorageAt",
params: [
_,
"0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7",
"latest"
]
},
_options ->
{:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"}
end)
end
end

@ -0,0 +1,384 @@
defmodule Explorer.Chain.SmartContract.ProxyTest do
use Explorer.DataCase, async: false
import Mox
alias Explorer.Chain.SmartContract
alias Explorer.Chain.SmartContract.Proxy
setup :verify_on_exit!
setup :set_mox_global
describe "proxy contracts features" do
@proxy_abi [
%{
"type" => "function",
"stateMutability" => "nonpayable",
"payable" => false,
"outputs" => [%{"type" => "bool", "name" => ""}],
"name" => "upgradeTo",
"inputs" => [%{"type" => "address", "name" => "newImplementation"}],
"constant" => false
},
%{
"type" => "function",
"stateMutability" => "view",
"payable" => false,
"outputs" => [%{"type" => "uint256", "name" => ""}],
"name" => "version",
"inputs" => [],
"constant" => true
},
%{
"type" => "function",
"stateMutability" => "view",
"payable" => false,
"outputs" => [%{"type" => "address", "name" => ""}],
"name" => "implementation",
"inputs" => [],
"constant" => true
},
%{
"type" => "function",
"stateMutability" => "nonpayable",
"payable" => false,
"outputs" => [],
"name" => "renounceOwnership",
"inputs" => [],
"constant" => false
},
%{
"type" => "function",
"stateMutability" => "view",
"payable" => false,
"outputs" => [%{"type" => "address", "name" => ""}],
"name" => "getOwner",
"inputs" => [],
"constant" => true
},
%{
"type" => "function",
"stateMutability" => "view",
"payable" => false,
"outputs" => [%{"type" => "address", "name" => ""}],
"name" => "getProxyStorage",
"inputs" => [],
"constant" => true
},
%{
"type" => "function",
"stateMutability" => "nonpayable",
"payable" => false,
"outputs" => [],
"name" => "transferOwnership",
"inputs" => [%{"type" => "address", "name" => "_newOwner"}],
"constant" => false
},
%{
"type" => "constructor",
"stateMutability" => "nonpayable",
"payable" => false,
"inputs" => [
%{"type" => "address", "name" => "_proxyStorage"},
%{"type" => "address", "name" => "_implementationAddress"}
]
},
%{"type" => "fallback", "stateMutability" => "nonpayable", "payable" => false},
%{
"type" => "event",
"name" => "Upgraded",
"inputs" => [
%{"type" => "uint256", "name" => "version", "indexed" => false},
%{"type" => "address", "name" => "implementation", "indexed" => true}
],
"anonymous" => false
},
%{
"type" => "event",
"name" => "OwnershipRenounced",
"inputs" => [%{"type" => "address", "name" => "previousOwner", "indexed" => true}],
"anonymous" => false
},
%{
"type" => "event",
"name" => "OwnershipTransferred",
"inputs" => [
%{"type" => "address", "name" => "previousOwner", "indexed" => true},
%{"type" => "address", "name" => "newOwner", "indexed" => true}
],
"anonymous" => false
}
]
@implementation_abi [
%{
"constant" => false,
"inputs" => [%{"name" => "x", "type" => "uint256"}],
"name" => "set",
"outputs" => [],
"payable" => false,
"stateMutability" => "nonpayable",
"type" => "function"
},
%{
"constant" => true,
"inputs" => [],
"name" => "get",
"outputs" => [%{"name" => "", "type" => "uint256"}],
"payable" => false,
"stateMutability" => "view",
"type" => "function"
}
]
# EIP-1967 + EIP-1822
defp request_zero_implementations do
EthereumJSONRPC.Mox
|> expect(:json_rpc, fn %{
id: 0,
method: "eth_getStorageAt",
params: [
_,
"0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc",
"latest"
]
},
_options ->
{:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"}
end)
|> expect(:json_rpc, fn %{
id: 0,
method: "eth_getStorageAt",
params: [
_,
"0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50",
"latest"
]
},
_options ->
{:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"}
end)
|> expect(:json_rpc, fn %{
id: 0,
method: "eth_getStorageAt",
params: [
_,
"0x7050c9e0f4ca769c69bd3a8ef740bc37934f8e2c036e5a723fd8ee048ed3f8c3",
"latest"
]
},
_options ->
{:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"}
end)
|> expect(:json_rpc, fn %{
id: 0,
method: "eth_getStorageAt",
params: [
_,
"0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7",
"latest"
]
},
_options ->
{:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"}
end)
end
test "combine_proxy_implementation_abi/2 returns empty [] abi if proxy abi is null" do
proxy_contract_address = insert(:contract_address)
assert Proxy.combine_proxy_implementation_abi(%SmartContract{address_hash: proxy_contract_address.hash, abi: nil}) ==
[]
end
test "combine_proxy_implementation_abi/2 returns [] abi for unverified proxy" do
proxy_contract_address = insert(:contract_address)
smart_contract =
insert(:smart_contract, address_hash: proxy_contract_address.hash, abi: [], contract_code_md5: "123")
request_zero_implementations()
assert Proxy.combine_proxy_implementation_abi(smart_contract) == []
end
test "combine_proxy_implementation_abi/2 returns proxy abi if implementation is not verified" do
proxy_contract_address = insert(:contract_address)
smart_contract =
insert(:smart_contract, address_hash: proxy_contract_address.hash, abi: @proxy_abi, contract_code_md5: "123")
assert Proxy.combine_proxy_implementation_abi(smart_contract) == @proxy_abi
end
test "combine_proxy_implementation_abi/2 returns proxy + implementation abi if implementation is verified" do
proxy_contract_address = insert(:contract_address)
smart_contract =
insert(:smart_contract, address_hash: proxy_contract_address.hash, abi: @proxy_abi, contract_code_md5: "123")
implementation_contract_address = insert(:contract_address)
insert(:smart_contract,
address_hash: implementation_contract_address.hash,
abi: @implementation_abi,
contract_code_md5: "123"
)
implementation_contract_address_hash_string =
Base.encode16(implementation_contract_address.hash.bytes, case: :lower)
expect(
EthereumJSONRPC.Mox,
:json_rpc,
fn [%{id: id, method: _, params: [%{data: _, to: _}, _]}], _options ->
{:ok,
[
%{
id: id,
jsonrpc: "2.0",
result: "0x000000000000000000000000" <> implementation_contract_address_hash_string
}
]}
end
)
combined_abi = Proxy.combine_proxy_implementation_abi(smart_contract)
assert Enum.any?(@proxy_abi, fn el -> el == Enum.at(@implementation_abi, 0) end) == false
assert Enum.any?(@proxy_abi, fn el -> el == Enum.at(@implementation_abi, 1) end) == false
assert Enum.any?(combined_abi, fn el -> el == Enum.at(@implementation_abi, 0) end) == true
assert Enum.any?(combined_abi, fn el -> el == Enum.at(@implementation_abi, 1) end) == true
end
test "get_implementation_abi_from_proxy/2 returns empty [] abi if proxy abi is null" do
proxy_contract_address = insert(:contract_address)
assert Proxy.get_implementation_abi_from_proxy(
%SmartContract{address_hash: proxy_contract_address.hash, abi: nil},
[]
) ==
[]
end
test "get_implementation_abi_from_proxy/2 returns [] abi for unverified proxy" do
proxy_contract_address = insert(:contract_address)
smart_contract =
insert(:smart_contract, address_hash: proxy_contract_address.hash, abi: [], contract_code_md5: "123")
request_zero_implementations()
assert Proxy.combine_proxy_implementation_abi(smart_contract) == []
end
test "get_implementation_abi_from_proxy/2 returns [] if implementation is not verified" do
proxy_contract_address = insert(:contract_address)
smart_contract =
insert(:smart_contract, address_hash: proxy_contract_address.hash, abi: @proxy_abi, contract_code_md5: "123")
assert Proxy.get_implementation_abi_from_proxy(smart_contract, []) == []
end
test "get_implementation_abi_from_proxy/2 returns implementation abi if implementation is verified" do
proxy_contract_address = insert(:contract_address)
smart_contract =
insert(:smart_contract, address_hash: proxy_contract_address.hash, abi: @proxy_abi, contract_code_md5: "123")
implementation_contract_address = insert(:contract_address)
insert(:smart_contract,
address_hash: implementation_contract_address.hash,
abi: @implementation_abi,
contract_code_md5: "123"
)
implementation_contract_address_hash_string =
Base.encode16(implementation_contract_address.hash.bytes, case: :lower)
expect(
EthereumJSONRPC.Mox,
:json_rpc,
fn [%{id: id, method: _, params: [%{data: _, to: _}, _]}], _options ->
{:ok,
[
%{
id: id,
jsonrpc: "2.0",
result: "0x000000000000000000000000" <> implementation_contract_address_hash_string
}
]}
end
)
implementation_abi = Proxy.get_implementation_abi_from_proxy(smart_contract, [])
assert implementation_abi == @implementation_abi
end
test "get_implementation_abi_from_proxy/2 returns implementation abi in case of EIP-1967 proxy pattern" do
proxy_contract_address = insert(:contract_address)
smart_contract =
insert(:smart_contract, address_hash: proxy_contract_address.hash, abi: [], contract_code_md5: "123")
implementation_contract_address = insert(:contract_address)
insert(:smart_contract,
address_hash: implementation_contract_address.hash,
abi: @implementation_abi,
contract_code_md5: "123"
)
implementation_contract_address_hash_string =
Base.encode16(implementation_contract_address.hash.bytes, case: :lower)
expect(
EthereumJSONRPC.Mox,
:json_rpc,
fn %{
id: _id,
method: "eth_getStorageAt",
params: [
_,
"0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc",
"latest"
]
},
_options ->
{:ok, "0x000000000000000000000000" <> implementation_contract_address_hash_string}
end
)
EthereumJSONRPC.Mox
|> expect(:json_rpc, fn %{
id: 0,
method: "eth_getStorageAt",
params: [
_,
"0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50",
"latest"
]
},
_options ->
{:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"}
end)
|> expect(:json_rpc, fn %{
id: 0,
method: "eth_getStorageAt",
params: [
_,
"0x7050c9e0f4ca769c69bd3a8ef740bc37934f8e2c036e5a723fd8ee048ed3f8c3",
"latest"
]
},
_options ->
{:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"}
end)
implementation_abi = Proxy.get_implementation_abi_from_proxy(smart_contract, [])
assert implementation_abi == @implementation_abi
end
end
end

@ -3,7 +3,8 @@ defmodule Explorer.Chain.SmartContractTest do
import Mox
alias Explorer.Chain
alias Explorer.Chain.SmartContract
alias Explorer.Chain.{Address, SmartContract}
alias Explorer.Chain.SmartContract.Proxy
doctest Explorer.Chain.SmartContract
@ -19,42 +20,38 @@ defmodule Explorer.Chain.SmartContractTest do
refute smart_contract.implementation_fetched_at
# fetch nil implementation and save it to db
# fetch nil implementation and don't save it to db
get_eip1967_implementation_zero_addresses()
refute SmartContract.proxy_contract?(smart_contract)
refute Proxy.proxy_contract?(smart_contract)
verify!(EthereumJSONRPC.Mox)
assert_empty_implementation(smart_contract.address_hash)
# extract proxy info from db
refute SmartContract.proxy_contract?(smart_contract)
verify!(EthereumJSONRPC.Mox)
assert_empty_implementation(smart_contract.address_hash)
assert_implementation_never_fetched(smart_contract.address_hash)
Application.put_env(:explorer, :fallback_ttl_cached_implementation_data_of_proxy, 0)
get_eip1967_implementation_error_response()
refute SmartContract.proxy_contract?(smart_contract)
refute Proxy.proxy_contract?(smart_contract)
verify!(EthereumJSONRPC.Mox)
get_eip1967_implementation_non_zero_address()
assert SmartContract.proxy_contract?(smart_contract)
assert Proxy.proxy_contract?(smart_contract)
verify!(EthereumJSONRPC.Mox)
assert_implementation_address(smart_contract.address_hash)
get_eip1967_implementation_non_zero_address()
assert SmartContract.proxy_contract?(smart_contract)
assert Proxy.proxy_contract?(smart_contract)
verify!(EthereumJSONRPC.Mox)
assert_implementation_address(smart_contract.address_hash)
Application.put_env(:explorer, :fallback_ttl_cached_implementation_data_of_proxy, :timer.seconds(20))
assert SmartContract.proxy_contract?(smart_contract)
assert Proxy.proxy_contract?(smart_contract)
Application.put_env(:explorer, :fallback_ttl_cached_implementation_data_of_proxy, 0)
get_eip1967_implementation_non_zero_address()
assert SmartContract.proxy_contract?(smart_contract)
assert Proxy.proxy_contract?(smart_contract)
verify!(EthereumJSONRPC.Mox)
get_eip1967_implementation_error_response()
assert SmartContract.proxy_contract?(smart_contract)
assert Proxy.proxy_contract?(smart_contract)
verify!(EthereumJSONRPC.Mox)
end
@ -67,16 +64,13 @@ defmodule Explorer.Chain.SmartContractTest do
refute smart_contract.implementation_fetched_at
# fetch nil implementation and save it to db
# fetch nil implementation and don't save it to db
get_eip1967_implementation_zero_addresses()
assert {nil, nil} = SmartContract.get_implementation_address_hash(smart_contract)
verify!(EthereumJSONRPC.Mox)
assert_empty_implementation(smart_contract.address_hash)
assert_implementation_never_fetched(smart_contract.address_hash)
# extract proxy info from db
assert {nil, nil} = SmartContract.get_implementation_address_hash(smart_contract)
assert_empty_implementation(smart_contract.address_hash)
Application.put_env(:explorer, :fallback_ttl_cached_implementation_data_of_proxy, 0)
string_implementation_address_hash = to_string(implementation_smart_contract.address_hash)
@ -93,6 +87,30 @@ defmodule Explorer.Chain.SmartContractTest do
_options ->
{:ok, string_implementation_address_hash}
end)
|> expect(:json_rpc, fn %{
id: 0,
method: "eth_getStorageAt",
params: [
_,
"0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50",
"latest"
]
},
_options ->
{:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"}
end)
|> expect(:json_rpc, fn %{
id: 0,
method: "eth_getStorageAt",
params: [
_,
"0x7050c9e0f4ca769c69bd3a8ef740bc37934f8e2c036e5a723fd8ee048ed3f8c3",
"latest"
]
},
_options ->
{:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"}
end)
assert {^string_implementation_address_hash, "proxy"} =
SmartContract.get_implementation_address_hash(smart_contract)
@ -118,23 +136,28 @@ defmodule Explorer.Chain.SmartContractTest do
implementation_smart_contract.name
)
contract_1 = Chain.address_hash_to_smart_contract(smart_contract.address_hash)
contract_1 = SmartContract.address_hash_to_smart_contract(smart_contract.address_hash)
Application.put_env(:explorer, :fallback_ttl_cached_implementation_data_of_proxy, :timer.seconds(20))
assert {^string_implementation_address_hash, "proxy"} =
SmartContract.get_implementation_address_hash(smart_contract)
contract_2 = Chain.address_hash_to_smart_contract(smart_contract.address_hash)
contract_2 = SmartContract.address_hash_to_smart_contract(smart_contract.address_hash)
assert contract_1.implementation_fetched_at == contract_2.implementation_fetched_at &&
contract_1.updated_at == contract_2.updated_at
Application.put_env(:explorer, :fallback_ttl_cached_implementation_data_of_proxy, 0)
get_eip1967_implementation_zero_addresses()
assert {nil, nil} = SmartContract.get_implementation_address_hash(smart_contract)
assert {^string_implementation_address_hash, "proxy"} =
SmartContract.get_implementation_address_hash(smart_contract)
verify!(EthereumJSONRPC.Mox)
assert_empty_implementation(smart_contract.address_hash)
assert contract_1.implementation_fetched_at == contract_2.implementation_fetched_at &&
contract_1.updated_at == contract_2.updated_at
end
test "test get_implementation_address_hash/1 for twins contract" do
@ -143,7 +166,7 @@ defmodule Explorer.Chain.SmartContractTest do
smart_contract = insert(:smart_contract)
another_address = insert(:contract_address)
twin = Chain.address_hash_to_smart_contract(another_address.hash)
twin = SmartContract.address_hash_to_smart_contract(another_address.hash)
implementation_smart_contract = insert(:smart_contract, name: "proxy")
Application.put_env(:explorer, :fallback_ttl_cached_implementation_data_of_proxy, :timer.seconds(20))
@ -162,18 +185,7 @@ defmodule Explorer.Chain.SmartContractTest do
string_implementation_address_hash = to_string(implementation_smart_contract.address_hash)
expect(EthereumJSONRPC.Mox, :json_rpc, fn %{
id: 0,
method: "eth_getStorageAt",
params: [
_,
"0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc",
"latest"
]
},
_options ->
{:ok, string_implementation_address_hash}
end)
expect_address_in_response(string_implementation_address_hash)
assert {^string_implementation_address_hash, "proxy"} = SmartContract.get_implementation_address_hash(twin)
@ -210,18 +222,7 @@ defmodule Explorer.Chain.SmartContractTest do
string_implementation_address_hash = to_string(implementation_smart_contract.address_hash)
expect(EthereumJSONRPC.Mox, :json_rpc, fn %{
id: 0,
method: "eth_getStorageAt",
params: [
_,
"0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc",
"latest"
]
},
_options ->
{:ok, string_implementation_address_hash}
end)
expect_address_in_response(string_implementation_address_hash)
assert {^string_implementation_address_hash, "proxy"} = SmartContract.get_implementation_address_hash(twin)
@ -268,18 +269,7 @@ defmodule Explorer.Chain.SmartContractTest do
string_implementation_address_hash = to_string(implementation_smart_contract.address_hash)
expect(EthereumJSONRPC.Mox, :json_rpc, fn %{
id: 0,
method: "eth_getStorageAt",
params: [
_,
"0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc",
"latest"
]
},
_options ->
{:ok, string_implementation_address_hash}
end)
expect_address_in_response(string_implementation_address_hash)
assert {^string_implementation_address_hash, "proxy"} = SmartContract.get_implementation_address_hash(twin)
@ -297,6 +287,14 @@ defmodule Explorer.Chain.SmartContractTest do
end
end
describe "address_hash_to_smart_contract/1" do
test "fetches a smart contract" do
smart_contract = insert(:smart_contract, contract_code_md5: "123")
assert ^smart_contract = SmartContract.address_hash_to_smart_contract(smart_contract.address_hash)
end
end
def get_eip1967_implementation_zero_addresses do
EthereumJSONRPC.Mox
|> expect(:json_rpc, fn %{
@ -335,6 +333,18 @@ defmodule Explorer.Chain.SmartContractTest do
_options ->
{:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"}
end)
|> expect(:json_rpc, fn %{
id: 0,
method: "eth_getStorageAt",
params: [
_,
"0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7",
"latest"
]
},
_options ->
{:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"}
end)
end
def get_eip1967_implementation_non_zero_address do
@ -350,6 +360,30 @@ defmodule Explorer.Chain.SmartContractTest do
_options ->
{:ok, "0x0000000000000000000000000000000000000000000000000000000000000001"}
end)
|> expect(:json_rpc, fn %{
id: 0,
method: "eth_getStorageAt",
params: [
_,
"0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50",
"latest"
]
},
_options ->
{:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"}
end)
|> expect(:json_rpc, fn %{
id: 0,
method: "eth_getStorageAt",
params: [
_,
"0x7050c9e0f4ca769c69bd3a8ef740bc37934f8e2c036e5a723fd8ee048ed3f8c3",
"latest"
]
},
_options ->
{:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"}
end)
end
def get_eip1967_implementation_error_response do
@ -366,38 +400,534 @@ defmodule Explorer.Chain.SmartContractTest do
_options ->
{:error, "error"}
end)
|> expect(:json_rpc, fn %{
id: 0,
method: "eth_getStorageAt",
params: [
_,
"0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50",
"latest"
]
},
_options ->
{:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"}
end)
|> expect(:json_rpc, fn %{
id: 0,
method: "eth_getStorageAt",
params: [
_,
"0x7050c9e0f4ca769c69bd3a8ef740bc37934f8e2c036e5a723fd8ee048ed3f8c3",
"latest"
]
},
_options ->
{:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"}
end)
|> expect(:json_rpc, fn %{
id: 0,
method: "eth_getStorageAt",
params: [
_,
"0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7",
"latest"
]
},
_options ->
{:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"}
end)
end
def assert_empty_implementation(address_hash) do
contract = Chain.address_hash_to_smart_contract(address_hash)
contract = SmartContract.address_hash_to_smart_contract(address_hash)
assert contract.implementation_fetched_at
refute contract.implementation_name
refute contract.implementation_address_hash
end
def assert_implementation_never_fetched(address_hash) do
contract = Chain.address_hash_to_smart_contract(address_hash)
contract = SmartContract.address_hash_to_smart_contract(address_hash)
refute contract.implementation_fetched_at
refute contract.implementation_name
refute contract.implementation_address_hash
end
def assert_implementation_address(address_hash) do
contract = Chain.address_hash_to_smart_contract(address_hash)
contract = SmartContract.address_hash_to_smart_contract(address_hash)
assert contract.implementation_fetched_at
assert contract.implementation_address_hash
end
def assert_implementation_name(address_hash) do
contract = Chain.address_hash_to_smart_contract(address_hash)
contract = SmartContract.address_hash_to_smart_contract(address_hash)
assert contract.implementation_fetched_at
assert contract.implementation_name
end
def assert_exact_name_and_address(address_hash, implementation_address_hash, implementation_name) do
contract = Chain.address_hash_to_smart_contract(address_hash)
contract = SmartContract.address_hash_to_smart_contract(address_hash)
assert contract.implementation_fetched_at
assert contract.implementation_name == implementation_name
assert to_string(contract.implementation_address_hash) == to_string(implementation_address_hash)
end
describe "create_smart_contract/1" do
setup do
smart_contract_bytecode =
"0x608060405234801561001057600080fd5b5060df8061001f6000396000f3006080604052600436106049576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806360fe47b114604e5780636d4ce63c146078575b600080fd5b348015605957600080fd5b5060766004803603810190808035906020019092919050505060a0565b005b348015608357600080fd5b50608a60aa565b6040518082815260200191505060405180910390f35b8060008190555050565b600080549050905600a165627a7a7230582040d82a7379b1ee1632ad4d8a239954fd940277b25628ead95259a85c5eddb2120029"
created_contract_address =
insert(
:address,
hash: "0x0f95fa9bc0383e699325f2658d04e8d96d87b90c",
contract_code: smart_contract_bytecode
)
transaction =
:transaction
|> insert()
|> with_block()
insert(
:internal_transaction_create,
transaction: transaction,
index: 0,
created_contract_address: created_contract_address,
created_contract_code: smart_contract_bytecode,
block_number: transaction.block_number,
block_hash: transaction.block_hash,
block_index: 0,
transaction_index: transaction.index
)
valid_attrs = %{
address_hash: "0x0f95fa9bc0383e699325f2658d04e8d96d87b90c",
name: "SimpleStorage",
compiler_version: "0.4.23",
optimization: false,
contract_source_code:
"pragma solidity ^0.4.23; contract SimpleStorage {uint storedData; function set(uint x) public {storedData = x; } function get() public constant returns (uint) {return storedData; } }",
abi: [
%{
"constant" => false,
"inputs" => [%{"name" => "x", "type" => "uint256"}],
"name" => "set",
"outputs" => [],
"payable" => false,
"stateMutability" => "nonpayable",
"type" => "function"
},
%{
"constant" => true,
"inputs" => [],
"name" => "get",
"outputs" => [%{"name" => "", "type" => "uint256"}],
"payable" => false,
"stateMutability" => "view",
"type" => "function"
}
]
}
{:ok, valid_attrs: valid_attrs, address: created_contract_address}
end
test "with valid data creates a smart contract", %{valid_attrs: valid_attrs} do
assert {:ok, %SmartContract{} = smart_contract} = SmartContract.create_smart_contract(valid_attrs)
assert smart_contract.name == "SimpleStorage"
assert smart_contract.compiler_version == "0.4.23"
assert smart_contract.optimization == false
assert smart_contract.contract_source_code != ""
assert smart_contract.abi != ""
assert Repo.get_by(
Address.Name,
address_hash: smart_contract.address_hash,
name: smart_contract.name,
primary: true
)
end
test "clears an existing primary name and sets the new one", %{valid_attrs: valid_attrs, address: address} do
insert(:address_name, address: address, primary: true)
assert {:ok, %SmartContract{} = smart_contract} = SmartContract.create_smart_contract(valid_attrs)
assert Repo.get_by(
Address.Name,
address_hash: smart_contract.address_hash,
name: smart_contract.name,
primary: true
)
end
test "trims whitespace from address name", %{valid_attrs: valid_attrs} do
attrs = %{valid_attrs | name: " SimpleStorage "}
assert {:ok, _} = SmartContract.create_smart_contract(attrs)
assert Repo.get_by(Address.Name, name: "SimpleStorage")
end
test "sets the address verified field to true", %{valid_attrs: valid_attrs} do
assert {:ok, %SmartContract{} = smart_contract} = SmartContract.create_smart_contract(valid_attrs)
assert Repo.get_by(Address, hash: smart_contract.address_hash).verified == true
end
end
describe "update_smart_contract/1" do
setup do
smart_contract_bytecode =
"0x608060405234801561001057600080fd5b5060df8061001f6000396000f3006080604052600436106049576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806360fe47b114604e5780636d4ce63c146078575b600080fd5b348015605957600080fd5b5060766004803603810190808035906020019092919050505060a0565b005b348015608357600080fd5b50608a60aa565b6040518082815260200191505060405180910390f35b8060008190555050565b600080549050905600a165627a7a7230582040d82a7379b1ee1632ad4d8a239954fd940277b25628ead95259a85c5eddb2120029"
created_contract_address =
insert(
:address,
hash: "0x0f95fa9bc0383e699325f2658d04e8d96d87b90c",
contract_code: smart_contract_bytecode
)
transaction =
:transaction
|> insert()
|> with_block()
insert(
:internal_transaction_create,
transaction: transaction,
index: 0,
created_contract_address: created_contract_address,
created_contract_code: smart_contract_bytecode,
block_number: transaction.block_number,
block_hash: transaction.block_hash,
block_index: 0,
transaction_index: transaction.index
)
valid_attrs = %{
address_hash: "0x0f95fa9bc0383e699325f2658d04e8d96d87b90c",
name: "SimpleStorage",
compiler_version: "0.4.23",
optimization: false,
contract_source_code:
"pragma solidity ^0.4.23; contract SimpleStorage {uint storedData; function set(uint x) public {storedData = x; } function get() public constant returns (uint) {return storedData; } }",
abi: [
%{
"constant" => false,
"inputs" => [%{"name" => "x", "type" => "uint256"}],
"name" => "set",
"outputs" => [],
"payable" => false,
"stateMutability" => "nonpayable",
"type" => "function"
},
%{
"constant" => true,
"inputs" => [],
"name" => "get",
"outputs" => [%{"name" => "", "type" => "uint256"}],
"payable" => false,
"stateMutability" => "view",
"type" => "function"
}
],
partially_verified: true
}
secondary_sources = [
%{
file_name: "storage.sol",
contract_source_code:
"pragma solidity >=0.7.0 <0.9.0;contract Storage {uint256 number;function store(uint256 num) public {number = num;}function retrieve_() public view returns (uint256){return number;}}",
address_hash: "0x0f95fa9bc0383e699325f2658d04e8d96d87b90c"
},
%{
file_name: "storage_1.sol",
contract_source_code:
"pragma solidity >=0.7.0 <0.9.0;contract Storage_1 {uint256 number;function store(uint256 num) public {number = num;}function retrieve_() public view returns (uint256){return number;}}",
address_hash: "0x0f95fa9bc0383e699325f2658d04e8d96d87b90c"
}
]
changed_sources = [
%{
file_name: "storage_2.sol",
contract_source_code:
"pragma solidity >=0.7.0 <0.9.0;contract Storage_2 {uint256 number;function store(uint256 num) public {number = num;}function retrieve_() public view returns (uint256){return number;}}",
address_hash: "0x0f95fa9bc0383e699325f2658d04e8d96d87b90c"
},
%{
file_name: "storage_3.sol",
contract_source_code:
"pragma solidity >=0.7.0 <0.9.0;contract Storage_3 {uint256 number;function store(uint256 num) public {number = num;}function retrieve_() public view returns (uint256){return number;}}",
address_hash: "0x0f95fa9bc0383e699325f2658d04e8d96d87b90c"
}
]
_ = SmartContract.create_smart_contract(valid_attrs, [], secondary_sources)
{:ok,
valid_attrs: valid_attrs,
address: created_contract_address,
secondary_sources: secondary_sources,
changed_sources: changed_sources}
end
test "change partially_verified field", %{valid_attrs: valid_attrs, address: address} do
sc_before_call = Repo.get_by(SmartContract, address_hash: address.hash)
assert sc_before_call.name == Map.get(valid_attrs, :name)
assert sc_before_call.partially_verified == Map.get(valid_attrs, :partially_verified)
assert {:ok, %SmartContract{}} =
SmartContract.update_smart_contract(%{
address_hash: address.hash,
partially_verified: false,
contract_source_code: "new code"
})
sc_after_call = Repo.get_by(SmartContract, address_hash: address.hash)
assert sc_after_call.name == Map.get(valid_attrs, :name)
assert sc_after_call.partially_verified == false
assert sc_after_call.compiler_version == Map.get(valid_attrs, :compiler_version)
assert sc_after_call.optimization == Map.get(valid_attrs, :optimization)
assert sc_after_call.contract_source_code == "new code"
end
test "check nothing changed", %{valid_attrs: valid_attrs, address: address} do
sc_before_call = Repo.get_by(SmartContract, address_hash: address.hash)
assert sc_before_call.name == Map.get(valid_attrs, :name)
assert sc_before_call.partially_verified == Map.get(valid_attrs, :partially_verified)
assert {:ok, %SmartContract{}} = SmartContract.update_smart_contract(%{address_hash: address.hash})
sc_after_call = Repo.get_by(SmartContract, address_hash: address.hash)
assert sc_after_call.name == Map.get(valid_attrs, :name)
assert sc_after_call.partially_verified == Map.get(valid_attrs, :partially_verified)
assert sc_after_call.compiler_version == Map.get(valid_attrs, :compiler_version)
assert sc_after_call.optimization == Map.get(valid_attrs, :optimization)
assert sc_after_call.contract_source_code == Map.get(valid_attrs, :contract_source_code)
end
test "check additional sources update", %{
address: address,
secondary_sources: secondary_sources,
changed_sources: changed_sources
} do
sc_before_call = Repo.get_by(Address, hash: address.hash) |> Repo.preload(:smart_contract_additional_sources)
assert sc_before_call.smart_contract_additional_sources
|> Enum.with_index()
|> Enum.all?(fn {el, ind} ->
{:ok, src} = Enum.fetch(secondary_sources, ind)
el.file_name == Map.get(src, :file_name) and
el.contract_source_code == Map.get(src, :contract_source_code)
end)
assert {:ok, %SmartContract{}} =
SmartContract.update_smart_contract(%{address_hash: address.hash}, [], changed_sources)
sc_after_call = Repo.get_by(Address, hash: address.hash) |> Repo.preload(:smart_contract_additional_sources)
assert sc_after_call.smart_contract_additional_sources
|> Enum.with_index()
|> Enum.all?(fn {el, ind} ->
{:ok, src} = Enum.fetch(changed_sources, ind)
el.file_name == Map.get(src, :file_name) and
el.contract_source_code == Map.get(src, :contract_source_code)
end)
end
end
test "get_smart_contract_abi/1 returns empty [] abi if implementation address is null" do
assert SmartContract.get_smart_contract_abi(nil) == []
end
test "get_smart_contract_abi/1 returns [] if implementation is not verified" do
implementation_contract_address = insert(:contract_address)
implementation_contract_address_hash_string =
Base.encode16(implementation_contract_address.hash.bytes, case: :lower)
assert SmartContract.get_smart_contract_abi("0x" <> implementation_contract_address_hash_string) == []
end
@abi [
%{
"constant" => false,
"inputs" => [%{"name" => "x", "type" => "uint256"}],
"name" => "set",
"outputs" => [],
"payable" => false,
"stateMutability" => "nonpayable",
"type" => "function"
},
%{
"constant" => true,
"inputs" => [],
"name" => "get",
"outputs" => [%{"name" => "", "type" => "uint256"}],
"payable" => false,
"stateMutability" => "view",
"type" => "function"
}
]
@proxy_abi [
%{
"type" => "function",
"stateMutability" => "nonpayable",
"payable" => false,
"outputs" => [%{"type" => "bool", "name" => ""}],
"name" => "upgradeTo",
"inputs" => [%{"type" => "address", "name" => "newImplementation"}],
"constant" => false
},
%{
"type" => "function",
"stateMutability" => "view",
"payable" => false,
"outputs" => [%{"type" => "uint256", "name" => ""}],
"name" => "version",
"inputs" => [],
"constant" => true
},
%{
"type" => "function",
"stateMutability" => "view",
"payable" => false,
"outputs" => [%{"type" => "address", "name" => ""}],
"name" => "implementation",
"inputs" => [],
"constant" => true
},
%{
"type" => "function",
"stateMutability" => "nonpayable",
"payable" => false,
"outputs" => [],
"name" => "renounceOwnership",
"inputs" => [],
"constant" => false
},
%{
"type" => "function",
"stateMutability" => "view",
"payable" => false,
"outputs" => [%{"type" => "address", "name" => ""}],
"name" => "getOwner",
"inputs" => [],
"constant" => true
},
%{
"type" => "function",
"stateMutability" => "view",
"payable" => false,
"outputs" => [%{"type" => "address", "name" => ""}],
"name" => "getProxyStorage",
"inputs" => [],
"constant" => true
},
%{
"type" => "function",
"stateMutability" => "nonpayable",
"payable" => false,
"outputs" => [],
"name" => "transferOwnership",
"inputs" => [%{"type" => "address", "name" => "_newOwner"}],
"constant" => false
},
%{
"type" => "constructor",
"stateMutability" => "nonpayable",
"payable" => false,
"inputs" => [
%{"type" => "address", "name" => "_proxyStorage"},
%{"type" => "address", "name" => "_implementationAddress"}
]
},
%{"type" => "fallback", "stateMutability" => "nonpayable", "payable" => false},
%{
"type" => "event",
"name" => "Upgraded",
"inputs" => [
%{"type" => "uint256", "name" => "version", "indexed" => false},
%{"type" => "address", "name" => "implementation", "indexed" => true}
],
"anonymous" => false
},
%{
"type" => "event",
"name" => "OwnershipRenounced",
"inputs" => [%{"type" => "address", "name" => "previousOwner", "indexed" => true}],
"anonymous" => false
},
%{
"type" => "event",
"name" => "OwnershipTransferred",
"inputs" => [
%{"type" => "address", "name" => "previousOwner", "indexed" => true},
%{"type" => "address", "name" => "newOwner", "indexed" => true}
],
"anonymous" => false
}
]
test "get_smart_contract_abi/1 returns implementation abi if implementation is verified" do
proxy_contract_address = insert(:contract_address)
insert(:smart_contract, address_hash: proxy_contract_address.hash, abi: @proxy_abi, contract_code_md5: "123")
implementation_contract_address = insert(:contract_address)
insert(:smart_contract,
address_hash: implementation_contract_address.hash,
abi: @abi,
contract_code_md5: "123"
)
implementation_contract_address_hash_string =
Base.encode16(implementation_contract_address.hash.bytes, case: :lower)
implementation_abi = SmartContract.get_smart_contract_abi("0x" <> implementation_contract_address_hash_string)
assert implementation_abi == @abi
end
defp expect_address_in_response(string_implementation_address_hash) do
expect(EthereumJSONRPC.Mox, :json_rpc, fn %{
id: 0,
method: "eth_getStorageAt",
params: [
_,
"0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc",
"latest"
]
},
_options ->
{:ok, string_implementation_address_hash}
end)
|> expect(:json_rpc, fn %{
id: 0,
method: "eth_getStorageAt",
params: [
_,
"0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50",
"latest"
]
},
_options ->
{:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"}
end)
|> expect(:json_rpc, fn %{
id: 0,
method: "eth_getStorageAt",
params: [
_,
"0x7050c9e0f4ca769c69bd3a8ef740bc37934f8e2c036e5a723fd8ee048ed3f8c3",
"latest"
]
},
_options ->
{:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"}
end)
end
end

@ -265,7 +265,7 @@ defmodule Explorer.Chain.TransactionTest do
|> insert()
|> Repo.preload(to_address: :smart_contract)
get_eip1967_implementation()
request_zero_implementations()
assert {{:ok, "60fe47b1", "set(uint256 x)", [{"x", "uint256", 50}]}, _, _} =
Transaction.decoded_input_data(transaction, [])
@ -288,7 +288,7 @@ defmodule Explorer.Chain.TransactionTest do
|> insert(to_address: contract.address, input: "0x" <> input_data)
|> Repo.preload(to_address: :smart_contract)
get_eip1967_implementation()
request_zero_implementations()
assert {{:ok, "60fe47b1", "set(uint256 x)", [{"x", "uint256", 10}]}, _, _} =
Transaction.decoded_input_data(transaction, [])
@ -309,7 +309,8 @@ defmodule Explorer.Chain.TransactionTest do
end
end
def get_eip1967_implementation do
# EIP-1967 + EIP-1822
defp request_zero_implementations do
EthereumJSONRPC.Mox
|> expect(:json_rpc, fn %{
id: 0,
@ -347,5 +348,17 @@ defmodule Explorer.Chain.TransactionTest do
_options ->
{:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"}
end)
|> expect(:json_rpc, fn %{
id: 0,
method: "eth_getStorageAt",
params: [
_,
"0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7",
"latest"
]
},
_options ->
{:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"}
end)
end
end

@ -4036,272 +4036,6 @@ defmodule Explorer.ChainTest do
end
end
describe "create_smart_contract/1" do
setup do
smart_contract_bytecode =
"0x608060405234801561001057600080fd5b5060df8061001f6000396000f3006080604052600436106049576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806360fe47b114604e5780636d4ce63c146078575b600080fd5b348015605957600080fd5b5060766004803603810190808035906020019092919050505060a0565b005b348015608357600080fd5b50608a60aa565b6040518082815260200191505060405180910390f35b8060008190555050565b600080549050905600a165627a7a7230582040d82a7379b1ee1632ad4d8a239954fd940277b25628ead95259a85c5eddb2120029"
created_contract_address =
insert(
:address,
hash: "0x0f95fa9bc0383e699325f2658d04e8d96d87b90c",
contract_code: smart_contract_bytecode
)
transaction =
:transaction
|> insert()
|> with_block()
insert(
:internal_transaction_create,
transaction: transaction,
index: 0,
created_contract_address: created_contract_address,
created_contract_code: smart_contract_bytecode,
block_number: transaction.block_number,
block_hash: transaction.block_hash,
block_index: 0,
transaction_index: transaction.index
)
valid_attrs = %{
address_hash: "0x0f95fa9bc0383e699325f2658d04e8d96d87b90c",
name: "SimpleStorage",
compiler_version: "0.4.23",
optimization: false,
contract_source_code:
"pragma solidity ^0.4.23; contract SimpleStorage {uint storedData; function set(uint x) public {storedData = x; } function get() public constant returns (uint) {return storedData; } }",
abi: [
%{
"constant" => false,
"inputs" => [%{"name" => "x", "type" => "uint256"}],
"name" => "set",
"outputs" => [],
"payable" => false,
"stateMutability" => "nonpayable",
"type" => "function"
},
%{
"constant" => true,
"inputs" => [],
"name" => "get",
"outputs" => [%{"name" => "", "type" => "uint256"}],
"payable" => false,
"stateMutability" => "view",
"type" => "function"
}
]
}
{:ok, valid_attrs: valid_attrs, address: created_contract_address}
end
test "with valid data creates a smart contract", %{valid_attrs: valid_attrs} do
assert {:ok, %SmartContract{} = smart_contract} = Chain.create_smart_contract(valid_attrs)
assert smart_contract.name == "SimpleStorage"
assert smart_contract.compiler_version == "0.4.23"
assert smart_contract.optimization == false
assert smart_contract.contract_source_code != ""
assert smart_contract.abi != ""
assert Repo.get_by(
Address.Name,
address_hash: smart_contract.address_hash,
name: smart_contract.name,
primary: true
)
end
test "clears an existing primary name and sets the new one", %{valid_attrs: valid_attrs, address: address} do
insert(:address_name, address: address, primary: true)
assert {:ok, %SmartContract{} = smart_contract} = Chain.create_smart_contract(valid_attrs)
assert Repo.get_by(
Address.Name,
address_hash: smart_contract.address_hash,
name: smart_contract.name,
primary: true
)
end
test "trims whitespace from address name", %{valid_attrs: valid_attrs} do
attrs = %{valid_attrs | name: " SimpleStorage "}
assert {:ok, _} = Chain.create_smart_contract(attrs)
assert Repo.get_by(Address.Name, name: "SimpleStorage")
end
test "sets the address verified field to true", %{valid_attrs: valid_attrs} do
assert {:ok, %SmartContract{} = smart_contract} = Chain.create_smart_contract(valid_attrs)
assert Repo.get_by(Address, hash: smart_contract.address_hash).verified == true
end
end
describe "update_smart_contract/1" do
setup do
smart_contract_bytecode =
"0x608060405234801561001057600080fd5b5060df8061001f6000396000f3006080604052600436106049576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806360fe47b114604e5780636d4ce63c146078575b600080fd5b348015605957600080fd5b5060766004803603810190808035906020019092919050505060a0565b005b348015608357600080fd5b50608a60aa565b6040518082815260200191505060405180910390f35b8060008190555050565b600080549050905600a165627a7a7230582040d82a7379b1ee1632ad4d8a239954fd940277b25628ead95259a85c5eddb2120029"
created_contract_address =
insert(
:address,
hash: "0x0f95fa9bc0383e699325f2658d04e8d96d87b90c",
contract_code: smart_contract_bytecode
)
transaction =
:transaction
|> insert()
|> with_block()
insert(
:internal_transaction_create,
transaction: transaction,
index: 0,
created_contract_address: created_contract_address,
created_contract_code: smart_contract_bytecode,
block_number: transaction.block_number,
block_hash: transaction.block_hash,
block_index: 0,
transaction_index: transaction.index
)
valid_attrs = %{
address_hash: "0x0f95fa9bc0383e699325f2658d04e8d96d87b90c",
name: "SimpleStorage",
compiler_version: "0.4.23",
optimization: false,
contract_source_code:
"pragma solidity ^0.4.23; contract SimpleStorage {uint storedData; function set(uint x) public {storedData = x; } function get() public constant returns (uint) {return storedData; } }",
abi: [
%{
"constant" => false,
"inputs" => [%{"name" => "x", "type" => "uint256"}],
"name" => "set",
"outputs" => [],
"payable" => false,
"stateMutability" => "nonpayable",
"type" => "function"
},
%{
"constant" => true,
"inputs" => [],
"name" => "get",
"outputs" => [%{"name" => "", "type" => "uint256"}],
"payable" => false,
"stateMutability" => "view",
"type" => "function"
}
],
partially_verified: true
}
secondary_sources = [
%{
file_name: "storage.sol",
contract_source_code:
"pragma solidity >=0.7.0 <0.9.0;contract Storage {uint256 number;function store(uint256 num) public {number = num;}function retrieve_() public view returns (uint256){return number;}}",
address_hash: "0x0f95fa9bc0383e699325f2658d04e8d96d87b90c"
},
%{
file_name: "storage_1.sol",
contract_source_code:
"pragma solidity >=0.7.0 <0.9.0;contract Storage_1 {uint256 number;function store(uint256 num) public {number = num;}function retrieve_() public view returns (uint256){return number;}}",
address_hash: "0x0f95fa9bc0383e699325f2658d04e8d96d87b90c"
}
]
changed_sources = [
%{
file_name: "storage_2.sol",
contract_source_code:
"pragma solidity >=0.7.0 <0.9.0;contract Storage_2 {uint256 number;function store(uint256 num) public {number = num;}function retrieve_() public view returns (uint256){return number;}}",
address_hash: "0x0f95fa9bc0383e699325f2658d04e8d96d87b90c"
},
%{
file_name: "storage_3.sol",
contract_source_code:
"pragma solidity >=0.7.0 <0.9.0;contract Storage_3 {uint256 number;function store(uint256 num) public {number = num;}function retrieve_() public view returns (uint256){return number;}}",
address_hash: "0x0f95fa9bc0383e699325f2658d04e8d96d87b90c"
}
]
_ = Chain.create_smart_contract(valid_attrs, [], secondary_sources)
{:ok,
valid_attrs: valid_attrs,
address: created_contract_address,
secondary_sources: secondary_sources,
changed_sources: changed_sources}
end
test "change partially_verified field", %{valid_attrs: valid_attrs, address: address} do
sc_before_call = Repo.get_by(SmartContract, address_hash: address.hash)
assert sc_before_call.name == Map.get(valid_attrs, :name)
assert sc_before_call.partially_verified == Map.get(valid_attrs, :partially_verified)
assert {:ok, %SmartContract{}} =
Chain.update_smart_contract(%{
address_hash: address.hash,
partially_verified: false,
contract_source_code: "new code"
})
sc_after_call = Repo.get_by(SmartContract, address_hash: address.hash)
assert sc_after_call.name == Map.get(valid_attrs, :name)
assert sc_after_call.partially_verified == false
assert sc_after_call.compiler_version == Map.get(valid_attrs, :compiler_version)
assert sc_after_call.optimization == Map.get(valid_attrs, :optimization)
assert sc_after_call.contract_source_code == "new code"
end
test "check nothing changed", %{valid_attrs: valid_attrs, address: address} do
sc_before_call = Repo.get_by(SmartContract, address_hash: address.hash)
assert sc_before_call.name == Map.get(valid_attrs, :name)
assert sc_before_call.partially_verified == Map.get(valid_attrs, :partially_verified)
assert {:ok, %SmartContract{}} = Chain.update_smart_contract(%{address_hash: address.hash})
sc_after_call = Repo.get_by(SmartContract, address_hash: address.hash)
assert sc_after_call.name == Map.get(valid_attrs, :name)
assert sc_after_call.partially_verified == Map.get(valid_attrs, :partially_verified)
assert sc_after_call.compiler_version == Map.get(valid_attrs, :compiler_version)
assert sc_after_call.optimization == Map.get(valid_attrs, :optimization)
assert sc_after_call.contract_source_code == Map.get(valid_attrs, :contract_source_code)
end
test "check additional sources update", %{
address: address,
secondary_sources: secondary_sources,
changed_sources: changed_sources
} do
sc_before_call = Repo.get_by(Address, hash: address.hash) |> Repo.preload(:smart_contract_additional_sources)
assert sc_before_call.smart_contract_additional_sources
|> Enum.with_index()
|> Enum.all?(fn {el, ind} ->
{:ok, src} = Enum.fetch(secondary_sources, ind)
el.file_name == Map.get(src, :file_name) and
el.contract_source_code == Map.get(src, :contract_source_code)
end)
assert {:ok, %SmartContract{}} = Chain.update_smart_contract(%{address_hash: address.hash}, [], changed_sources)
sc_after_call = Repo.get_by(Address, hash: address.hash) |> Repo.preload(:smart_contract_additional_sources)
assert sc_after_call.smart_contract_additional_sources
|> Enum.with_index()
|> Enum.all?(fn {el, ind} ->
{:ok, src} = Enum.fetch(changed_sources, ind)
el.file_name == Map.get(src, :file_name) and
el.contract_source_code == Map.get(src, :contract_source_code)
end)
end
end
describe "stream_unfetched_balances/2" do
test "with `t:Explorer.Chain.Address.CoinBalance.t/0` with value_fetched_at with same `address_hash` and `block_number` " <>
"does not return `t:Explorer.Chain.Block.t/0` `miner_hash`" do
@ -4816,14 +4550,6 @@ defmodule Explorer.ChainTest do
assert Chain.circulating_supply() == ProofOfAuthority.circulating()
end
describe "address_hash_to_smart_contract/1" do
test "fetches a smart contract" do
smart_contract = insert(:smart_contract, contract_code_md5: "123")
assert ^smart_contract = Chain.address_hash_to_smart_contract(smart_contract.address_hash)
end
end
describe "token_from_address_hash/1" do
test "with valid hash" do
token = insert(:token)
@ -5797,373 +5523,4 @@ defmodule Explorer.ChainTest do
assert [%SmartContract{address_hash: ^hash}] = Chain.verified_contracts(search: contract_name)
end
end
describe "proxy contracts features" do
@proxy_abi [
%{
"type" => "function",
"stateMutability" => "nonpayable",
"payable" => false,
"outputs" => [%{"type" => "bool", "name" => ""}],
"name" => "upgradeTo",
"inputs" => [%{"type" => "address", "name" => "newImplementation"}],
"constant" => false
},
%{
"type" => "function",
"stateMutability" => "view",
"payable" => false,
"outputs" => [%{"type" => "uint256", "name" => ""}],
"name" => "version",
"inputs" => [],
"constant" => true
},
%{
"type" => "function",
"stateMutability" => "view",
"payable" => false,
"outputs" => [%{"type" => "address", "name" => ""}],
"name" => "implementation",
"inputs" => [],
"constant" => true
},
%{
"type" => "function",
"stateMutability" => "nonpayable",
"payable" => false,
"outputs" => [],
"name" => "renounceOwnership",
"inputs" => [],
"constant" => false
},
%{
"type" => "function",
"stateMutability" => "view",
"payable" => false,
"outputs" => [%{"type" => "address", "name" => ""}],
"name" => "getOwner",
"inputs" => [],
"constant" => true
},
%{
"type" => "function",
"stateMutability" => "view",
"payable" => false,
"outputs" => [%{"type" => "address", "name" => ""}],
"name" => "getProxyStorage",
"inputs" => [],
"constant" => true
},
%{
"type" => "function",
"stateMutability" => "nonpayable",
"payable" => false,
"outputs" => [],
"name" => "transferOwnership",
"inputs" => [%{"type" => "address", "name" => "_newOwner"}],
"constant" => false
},
%{
"type" => "constructor",
"stateMutability" => "nonpayable",
"payable" => false,
"inputs" => [
%{"type" => "address", "name" => "_proxyStorage"},
%{"type" => "address", "name" => "_implementationAddress"}
]
},
%{"type" => "fallback", "stateMutability" => "nonpayable", "payable" => false},
%{
"type" => "event",
"name" => "Upgraded",
"inputs" => [
%{"type" => "uint256", "name" => "version", "indexed" => false},
%{"type" => "address", "name" => "implementation", "indexed" => true}
],
"anonymous" => false
},
%{
"type" => "event",
"name" => "OwnershipRenounced",
"inputs" => [%{"type" => "address", "name" => "previousOwner", "indexed" => true}],
"anonymous" => false
},
%{
"type" => "event",
"name" => "OwnershipTransferred",
"inputs" => [
%{"type" => "address", "name" => "previousOwner", "indexed" => true},
%{"type" => "address", "name" => "newOwner", "indexed" => true}
],
"anonymous" => false
}
]
@implementation_abi [
%{
"constant" => false,
"inputs" => [%{"name" => "x", "type" => "uint256"}],
"name" => "set",
"outputs" => [],
"payable" => false,
"stateMutability" => "nonpayable",
"type" => "function"
},
%{
"constant" => true,
"inputs" => [],
"name" => "get",
"outputs" => [%{"name" => "", "type" => "uint256"}],
"payable" => false,
"stateMutability" => "view",
"type" => "function"
}
]
test "combine_proxy_implementation_abi/2 returns empty [] abi if proxy abi is null" do
proxy_contract_address = insert(:contract_address)
assert Chain.combine_proxy_implementation_abi(%SmartContract{address_hash: proxy_contract_address.hash, abi: nil}) ==
[]
end
test "combine_proxy_implementation_abi/2 returns [] abi for unverified proxy" do
proxy_contract_address = insert(:contract_address)
smart_contract =
insert(:smart_contract, address_hash: proxy_contract_address.hash, abi: [], contract_code_md5: "123")
get_eip1967_implementation()
assert Chain.combine_proxy_implementation_abi(smart_contract) == []
end
test "combine_proxy_implementation_abi/2 returns proxy abi if implementation is not verified" do
proxy_contract_address = insert(:contract_address)
smart_contract =
insert(:smart_contract, address_hash: proxy_contract_address.hash, abi: @proxy_abi, contract_code_md5: "123")
assert Chain.combine_proxy_implementation_abi(smart_contract) == @proxy_abi
end
test "combine_proxy_implementation_abi/2 returns proxy + implementation abi if implementation is verified" do
proxy_contract_address = insert(:contract_address)
smart_contract =
insert(:smart_contract, address_hash: proxy_contract_address.hash, abi: @proxy_abi, contract_code_md5: "123")
implementation_contract_address = insert(:contract_address)
insert(:smart_contract,
address_hash: implementation_contract_address.hash,
abi: @implementation_abi,
contract_code_md5: "123"
)
implementation_contract_address_hash_string =
Base.encode16(implementation_contract_address.hash.bytes, case: :lower)
expect(
EthereumJSONRPC.Mox,
:json_rpc,
fn [%{id: id, method: _, params: [%{data: _, to: _}, _]}], _options ->
{:ok,
[
%{
id: id,
jsonrpc: "2.0",
result: "0x000000000000000000000000" <> implementation_contract_address_hash_string
}
]}
end
)
combined_abi = Chain.combine_proxy_implementation_abi(smart_contract)
assert Enum.any?(@proxy_abi, fn el -> el == Enum.at(@implementation_abi, 0) end) == false
assert Enum.any?(@proxy_abi, fn el -> el == Enum.at(@implementation_abi, 1) end) == false
assert Enum.any?(combined_abi, fn el -> el == Enum.at(@implementation_abi, 0) end) == true
assert Enum.any?(combined_abi, fn el -> el == Enum.at(@implementation_abi, 1) end) == true
end
test "get_implementation_abi_from_proxy/2 returns empty [] abi if proxy abi is null" do
proxy_contract_address = insert(:contract_address)
assert Chain.get_implementation_abi_from_proxy(
%SmartContract{address_hash: proxy_contract_address.hash, abi: nil},
[]
) ==
[]
end
test "get_implementation_abi_from_proxy/2 returns [] abi for unverified proxy" do
proxy_contract_address = insert(:contract_address)
smart_contract =
insert(:smart_contract, address_hash: proxy_contract_address.hash, abi: [], contract_code_md5: "123")
get_eip1967_implementation()
assert Chain.combine_proxy_implementation_abi(smart_contract) == []
end
test "get_implementation_abi_from_proxy/2 returns [] if implementation is not verified" do
proxy_contract_address = insert(:contract_address)
smart_contract =
insert(:smart_contract, address_hash: proxy_contract_address.hash, abi: @proxy_abi, contract_code_md5: "123")
assert Chain.get_implementation_abi_from_proxy(smart_contract, []) == []
end
test "get_implementation_abi_from_proxy/2 returns implementation abi if implementation is verified" do
proxy_contract_address = insert(:contract_address)
smart_contract =
insert(:smart_contract, address_hash: proxy_contract_address.hash, abi: @proxy_abi, contract_code_md5: "123")
implementation_contract_address = insert(:contract_address)
insert(:smart_contract,
address_hash: implementation_contract_address.hash,
abi: @implementation_abi,
contract_code_md5: "123"
)
implementation_contract_address_hash_string =
Base.encode16(implementation_contract_address.hash.bytes, case: :lower)
expect(
EthereumJSONRPC.Mox,
:json_rpc,
fn [%{id: id, method: _, params: [%{data: _, to: _}, _]}], _options ->
{:ok,
[
%{
id: id,
jsonrpc: "2.0",
result: "0x000000000000000000000000" <> implementation_contract_address_hash_string
}
]}
end
)
implementation_abi = Chain.get_implementation_abi_from_proxy(smart_contract, [])
assert implementation_abi == @implementation_abi
end
test "get_implementation_abi_from_proxy/2 returns implementation abi in case of EIP-1967 proxy pattern" do
proxy_contract_address = insert(:contract_address)
smart_contract =
insert(:smart_contract, address_hash: proxy_contract_address.hash, abi: [], contract_code_md5: "123")
implementation_contract_address = insert(:contract_address)
insert(:smart_contract,
address_hash: implementation_contract_address.hash,
abi: @implementation_abi,
contract_code_md5: "123"
)
implementation_contract_address_hash_string =
Base.encode16(implementation_contract_address.hash.bytes, case: :lower)
expect(
EthereumJSONRPC.Mox,
:json_rpc,
fn %{
id: _id,
method: "eth_getStorageAt",
params: [
_,
"0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc",
"latest"
]
},
_options ->
{:ok, "0x000000000000000000000000" <> implementation_contract_address_hash_string}
end
)
implementation_abi = Chain.get_implementation_abi_from_proxy(smart_contract, [])
assert implementation_abi == @implementation_abi
end
test "get_implementation_abi/1 returns empty [] abi if implementation address is null" do
assert Chain.get_implementation_abi(nil) == []
end
test "get_implementation_abi/1 returns [] if implementation is not verified" do
implementation_contract_address = insert(:contract_address)
implementation_contract_address_hash_string =
Base.encode16(implementation_contract_address.hash.bytes, case: :lower)
assert Chain.get_implementation_abi("0x" <> implementation_contract_address_hash_string) == []
end
test "get_implementation_abi/1 returns implementation abi if implementation is verified" do
proxy_contract_address = insert(:contract_address)
insert(:smart_contract, address_hash: proxy_contract_address.hash, abi: @proxy_abi, contract_code_md5: "123")
implementation_contract_address = insert(:contract_address)
insert(:smart_contract,
address_hash: implementation_contract_address.hash,
abi: @implementation_abi,
contract_code_md5: "123"
)
implementation_contract_address_hash_string =
Base.encode16(implementation_contract_address.hash.bytes, case: :lower)
implementation_abi = Chain.get_implementation_abi("0x" <> implementation_contract_address_hash_string)
assert implementation_abi == @implementation_abi
end
end
def get_eip1967_implementation do
EthereumJSONRPC.Mox
|> expect(:json_rpc, fn %{
id: 0,
method: "eth_getStorageAt",
params: [
_,
"0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc",
"latest"
]
},
_options ->
{:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"}
end)
|> expect(:json_rpc, fn %{
id: 0,
method: "eth_getStorageAt",
params: [
_,
"0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50",
"latest"
]
},
_options ->
{:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"}
end)
|> expect(:json_rpc, fn %{
id: 0,
method: "eth_getStorageAt",
params: [
_,
"0x7050c9e0f4ca769c69bd3a8ef740bc37934f8e2c036e5a723fd8ee048ed3f8c3",
"latest"
]
},
_options ->
{:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"}
end)
end
end

@ -33,7 +33,6 @@ defmodule Indexer.Block.Fetcher do
alias Indexer.Transform.{
AddressCoinBalances,
AddressCoinBalancesDaily,
Addresses,
AddressTokenBalances,
MintTransfers,

@ -346,6 +346,7 @@
"prederive",
"prederived",
"progressbar",
"proxiable",
"psql",
"purrstige",
"qdai",
@ -490,6 +491,7 @@
"upserts",
"urijs",
"Utqn",
"UUPS",
"valign",
"valuemax",
"valuemin",

Loading…
Cancel
Save