feat: enhance /api/v2/smart-contracts/:hash API endpoint (#10558)

* feat: enhance /api/v2/smart-contracts/:hash API endpoint

* Refactor implementation fetch

* Refactoring address controller

* Process review comment

* Fix address overview template

* Update apps/block_scout_web/lib/block_scout_web/controllers/api/v2/address_controller.ex

Co-authored-by: nikitosing <32202610+nikitosing@users.noreply.github.com>

---------

Co-authored-by: nikitosing <32202610+nikitosing@users.noreply.github.com>
pull/10241/head
Victor Baranov 3 months ago committed by GitHub
parent 5785ea89be
commit 8eb6f08f1e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 18
      apps/block_scout_web/lib/block_scout_web/controllers/api/v2/address_controller.ex
  2. 11
      apps/block_scout_web/lib/block_scout_web/controllers/api/v2/smart_contract_controller.ex
  3. 2
      apps/block_scout_web/lib/block_scout_web/templates/address/_tabs.html.eex
  4. 8
      apps/block_scout_web/lib/block_scout_web/templates/address/overview.html.eex
  5. 2
      apps/block_scout_web/lib/block_scout_web/templates/address_coin_balance/index.html.eex
  6. 22
      apps/block_scout_web/lib/block_scout_web/templates/address_contract/index.html.eex
  7. 2
      apps/block_scout_web/lib/block_scout_web/templates/address_decompiled_contract/index.html.eex
  8. 2
      apps/block_scout_web/lib/block_scout_web/templates/address_internal_transaction/index.html.eex
  9. 2
      apps/block_scout_web/lib/block_scout_web/templates/address_logs/index.html.eex
  10. 2
      apps/block_scout_web/lib/block_scout_web/templates/address_read_contract/index.html.eex
  11. 2
      apps/block_scout_web/lib/block_scout_web/templates/address_read_proxy/index.html.eex
  12. 2
      apps/block_scout_web/lib/block_scout_web/templates/address_token/index.html.eex
  13. 2
      apps/block_scout_web/lib/block_scout_web/templates/address_token_transfer/index.html.eex
  14. 2
      apps/block_scout_web/lib/block_scout_web/templates/address_transaction/index.html.eex
  15. 2
      apps/block_scout_web/lib/block_scout_web/templates/address_validation/index.html.eex
  16. 10
      apps/block_scout_web/lib/block_scout_web/templates/address_withdrawal/index.html.eex
  17. 2
      apps/block_scout_web/lib/block_scout_web/templates/address_write_contract/index.html.eex
  18. 2
      apps/block_scout_web/lib/block_scout_web/templates/address_write_proxy/index.html.eex
  19. 2
      apps/block_scout_web/lib/block_scout_web/templates/block_withdrawal/_withdrawal.html.eex
  20. 2
      apps/block_scout_web/lib/block_scout_web/templates/tokens/holder/_token_balances.html.eex
  21. 4
      apps/block_scout_web/lib/block_scout_web/templates/tokens/transfer/_token_transfer.html.eex
  22. 8
      apps/block_scout_web/lib/block_scout_web/templates/transaction/_actions.html.eex
  23. 4
      apps/block_scout_web/lib/block_scout_web/templates/transaction/_pending_tile.html.eex
  24. 4
      apps/block_scout_web/lib/block_scout_web/templates/transaction/_total_transfers_from_to.html.eex
  25. 8
      apps/block_scout_web/lib/block_scout_web/templates/transaction/overview.html.eex
  26. 4
      apps/block_scout_web/lib/block_scout_web/templates/transaction_state/_state_change.html.eex
  27. 4
      apps/block_scout_web/lib/block_scout_web/templates/transaction_token_transfer/_token_transfer.html.eex
  28. 2
      apps/block_scout_web/lib/block_scout_web/templates/withdrawal/_withdrawal.html.eex
  29. 1
      apps/block_scout_web/lib/block_scout_web/views/address_coin_balance_view.ex
  30. 1
      apps/block_scout_web/lib/block_scout_web/views/address_contract_view.ex
  31. 1
      apps/block_scout_web/lib/block_scout_web/views/address_decompiled_contract_view.ex
  32. 1
      apps/block_scout_web/lib/block_scout_web/views/address_internal_transaction_view.ex
  33. 1
      apps/block_scout_web/lib/block_scout_web/views/address_logs_view.ex
  34. 2
      apps/block_scout_web/lib/block_scout_web/views/address_read_contract_view.ex
  35. 2
      apps/block_scout_web/lib/block_scout_web/views/address_read_proxy_view.ex
  36. 1
      apps/block_scout_web/lib/block_scout_web/views/address_token_transfer_view.ex
  37. 1
      apps/block_scout_web/lib/block_scout_web/views/address_token_view.ex
  38. 1
      apps/block_scout_web/lib/block_scout_web/views/address_transaction_view.ex
  39. 2
      apps/block_scout_web/lib/block_scout_web/views/address_validation_view.ex
  40. 35
      apps/block_scout_web/lib/block_scout_web/views/address_view.ex
  41. 2
      apps/block_scout_web/lib/block_scout_web/views/address_withdrawal_view.ex
  42. 2
      apps/block_scout_web/lib/block_scout_web/views/address_write_contract_view.ex
  43. 2
      apps/block_scout_web/lib/block_scout_web/views/address_write_proxy_view.ex
  44. 33
      apps/block_scout_web/lib/block_scout_web/views/api/v2/address_view.ex
  45. 52
      apps/block_scout_web/lib/block_scout_web/views/api/v2/helper.ex
  46. 96
      apps/block_scout_web/lib/block_scout_web/views/api/v2/smart_contract_view.ex
  47. 2
      apps/block_scout_web/lib/block_scout_web/views/block_withdrawal_view.ex
  48. 2
      apps/block_scout_web/lib/block_scout_web/views/tokens/holder_view.ex
  49. 2
      apps/block_scout_web/lib/block_scout_web/views/transaction_state_view.ex
  50. 1
      apps/block_scout_web/lib/block_scout_web/views/transaction_token_transfer_view.ex
  51. 2
      apps/block_scout_web/lib/block_scout_web/views/transaction_view.ex
  52. 2
      apps/block_scout_web/lib/block_scout_web/views/withdrawal_view.ex
  53. 2
      apps/block_scout_web/test/block_scout_web/controllers/account/api/v2/user_controller_test.exs
  54. 2
      apps/block_scout_web/test/block_scout_web/controllers/address_read_contract_controller_test.exs
  55. 2
      apps/block_scout_web/test/block_scout_web/controllers/address_read_proxy_controller_test.exs
  56. 2
      apps/block_scout_web/test/block_scout_web/controllers/address_write_contract_controller_test.exs
  57. 2
      apps/block_scout_web/test/block_scout_web/controllers/address_write_proxy_controller_test.exs
  58. 3
      apps/block_scout_web/test/block_scout_web/controllers/api/v2/address_controller_test.exs
  59. 157
      apps/block_scout_web/test/block_scout_web/controllers/api/v2/smart_contract_controller_test.exs
  60. 17
      apps/block_scout_web/test/block_scout_web/views/address_view_test.exs
  61. 47
      apps/explorer/lib/explorer/chain.ex
  62. 23
      apps/explorer/lib/explorer/chain/address.ex
  63. 2
      apps/explorer/lib/explorer/chain/address/counters.ex
  64. 21
      apps/explorer/lib/explorer/chain/smart_contract.ex
  65. 89
      apps/explorer/lib/explorer/chain/smart_contract/proxy.ex
  66. 114
      apps/explorer/lib/explorer/chain/smart_contract/proxy/models/implementation.ex
  67. 2
      apps/explorer/lib/explorer/chain/transaction.ex
  68. 48
      apps/explorer/lib/explorer/smart_contract/helper.ex
  69. 36
      apps/explorer/test/explorer/chain/smart_contract/proxy/models/implementation_test.exs
  70. 5
      apps/explorer/test/explorer/chain_test.exs

@ -29,6 +29,7 @@ defmodule BlockScoutWeb.API.V2.AddressController do
alias Explorer.Chain.{Address, Hash, Transaction}
alias Explorer.Chain.Address.Counters
alias Explorer.Chain.Token.Instance
alias Explorer.SmartContract.Helper, as: SmartContractHelper
alias BlockScoutWeb.API.V2.CeloView
alias Explorer.Chain.Celo.ElectionReward, as: CeloElectionReward
@ -118,16 +119,23 @@ defmodule BlockScoutWeb.API.V2.AddressController do
action_fallback(BlockScoutWeb.API.V2.FallbackController)
def address(conn, %{"address_hash_param" => address_hash_string} = params) do
with {:ok, _address_hash, address} <- validate_address(address_hash_string, params, @address_options),
fully_preloaded_address <-
Address.maybe_preload_smart_contract_associations(address, @contract_address_preloads, @api_true) do
CoinBalanceOnDemand.trigger_fetch(fully_preloaded_address)
with {:ok, _address_hash, address} <- validate_address(address_hash_string, params, @address_options) do
fully_preloaded_address =
Address.maybe_preload_smart_contract_associations(address, @contract_address_preloads, @api_true)
{implementations, proxy_type} =
SmartContractHelper.pre_fetch_implementations(fully_preloaded_address)
CoinBalanceOnDemand.trigger_fetch(address)
ContractCodeOnDemand.trigger_fetch(address)
conn
|> put_status(200)
|> render(:address, %{address: fully_preloaded_address |> maybe_preload_ens_to_address()})
|> render(:address, %{
address: fully_preloaded_address |> maybe_preload_ens_to_address(),
implementations: implementations,
proxy_type: proxy_type
})
end
end

@ -14,6 +14,7 @@ defmodule BlockScoutWeb.API.V2.SmartContractController do
alias Explorer.Chain.{Address, SmartContract}
alias Explorer.Chain.SmartContract.AuditReport
alias Explorer.Chain.SmartContract.Proxy.Models.Implementation
alias Explorer.SmartContract.Helper, as: SmartContractHelper
alias Explorer.SmartContract.{Reader, Writer}
alias Explorer.SmartContract.Solidity.PublishHelper
alias Explorer.ThirdPartyIntegrations.SolidityScan
@ -22,7 +23,8 @@ defmodule BlockScoutWeb.API.V2.SmartContractController do
necessity_by_association: %{
:contracts_creation_internal_transaction => :optional,
[smart_contract: :smart_contract_additional_sources] => :optional,
:contracts_creation_transaction => :optional
:contracts_creation_transaction => :optional,
:proxy_implementations => :optional
},
api?: true
]
@ -37,9 +39,12 @@ defmodule BlockScoutWeb.API.V2.SmartContractController do
_ <- PublishHelper.sourcify_check(address_hash_string),
{:not_found, {:ok, address}} <-
{:not_found, Chain.find_contract_address(address_hash, @smart_contract_address_options, false)} do
{implementations, proxy_type} =
SmartContractHelper.pre_fetch_implementations(address)
conn
|> put_status(200)
|> render(:smart_contract, %{address: address})
|> render(:smart_contract, %{address: address, implementations: implementations, proxy_type: proxy_type})
end
end
@ -206,7 +211,7 @@ defmodule BlockScoutWeb.API.V2.SmartContractController do
end
@doc """
/api/v2/smart-contracts/${address_hash_string}/solidityscan-report logic
/api/v2/smart-contracts/:address_hash_string/solidityscan-report logic
"""
@spec solidityscan_report(Plug.Conn.t(), map()) ::
{:address, {:error, :not_found}}

@ -59,7 +59,7 @@
to: AccessHelper.get_path(@conn, :address_validation_path, :index, @address.hash)
) %>
<% end %>
<%= if contract?(@address) do %>
<%= if Address.smart_contract?(@address) do %>
<%= link(
to: AccessHelper.get_path(@conn, :address_contract_path, :index, @address.hash),
class: "card-tab #{tab_status("contracts", @conn.request_path)}") do %>

@ -40,7 +40,7 @@
<%= render BlockScoutWeb.CommonComponentsView, "_btn_qr_code.html" %>
</span>
</h1>
<h3 class="address-detail-hash-title mb-4 <%= if BlockScoutWeb.AddressView.contract?(@address) do %>contract-address<% end %>" data-test="address_detail_hash"><%= @address %></h3>
<h3 class="address-detail-hash-title mb-4 <%= if Address.smart_contract?(@address) do %>contract-address<% end %>" data-test="address_detail_hash"><%= @address %></h3>
<!-- Verify in other explorers -->
<!--
<%# <%= render "_verify_other_explorers.html", hash: @address.hash, type: "address" %> %>
@ -67,7 +67,7 @@
</dd>
</dl>
<% address_name -> %>
<%= if contract?(@address) do %>
<%= if Address.smart_contract?(@address) do %>
<dl class="row">
<dt class="col-sm-4 col-md-4 col-lg-3 text-muted">
<%= render BlockScoutWeb.CommonComponentsView, "_i_tooltip_2.html",
@ -94,7 +94,7 @@
<% end %>
<!-- Creator -->
<% from_address_hash = from_address_hash(@address) %>
<%= if contract?(@address) do %>
<%= if Address.smart_contract?(@address) do %>
<dl class="row">
<dt class="col-sm-4 col-md-4 col-lg-3 text-muted">
<%= render BlockScoutWeb.CommonComponentsView, "_i_tooltip_2.html",
@ -124,7 +124,7 @@
<% end %>
<!-- Implementation -->
<%= if @is_proxy do %>
<% {implementation_addresses, implementation_names} = Implementation.get_implementation(@address.smart_contract) %>
<% {implementation_addresses, implementation_names, _proxy_type} = Implementation.get_implementation(@address.smart_contract) %>
<% implementation_address_ = Enum.at(implementation_addresses, 0) %>
<% name = Enum.at(implementation_names, 0) %>
<% implementation_address = implementation_address_ || "0x0000000000000000000000000000000000000000" %>

