feat: Diamond proxy (EIP-2535) support (#10034)

* feat: Diamond proxy (eip-2535)

* Additional logic change

* Refactoring & backward compatibility

* Refactor specs

* Remove prepare_value function

* implementation_address_hash_from_db, implementation_name_from_db to plural form

* Remove check implementation_address_hash_strings is list

* address_hash_to_smart_contract_with_bytecode_twin function: return options param into the call of single_implementation_smart_contract_from_proxy

* Remove fallback "|| [burn_address_hash_string()]"

* Update spec of set_proxy_verification_result

* Fix web tests

* Change the order of enum values to match db enum

* Remove duplicated clause in save_implementation_data/4

* Remove duplicated line

* Add clause for [] to set_proxy_verification_result
pull/10046/head
Victor Baranov 7 months ago committed by GitHub
parent de755d81ad
commit fe9000cff9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 33
      apps/block_scout_web/lib/block_scout_web/controllers/api/v2/smart_contract_controller.ex
  2. 1
      apps/block_scout_web/lib/block_scout_web/controllers/smart_contract_controller.ex
  3. 10
      apps/block_scout_web/lib/block_scout_web/templates/address/_link.html.eex
  4. 10
      apps/block_scout_web/lib/block_scout_web/templates/address/_responsive_hash.html.eex
  5. 11
      apps/block_scout_web/lib/block_scout_web/templates/transaction_log/_logs.html.eex
  6. 46
      apps/block_scout_web/lib/block_scout_web/views/api/v2/address_view.ex
  7. 15
      apps/block_scout_web/lib/block_scout_web/views/api/v2/helper.ex
  8. 4
      apps/block_scout_web/test/block_scout_web/controllers/account/api/v2/user_controller_test.exs
  9. 6
      apps/block_scout_web/test/block_scout_web/controllers/api/v2/address_controller_test.exs
  10. 2
      apps/block_scout_web/test/block_scout_web/views/api/v2/address_view_test.exs
  11. 44
      apps/explorer/lib/explorer/chain.ex
  12. 27
      apps/explorer/lib/explorer/chain/smart_contract.ex
  13. 85
      apps/explorer/lib/explorer/chain/smart_contract/proxy.ex
  14. 9
      apps/explorer/lib/explorer/chain/smart_contract/proxy/basic.ex
  15. 36
      apps/explorer/lib/explorer/chain/smart_contract/proxy/eip_2535.ex
  16. 149
      apps/explorer/lib/explorer/chain/smart_contract/proxy/models/implementation.ex
  17. 7
      apps/explorer/lib/explorer/chain/smart_contract/proxy/verification_status.ex
  18. 14
      apps/explorer/lib/explorer/etherscan/contracts.ex
  19. 7
      apps/explorer/priv/repo/migrations/20240425185705_alter_proxy_type.exs
  20. 29
      apps/explorer/test/explorer/chain/smart_contract/proxy/models/implementation_test.exs

@ -6,7 +6,6 @@ defmodule BlockScoutWeb.API.V2.SmartContractController do
import BlockScoutWeb.PagingHelper,
only: [current_filter: 1, delete_parameters_from_next_page_params: 1, search_query: 1, smart_contracts_sorting: 1]
import Explorer.Chain.SmartContract, only: [burn_address_hash_string: 0]
import Explorer.SmartContract.Solidity.Verifier, only: [parse_boolean: 1]
alias BlockScoutWeb.{AccessHelper, AddressView}
@ -101,16 +100,25 @@ defmodule BlockScoutWeb.API.V2.SmartContractController do
{:not_found, {:ok, address}} <-
{:not_found, Chain.find_contract_address(address_hash, @smart_contract_address_options)},
{:not_found, false} <- {:not_found, is_nil(address.smart_contract)} do
implementation_address_hash_string =
implementation_address_hash_strings =
address.smart_contract
|> Implementation.get_implementation(@api_true)
|> Tuple.to_list()
|> List.first() || burn_address_hash_string()
|> List.first()
functions =
implementation_address_hash_strings
|> Enum.reduce([], fn implementation_address_hash_string, acc ->
functions_from_implementation =
Reader.read_only_functions_proxy(address_hash, implementation_address_hash_string, nil, @api_true)
acc ++ functions_from_implementation
end)
conn
|> put_status(200)
|> render(:read_functions, %{
functions: Reader.read_only_functions_proxy(address_hash, implementation_address_hash_string, nil, @api_true)
functions: functions
})
end
end
@ -123,17 +131,26 @@ defmodule BlockScoutWeb.API.V2.SmartContractController do
{:not_found, {:ok, address}} <-
{:not_found, Chain.find_contract_address(address_hash, @smart_contract_address_options)},
{:not_found, false} <- {:not_found, is_nil(address.smart_contract)} do
implementation_address_hash_string =
implementation_address_hash_strings =
address.smart_contract
|> Implementation.get_implementation(@api_true)
|> Tuple.to_list()
|> List.first() || burn_address_hash_string()
|> List.first()
functions =
implementation_address_hash_strings
|> Enum.reduce([], fn implementation_address_hash_string, acc ->
functions_from_implementation =
implementation_address_hash_string
|> Writer.write_functions_proxy(@api_true)
acc ++ functions_from_implementation
end)
conn
|> put_status(200)
|> json(
implementation_address_hash_string
|> Writer.write_functions_proxy(@api_true)
functions
|> Reader.get_abi_with_method_id()
)
end

@ -30,6 +30,7 @@ defmodule BlockScoutWeb.SmartContractController do
address.smart_contract
|> Implementation.get_implementation()
|> Tuple.to_list()
|> List.first()
|> List.first() || burn_address_hash_string()
else
burn_address_hash_string()

@ -1,6 +1,14 @@
<% implementation_names = Implementation.names(@address) %>
<% implementation_name =
if Enum.empty?(implementation_names) do
nil
else
implementation_names |> Enum.at(0)
end
%>
<%= if @address do %>
<%= if assigns[:show_full_hash] do %>
<%= if name = if assigns[:ignore_implementation_name], do: primary_name(@address), else: Implementation.name(@address) || primary_name(@address) do %>
<%= if name = if assigns[:ignore_implementation_name], do: primary_name(@address), else: implementation_name || primary_name(@address) do %>
<span><%= name %> |
<% end %>
<%= link to: address_path(BlockScoutWeb.Endpoint, :show, @address), "data-test": "address_hash_link", class: assigns[:class] do %>

@ -1,5 +1,13 @@
<% implementation_names = Implementation.names(@address) %>
<% implementation_name =
if Enum.empty?(implementation_names) do
nil
else
implementation_names |> Enum.at(0)
end
%>
<span class="<%= if @contract do %>contract-address<% end %>" data-address-hash="<%= @address %>">
<%= if name = if assigns[:ignore_implementation_name], do: primary_name(@address), else: Implementation.name(@address) || primary_name(@address) do %>
<%= if name = if assigns[:ignore_implementation_name], do: primary_name(@address), else: implementation_name || primary_name(@address) do %>
<%= if assigns[:no_tooltip] do %>
<%= if @use_custom_tooltip == true do %>
<span><%= name %> (<%= short_hash(@address) %>...)</span>

@ -16,11 +16,20 @@
<%= nil %>
<% end %>
<% implementation_names = Implementation.names(@log.address) %>
<% implementation_name =
if Enum.empty?(implementation_names) do
nil
else
implementation_names |> Enum.at(0)
end
%>
<dl class="row">
<dt class="col-lg-2"> <%= gettext "Address" %> </dt>
<dd class="col-lg-10">
<h3 class="logs-hash">
<% name = Implementation.name(@log.address) || primary_name(@log.address)%>
<% name = implementation_name || primary_name(@log.address)%>
<%= link(
(if name, do: name <> " | "<> to_string(@log.address), else: @log.address),
to: address_path(@conn, :show, @log.address),

@ -95,17 +95,24 @@ defmodule BlockScoutWeb.API.V2.AddressView do
is_proxy = AddressView.smart_contract_is_proxy?(address_with_smart_contract, @api_true)
{implementation_address, implementation_name} =
{implementation_addresses, implementation_names} =
with true <- is_proxy,
{address, name} <-
{addresses, names} <-
Implementation.get_implementation(address_with_smart_contract.smart_contract, @api_true),
false <- is_nil(address),
{:ok, address_hash} <- Chain.string_to_address_hash(address),
checksummed_address <- Address.checksum(address_hash) do
{checksummed_address, name}
false <- addresses && Enum.empty?(addresses) do
addresses
|> Enum.zip(names)
|> Enum.reduce({[], []}, fn {address, name}, {addresses, names} = acc ->
with {:ok, address_hash} <- Chain.string_to_address_hash(address),
checksummed_address <- Address.checksum(address_hash) do
{[checksummed_address | addresses], [name | names]}
else
_ -> acc
end
end)
else
_ ->
{nil, nil}
{[], []}
end
balance = address.fetched_coin_balance && address.fetched_coin_balance.value
@ -118,14 +125,21 @@ defmodule BlockScoutWeb.API.V2.AddressView do
write_custom_abi? = AddressView.has_address_custom_abi_with_write_functions?(conn, address.hash)
read_custom_abi? = AddressView.has_address_custom_abi_with_read_functions?(conn, address.hash)
# todo: added for backward compatibility, remove when frontend unbound from these props
{implementation_address, implementation_name} =
single_implementation(implementation_addresses, implementation_names)
Map.merge(base_info, %{
"creator_address_hash" => creator_hash && Address.checksum(creator_hash),
"creation_tx_hash" => creation_tx,
"token" => token,
"coin_balance" => balance,
"exchange_rate" => exchange_rate,
# todo: added for backward compatibility, remove when frontend unbound from these props
"implementation_name" => implementation_name,
"implementation_names" => implementation_names,
"implementation_address" => implementation_address,
"implementation_addresses" => implementation_addresses,
"block_number_balance_updated_at" => address.fetched_coin_balance_block_number,
"has_custom_methods_read" => read_custom_abi?,
"has_custom_methods_write" => write_custom_abi?,
@ -144,6 +158,24 @@ defmodule BlockScoutWeb.API.V2.AddressView do
})
end
defp single_implementation(implementation_addresses, implementation_names) do
implementation_name =
if implementation_names && !Enum.empty?(implementation_names) do
implementation_names |> Enum.at(0)
else
nil
end
implementation_address =
if implementation_addresses && !Enum.empty?(implementation_addresses) do
implementation_addresses |> Enum.at(0)
else
nil
end
{implementation_address, implementation_name}
end
@spec prepare_token_balance(Chain.Address.TokenBalance.t(), boolean()) :: map()
defp prepare_token_balance(token_balance, fetch_token_instance? \\ false) do
%{

@ -54,11 +54,22 @@ defmodule BlockScoutWeb.API.V2.Helper do
"""
@spec address_with_info(any(), any()) :: nil | %{optional(<<_::32, _::_*8>>) => any()}
def address_with_info(%Address{} = address, _address_hash) do
implementation_names = Implementation.names(address)
implementation_name =
if Enum.empty?(implementation_names) do
nil
else
implementation_names |> Enum.at(0)
end
%{
"hash" => Address.checksum(address),
"is_contract" => Address.smart_contract?(address),
"name" => address_name(address),
"implementation_name" => Implementation.name(address),
# todo: added for backward compatibility, remove when frontend unbound from these props
"implementation_name" => implementation_name,
"implementation_names" => implementation_names,
"is_verified" => verified?(address),
"ens_domain_name" => address.ens_domain_name,
"metadata" => address.metadata
@ -85,7 +96,9 @@ defmodule BlockScoutWeb.API.V2.Helper do
"hash" => Address.checksum(address_hash),
"is_contract" => false,
"name" => nil,
# todo: added for backward compatibility, remove when frontend unbound from these props
"implementation_name" => nil,
"implementation_names" => [],
"is_verified" => nil,
"ens_domain_name" => nil,
"metadata" => nil

@ -151,7 +151,9 @@ defmodule BlockScoutWeb.Account.Api.V2.UserControllerTest do
"name" => name,
"address" => %{
"hash" => Address.checksum(addr),
# todo: added for backward compatibility, remove when frontend unbound from these props
"implementation_name" => nil,
"implementation_names" => [],
"is_contract" => false,
"is_verified" => false,
"name" => nil,
@ -205,7 +207,9 @@ defmodule BlockScoutWeb.Account.Api.V2.UserControllerTest do
"name" => name,
"address" => %{
"hash" => Address.checksum(addr),
# todo: added for backward compatibility, remove when frontend unbound from these props
"implementation_name" => nil,
"implementation_names" => [],
"is_contract" => false,
"is_verified" => false,
"name" => nil,

@ -72,8 +72,11 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do
"token" => nil,
"coin_balance" => nil,
"exchange_rate" => nil,
# todo: added for backward compatibility, remove when frontend unbound from these props
"implementation_name" => nil,
"implementation_names" => [],
"implementation_address" => nil,
"implementation_addresses" => [],
"block_number_balance_updated_at" => nil,
"has_custom_methods_read" => false,
"has_custom_methods_write" => false,
@ -138,7 +141,8 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do
"watchlist_names" => [],
"creator_address_hash" => ^from,
"creation_tx_hash" => ^tx_hash,
"implementation_address" => ^implementation_address_hash_string
"implementation_address" => ^implementation_address_hash_string,
"implementation_addresses" => [^implementation_address_hash_string]
} = json_response(request, 200)
end

@ -19,7 +19,7 @@ defmodule BlockScoutWeb.API.V2.AddressViewTest do
proxy_address_hash: proxy_address.hash,
proxy_type: "eip1967",
address_hashes: [implementation_address.hash],
names: []
names: [nil]
)
assert implementation.proxy_type == :eip1967

@ -1176,7 +1176,7 @@ defmodule Explorer.Chain do
else
LookUpSmartContractSourcesOnDemand.trigger_fetch(address_result, nil)
{implementation_address_hash, _} =
{implementation_address_hashes, _} =
Implementation.get_implementation(
%{
updated: %SmartContract{
@ -1189,20 +1189,7 @@ defmodule Explorer.Chain do
Keyword.put(options, :unverified_proxy_only?, true)
)
implementation_smart_contract =
implementation_address_hash
|> Proxy.implementation_to_smart_contract(options)
address_verified_bytecode_twin_contract =
implementation_smart_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)
)).()
add_implementation_and_bytecode_twin_to_result(address_result, implementation_address_hashes, hash, options)
end
_ ->
@ -1218,6 +1205,33 @@ 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
address_verified_bytecode_twin_contract =
implementation_smart_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}
def find_decompiled_contract_address(%Hash{byte_count: unquote(Hash.Address.byte_count())} = hash) do
query =

@ -604,6 +604,18 @@ 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)
if implementation_address_hashes && Enum.count(implementation_address_hashes) == 1 do
implementation_address_hashes
|> Enum.at(0)
|> Proxy.implementation_to_smart_contract(options)
else
nil
end
end
@doc """
Finds metadata for verification of a contract from verified twins: contracts with the same bytecode
which were verified previously, returns a single t:SmartContract.t/0
@ -934,13 +946,14 @@ 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
{implementation_address, implementation_address_fetched?} =
{implementation_smart_contract, implementation_address_fetched?} =
if fetch_implementation? do
{implementation_address_hash, _} =
Implementation.get_implementation(
implementation_smart_contract =
SmartContract.single_implementation_smart_contract_from_proxy(
%{
updated: %__MODULE__{
address_hash: address_hash
updated: %SmartContract{
address_hash: address_hash,
abi: nil
},
implementation_updated_at: nil,
implementation_address_fetched?: false,
@ -949,13 +962,13 @@ defmodule Explorer.Chain.SmartContract do
Keyword.put(options, :unverified_proxy_only?, true)
)
{implementation_address_hash |> Proxy.implementation_to_smart_contract(options), true}
{implementation_smart_contract, true}
else
{nil, false}
end
address_verified_bytecode_twin_contract =
implementation_address ||
implementation_smart_contract ||
get_address_verified_bytecode_twin_contract(address_hash, options).verified_contract
smart_contract =

@ -6,7 +6,7 @@ defmodule Explorer.Chain.SmartContract.Proxy do
alias EthereumJSONRPC.Contract
alias Explorer.Chain.{Hash, SmartContract}
alias Explorer.Chain.SmartContract.Proxy
alias Explorer.Chain.SmartContract.Proxy.{Basic, EIP1167, EIP1822, EIP1967, EIP930, MasterCopy}
alias Explorer.Chain.SmartContract.Proxy.{Basic, EIP1167, EIP1822, EIP1967, EIP2535, EIP930, MasterCopy}
import Explorer.Chain,
only: [
@ -41,30 +41,26 @@ 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() | nil | :empty, String.t() | nil | :empty}
{[String.t()] | :empty | :error, [String.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_string: implementation_address_hash_string, proxy_type: proxy_type} =
%{implementation_address_hash_strings: implementation_address_hash_strings, proxy_type: proxy_type} =
if options[:unverified_proxy_only?] do
get_implementation_address_hash_string_for_non_verified_proxy(proxy_address_hash)
else
get_implementation_address_hash_string(proxy_address_hash, proxy_abi)
end
if implementation_address_hash_string !== :error do
save_implementation_data(
implementation_address_hash_string,
proxy_address_hash,
proxy_type,
options
)
else
{nil, nil}
end
save_implementation_data(
implementation_address_hash_strings,
proxy_address_hash,
proxy_type,
options
)
end
def fetch_implementation_address_hash(_, _, _) do
{nil, nil}
{:empty, :empty}
end
@doc """
@ -82,12 +78,20 @@ defmodule Explorer.Chain.SmartContract.Proxy do
true
else
_ ->
{implementation_address_hash_string, _implementation_name} = get_implementation(smart_contract, options)
with false <- is_nil(implementation_address_hash_string),
{:ok, implementation_address_hash} <- string_to_address_hash(implementation_address_hash_string),
false <- implementation_address_hash.bytes == burn_address_hash.bytes do
true
{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)
else
_ ->
false
@ -124,9 +128,12 @@ defmodule Explorer.Chain.SmartContract.Proxy do
options
)
when not is_nil(proxy_address_hash) and not is_nil(abi) do
{implementation_address_hash_string, _name} = get_implementation(smart_contract, options)
{implementation_address_hash_strings, _names} = get_implementation(smart_contract, options)
SmartContract.get_smart_contract_abi(implementation_address_hash_string)
implementation_address_hash_strings
|> Enum.reduce([], fn implementation_address_hash_string, acc ->
SmartContract.get_smart_contract_abi(implementation_address_hash_string) ++ acc
end)
end
def get_implementation_abi_from_proxy(_, _), do: []
@ -205,7 +212,7 @@ defmodule Explorer.Chain.SmartContract.Proxy do
Returns EIP-1167 implementation address or tries next proxy pattern
"""
@spec get_implementation_address_hash_string_eip1167(Hash.Address.t(), any(), bool()) ::
%{implementation_address_hash_string: String.t() | :error | nil, proxy_type: atom()}
%{implementation_address_hash_strings: [String.t() | :error | nil], proxy_type: atom()}
def get_implementation_address_hash_string_eip1167(proxy_address_hash, proxy_abi, go_to_fallback? \\ true) do
get_implementation_address_hash_string_by_module(
EIP1167,
@ -223,7 +230,7 @@ defmodule Explorer.Chain.SmartContract.Proxy do
Returns EIP-1967 implementation address or tries next proxy pattern
"""
@spec get_implementation_address_hash_string_eip1967(Hash.Address.t(), any(), bool()) :: %{
implementation_address_hash_string: String.t() | :error | nil,
implementation_address_hash_strings: [String.t() | :error | nil],
proxy_type: atom()
}
def get_implementation_address_hash_string_eip1967(proxy_address_hash, proxy_abi, go_to_fallback?) do
@ -243,7 +250,7 @@ defmodule Explorer.Chain.SmartContract.Proxy do
Returns EIP-1822 implementation address or tries next proxy pattern
"""
@spec get_implementation_address_hash_string_eip1822(Hash.Address.t(), any(), bool()) :: %{
implementation_address_hash_string: String.t() | :error | nil,
implementation_address_hash_strings: [String.t() | :error | nil],
proxy_type: atom()
}
def get_implementation_address_hash_string_eip1822(proxy_address_hash, proxy_abi, go_to_fallback?) do
@ -260,7 +267,7 @@ defmodule Explorer.Chain.SmartContract.Proxy do
if !is_nil(implementation_address_hash_string) && implementation_address_hash_string !== burn_address_hash_string() &&
implementation_address_hash_string !== :error do
%{implementation_address_hash_string: implementation_address_hash_string, proxy_type: proxy_type}
%{implementation_address_hash_strings: [implementation_address_hash_string], proxy_type: proxy_type}
else
cond do
next_func !== :fallback_proxy_detection ->
@ -278,13 +285,13 @@ defmodule Explorer.Chain.SmartContract.Proxy do
end
defp implementation_fallback_value(implementation_address_hash_string) do
value = if implementation_address_hash_string == :error, do: :error, else: nil
value = if implementation_address_hash_string == :error, do: :error, else: []
%{implementation_address_hash_string: value, proxy_type: :unknown}
%{implementation_address_hash_strings: value, proxy_type: :unknown}
end
@spec fallback_proxy_detection(Hash.Address.t(), any(), :error | nil) :: %{
implementation_address_hash_string: String.t() | :error | nil,
implementation_address_hash_strings: [String.t() | :error | nil],
proxy_type: atom()
}
def fallback_proxy_detection(proxy_address_hash, proxy_abi, fallback_value \\ nil) do
@ -294,26 +301,36 @@ defmodule Explorer.Chain.SmartContract.Proxy do
comptroller_implementation_method_abi = get_naive_implementation_abi(proxy_abi, "comptrollerImplementation")
diamond_implementation_method_abi = get_naive_implementation_abi(proxy_abi, "facetAddresses")
master_copy_method_abi = get_master_copy_pattern(proxy_abi)
get_address_method_abi = get_naive_implementation_abi(proxy_abi, "getAddress")
cond do
diamond_implementation_method_abi ->
implementation_address_hash_strings = EIP2535.get_implementation_address_hash_strings(proxy_address_hash)
%{implementation_address_hash_strings: implementation_address_hash_strings, proxy_type: :eip2535}
implementation_method_abi ->
implementation_address_hash_string =
Basic.get_implementation_address_hash_string(@implementation_signature, proxy_address_hash, proxy_abi)
%{implementation_address_hash_string: implementation_address_hash_string, proxy_type: :basic_implementation}
%{implementation_address_hash_strings: [implementation_address_hash_string], proxy_type: :basic_implementation}
get_implementation_method_abi ->
implementation_address_hash_string =
Basic.get_implementation_address_hash_string(@get_implementation_signature, proxy_address_hash, proxy_abi)
%{implementation_address_hash_string: implementation_address_hash_string, proxy_type: :basic_get_implementation}
%{
implementation_address_hash_strings: [implementation_address_hash_string],
proxy_type: :basic_get_implementation
}
master_copy_method_abi ->
implementation_address_hash_string = MasterCopy.get_implementation_address_hash_string(proxy_address_hash)
%{implementation_address_hash_string: implementation_address_hash_string, proxy_type: :master_copy}
%{implementation_address_hash_strings: [implementation_address_hash_string], proxy_type: :master_copy}
comptroller_implementation_method_abi ->
implementation_address_hash_string =
@ -323,13 +340,13 @@ defmodule Explorer.Chain.SmartContract.Proxy do
proxy_abi
)
%{implementation_address_hash_string: implementation_address_hash_string, proxy_type: :comptroller}
%{implementation_address_hash_strings: [implementation_address_hash_string], proxy_type: :comptroller}
get_address_method_abi ->
implementation_address_hash_string =
EIP930.get_implementation_address_hash_string(@get_address_signature, proxy_address_hash, proxy_abi)
%{implementation_address_hash_string: implementation_address_hash_string, proxy_type: :eip_930}
%{implementation_address_hash_strings: [implementation_address_hash_string], proxy_type: :eip_930}
true ->
fallback_value

@ -9,7 +9,7 @@ defmodule Explorer.Chain.SmartContract.Proxy.Basic do
@doc """
Gets implementation hash string of proxy contract from getter.
"""
@spec get_implementation_address_hash_string(binary, binary, SmartContract.abi()) :: nil | binary
@spec get_implementation_address_hash_string(binary, binary, SmartContract.abi()) :: nil | binary() | [binary()]
def get_implementation_address_hash_string(signature, proxy_address_hash, abi) do
implementation_address =
case Reader.query_contract(
@ -30,9 +30,14 @@ defmodule Explorer.Chain.SmartContract.Proxy.Basic do
@doc """
Adds 0x to address at the beginning
"""
@spec adds_0x_to_address(nil | binary()) :: nil | binary()
@spec adds_0x_to_address(nil | binary()) :: nil | binary() | [binary()]
def adds_0x_to_address(nil), do: nil
def adds_0x_to_address(addresses) when is_list(addresses) do
addresses
|> Enum.map(fn address -> adds_0x_to_address(address) end)
end
def adds_0x_to_address(address) do
if address do
if String.starts_with?(address, "0x") do

@ -0,0 +1,36 @@
defmodule Explorer.Chain.SmartContract.Proxy.EIP2535 do
@moduledoc """
Module for fetching proxy implementation from https://eips.ethereum.org/EIPS/eip-2535 (Diamond Proxy)
"""
# 52ef6b2c = keccak256(facetAddresses())
@facet_addresses_signature "52ef6b2c"
alias Explorer.Chain.Hash
alias Explorer.Chain.SmartContract.Proxy.Basic
@facet_addresses_method_abi [
%{
"inputs" => [],
"name" => "facetAddresses",
"outputs" => [%{"internalType" => "address[]", "name" => "facetAddresses_", "type" => "address[]"}],
"stateMutability" => "view",
"type" => "function"
}
]
@spec get_implementation_address_hash_strings(Hash.Address.t()) :: nil | [binary]
def get_implementation_address_hash_strings(proxy_address_hash) do
case @facet_addresses_signature
|> Basic.get_implementation_address_hash_string(
to_string(proxy_address_hash),
@facet_addresses_method_abi
) do
implementation_addresses when is_list(implementation_addresses) ->
implementation_addresses
_ ->
nil
end
end
end

@ -30,6 +30,7 @@ defmodule Explorer.Chain.SmartContract.Proxy.Models.Implementation do
typed_schema "proxy_implementations" do
field(:proxy_address_hash, Hash.Address, primary_key: true, null: false)
# the order matches order of enum values in the DB
field(:proxy_type, Ecto.Enum,
values: [
:eip1167,
@ -40,6 +41,7 @@ defmodule Explorer.Chain.SmartContract.Proxy.Models.Implementation do
:basic_implementation,
:basic_get_implementation,
:comptroller,
:eip2535,
:unknown
],
null: true
@ -153,7 +155,7 @@ defmodule Explorer.Chain.SmartContract.Proxy.Models.Implementation do
},
options
) do
{implementation_address_hash_from_db, implementation_name_from_db, implementation_updated_at_from_db} =
{implementation_addresses_hash_from_db, implementation_names_from_db, implementation_updated_at_from_db} =
implementation_from_db(address_hash, options)
implementation_updated_at = implementation_updated_at || implementation_updated_at_from_db
@ -177,22 +179,26 @@ 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}} ->
{nil, nil}
{[], []}
{:ok, {:error, :error}} ->
{db_implementation_data_converter(implementation_addresses_hash_from_db),
db_implementation_data_converter(implementation_names_from_db)}
{:ok, {address_hash, _name} = result} when not is_nil(address_hash) ->
result
_ ->
{db_implementation_data_converter(implementation_address_hash_from_db),
db_implementation_data_converter(implementation_name_from_db)}
{db_implementation_data_converter(implementation_addresses_hash_from_db),
db_implementation_data_converter(implementation_names_from_db)}
end
else
{db_implementation_data_converter(implementation_address_hash_from_db),
db_implementation_data_converter(implementation_name_from_db)}
{db_implementation_data_converter(implementation_addresses_hash_from_db),
db_implementation_data_converter(implementation_names_from_db)}
end
end
def get_implementation(_, _), do: {nil, nil}
def get_implementation(_, _), do: {[], []}
defp fetch_implementation?(implementation_address_fetched?, refetch_necessity_checked?, implementation_updated_at) do
(!implementation_address_fetched? || !refetch_necessity_checked?) &&
@ -202,20 +208,10 @@ defmodule Explorer.Chain.SmartContract.Proxy.Models.Implementation do
defp implementation_from_db(address_hash, options) do
proxy_implementations = get_proxy_implementations(address_hash, options)
# todo: process multiple implementations in case of Diamond proxy
if proxy_implementations do
{implementation_address_hash, implementation_name} =
if Enum.count(proxy_implementations.address_hashes) == 1 do
implementation_address_hash = proxy_implementations.address_hashes |> Enum.at(0)
implementation_name = proxy_implementations.names |> Enum.at(0)
{implementation_address_hash, implementation_name}
else
{nil, nil}
end
{implementation_address_hash, implementation_name, proxy_implementations.updated_at}
{proxy_implementations.address_hashes, proxy_implementations.names, proxy_implementations.updated_at}
else
{nil, nil, nil}
{[], [], nil}
end
end
@ -273,68 +269,90 @@ defmodule Explorer.Chain.SmartContract.Proxy.Models.Implementation do
@doc """
Saves proxy's implementation into the DB
"""
@spec save_implementation_data(String.t() | nil, Hash.Address.t(), atom() | nil, Keyword.t()) ::
{nil, nil} | {String.t(), String.t() | nil}
@spec save_implementation_data([String.t()], Hash.Address.t(), atom() | nil, Keyword.t()) ::
{[String.t()], [String.t()]} | {:empty, :empty} | {:error, :error}
def save_implementation_data(:error, _proxy_address_hash, _proxy_type, _options) do
{:error, :error}
end
def save_implementation_data(implementation_address_hash_strings, proxy_address_hash, proxy_type, options)
when is_nil(implementation_address_hash_strings) or
implementation_address_hash_strings == [] do
upsert_implementation(proxy_address_hash, proxy_type, [], [], options)
{:empty, :empty}
end
def save_implementation_data(
implementation_address_hash_string,
[empty_implementation_address_hash_string],
proxy_address_hash,
proxy_type,
options
)
when is_nil(implementation_address_hash_string) or is_burn_signature(implementation_address_hash_string) do
upsert_implementation(proxy_address_hash, proxy_type, nil, nil, options)
when is_burn_signature(empty_implementation_address_hash_string) do
upsert_implementation(proxy_address_hash, proxy_type, [], [], options)
{:empty, :empty}
end
def save_implementation_data(
implementation_address_hash_string,
implementation_address_hash_strings,
proxy_address_hash,
proxy_type,
options
)
when is_binary(implementation_address_hash_string) do
with {:ok, implementation_address_hash} <- string_to_address_hash(implementation_address_hash_string),
{:implementation, {%SmartContract{name: name}, _}} <-
{:implementation,
SmartContract.address_hash_to_smart_contract_with_bytecode_twin(implementation_address_hash, options, false)} do
upsert_implementation(proxy_address_hash, proxy_type, implementation_address_hash_string, name, options)
{implementation_address_hash_string, name}
) do
{implementation_addresses, implementation_names} =
implementation_address_hash_strings
|> Enum.map(fn implementation_address_hash_string ->
with {:ok, implementation_address_hash} <- string_to_address_hash(implementation_address_hash_string),
{:implementation, {%SmartContract{name: name}, _}} <- {
:implementation,
SmartContract.address_hash_to_smart_contract_with_bytecode_twin(implementation_address_hash, options)
} do
{implementation_address_hash_string, name}
else
:error ->
:error
{:implementation, _} ->
{implementation_address_hash_string, nil}
end
end)
|> Enum.filter(&(&1 !== :error))
|> Enum.unzip()
if Enum.empty?(implementation_addresses) do
{:empty, :empty}
else
:error ->
{:empty, :empty}
{:implementation, _} ->
upsert_implementation(
proxy_address_hash,
proxy_type,
implementation_address_hash_string,
nil,
options
)
{implementation_address_hash_string, nil}
upsert_implementation(
proxy_address_hash,
proxy_type,
implementation_addresses,
implementation_names,
options
)
{implementation_addresses, implementation_names}
end
end
defp upsert_implementation(proxy_address_hash, proxy_type, implementation_address_hash_string, name, options) do
defp upsert_implementation(proxy_address_hash, proxy_type, implementation_address_hash_strings, names, options) do
proxy = get_proxy_implementations(proxy_address_hash, options)
if proxy do
update_implementation(proxy, proxy_type, implementation_address_hash_string, name)
update_implementation(proxy, proxy_type, implementation_address_hash_strings, names)
else
insert_implementation(proxy_address_hash, proxy_type, implementation_address_hash_string, name)
insert_implementation(proxy_address_hash, proxy_type, implementation_address_hash_strings, names)
end
end
defp insert_implementation(proxy_address_hash, proxy_type, implementation_address_hash_string, name)
defp insert_implementation(proxy_address_hash, proxy_type, implementation_address_hash_strings, names)
when not is_nil(proxy_address_hash) do
changeset = %{
proxy_address_hash: proxy_address_hash,
proxy_type: proxy_type,
address_hashes: (implementation_address_hash_string && [implementation_address_hash_string]) || [],
names: (name && [name]) || []
address_hashes: implementation_address_hash_strings,
names: names
}
%__MODULE__{}
@ -342,36 +360,39 @@ defmodule Explorer.Chain.SmartContract.Proxy.Models.Implementation do
|> Repo.insert()
end
defp update_implementation(proxy, proxy_type, implementation_address_hash_string, name) do
defp update_implementation(proxy, proxy_type, implementation_address_hash_strings, names) do
proxy
|> changeset(%{
proxy_type: proxy_type,
address_hashes: (implementation_address_hash_string && [implementation_address_hash_string]) || [],
names: (name && [name]) || []
address_hashes: implementation_address_hash_strings,
names: names
})
|> Repo.update()
end
defp db_implementation_data_converter(nil), do: nil
defp db_implementation_data_converter(list) when is_list(list),
do: list |> Enum.map(&db_implementation_data_converter(&1))
defp db_implementation_data_converter(string) when is_binary(string), do: string
defp db_implementation_data_converter(other), do: to_string(other)
@doc """
Returns proxy's implementation name
Returns proxy's implementation names
"""
@spec name(Address.t() | nil) :: String.t() | nil
def name(_proxy_address, options \\ [])
@spec names(Address.t() | nil) :: String.t() | [String.t()]
def names(_proxy_address, options \\ [])
def name(proxy_address, options) when not is_nil(proxy_address) do
def names(proxy_address, options) when not is_nil(proxy_address) do
proxy_implementations = get_proxy_implementations(proxy_address.hash, options)
# todo: process multiple implementations in case of Diamond proxy
if proxy_implementations && not Enum.empty?(proxy_implementations.names) do
proxy_implementations.names |> Enum.at(0)
proxy_implementations.names
else
nil
[]
end
end
def name(_, _), do: nil
def names(_, _), do: []
end

@ -109,9 +109,12 @@ defmodule Explorer.Chain.SmartContract.Proxy.VerificationStatus do
@doc """
Sets proxy verification result
"""
@spec set_proxy_verification_result({String.t() | nil | :empty, String.t() | nil | :empty}, String.t()) ::
@spec set_proxy_verification_result({[String.t()] | :empty | :error, [String.t()] | :empty | :error}, String.t()) ::
__MODULE__.t()
def set_proxy_verification_result({empty_or_nil, _}, uid) when empty_or_nil in [:empty, nil],
def set_proxy_verification_result({empty_or_error, _}, uid) when empty_or_error in [:empty, :error],
do: update_status(uid, :fail)
def set_proxy_verification_result({[], _}, uid),
do: update_status(uid, :fail)
def set_proxy_verification_result({_, _}, uid), do: update_status(uid, :pass)

@ -41,8 +41,8 @@ defmodule Explorer.Etherscan.Contracts do
| smart_contract: %{address_with_smart_contract.smart_contract | contract_source_code: formatted_code}
}
else
{implementation_address_hash, _} =
Implementation.get_implementation(
implementation_smart_contract =
SmartContract.single_implementation_smart_contract_from_proxy(
%{
updated: %SmartContract{
address_hash: address_hash,
@ -52,13 +52,11 @@ defmodule Explorer.Etherscan.Contracts do
implementation_address_fetched?: false,
refetch_necessity_checked?: false
},
unverified_proxy_only?: true
[
{:unverified_proxy_only?, true}
]
)
implementation_smart_contract =
implementation_address_hash
|> Proxy.implementation_to_smart_contract([])
address_verified_bytecode_twin_contract =
implementation_smart_contract || maybe_fetch_bytecode_twin(twin_needed?, address_hash)
@ -95,7 +93,7 @@ defmodule Explorer.Etherscan.Contracts do
smart_contract
|> Map.put(:is_proxy, true)
|> Map.put(
:implementation_address_hash_string,
:implementation_address_hash_strings,
smart_contract
|> Implementation.get_implementation()
|> Tuple.to_list()

@ -0,0 +1,7 @@
defmodule Explorer.Repo.Migrations.AlterProxyType do
use Ecto.Migration
def change do
execute("ALTER TYPE proxy_type ADD VALUE 'eip2535' BEFORE 'unknown'")
end
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 {nil, nil} = Implementation.get_implementation(smart_contract)
assert {[], []} = Implementation.get_implementation(smart_contract)
verify!(EthereumJSONRPC.Mox)
assert_empty_implementation(smart_contract.address_hash)
@ -44,7 +44,8 @@ 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(smart_contract)
assert {[^string_implementation_address_hash], ["implementation"]} =
Implementation.get_implementation(smart_contract)
verify!(EthereumJSONRPC.Mox)
@ -56,7 +57,8 @@ defmodule Explorer.Chain.SmartContract.Proxy.Models.Implementation.Test do
TestHelper.get_eip1967_implementation_error_response()
assert {^string_implementation_address_hash, "implementation"} = Implementation.get_implementation(smart_contract)
assert {[^string_implementation_address_hash], ["implementation"]} =
Implementation.get_implementation(smart_contract)
verify!(EthereumJSONRPC.Mox)
@ -76,7 +78,8 @@ defmodule Explorer.Chain.SmartContract.Proxy.Models.Implementation.Test do
Application.put_env(:explorer, :proxy, proxy)
assert {^string_implementation_address_hash, "implementation"} = Implementation.get_implementation(smart_contract)
assert {[^string_implementation_address_hash], ["implementation"]} =
Implementation.get_implementation(smart_contract)
{contract_2, _} = SmartContract.address_hash_to_smart_contract_with_bytecode_twin(smart_contract.address_hash)
implementation_2 = Implementation.get_proxy_implementations(smart_contract.address_hash)
@ -93,7 +96,7 @@ defmodule Explorer.Chain.SmartContract.Proxy.Models.Implementation.Test do
TestHelper.get_eip1967_implementation_zero_addresses()
assert {nil, nil} = Implementation.get_implementation(smart_contract)
assert {[], []} = Implementation.get_implementation(smart_contract)
verify!(EthereumJSONRPC.Mox)
@ -103,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 {nil, nil} = Implementation.get_implementation(nil)
assert {[], []} = Implementation.get_implementation(nil)
smart_contract = insert(:smart_contract)
twin_address = insert(:contract_address)
@ -120,11 +123,11 @@ defmodule Explorer.Chain.SmartContract.Proxy.Models.Implementation.Test do
Application.put_env(:explorer, :proxy, proxy)
# fetch nil implementation
assert {nil, nil} = Implementation.get_implementation(bytecode_twin)
assert {[], []} = Implementation.get_implementation(bytecode_twin)
verify!(EthereumJSONRPC.Mox)
refute_implementations(smart_contract.address_hash)
assert {nil, nil} = Implementation.get_implementation(bytecode_twin)
assert {[], []} = Implementation.get_implementation(bytecode_twin)
verify!(EthereumJSONRPC.Mox)
refute_implementations(smart_contract.address_hash)
@ -140,7 +143,8 @@ 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"]} =
Implementation.get_implementation(bytecode_twin)
verify!(EthereumJSONRPC.Mox)
@ -154,7 +158,8 @@ defmodule Explorer.Chain.SmartContract.Proxy.Models.Implementation.Test do
refute_implementations(smart_contract.address_hash)
assert {^string_implementation_address_hash, "implementation"} = Implementation.get_implementation(bytecode_twin)
assert {[^string_implementation_address_hash], ["implementation"]} =
Implementation.get_implementation(bytecode_twin)
verify!(EthereumJSONRPC.Mox)
@ -166,11 +171,11 @@ defmodule Explorer.Chain.SmartContract.Proxy.Models.Implementation.Test do
_implementation_smart_contract = insert(:smart_contract, name: "implementation")
# fetch nil implementation
assert {nil, nil} = Implementation.get_implementation(bytecode_twin)
assert {[], []} = Implementation.get_implementation(bytecode_twin)
verify!(EthereumJSONRPC.Mox)
refute_implementations(smart_contract.address_hash)
assert {nil, nil} = Implementation.get_implementation(bytecode_twin)
assert {[], []} = Implementation.get_implementation(bytecode_twin)
verify!(EthereumJSONRPC.Mox)
refute_implementations(smart_contract.address_hash)

Loading…
Cancel
Save