@ -1,7 +1,7 @@
<section class="container" data-page="coin-balance-history">
<script defer src="<%= static_path(@conn, "/js/balance-chart-loader.js") %>"></script>
<% is_proxy = BlockScoutWeb.AddressView.smart_contract_is_proxy?(@address) %>
<% is_proxy = SmartContractHelper.address_is_proxy?(@address) %>
<%= render BlockScoutWeb.AddressView, "overview.html", address: @address, is_proxy: is_proxy, conn: @conn, exchange_rate: @exchange_rate, coin_balance_status: @coin_balance_status, counters_path: @counters_path, tags: @tags %>

@ -1,12 +1,12 @@
<% contract_creation_code = contract_creation_code(@address) %>
<% minimal_proxy_template = EIP1167.get_implementation_smart_contract(@address.hash) %>
<% implementation_or_bytecode_twin_contract = minimal_proxy_template || SmartContract.get_address_verified_bytecode_twin_contract(@address.hash).verified_contract %>
<% bytecode_twin_contract = SmartContract.get_address_verified_bytecode_twin_contract(@address.hash).verified_contract %>
<% smart_contract_verified = BlockScoutWeb.AddressView.smart_contract_verified?(@address) %>
<% fully_verified = SmartContract.verified_with_full_match?(@address.hash)%>
<% additional_sources = BlockScoutWeb.API.V2.SmartContractView.get_additional_sources(@address.smart_contract, smart_contract_verified, implementation_or_bytecode_twin_contract) %>
<% additional_sources = BlockScoutWeb.API.V2.SmartContractView.get_additional_sources(@address.smart_contract, smart_contract_verified, bytecode_twin_contract) %>
<% visualize_sol2uml_enabled = Explorer.Visualize.Sol2uml.enabled?() %>
<section class="container">
<% is_proxy = BlockScoutWeb.AddressView.smart_contract_is_proxy?(@address) %>
<% is_proxy = SmartContractHelper.address_is_proxy?(@address) %>
<%= render BlockScoutWeb.AddressView, "overview.html", address: @address, is_proxy: is_proxy, conn: @conn, exchange_rate: @exchange_rate, coin_balance_status: @coin_balance_status, counters_path: @counters_path, tags: @tags %>
@ -15,16 +15,16 @@
<div class="card-body">
<%= unless smart_contract_verified do %>
<%= if minimal_proxy_template do %>
<%= render BlockScoutWeb.CommonComponentsView, "_minimal_proxy_pattern.html", address_hash: implementation_or_bytecode_twin_contract.address_hash, conn: @conn %>
<%= render BlockScoutWeb.CommonComponentsView, "_minimal_proxy_pattern.html", address_hash: bytecode_twin_contract.address_hash, conn: @conn %>
<% else %>
<%= if implementation_or_bytecode_twin_contract do %>
<%= if bytecode_twin_contract do %>
<% path = address_verify_contract_path(@conn, :new, @address.hash) %>
<div class="mb-4">
<div style="display: inline-block;">
<%= render BlockScoutWeb.CommonComponentsView, "_info.html" %>
<span> <%= gettext("Contract is not verified. However, we found a verified contract with the same bytecode in Blockscout DB") %> <%= link(
implementation_or_bytecode_twin_contract.address_hash,
to: address_contract_path(@conn, :index, implementation_or_bytecode_twin_contract.address_hash)) %>.<br/> <%= gettext("All metadata displayed below is from that contract. In order to verify current contract, click") %> <i><%= gettext("Verify & Publish") %></i> <%= gettext("button") %></span>
bytecode_twin_contract.address_hash,
to: address_contract_path(@conn, :index, bytecode_twin_contract.address_hash)) %>.<br/> <%= gettext("All metadata displayed below is from that contract. In order to verify current contract, click") %> <i><%= gettext("Verify & Publish") %></i> <%= gettext("button") %></span>
</div>
<%= link(gettext("Verify & Publish"), to: path, class: "button button-primary button-sm float-right ml-3", "data-test": "verify_and_publish") %>
</div>
@ -40,8 +40,8 @@
</div>
<% end %>
<% end %>
<%= if smart_contract_verified || (!smart_contract_verified && implementation_or_bytecode_twin_contract) do %>
<% target_contract = if smart_contract_verified, do: @address.smart_contract, else: implementation_or_bytecode_twin_contract %>
<%= if smart_contract_verified || (!smart_contract_verified && bytecode_twin_contract) do %>
<% target_contract = if smart_contract_verified, do: @address.smart_contract, else: bytecode_twin_contract %>
<%= 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.") %>
@ -240,8 +240,8 @@
<% end %>
</section>
<%= if smart_contract_verified || (!smart_contract_verified && implementation_or_bytecode_twin_contract) do %>
<% target_contract = if smart_contract_verified, do: @address.smart_contract, else: implementation_or_bytecode_twin_contract %>
<%= if smart_contract_verified || (!smart_contract_verified && bytecode_twin_contract) do %>
<% target_contract = if smart_contract_verified, do: @address.smart_contract, else: bytecode_twin_contract %>
<%= if target_contract.external_libraries && target_contract.external_libraries != [] do %>
<section>
<div class="d-flex justify-content-between align-items-baseline">

@ -1,5 +1,5 @@
<section class="container">
<% is_proxy = BlockScoutWeb.AddressView.smart_contract_is_proxy?(@address) %>
<% is_proxy = SmartContractHelper.address_is_proxy?(@address) %>
<%= render BlockScoutWeb.AddressView, "overview.html", address: @address, is_proxy: is_proxy, conn: @conn, exchange_rate: @exchange_rate, coin_balance_status: @coin_balance_status, counters_path: @counters_path, tags: @tags %>
<div class="card">

@ -1,5 +1,5 @@
<section class="container">
<% is_proxy = BlockScoutWeb.AddressView.smart_contract_is_proxy?(@address) %>
<% is_proxy = SmartContractHelper.address_is_proxy?(@address) %>
<%= render BlockScoutWeb.AddressView, "overview.html", address: @address, is_proxy: is_proxy, conn: @conn, exchange_rate: @exchange_rate, coin_balance_status: @coin_balance_status, counters_path: @counters_path, tags: @tags %>

@ -1,5 +1,5 @@
<section class="container">
<% is_proxy = BlockScoutWeb.AddressView.smart_contract_is_proxy?(@address) %>
<% is_proxy = SmartContractHelper.address_is_proxy?(@address) %>
<%= render BlockScoutWeb.AddressView, "overview.html", address: @address, is_proxy: is_proxy, conn: @conn, exchange_rate: @exchange_rate, coin_balance_status: @coin_balance_status, counters_path: @counters_path, tags: @tags %>
<section data-page="address-logs">

@ -1,6 +1,6 @@
<section class="container">
<% is_proxy = BlockScoutWeb.AddressView.smart_contract_is_proxy?(@address) %>
<% is_proxy = SmartContractHelper.address_is_proxy?(@address) %>
<%= render BlockScoutWeb.AddressView, "overview.html", address: @address, is_proxy: is_proxy, conn: @conn, exchange_rate: @exchange_rate, coin_balance_status: @coin_balance_status, counters_path: @counters_path, tags: @tags %>

@ -1,6 +1,6 @@
<section class="container">
<% is_proxy = BlockScoutWeb.AddressView.smart_contract_is_proxy?(@address) %>
<% is_proxy = SmartContractHelper.address_is_proxy?(@address) %>
<%= render BlockScoutWeb.AddressView, "overview.html", address: @address, is_proxy: is_proxy, conn: @conn, exchange_rate: @exchange_rate, coin_balance_status: @coin_balance_status, counters_path: @counters_path, tags: @tags %>

@ -1,5 +1,5 @@
<section class="container">
<% is_proxy = BlockScoutWeb.AddressView.smart_contract_is_proxy?(@address) %>
<% is_proxy = SmartContractHelper.address_is_proxy?(@address) %>
<%= render BlockScoutWeb.AddressView, "overview.html", address: @address, is_proxy: is_proxy, conn: @conn, exchange_rate: @exchange_rate, coin_balance_status: @coin_balance_status, counters_path: @counters_path, tags: @tags %>

@ -1,5 +1,5 @@
<section class="container">
<% is_proxy = BlockScoutWeb.AddressView.smart_contract_is_proxy?(@address) %>
<% is_proxy = SmartContractHelper.address_is_proxy?(@address) %>
<%= render BlockScoutWeb.AddressView, "overview.html", address: @address, is_proxy: is_proxy, conn: @conn, exchange_rate: @exchange_rate, coin_balance_status: @coin_balance_status, counters_path: @counters_path, tags: @tags %>

@ -1,6 +1,6 @@
<section class="container">
<% is_proxy = BlockScoutWeb.AddressView.smart_contract_is_proxy?(@address) %>
<% is_proxy = SmartContractHelper.address_is_proxy?(@address) %>
<%= render BlockScoutWeb.AddressView, "overview.html", address: @address, is_proxy: is_proxy, conn: @conn, exchange_rate: @exchange_rate, coin_balance_status: @coin_balance_status, counters_path: @counters_path, tags: @tags %>

@ -1,5 +1,5 @@
<section class="container">
<% is_proxy = BlockScoutWeb.AddressView.smart_contract_is_proxy?(@address) %>
<% is_proxy = SmartContractHelper.address_is_proxy?(@address) %>
<%= render BlockScoutWeb.AddressView, "overview.html", address: @address, is_proxy: is_proxy, conn: @conn, exchange_rate: @exchange_rate, coin_balance_status: @coin_balance_status, counters_path: @counters_path, tags: @tags %>

@ -1,6 +1,6 @@
<section class="container">
<% is_proxy = BlockScoutWeb.AddressView.smart_contract_is_proxy?(@address) %>
<% is_proxy = SmartContractHelper.address_is_proxy?(@address) %>
<%= render BlockScoutWeb.AddressView, "overview.html", address: @address, is_proxy: is_proxy, conn: @conn, exchange_rate: @exchange_rate, coin_balance_status: @coin_balance_status, counters_path: @counters_path, tags: @tags %>
@ -9,17 +9,17 @@
<%= render BlockScoutWeb.AddressView, "_tabs.html", address: @address, is_proxy: is_proxy, conn: @conn %>
<div class="card-body" data-async-load data-async-listing="<%= @current_path %>">
<div class="clearfix">
<h2 class="card-title float-left"><%= gettext "Withdrawals" %></h2>
<div class="top-pagination-outer-container float-right">
<%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "top", show_pagination_limit: true, data_next_page_button: true, data_prev_page_button: true %>
</div>
</div>
<button data-error-message class="alert alert-danger col-12 text-left" style="display: none;">
<span href="#" class="alert-link"><%= gettext("Something went wrong, click to reload.") %></span>
</button>
<div class="addresses-table-container">
<div class="stakes-table-container">
<table>
@ -54,7 +54,7 @@
<%= gettext "There are no withdrawals for this address." %>
</div>
</div>
<div class="top-pagination-outer-container float-right">
<%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "bottom", show_pagination_limit: true, data_next_page_button: true, data_prev_page_button: true %>
</div>

@ -1,6 +1,6 @@
<section class="container">
<% is_proxy = BlockScoutWeb.AddressView.smart_contract_is_proxy?(@address) %>
<% is_proxy = SmartContractHelper.address_is_proxy?(@address) %>
<%= render BlockScoutWeb.AddressView, "overview.html", address: @address, is_proxy: is_proxy, conn: @conn, exchange_rate: @exchange_rate, coin_balance_status: @coin_balance_status, counters_path: @counters_path, tags: @tags %>

@ -1,6 +1,6 @@
<section class="container">
<% is_proxy = BlockScoutWeb.AddressView.smart_contract_is_proxy?(@address) %>
<% is_proxy = SmartContractHelper.address_is_proxy?(@address) %>
<%= render BlockScoutWeb.AddressView, "overview.html", address: @address, is_proxy: is_proxy, conn: @conn, exchange_rate: @exchange_rate, coin_balance_status: @coin_balance_status, counters_path: @counters_path, tags: @tags %>

@ -12,7 +12,7 @@
<%= render BlockScoutWeb.AddressView,
"_link.html",
address: @withdrawal.address,
contract: BlockScoutWeb.AddressView.contract?(@withdrawal.address),
contract: Address.smart_contract?(@withdrawal.address),
use_custom_tooltip: false
%>
</td>

@ -2,7 +2,7 @@
<div class="row">
<div class="col-md-7 col-lg-8 d-flex flex-column">
<span>
<%= render BlockScoutWeb.AddressView, "_link.html", address: @token_balance.address, contract: BlockScoutWeb.AddressView.contract?(@token_balance.address), use_custom_tooltip: false %>
<%= render BlockScoutWeb.AddressView, "_link.html", address: @token_balance.address, contract: Address.smart_contract?(@token_balance.address), use_custom_tooltip: false %>
</span>
<span>

@ -15,7 +15,7 @@
BlockScoutWeb.AddressView,
"_responsive_hash.html",
address: @token_transfer.from_address,
contract: BlockScoutWeb.AddressView.contract?(@token_transfer.from_address),
contract: Address.smart_contract?(@token_transfer.from_address),
use_custom_tooltip: false
) %>
<% end %>
@ -25,7 +25,7 @@
BlockScoutWeb.AddressView,
"_responsive_hash.html",
address: @token_transfer.to_address,
contract: BlockScoutWeb.AddressView.contract?(@token_transfer.to_address),
contract: Address.smart_contract?(@token_transfer.to_address),
use_custom_tooltip: false
) %>
<% end %>

@ -84,7 +84,7 @@
<% address_string = Map.get(@action.data, "address") %>
<% {address_status, address} = transaction_action_string_to_address(address_string) %>
<% address = if address_status == :ok, do: render_to_string(BlockScoutWeb.AddressView, "_link.html", address: address, contract: BlockScoutWeb.AddressView.contract?(address), use_custom_tooltip: false, trimmed: false), else: render_to_string(BlockScoutWeb.TransactionView, "_actions_address.html", address_string: address_string, action: @action) %>
<% address = if address_status == :ok, do: render_to_string(BlockScoutWeb.AddressView, "_link.html", address: address, contract: Address.smart_contract?(address), use_custom_tooltip: false, trimmed: false), else: render_to_string(BlockScoutWeb.TransactionView, "_actions_address.html", address_string: address_string, action: @action) %>
<% to_address = Map.get(@action.data, "to") %>
<% to_content = raw(render_to_string BlockScoutWeb.TransactionView, "_actions_to.html", address: to_address) %>
<% to = link to: address_path(BlockScoutWeb.Endpoint, :show, to_address), "data-test": "address_hash_link", do: to_content %>
@ -114,11 +114,11 @@
<% address0 = Map.get(@action.data, "address0") %>
<% symbol1 = Map.get(@action.data, "symbol1") %>
<% address1 = Map.get(@action.data, "address1") %>
<i class="fa fa-caret-right"></i>
<% symbol0 = if symbol0 != "Ether", do: link(symbol0, to: token_path(BlockScoutWeb.Endpoint, :show, address0), "data-test": "token_link"), else: raw(symbol0) %>
<% symbol1 = if symbol1 != "Ether", do: link(symbol1, to: token_path(BlockScoutWeb.Endpoint, :show, address1), "data-test": "token_link"), else: raw(symbol1) %>
<%= if @action.type == :mint do %>

@ -8,10 +8,10 @@
<div class="col-md-7 col-lg-8 d-flex flex-column pr-2 pr-sm-2 pr-md-0">
<%= render BlockScoutWeb.TransactionView, "_link.html", transaction_hash: @transaction.hash %>
<span class="text-nowrap">
<%= render BlockScoutWeb.AddressView, "_link.html", address: @transaction.from_address, contract: BlockScoutWeb.AddressView.contract?(@transaction.from_address), use_custom_tooltip: false %>
<%= render BlockScoutWeb.AddressView, "_link.html", address: @transaction.from_address, contract: Address.smart_contract?(@transaction.from_address), use_custom_tooltip: false %>
&rarr;
<%= if @transaction.to_address_hash do %>
<%= render BlockScoutWeb.AddressView, "_link.html", address: @transaction.to_address, contract: BlockScoutWeb.AddressView.contract?(@transaction.to_address), use_custom_tooltip: false %>
<%= render BlockScoutWeb.AddressView, "_link.html", address: @transaction.to_address, contract: Address.smart_contract?(@transaction.to_address), use_custom_tooltip: false %>
<% else %>
<%= gettext("Contract Address Pending") %>
<% end %>

@ -7,7 +7,7 @@
<b style="margin-right: 15px;">From</b>
</td>
<td class="address-mobile" style="word-break: break-all; width: 1%; white-space: nowrap;">
<%= render BlockScoutWeb.AddressView, "_link.html", address: from_address, contract: BlockScoutWeb.AddressView.contract?(from_address), use_custom_tooltip: false, trimmed: false %>
<%= render BlockScoutWeb.AddressView, "_link.html", address: from_address, contract: Address.smart_contract?(from_address), use_custom_tooltip: false, trimmed: false %>
<%= render BlockScoutWeb.AddressView, "_labels.html", tags: from_tags %>
</td>
<td style="word-break: break-all; white-space: nowrap;">
@ -24,7 +24,7 @@ style: "position: relative;" %>
<b>To</b>
</td>
<td class="address-mobile" style="word-break: break-all; width: 1%; white-space: nowrap;">
<%= render BlockScoutWeb.AddressView, "_link.html", address: to_address, contract: BlockScoutWeb.AddressView.contract?(to_address), use_custom_tooltip: false, trimmed: false %>
<%= render BlockScoutWeb.AddressView, "_link.html", address: to_address, contract: Address.smart_contract?(to_address), use_custom_tooltip: false, trimmed: false %>
<%= render BlockScoutWeb.AddressView, "_labels.html", tags: to_tags %>
</td>
<td style="word-break: break-all; white-space: nowrap;">

@ -245,7 +245,7 @@
text: gettext("Address (external or contract) sending the transaction.") %>
<%= gettext "From" %></dt>
<dd class="col-sm-9 col-lg-10">
<%= render BlockScoutWeb.AddressView, "_link.html", address: from_address, contract: BlockScoutWeb.AddressView.contract?(from_address), use_custom_tooltip: false, trimmed: false %>
<%= render BlockScoutWeb.AddressView, "_link.html", address: from_address, contract: Address.smart_contract?(from_address), use_custom_tooltip: false, trimmed: false %>
<%= render BlockScoutWeb.AddressView, "_labels.html", tags: @from_tags %>
<%= render BlockScoutWeb.CommonComponentsView, "_btn_copy.html",
additional_classes: ["btn-copy-icon-small", "btn-copy-icon-custom", "btn-copy-icon-no-borders"],
@ -261,7 +261,7 @@
<dt class="col-sm-3 col-lg-2 text-muted">
<%= render BlockScoutWeb.CommonComponentsView, "_i_tooltip_2.html",
text: gettext("Address (external or contract) receiving the transaction.") %>
<%= if BlockScoutWeb.AddressView.contract?(to_address) && !created_address_hash do %>
<%= if Address.smart_contract?(to_address) && !created_address_hash do %>
<%= gettext "Interacted With (To)" %>
<% else %>
<%= gettext "To" %>
@ -271,7 +271,7 @@
<%= cond do %>
<% created_address_hash -> %>
[<%= gettext("Contract") %>&nbsp;
<%= render BlockScoutWeb.AddressView, "_link.html", address: to_address, contract: BlockScoutWeb.AddressView.contract?(to_address), use_custom_tooltip: false, trimmed: false %>
<%= render BlockScoutWeb.AddressView, "_link.html", address: to_address, contract: Address.smart_contract?(to_address), use_custom_tooltip: false, trimmed: false %>
<%= render BlockScoutWeb.AddressView, "_labels.html", tags: @to_tags %>
&nbsp;<%= gettext("created") %>]
<%= render BlockScoutWeb.CommonComponentsView, "_btn_copy.html",
@ -280,7 +280,7 @@
aria_label: gettext("Copy To Address"),
title: gettext("Copy To Address") %>
<% recipient_address_hash -> %>
<%= render BlockScoutWeb.AddressView, "_link.html", address: to_address, contract: BlockScoutWeb.AddressView.contract?(to_address), use_custom_tooltip: false, trimmed: false %>
<%= render BlockScoutWeb.AddressView, "_link.html", address: to_address, contract: Address.smart_contract?(to_address), use_custom_tooltip: false, trimmed: false %>
<%= render BlockScoutWeb.AddressView, "_labels.html", tags: @to_tags %>
<%= render BlockScoutWeb.CommonComponentsView, "_btn_copy.html",
additional_classes: ["btn-copy-icon-small", "btn-copy-icon-custom", "btn-copy-icon-no-borders"],

@ -9,7 +9,7 @@
</dt>
</td>
<td class="stakes-td">
<%= render BlockScoutWeb.AddressView, "_link.html", address: @address, contract: BlockScoutWeb.AddressView.contract?(@address), use_custom_tooltip: false %>
<%= render BlockScoutWeb.AddressView, "_link.html", address: @address, contract: Address.smart_contract?(@address), use_custom_tooltip: false %>
</td>
<td class="stakes-td"></td>
<td class="stakes-td"></td>
@ -28,7 +28,7 @@
<% else %>
<td class="stakes-td"></td>
<td class="stakes-td">
<%= render BlockScoutWeb.AddressView, "_link.html", address: @address, contract: BlockScoutWeb.AddressView.contract?(@address), use_custom_tooltip: false %>
<%= render BlockScoutWeb.AddressView, "_link.html", address: @address, contract: Address.smart_contract?(@address), use_custom_tooltip: false %>
</td>
<% end %>
<%= if not_negative?(@balance_before) and not_negative?(@balance_after) do %>

@ -7,9 +7,9 @@
<div class="col-12 col-md-8 col-lg-10 d-flex flex-column text-nowrap">
<%= render BlockScoutWeb.TransactionView, "_link.html", transaction_hash: @token_transfer.transaction_hash %>
<span class="text-nowrap">
<%= render BlockScoutWeb.AddressView, "_link.html", address: @token_transfer.from_address, contract: BlockScoutWeb.AddressView.contract?(@token_transfer.from_address), use_custom_tooltip: false %>
<%= render BlockScoutWeb.AddressView, "_link.html", address: @token_transfer.from_address, contract: Address.smart_contract?(@token_transfer.from_address), use_custom_tooltip: false %>
&rarr;
<%= render BlockScoutWeb.AddressView, "_link.html", address: @token_transfer.to_address, contract: BlockScoutWeb.AddressView.contract?(@token_transfer.to_address), use_custom_tooltip: false %>
<%= render BlockScoutWeb.AddressView, "_link.html", address: @token_transfer.to_address, contract: Address.smart_contract?(@token_transfer.to_address), use_custom_tooltip: false %>
</span>
<span class="tile-title">

@ -19,7 +19,7 @@
<%= render BlockScoutWeb.AddressView,
"_link.html",
address: @withdrawal.address,
contract: BlockScoutWeb.AddressView.contract?(@withdrawal.address),
contract: Address.smart_contract?(@withdrawal.address),
use_custom_tooltip: false
%>
</td>

@ -3,6 +3,7 @@ defmodule BlockScoutWeb.AddressCoinBalanceView do
alias BlockScoutWeb.AccessHelper
alias Explorer.Chain.Wei
alias Explorer.SmartContract.Helper, as: SmartContractHelper
def format(%Wei{} = value) do
format_wei_value(value, :ether)

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

@ -1,5 +1,6 @@
defmodule BlockScoutWeb.AddressDecompiledContractView do
use BlockScoutWeb, :view
alias Explorer.SmartContract.Helper, as: SmartContractHelper
@colors %{
"\e[95m" => "",

@ -3,6 +3,7 @@ defmodule BlockScoutWeb.AddressInternalTransactionView do
alias BlockScoutWeb.AccessHelper
alias Explorer.Chain.Address
alias Explorer.SmartContract.Helper, as: SmartContractHelper
def format_current_filter(filter) do
case filter do

@ -2,6 +2,7 @@ defmodule BlockScoutWeb.AddressLogsView do
use BlockScoutWeb, :view
alias Explorer.Chain.Address
alias Explorer.SmartContract.Helper, as: SmartContractHelper
import BlockScoutWeb.AddressView, only: [decode: 2]
end

@ -1,3 +1,5 @@
defmodule BlockScoutWeb.AddressReadContractView do
use BlockScoutWeb, :view
alias Explorer.SmartContract.Helper, as: SmartContractHelper
end

@ -1,3 +1,5 @@
defmodule BlockScoutWeb.AddressReadProxyView do
use BlockScoutWeb, :view
alias Explorer.SmartContract.Helper, as: SmartContractHelper
end

@ -3,6 +3,7 @@ defmodule BlockScoutWeb.AddressTokenTransferView do
alias BlockScoutWeb.AccessHelper
alias Explorer.Chain.Address
alias Explorer.SmartContract.Helper, as: SmartContractHelper
def format_current_filter(filter) do
case filter do

@ -4,4 +4,5 @@ defmodule BlockScoutWeb.AddressTokenView do
alias BlockScoutWeb.{AddressView, ChainView}
alias Explorer.Chain
alias Explorer.Chain.{Address, Wei}
alias Explorer.SmartContract.Helper, as: SmartContractHelper
end

@ -3,6 +3,7 @@ defmodule BlockScoutWeb.AddressTransactionView do
alias BlockScoutWeb.AccessHelper
alias Explorer.Chain.Address
alias Explorer.SmartContract.Helper, as: SmartContractHelper
def format_current_filter(filter) do
case filter do

@ -1,5 +1,5 @@
defmodule BlockScoutWeb.AddressValidationView do
use BlockScoutWeb, :view
# import BlockScoutWeb.AddressView, only: [contract?: 1, smart_contract_verified?: 1]
alias Explorer.SmartContract.Helper, as: SmartContractHelper
end

@ -36,7 +36,7 @@ defmodule BlockScoutWeb.AddressView do
def address_partial_selector(struct_to_render_from, direction, current_address, truncate \\ false)
def address_partial_selector(%Address{} = address, _, current_address, truncate) do
matching_address_check(current_address, address, contract?(address), truncate)
matching_address_check(current_address, address, Address.smart_contract?(address), truncate)
end
def address_partial_selector(
@ -58,19 +58,19 @@ defmodule BlockScoutWeb.AddressView do
end
def address_partial_selector(%InternalTransaction{to_address: address}, :to, current_address, truncate) do
matching_address_check(current_address, address, contract?(address), truncate)
matching_address_check(current_address, address, Address.smart_contract?(address), truncate)
end
def address_partial_selector(%InternalTransaction{from_address: address}, :from, current_address, truncate) do
matching_address_check(current_address, address, contract?(address), truncate)
matching_address_check(current_address, address, Address.smart_contract?(address), truncate)
end
def address_partial_selector(%TokenTransfer{to_address: address}, :to, current_address, truncate) do
matching_address_check(current_address, address, contract?(address), truncate)
matching_address_check(current_address, address, Address.smart_contract?(address), truncate)
end
def address_partial_selector(%TokenTransfer{from_address: address}, :from, current_address, truncate) do
matching_address_check(current_address, address, contract?(address), truncate)
matching_address_check(current_address, address, Address.smart_contract?(address), truncate)
end
def address_partial_selector(
@ -92,11 +92,11 @@ defmodule BlockScoutWeb.AddressView do
end
def address_partial_selector(%Transaction{to_address: address}, :to, current_address, truncate) do
matching_address_check(current_address, address, contract?(address), truncate)
matching_address_check(current_address, address, Address.smart_contract?(address), truncate)
end
def address_partial_selector(%Transaction{from_address: address}, :from, current_address, truncate) do
matching_address_check(current_address, address, contract?(address), truncate)
matching_address_check(current_address, address, Address.smart_contract?(address), truncate)
end
def address_partial_selector(%Reward{address: address}, _, current_address, truncate) do
@ -104,7 +104,7 @@ defmodule BlockScoutWeb.AddressView do
end
def address_title(%Address{} = address) do
if contract?(address) do
if Address.smart_contract?(address) do
gettext("Contract Address")
else
gettext("Address")
@ -167,12 +167,6 @@ defmodule BlockScoutWeb.AddressView do
to_string(fetched_coin_balance_block_number)
end
def contract?(%Address{contract_code: nil}), do: false
def contract?(%Address{contract_code: _}), do: true
def contract?(nil), do: true
def validator?(val) when val > 0, do: true
def validator?(_), do: false
@ -259,14 +253,6 @@ defmodule BlockScoutWeb.AddressView do
def read_function?(function), do: Helper.queriable_method?(function) || Helper.read_with_wallet_method?(function)
def smart_contract_is_proxy?(address, options \\ [])
def smart_contract_is_proxy?(%Address{smart_contract: %SmartContract{} = smart_contract}, options) do
Proxy.proxy_contract?(smart_contract, options)
end
def smart_contract_is_proxy?(%Address{smart_contract: _}, _), do: false
def smart_contract_with_write_functions?(%Address{smart_contract: %SmartContract{}} = address) do
!contract_interaction_disabled?() &&
Enum.any?(
@ -447,13 +433,14 @@ defmodule BlockScoutWeb.AddressView do
def address_page_title(address) do
cond do
smart_contract_verified?(address) -> "#{address.smart_contract.name} (#{to_string(address)})"
contract?(address) -> "Contract #{to_string(address)}"
Address.smart_contract?(address) -> "Contract #{to_string(address)}"
true -> "#{to_string(address)}"
end
end
def smart_contract_is_gnosis_safe_proxy?(%Address{smart_contract: %SmartContract{}} = address) do
address.smart_contract.name == "GnosisSafeProxy" && Proxy.gnosis_safe_contract?(address.smart_contract.abi)
address.smart_contract.name == "GnosisSafeProxy" &&
Proxy.gnosis_safe_contract?(address.smart_contract.abi)
end
def smart_contract_is_gnosis_safe_proxy?(_address), do: false

@ -1,3 +1,5 @@
defmodule BlockScoutWeb.AddressWithdrawalView do
use BlockScoutWeb, :view
alias Explorer.SmartContract.Helper, as: SmartContractHelper
end

@ -1,3 +1,5 @@
defmodule BlockScoutWeb.AddressWriteContractView do
use BlockScoutWeb, :view
alias Explorer.SmartContract.Helper, as: SmartContractHelper
end

@ -1,3 +1,5 @@
defmodule BlockScoutWeb.AddressWriteProxyView do
use BlockScoutWeb, :view
alias Explorer.SmartContract.Helper, as: SmartContractHelper
end

@ -5,11 +5,9 @@ defmodule BlockScoutWeb.API.V2.AddressView do
alias BlockScoutWeb.AddressView
alias BlockScoutWeb.API.V2.{ApiView, Helper, TokenView}
alias BlockScoutWeb.API.V2.Helper
alias Explorer.{Chain, Market}
alias Explorer.Chain.Address
alias Explorer.Chain.Address.Counters
alias Explorer.Chain.SmartContract.Proxy.Models.Implementation
alias Explorer.Chain.Token.Instance
@api_true [api?: true]
@ -18,8 +16,8 @@ defmodule BlockScoutWeb.API.V2.AddressView do
ApiView.render("message.json", assigns)
end
def render("address.json", %{address: address, conn: conn}) do
prepare_address(address, conn)
def render("address.json", %{address: address, implementations: implementations, proxy_type: proxy_type, conn: conn}) do
prepare_address(address, conn, implementations, proxy_type)
end
def render("token_balances.json", %{token_balances: token_balances}) do
@ -83,29 +81,13 @@ defmodule BlockScoutWeb.API.V2.AddressView do
|> Map.put(:coin_balance, if(address.fetched_coin_balance, do: address.fetched_coin_balance.value))
end
def prepare_address(address, conn \\ nil) do
@doc """
Prepares address properties for rendering in /addresses and /addresses/:address_hash_param API v2 endpoints
"""
@spec prepare_address(Address.t(), Plug.Conn.t() | nil, list(), String.t() | nil) :: map()
def prepare_address(address, conn \\ nil, implementations \\ [], proxy_type \\ nil) do
base_info = Helper.address_with_info(conn, address, address.hash, true)
{:ok, address_with_smart_contract} =
Chain.hash_to_address(
address.hash,
[necessity_by_association: %{:smart_contract => :optional}],
false
)
is_proxy = AddressView.smart_contract_is_proxy?(address_with_smart_contract, @api_true)
implementations =
with true <- is_proxy,
{addresses, names} <-
Implementation.get_implementation(address_with_smart_contract.smart_contract, @api_true),
false <- addresses && Enum.empty?(addresses) do
Helper.proxy_object_info(addresses, names)
else
_ ->
[]
end
balance = address.fetched_coin_balance && address.fetched_coin_balance.value
exchange_rate = Market.get_coin_exchange_rate().usd_value
@ -134,6 +116,7 @@ defmodule BlockScoutWeb.API.V2.AddressView do
extended_info
else
Map.merge(extended_info, %{
"proxy_type" => proxy_type,
"implementations" => implementations
})
end

@ -4,8 +4,8 @@ defmodule BlockScoutWeb.API.V2.Helper do
"""
alias Ecto.Association.NotLoaded
alias Explorer.Chain
alias Explorer.Chain.{Address, Hash}
alias Explorer.Chain.Address
alias Explorer.Chain.SmartContract.Proxy
alias Explorer.Chain.Transaction.History.TransactionStats
import BlockScoutWeb.Account.AuthController, only: [current_user: 1]
@ -64,26 +64,28 @@ defmodule BlockScoutWeb.API.V2.Helper do
def address_with_info(%Address{} = address, _address_hash) do
smart_contract? = Address.smart_contract?(address)
{proxy_implementations, implementation_address_hashes, implementation_names} =
{proxy_implementations, implementation_address_hashes, implementation_names, proxy_type} =
case address.proxy_implementations do
%NotLoaded{} ->
{nil, [], []}
{nil, [], [], nil}
nil ->
{nil, [], []}
{nil, [], [], nil}
proxy_implementations ->
address_hashes = proxy_implementations.address_hashes
names = proxy_implementations.names
proxy_type = proxy_implementations.proxy_type
{proxy_implementations, address_hashes, names}
{proxy_implementations, address_hashes, names, proxy_type}
end
%{
"hash" => Address.checksum(address),
"is_contract" => smart_contract?,
"name" => address_name(address),
"implementations" => proxy_object_info(implementation_address_hashes, implementation_names),
"proxy_type" => proxy_type,
"implementations" => Proxy.proxy_object_info(implementation_address_hashes, implementation_names),
"is_verified" => verified?(address) || verified_minimal_proxy?(proxy_implementations),
"ens_domain_name" => address.ens_domain_name,
"metadata" => address.metadata
@ -110,6 +112,7 @@ defmodule BlockScoutWeb.API.V2.Helper do
"hash" => Address.checksum(address_hash),
"is_contract" => false,
"name" => nil,
"proxy_type" => nil,
"implementations" => [],
"is_verified" => nil,
"ens_domain_name" => nil,
@ -117,41 +120,6 @@ defmodule BlockScoutWeb.API.V2.Helper do
}
end
@doc """
Retrieves formatted proxy object based on its implementation addresses and names.
## Parameters
* `implementation_addresses` - A list of implementation addresses for the proxy object.
* `implementation_names` - A list of implementation names for the proxy object.
## Returns
A list of maps containing information about the proxy object.
"""
@spec proxy_object_info([String.t() | Hash.Address.t()], [String.t() | nil]) :: [map()]
def proxy_object_info([], []), do: []
def proxy_object_info(implementation_addresses, implementation_names) do
implementation_addresses
|> Enum.zip(implementation_names)
|> Enum.reduce([], fn {address, name}, acc ->
case address do
%Hash{} = address_hash ->
[%{"address" => Address.checksum(address_hash), "name" => name} | acc]
_ ->
with {:ok, address_hash} <- Chain.string_to_address_hash(address),
checksummed_address <- Address.checksum(address_hash) do
[%{"address" => checksummed_address, "name" => name} | acc]
else
_ -> acc
end
end
end)
end
defp minimal_proxy_pattern?(proxy_implementations) do
proxy_implementations.proxy_type == :eip1167
end

@ -11,18 +11,28 @@ defmodule BlockScoutWeb.API.V2.SmartContractView do
alias Ecto.Changeset
alias Explorer.Chain
alias Explorer.Chain.{Address, SmartContract, SmartContractAdditionalSource}
alias Explorer.SmartContract.Helper, as: SmartContractHelper
alias Explorer.Visualize.Sol2uml
require Logger
@api_true [api?: true]
# Option to skip fetching implementation from the node,
# when checking, if smart-contract is proxy. It is used in smart contract view
# to prevent double request to the JSON RPC node.
@skip_implementation_fetch_true [skip_implementation_fetch?: true]
def render("smart_contracts.json", %{smart_contracts: smart_contracts, next_page_params: next_page_params}) do
%{"items" => Enum.map(smart_contracts, &prepare_smart_contract_for_list/1), "next_page_params" => next_page_params}
end
def render("smart_contract.json", %{address: address, conn: conn}) do
prepare_smart_contract(address, conn)
def render("smart_contract.json", %{
address: address,
implementations: implementations,
proxy_type: proxy_type,
conn: conn
}) do
prepare_smart_contract(address, implementations, proxy_type, conn)
end
def render("read_functions.json", %{functions: functions}) do
@ -145,15 +155,20 @@ defmodule BlockScoutWeb.API.V2.SmartContractView do
defp prepare_output(output), do: output
# credo:disable-for-next-line
def prepare_smart_contract(%Address{smart_contract: %SmartContract{} = smart_contract} = address, conn) do
def prepare_smart_contract(
%Address{smart_contract: %SmartContract{} = smart_contract} = address,
implementations,
proxy_type,
conn
) do
bytecode_twin = SmartContract.get_address_verified_bytecode_twin_contract(address.hash, @api_true)
minimal_proxy_address_hash = address.implementation
implementation_or_bytecode_twin_contract = address.implementation || bytecode_twin.verified_contract
bytecode_twin_contract = bytecode_twin.verified_contract
smart_contract_verified = AddressView.smart_contract_verified?(address)
fully_verified = SmartContract.verified_with_full_match?(address.hash, @api_true)
write_methods? = AddressView.smart_contract_with_write_functions?(address)
is_proxy = AddressView.smart_contract_is_proxy?(address, @api_true)
is_proxy = SmartContractHelper.address_is_proxy?(address, Keyword.merge(@api_true, @skip_implementation_fetch_true))
read_custom_abi? = AddressView.has_address_custom_abi_with_read_functions?(conn, address.hash)
write_custom_abi? = AddressView.has_address_custom_abi_with_write_functions?(conn, address.hash)
@ -162,57 +177,62 @@ defmodule BlockScoutWeb.API.V2.SmartContractView do
get_additional_sources(
smart_contract,
smart_contract_verified,
implementation_or_bytecode_twin_contract
bytecode_twin_contract
)
visualize_sol2uml_enabled = Sol2uml.enabled?()
target_contract =
if smart_contract_verified, do: address.smart_contract, else: implementation_or_bytecode_twin_contract
if smart_contract_verified, do: smart_contract, else: bytecode_twin_contract
%{
"verified_twin_address_hash" =>
implementation_or_bytecode_twin_contract &&
Address.checksum(implementation_or_bytecode_twin_contract.address_hash),
bytecode_twin_contract &&
Address.checksum(bytecode_twin_contract.address_hash),
"is_verified" => smart_contract_verified,
"is_changed_bytecode" => smart_contract_verified && address.smart_contract.is_changed_bytecode,
"is_partially_verified" => address.smart_contract.partially_verified && smart_contract_verified,
"is_changed_bytecode" => smart_contract_verified && smart_contract.is_changed_bytecode,
"is_partially_verified" => smart_contract.partially_verified && smart_contract_verified,
"is_fully_verified" => fully_verified,
"is_verified_via_sourcify" => address.smart_contract.verified_via_sourcify && smart_contract_verified,
"is_verified_via_eth_bytecode_db" => address.smart_contract.verified_via_eth_bytecode_db,
"is_verified_via_verifier_alliance" => address.smart_contract.verified_via_verifier_alliance,
"is_vyper_contract" => target_contract.is_vyper_contract,
"is_verified_via_sourcify" => smart_contract.verified_via_sourcify && smart_contract_verified,
"is_verified_via_eth_bytecode_db" => smart_contract.verified_via_eth_bytecode_db,
"is_verified_via_verifier_alliance" => smart_contract.verified_via_verifier_alliance,
"is_vyper_contract" => target_contract && target_contract.is_vyper_contract,
"has_custom_methods_read" => read_custom_abi?,
"has_custom_methods_write" => write_custom_abi?,
"has_methods_read" => AddressView.smart_contract_with_read_only_functions?(address),
"has_methods_write" => write_methods?,
"has_methods_read_proxy" => is_proxy,
"has_methods_write_proxy" => is_proxy && write_methods?,
# todo: remove this property once frontend is bound to "implementations"
"minimal_proxy_address_hash" =>
minimal_proxy_address_hash && Address.checksum(minimal_proxy_address_hash.address_hash),
"proxy_type" => proxy_type,
"implementations" => implementations,
"sourcify_repo_url" =>
if(address.smart_contract.verified_via_sourcify && smart_contract_verified,
do: AddressContractView.sourcify_repo_url(address.hash, address.smart_contract.partially_verified)
if(smart_contract.verified_via_sourcify && smart_contract_verified,
do: AddressContractView.sourcify_repo_url(address.hash, smart_contract.partially_verified)
),
"can_be_visualized_via_sol2uml" =>
visualize_sol2uml_enabled && !target_contract.is_vyper_contract && !is_nil(target_contract.abi),
"name" => target_contract.name,
"compiler_version" => target_contract.compiler_version,
"optimization_enabled" => target_contract.optimization,
"optimization_runs" => target_contract.optimization_runs,
"evm_version" => target_contract.evm_version,
"verified_at" => target_contract.inserted_at,
"abi" => target_contract.abi,
"source_code" => target_contract.contract_source_code,
"file_path" => target_contract.file_path,
visualize_sol2uml_enabled && target_contract && !target_contract.is_vyper_contract &&
!is_nil(target_contract.abi),
"name" => target_contract && target_contract.name,
"compiler_version" => target_contract && target_contract.compiler_version,
"optimization_enabled" => target_contract && target_contract.optimization,
"optimization_runs" => target_contract && target_contract.optimization_runs,
"evm_version" => target_contract && target_contract.evm_version,
"verified_at" => target_contract && target_contract.inserted_at,
"abi" => target_contract && target_contract.abi,
"source_code" => target_contract && target_contract.contract_source_code,
"file_path" => target_contract && target_contract.file_path,
"additional_sources" =>
(is_list(additional_sources) && Enum.map(additional_sources, &prepare_additional_source/1)) || [],
"compiler_settings" => target_contract.compiler_settings,
"external_libraries" => prepare_external_libraries(target_contract.external_libraries),
"constructor_args" => if(smart_contract_verified, do: target_contract.constructor_arguments),
"compiler_settings" => target_contract && target_contract.compiler_settings,
"external_libraries" => (target_contract && prepare_external_libraries(target_contract.external_libraries)) || [],
"constructor_args" => if(smart_contract_verified, do: target_contract && target_contract.constructor_arguments),
"decoded_constructor_args" =>
if(smart_contract_verified,
do: format_constructor_arguments(target_contract.abi, target_contract.constructor_arguments)
do:
target_contract && format_constructor_arguments(target_contract.abi, target_contract.constructor_arguments)
),
"language" => smart_contract_language(smart_contract),
"license_type" => smart_contract.license_type,
@ -222,26 +242,28 @@ defmodule BlockScoutWeb.API.V2.SmartContractView do
|> Map.merge(bytecode_info(address))
end
def prepare_smart_contract(address, conn) do
def prepare_smart_contract(address, implementations, proxy_type, conn) do
read_custom_abi? = AddressView.has_address_custom_abi_with_read_functions?(conn, address.hash)
write_custom_abi? = AddressView.has_address_custom_abi_with_write_functions?(conn, address.hash)
%{
"has_custom_methods_read" => read_custom_abi?,
"has_custom_methods_write" => write_custom_abi?
"has_custom_methods_write" => write_custom_abi?,
"proxy_type" => proxy_type,
"implementations" => implementations
}
|> Map.merge(bytecode_info(address))
end
@doc """
Returns additional sources of the smart-contract or from bytecode twin or from implementation, if it fits minimal proxy pattern (EIP-1167, Clone with immutable arguments)
Returns additional sources of the smart-contract from bytecode twin
"""
@spec get_additional_sources(SmartContract.t(), boolean, SmartContract.t() | nil) ::
[SmartContractAdditionalSource.t()] | nil
def get_additional_sources(smart_contract, smart_contract_verified, implementation_or_bytecode_twin_contract) do
def get_additional_sources(smart_contract, smart_contract_verified, bytecode_twin_contract) do
cond do
!is_nil(implementation_or_bytecode_twin_contract) ->
implementation_or_bytecode_twin_contract.smart_contract_additional_sources
!is_nil(bytecode_twin_contract) ->
bytecode_twin_contract.smart_contract_additional_sources
smart_contract_verified ->
smart_contract.smart_contract_additional_sources

@ -1,3 +1,5 @@
defmodule BlockScoutWeb.BlockWithdrawalView do
use BlockScoutWeb, :view
alias Explorer.Chain.Address
end

@ -2,7 +2,7 @@ defmodule BlockScoutWeb.Tokens.HolderView do
use BlockScoutWeb, :view
alias BlockScoutWeb.Tokens.OverviewView
alias Explorer.Chain.Token
alias Explorer.Chain.{Address, Token}
@doc """
Checks if the total supply percentage must be shown.

@ -2,7 +2,7 @@ defmodule BlockScoutWeb.TransactionStateView do
use BlockScoutWeb, :view
alias Explorer.Chain
alias Explorer.Chain.Wei
alias Explorer.Chain.{Address, Wei}
import Explorer.Chain.Transaction.StateChange, only: [from_loss: 1, has_diff?: 1, to_profit: 1]

@ -2,4 +2,5 @@ defmodule BlockScoutWeb.TransactionTokenTransferView do
use BlockScoutWeb, :view
alias Explorer.Chain
alias Explorer.Chain.Address
end

@ -449,7 +449,7 @@ defmodule BlockScoutWeb.TransactionView do
end
def involves_contract?(%Transaction{from_address: from_address, to_address: to_address}) do
AddressView.contract?(from_address) || AddressView.contract?(to_address)
Address.smart_contract?(from_address) || Address.smart_contract?(to_address)
end
def involves_token_transfers?(%Transaction{token_transfers: []}), do: false

@ -1,3 +1,5 @@
defmodule BlockScoutWeb.WithdrawalView do
use BlockScoutWeb, :view
alias Explorer.Chain.Address
end

@ -151,6 +151,7 @@ defmodule BlockScoutWeb.Account.Api.V2.UserControllerTest do
"name" => name,
"address" => %{
"hash" => Address.checksum(addr),
"proxy_type" => nil,
"implementations" => [],
"is_contract" => false,
"is_verified" => false,
@ -207,6 +208,7 @@ defmodule BlockScoutWeb.Account.Api.V2.UserControllerTest do
"name" => name,
"address" => %{
"hash" => Address.checksum(addr),
"proxy_type" => nil,
"implementations" => [],
"is_contract" => false,
"is_verified" => false,

@ -78,8 +78,6 @@ defmodule BlockScoutWeb.AddressReadContractControllerTest do
block_index: 0
)
TestHelper.get_eip1967_implementation_zero_addresses()
conn =
get(conn, address_read_contract_path(BlockScoutWeb.Endpoint, :index, Address.checksum(contract_address.hash)))

@ -77,8 +77,6 @@ defmodule BlockScoutWeb.AddressReadProxyControllerTest do
block_index: 0
)
TestHelper.get_eip1967_implementation_zero_addresses()
conn = get(conn, address_read_proxy_path(BlockScoutWeb.Endpoint, :index, Address.checksum(contract_address.hash)))
assert html_response(conn, 404)

@ -80,8 +80,6 @@ defmodule BlockScoutWeb.AddressWriteContractControllerTest do
block_index: 0
)
TestHelper.get_eip1967_implementation_zero_addresses()
conn =
get(conn, address_write_contract_path(BlockScoutWeb.Endpoint, :index, Address.checksum(contract_address.hash)))

@ -78,8 +78,6 @@ defmodule BlockScoutWeb.AddressWriteProxyControllerTest do
block_index: 0
)
TestHelper.get_eip1967_implementation_zero_addresses()
conn =
get(conn, address_write_proxy_path(BlockScoutWeb.Endpoint, :index, Address.checksum(contract_address.hash)))

@ -85,6 +85,7 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do
"creation_tx_hash" => nil,
"token" => nil,
"coin_balance" => nil,
"proxy_type" => nil,
"implementations" => [],
"block_number_balance_updated_at" => nil,
"has_decompiled_code" => false,
@ -220,6 +221,7 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do
"watchlist_names" => [],
"creator_address_hash" => ^from,
"creation_tx_hash" => ^tx_hash,
"proxy_type" => "eip1167",
"implementations" => [
%{"address" => ^checksummed_implementation_contract_address_hash, "name" => ^name}
]
@ -265,6 +267,7 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do
"watchlist_names" => [],
"creator_address_hash" => ^from,
"creation_tx_hash" => ^tx_hash,
"proxy_type" => "eip1967",
"implementations" => [%{"address" => ^implementation_address_hash_string, "name" => nil}]
} = json_response(request, 200)
end

@ -32,13 +32,15 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do
test "get unverified smart-contract info", %{conn: conn} do
address = insert(:contract_address)
TestHelper.get_eip1967_implementation_zero_addresses()
TestHelper.get_eip1967_implementation_error_response()
request = get(conn, "/api/v2/smart-contracts/#{Address.checksum(address.hash)}")
response = json_response(request, 200)
assert response ==
%{
"proxy_type" => nil,
"implementations" => [],
"has_custom_methods_read" => false,
"has_custom_methods_write" => false,
"is_self_destructed" => false,
@ -53,11 +55,15 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do
)
|> with_block()
TestHelper.get_eip1967_implementation_error_response()
request = get(conn, "/api/v2/smart-contracts/#{Address.checksum(address.hash)}")
response = json_response(request, 200)
assert response ==
%{
"proxy_type" => nil,
"implementations" => [],
"has_custom_methods_read" => false,
"has_custom_methods_write" => false,
"is_self_destructed" => false,
@ -112,6 +118,10 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do
)
|> with_block()
implementation_address = insert(:address)
implementation_address_hash_string = to_string(implementation_address.hash)
formatted_implementation_address_hash_string = to_string(Address.checksum(implementation_address.hash))
correct_response = %{
"verified_twin_address_hash" => nil,
"is_verified" => true,
@ -151,6 +161,8 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do
"creation_bytecode" =>
"0x608060405234801561001057600080fd5b5060df8061001f6000396000f3006080604052600436106049576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806360fe47b114604e5780636d4ce63c146078575b600080fd5b348015605957600080fd5b5060766004803603810190808035906020019092919050505060a0565b005b348015608357600080fd5b50608a60aa565b6040518082815260200191505060405180910390f35b8060008190555050565b600080549050905600a165627a7a7230582061b7676067d537e410bb704932a9984739a959416170ea17bda192ac1218d2790029",
"abi" => target_contract.abi,
"proxy_type" => "eip1967",
"implementations" => [%{"address" => formatted_implementation_address_hash_string, "name" => nil}],
"is_verified_via_eth_bytecode_db" => target_contract.verified_via_eth_bytecode_db,
"is_verified_via_verifier_alliance" => target_contract.verified_via_verifier_alliance,
"language" => smart_contract_language(target_contract),
@ -159,8 +171,6 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do
"is_blueprint" => false
}
implementation_address = insert(:address)
implementation_address_hash_string = to_string(implementation_address.hash)
TestHelper.get_eip1967_implementation_non_zero_address(implementation_address_hash_string)
request = get(conn, "/api/v2/smart-contracts/#{Address.checksum(target_contract.address_hash)}")
@ -257,6 +267,8 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do
"creation_bytecode" =>
"0x608060405234801561001057600080fd5b5060df8061001f6000396000f3006080604052600436106049576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806360fe47b114604e5780636d4ce63c146078575b600080fd5b348015605957600080fd5b5060766004803603810190808035906020019092919050505060a0565b005b348015608357600080fd5b50608a60aa565b6040518082815260200191505060405180910390f35b8060008190555050565b600080549050905600a165627a7a7230582061b7676067d537e410bb704932a9984739a959416170ea17bda192ac1218d2790029",
"abi" => target_contract.abi,
"proxy_type" => nil,
"implementations" => [],
"is_verified_via_eth_bytecode_db" => target_contract.verified_via_eth_bytecode_db,
"is_verified_via_verifier_alliance" => target_contract.verified_via_verifier_alliance,
"language" => smart_contract_language(target_contract),
@ -363,6 +375,8 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do
"creation_bytecode" =>
"0x608060405234801561001057600080fd5b5060df8061001f6000396000f3006080604052600436106049576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806360fe47b114604e5780636d4ce63c146078575b600080fd5b348015605957600080fd5b5060766004803603810190808035906020019092919050505060a0565b005b348015608357600080fd5b50608a60aa565b6040518082815260200191505060405180910390f35b8060008190555050565b600080549050905600a165627a7a7230582061b7676067d537e410bb704932a9984739a959416170ea17bda192ac1218d2790029",
"abi" => target_contract.abi,
"proxy_type" => nil,
"implementations" => [],
"is_verified_via_eth_bytecode_db" => target_contract.verified_via_eth_bytecode_db,
"is_verified_via_verifier_alliance" => target_contract.verified_via_verifier_alliance,
"language" => smart_contract_language(target_contract),
@ -379,7 +393,7 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do
assert correct_response == response
end
test "get smart-contract multiple additional sources from EIP-1167 implementation", %{conn: conn} do
test "doesn't get smart-contract multiple additional sources from EIP-1167 implementation", %{conn: conn} do
implementation_contract =
insert(:smart_contract,
external_libraries: [],
@ -449,48 +463,18 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do
|> with_block(status: :ok)
correct_response = %{
"verified_twin_address_hash" => Address.checksum(implementation_contract.address_hash),
"is_verified" => false,
"is_changed_bytecode" => false,
"is_partially_verified" => implementation_contract.partially_verified,
"is_fully_verified" => false,
"is_verified_via_sourcify" => false,
"is_vyper_contract" => implementation_contract.is_vyper_contract,
"has_methods_read" => true,
"has_methods_write" => true,
"has_methods_read_proxy" => true,
"has_methods_write_proxy" => true,
"has_custom_methods_read" => false,
"has_custom_methods_write" => false,
"minimal_proxy_address_hash" => Address.checksum(implementation_contract.address_hash),
"sourcify_repo_url" => nil,
"can_be_visualized_via_sol2uml" => false,
"name" => implementation_contract && implementation_contract.name,
"compiler_version" => implementation_contract.compiler_version,
"optimization_enabled" => implementation_contract.optimization,
"optimization_runs" => implementation_contract.optimization_runs,
"evm_version" => implementation_contract.evm_version,
"verified_at" => implementation_contract.inserted_at |> to_string() |> String.replace(" ", "T"),
"source_code" => implementation_contract.contract_source_code,
"file_path" => implementation_contract.file_path,
"additional_sources" => [
%{"file_path" => "test1", "source_code" => "test2"},
%{"file_path" => "test3", "source_code" => "test4"}
],
"compiler_settings" => implementation_contract.compiler_settings,
"external_libraries" => [],
"constructor_args" => nil,
"decoded_constructor_args" => nil,
"is_self_destructed" => false,
"deployed_bytecode" => proxy_deployed_bytecode,
"creation_bytecode" => proxy_tx_input,
"abi" => implementation_contract.abi,
"is_verified_via_eth_bytecode_db" => implementation_contract.verified_via_eth_bytecode_db,
"is_verified_via_verifier_alliance" => implementation_contract.verified_via_verifier_alliance,
"language" => smart_contract_language(implementation_contract),
"license_type" => "bsd_3_clause",
"certified" => false,
"is_blueprint" => false
"proxy_type" => "eip1167",
"implementations" => [
%{
"address" => Address.checksum(implementation_contract.address_hash),
"name" => implementation_contract.name
}
]
}
request = get(conn, "/api/v2/smart-contracts/#{Address.checksum(proxy_address.hash)}")
@ -551,6 +535,8 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do
"creation_bytecode" =>
"0x608060405234801561001057600080fd5b5060df8061001f6000396000f3006080604052600436106049576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806360fe47b114604e5780636d4ce63c146078575b600080fd5b348015605957600080fd5b5060766004803603810190808035906020019092919050505060a0565b005b348015608357600080fd5b50608a60aa565b6040518082815260200191505060405180910390f35b8060008190555050565b600080549050905600a165627a7a7230582061b7676067d537e410bb704932a9984739a959416170ea17bda192ac1218d2790029",
"abi" => target_contract.abi,
"proxy_type" => nil,
"implementations" => [],
"is_verified_via_eth_bytecode_db" => target_contract.verified_via_eth_bytecode_db,
"is_verified_via_verifier_alliance" => target_contract.verified_via_verifier_alliance,
"language" => smart_contract_language(target_contract),
@ -568,7 +554,7 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do
end
end
test "get smart-contract implementation for 'Clones with immutable arguments' pattern", %{conn: conn} do
test "doesn't get smart-contract implementation for 'Clones with immutable arguments' pattern", %{conn: conn} do
implementation_contract =
insert(:smart_contract,
external_libraries: [],
@ -631,48 +617,18 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do
)
|> with_block(status: :ok)
formatted_implementation_address_hash_string = to_string(Address.checksum(implementation_contract.address_hash))
correct_response = %{
"verified_twin_address_hash" => Address.checksum(implementation_contract.address_hash),
"is_verified" => false,
"is_changed_bytecode" => false,
"is_partially_verified" => implementation_contract.partially_verified,
"is_fully_verified" => false,
"is_verified_via_sourcify" => false,
"is_vyper_contract" => implementation_contract.is_vyper_contract,
"has_methods_read" => true,
"has_methods_write" => true,
"has_methods_read_proxy" => true,
"has_methods_write_proxy" => true,
"proxy_type" => "clone_with_immutable_arguments",
"implementations" => [
%{"address" => formatted_implementation_address_hash_string, "name" => implementation_contract.name}
],
"has_custom_methods_read" => false,
"has_custom_methods_write" => false,
"minimal_proxy_address_hash" => Address.checksum(implementation_contract.address_hash),
"sourcify_repo_url" => nil,
"can_be_visualized_via_sol2uml" => false,
"name" => implementation_contract && implementation_contract.name,
"compiler_version" => implementation_contract.compiler_version,
"optimization_enabled" => implementation_contract.optimization,
"optimization_runs" => implementation_contract.optimization_runs,
"evm_version" => implementation_contract.evm_version,
"verified_at" => implementation_contract.inserted_at |> to_string() |> String.replace(" ", "T"),
"source_code" => implementation_contract.contract_source_code,
"file_path" => implementation_contract.file_path,
"additional_sources" => [
%{"file_path" => "test1", "source_code" => "test2"}
],
"compiler_settings" => implementation_contract.compiler_settings,
"external_libraries" => [],
"constructor_args" => nil,
"decoded_constructor_args" => nil,
"is_self_destructed" => false,
"deployed_bytecode" => proxy_deployed_bytecode,
"creation_bytecode" => proxy_tx_input,
"abi" => implementation_contract.abi,
"is_verified_via_eth_bytecode_db" => implementation_contract.verified_via_eth_bytecode_db,
"is_verified_via_verifier_alliance" => implementation_contract.verified_via_verifier_alliance,
"language" => smart_contract_language(implementation_contract),
"license_type" => "bsd_3_clause",
"certified" => false,
"is_blueprint" => false
"creation_bytecode" => proxy_tx_input
}
request = get(conn, "/api/v2/smart-contracts/#{Address.checksum(proxy_address.hash)}")
@ -752,6 +708,8 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do
assert response ==
%{
"proxy_type" => nil,
"implementations" => [],
"has_custom_methods_read" => false,
"has_custom_methods_write" => false,
"is_self_destructed" => false,
@ -837,6 +795,8 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do
assert response ==
%{
"proxy_type" => nil,
"implementations" => [],
"has_custom_methods_read" => false,
"has_custom_methods_write" => false,
"is_self_destructed" => false,
@ -969,6 +929,8 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do
assert response ==
%{
"proxy_type" => nil,
"implementations" => [],
"has_custom_methods_read" => false,
"has_custom_methods_write" => false,
"is_self_destructed" => false,
@ -1034,7 +996,7 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do
Conn.resp(conn, 200, eth_bytecode_response)
end)
TestHelper.get_eip1967_implementation_error_response()
TestHelper.get_eip1967_implementation_zero_addresses()
request = get(conn, "/api/v2/smart-contracts/#{Address.checksum(address.hash)}")
@ -1056,6 +1018,8 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do
assert response ==
%{
"proxy_type" => nil,
"implementations" => [],
"has_custom_methods_read" => false,
"has_custom_methods_write" => false,
"is_self_destructed" => false,
@ -1064,8 +1028,6 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do
"0x608060405234801561001057600080fd5b5060df8061001f6000396000f3006080604052600436106049576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806360fe47b114604e5780636d4ce63c146078575b600080fd5b348015605957600080fd5b5060766004803603810190808035906020019092919050505060a0565b005b348015608357600080fd5b50608a60aa565b6040518082815260200191505060405180910390f35b8060008190555050565b600080549050905600a165627a7a7230582061b7676067d537e410bb704932a9984739a959416170ea17bda192ac1218d2790029"
}
TestHelper.get_eip1967_implementation_zero_addresses()
request = get(conn, "/api/v2/smart-contracts/#{Address.checksum(address.hash)}")
assert response = json_response(request, 200)
@ -1148,6 +1110,7 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do
implementation_address = insert(:address)
implementation_address_hash_string = to_string(implementation_address.hash)
formatted_implementation_address_hash_string = to_string(Address.checksum(implementation_address.hash))
TestHelper.get_eip1967_implementation_non_zero_address(implementation_address_hash_string)
request = get(conn, "/api/v2/smart-contracts/#{Address.checksum(address.hash)}")
@ -1170,6 +1133,8 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do
assert response ==
%{
"proxy_type" => nil,
"implementations" => [],
"has_custom_methods_read" => false,
"has_custom_methods_write" => false,
"is_self_destructed" => false,
@ -1180,6 +1145,11 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do
request = get(conn, "/api/v2/smart-contracts/#{Address.checksum(address.hash)}")
assert response = json_response(request, 200)
assert %{"proxy_type" => "eip1967"} = response
assert %{"implementations" => [%{"address" => ^formatted_implementation_address_hash_string, "name" => nil}]} =
response
assert %{"is_verified" => true} = response
assert %{"is_verified_via_eth_bytecode_db" => true} = response
assert %{"is_partially_verified" => true} = response
@ -1265,6 +1235,7 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do
implementation_address = insert(:address)
implementation_address_hash_string = to_string(implementation_address.hash)
formatted_implementation_address_hash_string = to_string(Address.checksum(implementation_address.hash))
TestHelper.get_eip1967_implementation_non_zero_address(implementation_address_hash_string)
request = get(conn, "/api/v2/smart-contracts/#{Address.checksum(address.hash)}")
@ -1287,6 +1258,8 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do
assert response ==
%{
"proxy_type" => nil,
"implementations" => [],
"has_custom_methods_read" => false,
"has_custom_methods_write" => false,
"is_self_destructed" => false,
@ -1297,6 +1270,11 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do
request = get(conn, "/api/v2/smart-contracts/#{Address.checksum(address.hash)}")
assert response = json_response(request, 200)
assert %{"proxy_type" => "eip1967"} = response
assert %{"implementations" => [%{"address" => ^formatted_implementation_address_hash_string, "name" => nil}]} =
response
assert %{"is_verified" => true} = response
assert %{"is_verified_via_eth_bytecode_db" => true} = response
assert %{"is_partially_verified" => false} = response
@ -1382,6 +1360,7 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do
implementation_address = insert(:address)
implementation_address_hash_string = to_string(implementation_address.hash)
formatted_implementation_address_hash_string = to_string(Address.checksum(implementation_address.hash))
TestHelper.get_eip1967_implementation_non_zero_address(implementation_address_hash_string)
request = get(conn, "/api/v2/smart-contracts/#{Address.checksum(address.hash)}")
@ -1404,6 +1383,8 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do
assert response ==
%{
"proxy_type" => nil,
"implementations" => [],
"has_custom_methods_read" => false,
"has_custom_methods_write" => false,
"is_self_destructed" => false,
@ -1414,6 +1395,11 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do
request = get(conn, "/api/v2/smart-contracts/#{Address.checksum(address.hash)}")
assert response = json_response(request, 200)
assert %{"proxy_type" => "eip1967"} = response
assert %{"implementations" => [%{"address" => ^formatted_implementation_address_hash_string, "name" => nil}]} =
response
assert %{"is_verified" => true} = response
assert %{"is_verified_via_eth_bytecode_db" => true} = response
assert %{"is_partially_verified" => false} = response
@ -2260,8 +2246,6 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do
test "return 404 on unverified contract", %{conn: conn} do
address = insert(:contract_address)
TestHelper.get_eip1967_implementation_zero_addresses()
request =
post(conn, "/api/v2/smart-contracts/#{Address.checksum(address.hash)}/query-read-method", %{
"contract_type" => "regular",
@ -2909,8 +2893,6 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do
custom_abi
)
TestHelper.get_eip1967_implementation_zero_addresses()
expect(
EthereumJSONRPC.Mox,
:json_rpc,
@ -3050,8 +3032,6 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do
test "return 404 on unverified contract", %{conn: conn} do
address = insert(:contract_address)
TestHelper.get_eip1967_implementation_zero_addresses()
request = get(conn, "/api/v2/smart-contracts/#{Address.checksum(address.hash)}/methods-read-proxy")
assert %{"message" => "Not found"} = json_response(request, 404)
end
@ -3421,7 +3401,6 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do
test "return 404 on unverified contract", %{conn: conn} do
address = insert(:contract_address)
TestHelper.get_eip1967_implementation_zero_addresses()
request = get(conn, "/api/v2/smart-contracts/#{Address.checksum(address.hash)}/methods-write-proxy")
assert %{"message" => "Not found"} = json_response(request, 404)
end

@ -176,23 +176,6 @@ defmodule BlockScoutWeb.AddressViewTest do
assert "" = AddressView.balance_percentage(address, nil)
end
describe "contract?/1" do
test "with a smart contract" do
{:ok, code} = Data.cast("0x000000000000000000000000862d67cb0773ee3f8ce7ea89b328ffea861ab3ef")
address = insert(:address, contract_code: code)
assert AddressView.contract?(address)
end
test "with an account" do
address = insert(:address, contract_code: nil)
refute AddressView.contract?(address)
end
test "with nil address" do
assert AddressView.contract?(nil)
end
end
describe "hash/1" do
test "gives a string version of an address's hash" do
address = %Address{

@ -85,8 +85,6 @@ 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
alias Explorer.Chain.SmartContract.Proxy.Models.Implementation
alias Explorer.Market.MarketHistoryCache
alias Explorer.{PagingOptions, Repo}
@ -1136,7 +1134,8 @@ defmodule Explorer.Chain do
options
|> Keyword.get(:necessity_by_association, %{})
|> Map.merge(%{
[smart_contract: :smart_contract_additional_sources] => :optional
[smart_contract: :smart_contract_additional_sources] => :optional,
:proxy_implementations => :optional
})
query =
@ -1171,20 +1170,7 @@ defmodule Explorer.Chain do
nil
)
{implementation_address_hashes, _} =
Implementation.get_implementation(
%{
updated: %SmartContract{
address_hash: hash
},
implementation_updated_at: nil,
implementation_address_fetched?: false,
refetch_necessity_checked?: false
},
Keyword.put(options, :proxy_without_abi?, true)
)
add_implementation_and_bytecode_twin_to_result(address_result, implementation_address_hashes, hash, options)
add_bytecode_twin_to_result(address_result, hash, options)
end
_ ->
@ -1206,31 +1192,12 @@ defmodule Explorer.Chain do
end
end
defp add_implementation_and_bytecode_twin_to_result(address_result, implementation_address_hashes, hash, options) do
# implementation is added only in the case when mapping proxy to implementation is 1:1 (excluding Diamond proxy)
{implementation_smart_contract, implementation_address_hash} =
if implementation_address_hashes && Enum.count(implementation_address_hashes) == 1 do
implementation_address_hash = implementation_address_hashes |> Enum.at(0)
implementation_smart_contract =
implementation_address_hash
|> Proxy.implementation_to_smart_contract(options)
{implementation_smart_contract, implementation_address_hash}
else
{nil, nil}
end
defp add_bytecode_twin_to_result(address_result, hash, options) do
address_verified_bytecode_twin_contract =
implementation_smart_contract ||
SmartContract.get_address_verified_bytecode_twin_contract(hash, options).verified_contract
SmartContract.get_address_verified_bytecode_twin_contract(hash, options).verified_contract
address_result
|> SmartContract.add_bytecode_twin_info_to_contract(address_verified_bytecode_twin_contract, hash)
|> (&if(is_nil(implementation_smart_contract),
do: &1,
else: SmartContract.add_implementation_info_to_contract(&1, implementation_address_hash)
)).()
end
@spec find_decompiled_contract_address(Hash.Address.t()) :: {:ok, Address.t()} | {:error, :not_found}
@ -1849,10 +1816,6 @@ defmodule Explorer.Chain do
Decimal.mult(tokens, fiat_value)
end
def contract?(%{contract_code: nil}), do: false
def contract?(%{contract_code: _}), do: true
@doc """
Returns a stream of unfetched `t:Explorer.Chain.Address.CoinBalance.t/0`.

@ -26,6 +26,7 @@ defmodule Explorer.Chain.Address do
}
alias Explorer.Chain.Cache.{Accounts, NetVersion}
alias Explorer.Chain.SmartContract.Proxy
alias Explorer.Chain.SmartContract.Proxy.Models.Implementation
@optional_attrs ~w(contract_code fetched_coin_balance fetched_coin_balance_block_number nonce decompiled verified gas_used transactions_count token_transfers_count)a
@ -93,6 +94,8 @@ defmodule Explorer.Chain.Address do
field(:gas_used, :integer)
field(:ens_domain_name, :string, virtual: true)
field(:metadata, :any, virtual: true)
# todo: remove virtual field for a single implementation when frontend is bound to "implementations" object value in API
field(:implementation, :any, virtual: true)
has_one(:smart_contract, SmartContract, references: :hash)
@ -460,4 +463,24 @@ defmodule Explorer.Chain.Address do
timeout: @timeout
)
end
@doc """
Prepares implementations object and proxy type from address
"""
@spec parse_implementation_and_proxy_type(__MODULE__.t()) :: {list(), String.t() | nil}
def parse_implementation_and_proxy_type(address) do
with %__MODULE__{
proxy_implementations: %Implementation{
address_hashes: address_hashes,
names: names,
proxy_type: proxy_type
}
} <- address,
false <- address_hashes && Enum.empty?(address_hashes) do
{Proxy.proxy_object_info(address_hashes, names), proxy_type}
else
_ ->
{[], nil}
end
end
end

@ -228,7 +228,7 @@ defmodule Explorer.Chain.Address.Counters do
@spec address_to_gas_usage_count(Address.t()) :: Decimal.t() | nil
def address_to_gas_usage_count(address) do
if Chain.contract?(address) do
if Address.smart_contract?(address) do
incoming_transaction_gas_usage = address_to_incoming_transaction_gas_usage(address.hash)
cond do

@ -594,7 +594,7 @@ defmodule Explorer.Chain.SmartContract do
refetch_necessity_checked?: false
}
{implementation_address_hash, _} =
{implementation_address_hash, _names, _proxy_type} =
Implementation.get_implementation(
smart_contract,
Keyword.put(options, :proxy_without_abi?, true)
@ -618,7 +618,7 @@ defmodule Explorer.Chain.SmartContract do
def compose_address_for_unverified_smart_contract(address_result, _hash, _options), do: address_result
def single_implementation_smart_contract_from_proxy(proxy_hash, options) do
{implementation_address_hashes, _} = Implementation.get_implementation(proxy_hash, options)
{implementation_address_hashes, _names, _proxy_type} = Implementation.get_implementation(proxy_hash, options)
if implementation_address_hashes && Enum.count(implementation_address_hashes) == 1 do
implementation_address_hashes
@ -740,21 +740,6 @@ defmodule Explorer.Chain.SmartContract do
|> Map.put(:smart_contract, address_verified_bytecode_twin_contract_updated)
end
def add_implementation_info_to_contract(address_result, nil), do: address_result
def add_implementation_info_to_contract(
%Address{smart_contract: smart_contract} = address_result,
implementation_address_hash
)
when not is_nil(smart_contract) do
implementation = Map.put(smart_contract, :address_hash, implementation_address_hash)
address_result
|> Map.put(:implementation, implementation)
end
def add_implementation_info_to_contract(address_result, _), do: address_result
@doc """
Inserts a new smart contract and associated data into the database.
@ -958,7 +943,7 @@ defmodule Explorer.Chain.SmartContract do
with true <- is_nil(current_smart_contract),
{:ok, address} <- Chain.hash_to_address(address_hash),
true <- Chain.contract?(address) do
true <- Address.smart_contract?(address) do
{implementation_smart_contract, implementation_address_fetched?} =
if fetch_implementation? do
implementation_smart_contract =

@ -4,8 +4,9 @@ defmodule Explorer.Chain.SmartContract.Proxy do
"""
alias EthereumJSONRPC.Contract
alias Explorer.Chain.{Hash, SmartContract}
alias Explorer.Chain.{Address, Hash, SmartContract}
alias Explorer.Chain.SmartContract.Proxy
alias Explorer.Chain.SmartContract.Proxy.Models.Implementation
alias Explorer.Chain.SmartContract.Proxy.{
Basic,
@ -51,7 +52,7 @@ defmodule Explorer.Chain.SmartContract.Proxy do
Fetches into DB proxy contract implementation's address and name from different proxy patterns
"""
@spec fetch_implementation_address_hash(Hash.Address.t(), list(), options) ::
{[String.t()] | :empty | :error, [String.t()] | :empty | :error}
Implementation.t() | :empty | :error
def fetch_implementation_address_hash(proxy_address_hash, proxy_abi, options)
when not is_nil(proxy_address_hash) do
%{implementation_address_hash_strings: implementation_address_hash_strings, proxy_type: proxy_type} =
@ -70,7 +71,7 @@ defmodule Explorer.Chain.SmartContract.Proxy do
end
def fetch_implementation_address_hash(_, _, _) do
{:empty, :empty}
:empty
end
@doc """
@ -79,32 +80,37 @@ defmodule Explorer.Chain.SmartContract.Proxy do
@spec proxy_contract?(SmartContract.t(), Keyword.t()) :: boolean()
def proxy_contract?(smart_contract, options \\ []) do
{:ok, burn_address_hash} = string_to_address_hash(SmartContract.burn_address_hash_string())
implementation = get_proxy_implementations(smart_contract.address_hash)
proxy_implementations = get_proxy_implementations(smart_contract.address_hash)
with false <- is_nil(implementation),
false <- Enum.empty?(implementation.address_hashes),
implementation_address_hash = Enum.at(implementation.address_hashes, 0),
with false <- is_nil(proxy_implementations),
false <- Enum.empty?(proxy_implementations.address_hashes),
implementation_address_hash = Enum.at(proxy_implementations.address_hashes, 0),
false <- implementation_address_hash.bytes == burn_address_hash.bytes do
true
else
_ ->
{implementation_address_hash_strings, _implementation_names} = get_implementation(smart_contract, options)
with false <- is_nil(implementation_address_hash_strings),
false <- Enum.empty?(implementation_address_hash_strings) do
implementation_address_hash_strings
|> Enum.reduce_while(false, fn implementation_address_hash_string, acc ->
with {:ok, implementation_address_hash} <- string_to_address_hash(implementation_address_hash_string),
false <- implementation_address_hash.bytes == burn_address_hash.bytes do
{:halt, true}
else
_ ->
{:cont, acc}
end
end)
if options[:skip_implementation_fetch?] do
false
else
_ ->
false
{implementation_address_hash_strings, _implementation_names, _proxy_type} =
get_implementation(smart_contract, options)
with false <- is_nil(implementation_address_hash_strings),
false <- Enum.empty?(implementation_address_hash_strings) do
implementation_address_hash_strings
|> Enum.reduce_while(false, fn implementation_address_hash_string, acc ->
with {:ok, implementation_address_hash} <- string_to_address_hash(implementation_address_hash_string),
false <- implementation_address_hash.bytes == burn_address_hash.bytes do
{:halt, true}
else
_ ->
{:cont, acc}
end
end)
else
_ ->
false
end
end
end
end
@ -138,7 +144,7 @@ defmodule Explorer.Chain.SmartContract.Proxy do
options
)
when not is_nil(proxy_address_hash) and not is_nil(abi) do
{implementation_address_hash_strings, _names} = get_implementation(smart_contract, options)
{implementation_address_hash_strings, _names, _proxy_type} = get_implementation(smart_contract, options)
implementation_address_hash_strings
|> Enum.reduce([], fn implementation_address_hash_string, acc ->
@ -452,4 +458,39 @@ defmodule Explorer.Chain.SmartContract.Proxy do
|> join_associations(necessity_by_association)
|> select_repo(options).one(timeout: 10_000)
end
@doc """
Retrieves formatted proxy object based on its implementation addresses and names.
## Parameters
* `implementation_addresses` - A list of implementation addresses for the proxy object.
* `implementation_names` - A list of implementation names for the proxy object.
## Returns
A list of maps containing information about the proxy object.
"""
@spec proxy_object_info([String.t() | Hash.Address.t()], [String.t() | nil]) :: [map()]
def proxy_object_info([], []), do: []
def proxy_object_info(implementation_addresses, implementation_names) do
implementation_addresses
|> Enum.zip(implementation_names)
|> Enum.reduce([], fn {address, name}, acc ->
case address do
%Hash{} = address_hash ->
[%{"address" => Address.checksum(address_hash), "name" => name} | acc]
_ ->
with {:ok, address_hash} <- string_to_address_hash(address),
checksummed_address <- Address.checksum(address_hash) do
[%{"address" => checksummed_address, "name" => name} | acc]
else
_ -> acc
end
end
end)
end
end

@ -7,10 +7,17 @@ defmodule Explorer.Chain.SmartContract.Proxy.Models.Implementation do
use Explorer.Schema
import Ecto.Query,
only: [
from: 2,
select: 3
]
import Explorer.Chain, only: [select_repo: 1, string_to_address_hash: 1]
alias Explorer.Chain.{Address, Hash, SmartContract}
alias Explorer.Chain.SmartContract.Proxy
alias Explorer.Chain.SmartContract.Proxy.Models.Implementation
alias Explorer.Counters.AverageBlockTime
alias Explorer.Repo
alias Timex.Duration
@ -78,14 +85,9 @@ defmodule Explorer.Chain.SmartContract.Proxy.Models.Implementation do
Returns all implementations for the given smart-contract address hash
"""
@spec get_proxy_implementations(Hash.Address.t() | nil, Keyword.t()) :: __MODULE__.t() | nil
def get_proxy_implementations(address_hash, options \\ []) do
all_implementations_query =
from(
p in __MODULE__,
where: p.proxy_address_hash == ^address_hash
)
all_implementations_query
def get_proxy_implementations(proxy_address_hash, options \\ []) do
proxy_address_hash
|> get_proxy_implementations_query()
|> select_repo(options).one()
end
@ -93,22 +95,24 @@ defmodule Explorer.Chain.SmartContract.Proxy.Models.Implementation do
Returns the last implementation updated_at for the given smart-contract address hash
"""
@spec get_proxy_implementation_updated_at(Hash.Address.t() | nil, Keyword.t()) :: DateTime.t()
def get_proxy_implementation_updated_at(address_hash, options) do
updated_at_query =
from(
p in __MODULE__,
where: p.proxy_address_hash == ^address_hash,
select: p.updated_at
)
updated_at_query
def get_proxy_implementation_updated_at(proxy_address_hash, options) do
proxy_address_hash
|> get_proxy_implementations_query()
|> select([p], p.updated_at)
|> select_repo(options).one()
end
defp get_proxy_implementations_query(proxy_address_hash) do
from(
p in __MODULE__,
where: p.proxy_address_hash == ^proxy_address_hash
)
end
@doc """
Returns implementation address and name of the given SmartContract by hash address
Returns implementation address, name and proxy type for the given SmartContract
"""
@spec get_implementation(any(), any()) :: {any(), any()}
@spec get_implementation(any(), any()) :: {any(), any(), atom() | nil}
def get_implementation(smart_contract, options \\ [])
def get_implementation(
@ -152,6 +156,7 @@ defmodule Explorer.Chain.SmartContract.Proxy.Models.Implementation do
)
end
# credo:disable-for-next-line Credo.Check.Refactor.CyclomaticComplexity
def get_implementation(
%{
updated: %SmartContract{
@ -164,14 +169,15 @@ defmodule Explorer.Chain.SmartContract.Proxy.Models.Implementation do
},
options
) do
{implementation_addresses_hash_from_db, implementation_names_from_db, implementation_updated_at_from_db} =
implementation_from_db(address_hash, options)
proxy_implementations = get_proxy_implementations(address_hash, options)
implementation_updated_at = implementation_updated_at || implementation_updated_at_from_db
implementation_updated_at = implementation_updated_at || (proxy_implementations && proxy_implementations.updated_at)
if fetch_implementation?(implementation_address_fetched?, refetch_necessity_checked?, implementation_updated_at) do
get_implementation_address_hash_task =
Task.async(fn ->
# Here and only here we fetch implementations for the given address
# using requests to the JSON RPC node for known proxy patterns
result = Proxy.fetch_implementation_address_hash(address_hash, abi, options)
callback = Keyword.get(options, :callback, nil)
@ -187,41 +193,34 @@ defmodule Explorer.Chain.SmartContract.Proxy.Models.Implementation do
case Task.yield(get_implementation_address_hash_task, timeout) ||
Task.ignore(get_implementation_address_hash_task) do
{:ok, {:empty, :empty}} ->
{[], []}
{:ok, :empty} ->
{[], [], nil}
{:ok, {:error, :error}} ->
{db_implementation_data_converter(implementation_addresses_hash_from_db),
db_implementation_data_converter(implementation_names_from_db)}
{:ok, :error} ->
format_proxy_implementations_response(proxy_implementations)
{:ok, {address_hash, _name} = result} when not is_nil(address_hash) ->
result
{:ok, %Implementation{} = result} ->
format_proxy_implementations_response(result)
_ ->
{db_implementation_data_converter(implementation_addresses_hash_from_db),
db_implementation_data_converter(implementation_names_from_db)}
format_proxy_implementations_response(proxy_implementations)
end
else
{db_implementation_data_converter(implementation_addresses_hash_from_db),
db_implementation_data_converter(implementation_names_from_db)}
format_proxy_implementations_response(proxy_implementations)
end
end
def get_implementation(_, _), do: {[], []}
def get_implementation(_, _), do: {[], [], nil}
defp fetch_implementation?(implementation_address_fetched?, refetch_necessity_checked?, implementation_updated_at) do
(!implementation_address_fetched? || !refetch_necessity_checked?) &&
check_implementation_refetch_necessity(implementation_updated_at)
end
defp implementation_from_db(address_hash, options) do
proxy_implementations = get_proxy_implementations(address_hash, options)
if proxy_implementations do
{proxy_implementations.address_hashes, proxy_implementations.names, proxy_implementations.updated_at}
else
{[], [], nil}
end
defp format_proxy_implementations_response(proxy_implementations) do
{(proxy_implementations && db_implementation_data_converter(proxy_implementations.address_hashes)) || [],
(proxy_implementations && db_implementation_data_converter(proxy_implementations.names)) || [],
proxy_implementations && proxy_implementations.proxy_type}
end
@doc """
@ -279,9 +278,9 @@ defmodule Explorer.Chain.SmartContract.Proxy.Models.Implementation do
Saves proxy's implementation into the DB
"""
@spec save_implementation_data([String.t()], Hash.Address.t(), atom() | nil, Keyword.t()) ::
{[String.t()], [String.t()]} | {:empty, :empty} | {:error, :error}
Implementation.t() | :empty | :error
def save_implementation_data(:error, _proxy_address_hash, _proxy_type, _options) do
{:error, :error}
:error
end
def save_implementation_data(implementation_address_hash_strings, proxy_address_hash, proxy_type, options)
@ -289,7 +288,7 @@ defmodule Explorer.Chain.SmartContract.Proxy.Models.Implementation do
implementation_address_hash_strings == [] do
upsert_implementation(proxy_address_hash, proxy_type, [], [], options)
{:empty, :empty}
:empty
end
def save_implementation_data(
@ -301,7 +300,7 @@ defmodule Explorer.Chain.SmartContract.Proxy.Models.Implementation do
when is_burn_signature(empty_implementation_address_hash_string) do
upsert_implementation(proxy_address_hash, proxy_type, [], [], options)
{:empty, :empty}
:empty
end
def save_implementation_data(
@ -331,17 +330,22 @@ defmodule Explorer.Chain.SmartContract.Proxy.Models.Implementation do
|> Enum.unzip()
if Enum.empty?(implementation_addresses) do
{:empty, :empty}
:empty
else
upsert_implementation(
proxy_address_hash,
proxy_type,
implementation_addresses,
implementation_names,
options
)
case upsert_implementation(
proxy_address_hash,
proxy_type,
implementation_addresses,
implementation_names,
options
) do
{:ok, result} ->
result
{implementation_addresses, implementation_names}
{:error, error} ->
Logger.error("Error while upserting proxy implementations data into the DB: #{inspect(error)}")
:error
end
end
end

@ -1873,7 +1873,7 @@ defmodule Explorer.Chain.Transaction do
@spec where_transactions_to_from(Hash.Address.t()) :: any()
def where_transactions_to_from(address_hash) do
with {:ok, address} <- Chain.hash_to_address(address_hash),
true <- Chain.contract?(address) do
true <- Address.smart_contract?(address) do
dynamic([transaction], transaction.to_address_hash == ^address_hash)
else
_ ->

@ -4,10 +4,14 @@ defmodule Explorer.SmartContract.Helper do
"""
alias Explorer.{Chain, Helper}
alias Explorer.Chain.{Hash, SmartContract}
alias Explorer.Chain.{Address, Hash, SmartContract}
alias Explorer.Chain.SmartContract.Proxy
alias Explorer.Chain.SmartContract.Proxy.Models.Implementation
alias Explorer.SmartContract.Writer
alias Phoenix.HTML
@api_true [api?: true]
def queriable_method?(method) do
method["constant"] || method["stateMutability"] == "view" || method["stateMutability"] == "pure"
end
@ -210,4 +214,46 @@ defmodule Explorer.SmartContract.Helper do
def prepare_license_type(binary) when is_binary(binary), do: Helper.parse_integer(binary) || binary
def prepare_license_type(_), do: nil
@doc """
Pre-fetches implementation for unverified smart contract or verified proxy smart-contract
"""
@spec pre_fetch_implementations(Address.t()) :: {any(), atom() | nil}
def pre_fetch_implementations(address) do
{implementation_address_hashes, implementation_names, proxy_type} =
with {:verified_smart_contract, %SmartContract{}} <- {:verified_smart_contract, address.smart_contract},
{:proxy?, true} <- {:proxy?, address_is_proxy?(address, @api_true)} do
Implementation.get_implementation(address.smart_contract, @api_true)
else
{:verified_smart_contract, _} ->
if Address.smart_contract?(address) do
smart_contract = %SmartContract{
address_hash: address.hash
}
Implementation.get_implementation(smart_contract, @api_true)
else
{[], [], nil}
end
{:proxy?, false} ->
{[], [], nil}
end
implementations = Proxy.proxy_object_info(implementation_address_hashes, implementation_names)
{implementations, proxy_type}
end
@doc """
Checks if given address is proxy smart contract
"""
@spec address_is_proxy?(Address.t(), list()) :: boolean()
def address_is_proxy?(address, options \\ [])
def address_is_proxy?(%Address{smart_contract: %SmartContract{} = smart_contract}, options) do
Proxy.proxy_contract?(smart_contract, options)
end
def address_is_proxy?(%Address{smart_contract: _}, _), do: false
end

@ -28,7 +28,7 @@ defmodule Explorer.Chain.SmartContract.Proxy.Models.Implementation.Test do
# fetch nil implementation and don't save it to db
TestHelper.get_eip1967_implementation_zero_addresses()
assert {[], []} = Implementation.get_implementation(smart_contract)
assert {[], [], nil} = Implementation.get_implementation(smart_contract)
verify!(EthereumJSONRPC.Mox)
assert_empty_implementation(smart_contract.address_hash)
@ -44,7 +44,7 @@ defmodule Explorer.Chain.SmartContract.Proxy.Models.Implementation.Test do
expect_address_in_oz_slot_response(string_implementation_address_hash)
assert {[^string_implementation_address_hash], ["implementation"]} =
assert {[^string_implementation_address_hash], ["implementation"], :eip1967} =
Implementation.get_implementation(smart_contract)
verify!(EthereumJSONRPC.Mox)
@ -57,7 +57,7 @@ defmodule Explorer.Chain.SmartContract.Proxy.Models.Implementation.Test do
TestHelper.get_eip1967_implementation_error_response()
assert {[^string_implementation_address_hash], ["implementation"]} =
assert {[^string_implementation_address_hash], ["implementation"], :eip1967} =
Implementation.get_implementation(smart_contract)
verify!(EthereumJSONRPC.Mox)
@ -78,7 +78,7 @@ defmodule Explorer.Chain.SmartContract.Proxy.Models.Implementation.Test do
Application.put_env(:explorer, :proxy, proxy)
assert {[^string_implementation_address_hash], ["implementation"]} =
assert {[^string_implementation_address_hash], ["implementation"], :eip1967} =
Implementation.get_implementation(smart_contract)
{contract_2, _} = SmartContract.address_hash_to_smart_contract_with_bytecode_twin(smart_contract.address_hash)
@ -96,7 +96,7 @@ defmodule Explorer.Chain.SmartContract.Proxy.Models.Implementation.Test do
TestHelper.get_eip1967_implementation_zero_addresses()
assert {[], []} = Implementation.get_implementation(smart_contract)
assert {[], [], nil} = Implementation.get_implementation(smart_contract)
verify!(EthereumJSONRPC.Mox)
@ -106,7 +106,7 @@ defmodule Explorer.Chain.SmartContract.Proxy.Models.Implementation.Test do
test "get_implementation/1 for twins contract" do
# return nils for nil
assert {[], []} = Implementation.get_implementation(nil)
assert {[], [], nil} = Implementation.get_implementation(nil)
smart_contract = insert(:smart_contract)
twin_address = insert(:contract_address)
@ -123,11 +123,11 @@ defmodule Explorer.Chain.SmartContract.Proxy.Models.Implementation.Test do
Application.put_env(:explorer, :proxy, proxy)
# fetch nil implementation
assert {[], []} = Implementation.get_implementation(bytecode_twin)
assert {[], [], :unknown} = Implementation.get_implementation(bytecode_twin)
verify!(EthereumJSONRPC.Mox)
refute_implementations(smart_contract.address_hash)
assert {[], []} = Implementation.get_implementation(bytecode_twin)
assert {[], [], :unknown} = Implementation.get_implementation(bytecode_twin)
verify!(EthereumJSONRPC.Mox)
refute_implementations(smart_contract.address_hash)
@ -143,7 +143,7 @@ defmodule Explorer.Chain.SmartContract.Proxy.Models.Implementation.Test do
expect_address_in_oz_slot_response(string_implementation_address_hash)
assert {[^string_implementation_address_hash], ["implementation"]} =
assert {[^string_implementation_address_hash], ["implementation"], :eip1967} =
Implementation.get_implementation(bytecode_twin)
verify!(EthereumJSONRPC.Mox)
@ -158,7 +158,7 @@ defmodule Explorer.Chain.SmartContract.Proxy.Models.Implementation.Test do
refute_implementations(smart_contract.address_hash)
assert {[^string_implementation_address_hash], ["implementation"]} =
assert {[^string_implementation_address_hash], ["implementation"], :eip1967} =
Implementation.get_implementation(bytecode_twin)
verify!(EthereumJSONRPC.Mox)
@ -171,11 +171,11 @@ defmodule Explorer.Chain.SmartContract.Proxy.Models.Implementation.Test do
_implementation_smart_contract = insert(:smart_contract, name: "implementation")
# fetch nil implementation
assert {[], []} = Implementation.get_implementation(bytecode_twin)
assert {[], [], nil} = Implementation.get_implementation(bytecode_twin)
verify!(EthereumJSONRPC.Mox)
refute_implementations(smart_contract.address_hash)
assert {[], []} = Implementation.get_implementation(bytecode_twin)
assert {[], [], nil} = Implementation.get_implementation(bytecode_twin)
verify!(EthereumJSONRPC.Mox)
refute_implementations(smart_contract.address_hash)
@ -192,7 +192,7 @@ defmodule Explorer.Chain.SmartContract.Proxy.Models.Implementation.Test do
# expect_address_in_oz_slot_response(string_implementation_address_hash)
# assert {^string_implementation_address_hash, "implementation"} = Implementation.get_implementation(bytecode_twin)
# assert {^string_implementation_address_hash, "implementation", :eip1967} = Implementation.get_implementation(bytecode_twin)
# verify!(EthereumJSONRPC.Mox)
@ -200,7 +200,7 @@ defmodule Explorer.Chain.SmartContract.Proxy.Models.Implementation.Test do
# TestHelper.get_eip1967_implementation_error_response()
# assert {^string_implementation_address_hash, "implementation"} = Implementation.get_implementation(bytecode_twin)
# assert {^string_implementation_address_hash, "implementation", :eip1967} = Implementation.get_implementation(bytecode_twin)
# verify!(EthereumJSONRPC.Mox)
@ -233,12 +233,12 @@ defmodule Explorer.Chain.SmartContract.Proxy.Models.Implementation.Test do
# # fetch nil implementation
# TestHelper.get_eip1967_implementation_zero_addresses()
# assert {nil, nil} = Implementation.get_implementation(bytecode_twin)
# assert {[], [], nil} = Implementation.get_implementation(bytecode_twin)
# verify!(EthereumJSONRPC.Mox)
# refute_implementations(smart_contract.address_hash)
# TestHelper.get_eip1967_implementation_zero_addresses()
# assert {nil, nil} = Implementation.get_implementation(bytecode_twin)
# assert {[], [], nil} = Implementation.get_implementation(bytecode_twin)
# verify!(EthereumJSONRPC.Mox)
# refute_implementations(smart_contract.address_hash)
@ -246,7 +246,7 @@ defmodule Explorer.Chain.SmartContract.Proxy.Models.Implementation.Test do
# expect_address_in_oz_slot_response(string_implementation_address_hash)
# assert {^string_implementation_address_hash, "implementation"} = Implementation.get_implementation(bytecode_twin)
# assert {^string_implementation_address_hash, "implementation", :eip1967} = Implementation.get_implementation(bytecode_twin)
# verify!(EthereumJSONRPC.Mox)
@ -254,7 +254,7 @@ defmodule Explorer.Chain.SmartContract.Proxy.Models.Implementation.Test do
# TestHelper.get_eip1967_implementation_zero_addresses()
# assert {nil, nil} = Implementation.get_implementation(bytecode_twin)
# assert {[], [], nil} = Implementation.get_implementation(bytecode_twin)
# verify!(EthereumJSONRPC.Mox)

@ -2599,7 +2599,8 @@ defmodule Explorer.ChainTest do
:contracts_creation_internal_transaction,
:contracts_creation_transaction,
:token,
[smart_contract: :smart_contract_additional_sources]
[smart_contract: :smart_contract_additional_sources],
:proxy_implementations
])
options = [
@ -2612,8 +2613,6 @@ defmodule Explorer.ChainTest do
}
]
TestHelper.get_eip1967_implementation_zero_addresses()
response = Chain.find_contract_address(address.hash, options, true)
assert response == {:ok, address}

Loading…
Cancel
Save