chore: Return is verified=true for verified minimal proxy pattern (#10132)

* Return is verified=true for verified minimal proxy pattern

* Refactor proxy object for API
pull/10200/head
Victor Baranov 6 months ago committed by GitHub
parent ba49416709
commit a072be34b7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 4
      apps/block_scout_web/lib/block_scout_web/templates/address/overview.html.eex
  2. 63
      apps/block_scout_web/lib/block_scout_web/views/api/v2/address_view.ex
  3. 78
      apps/block_scout_web/lib/block_scout_web/views/api/v2/helper.ex
  4. 10
      apps/block_scout_web/test/block_scout_web/controllers/account/api/v2/user_controller_test.exs
  5. 95
      apps/block_scout_web/test/block_scout_web/controllers/api/v2/address_controller_test.exs

@ -124,7 +124,9 @@
<% end %>
<!-- Implementation -->
<%= if @is_proxy do %>
<% {implementation_address_, name} = Implementation.get_implementation(@address.smart_contract) %>
<% {implementation_addresses, implementation_names} = Implementation.get_implementation(@address.smart_contract) %>
<% implementation_address_ = Enum.at(implementation_addresses, 0) %>
<% name = Enum.at(implementation_names, 0) %>
<% implementation_address = implementation_address_ || "0x0000000000000000000000000000000000000000" %>
<dl class="row">
<dt class="col-sm-4 col-md-4 col-lg-3 text-muted">

@ -100,16 +100,7 @@ defmodule BlockScoutWeb.API.V2.AddressView do
{addresses, names} <-
Implementation.get_implementation(address_with_smart_contract.smart_contract, @api_true),
false <- addresses && Enum.empty?(addresses) do
addresses
|> Enum.zip(names)
|> Enum.reduce([], fn {address, 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)
Helper.proxy_object_info(addresses, names)
else
_ ->
[]
@ -125,33 +116,41 @@ defmodule BlockScoutWeb.API.V2.AddressView do
# todo: added for backward compatibility, remove when frontend unbound from these props
{implementation_address, implementation_name} = single_implementation(implementations)
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_address" => implementation_address,
"implementation_name" => implementation_name,
"implementations" => implementations,
"block_number_balance_updated_at" => address.fetched_coin_balance_block_number,
"has_decompiled_code" => AddressView.has_decompiled_code?(address),
"has_validated_blocks" => Counters.check_if_validated_blocks_at_address(address.hash, @api_true),
"has_logs" => Counters.check_if_logs_at_address(address.hash, @api_true),
"has_tokens" => Counters.check_if_tokens_at_address(address.hash, @api_true),
"has_token_transfers" => Counters.check_if_token_transfers_at_address(address.hash, @api_true),
"watchlist_address_id" => Chain.select_watchlist_address_id(get_watchlist_id(conn), address.hash),
"has_beacon_chain_withdrawals" => Counters.check_if_withdrawals_at_address(address.hash, @api_true)
})
extended_info =
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,
"block_number_balance_updated_at" => address.fetched_coin_balance_block_number,
"has_decompiled_code" => AddressView.has_decompiled_code?(address),
"has_validated_blocks" => Counters.check_if_validated_blocks_at_address(address.hash, @api_true),
"has_logs" => Counters.check_if_logs_at_address(address.hash, @api_true),
"has_tokens" => Counters.check_if_tokens_at_address(address.hash, @api_true),
"has_token_transfers" => Counters.check_if_token_transfers_at_address(address.hash, @api_true),
"watchlist_address_id" => Chain.select_watchlist_address_id(get_watchlist_id(conn), address.hash),
"has_beacon_chain_withdrawals" => Counters.check_if_withdrawals_at_address(address.hash, @api_true)
})
if Enum.empty?(implementations) do
extended_info
else
Map.merge(extended_info, %{
# todo: added for backward compatibility, remove when frontend unbound from these props
"implementation_address" => implementation_address,
"implementation_name" => implementation_name,
"implementations" => implementations
})
end
end
defp single_implementation(implementations) do
%{"address" => implementation_address, "name" => implementation_name} =
if implementations && !Enum.empty?(implementations) do
implementations |> Enum.at(0)
else
if Enum.empty?(implementations) do
%{"address" => nil, "name" => nil}
else
implementations |> Enum.at(0)
end
{implementation_address, implementation_name}

@ -4,7 +4,8 @@ defmodule BlockScoutWeb.API.V2.Helper do
"""
alias Ecto.Association.NotLoaded
alias Explorer.Chain.Address
alias Explorer.Chain
alias Explorer.Chain.{Address, Hash}
alias Explorer.Chain.SmartContract.Proxy.Models.Implementation
alias Explorer.Chain.Transaction.History.TransactionStats
@ -55,19 +56,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
smart_contract? = Address.smart_contract?(address)
implementation_names = if smart_contract?, do: Implementation.names(address), else: []
formatted_implementation_names =
implementation_names
|> Enum.map(fn name ->
%{"name" => name}
end)
{implementation_address_hashes, implementation_names, implementation_address, implementation_name,
proxy_implementations} =
if smart_contract? do
proxy_implementations = Implementation.get_proxy_implementations(address.hash)
implementation_name =
if Enum.empty?(implementation_names) do
nil
implementation_address_hashes = (proxy_implementations && proxy_implementations.address_hashes) || []
implementation_names = (proxy_implementations && proxy_implementations.names) || []
implementation_address = implementation_address_hashes |> Enum.at(0)
implementation_name = implementation_names |> Enum.at(0)
{implementation_address_hashes, implementation_names, implementation_address, implementation_name,
proxy_implementations}
else
implementation_names |> Enum.at(0)
{[], [], nil, nil, nil}
end
%{
@ -75,9 +79,10 @@ defmodule BlockScoutWeb.API.V2.Helper do
"is_contract" => smart_contract?,
"name" => address_name(address),
# todo: added for backward compatibility, remove when frontend unbound from these props
"implementation_address" => implementation_address,
"implementation_name" => implementation_name,
"implementations" => formatted_implementation_names,
"is_verified" => verified?(address),
"implementations" => 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
}
@ -104,6 +109,7 @@ defmodule BlockScoutWeb.API.V2.Helper do
"is_contract" => false,
"name" => nil,
# todo: added for backward compatibility, remove when frontend unbound from these props
"implementation_address" => nil,
"implementation_name" => nil,
"implementations" => [],
"is_verified" => nil,
@ -112,6 +118,52 @@ 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
defp verified_minimal_proxy?(nil), do: false
defp verified_minimal_proxy?(proxy_implementations) do
(minimal_proxy_pattern?(proxy_implementations) &&
Enum.any?(proxy_implementations.names, fn name -> !is_nil(name) end)) || false
end
def address_name(%Address{names: [_ | _] = address_names}) do
case Enum.find(address_names, &(&1.primary == true)) do
nil ->

@ -152,6 +152,7 @@ defmodule BlockScoutWeb.Account.Api.V2.UserControllerTest do
"address" => %{
"hash" => Address.checksum(addr),
# todo: added for backward compatibility, remove when frontend unbound from these props
"implementation_address" => nil,
"implementation_name" => nil,
"implementations" => [],
"is_contract" => false,
@ -166,7 +167,7 @@ defmodule BlockScoutWeb.Account.Api.V2.UserControllerTest do
}}
end)
assert Enum.all?(created, fn {addr, map_tag, _} ->
assert Enum.all?(created, fn {addr, map_tag, map} ->
response =
conn
|> get("/api/account/v2/tags/address/#{addr}")
@ -182,7 +183,9 @@ defmodule BlockScoutWeb.Account.Api.V2.UserControllerTest do
|> json_response(200)
|> Map.get("items")
assert Enum.all?(created, fn {_, _, map} -> map in response end)
assert Enum.all?(created, fn {_, _, map} ->
map in response
end)
end
test "delete address tag", %{conn: conn} do
@ -208,6 +211,7 @@ defmodule BlockScoutWeb.Account.Api.V2.UserControllerTest do
"address" => %{
"hash" => Address.checksum(addr),
# todo: added for backward compatibility, remove when frontend unbound from these props
"implementation_address" => nil,
"implementation_name" => nil,
"implementations" => [],
"is_contract" => false,
@ -222,7 +226,7 @@ defmodule BlockScoutWeb.Account.Api.V2.UserControllerTest do
}}
end)
assert Enum.all?(created, fn {addr, map_tag, _} ->
assert Enum.all?(created, fn {addr, map_tag, map} ->
response =
conn
|> get("/api/account/v2/tags/address/#{addr}")

@ -62,7 +62,7 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do
correct_response = %{
"hash" => Address.checksum(address.hash),
"is_contract" => false,
"is_verified" => nil,
"is_verified" => false,
"name" => nil,
"private_tags" => [],
"public_tags" => [],
@ -73,8 +73,8 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do
"coin_balance" => nil,
"exchange_rate" => nil,
# todo: added for backward compatibility, remove when frontend unbound from these props
"implementation_name" => nil,
"implementation_address" => nil,
"implementation_name" => nil,
"implementations" => [],
"block_number_balance_updated_at" => nil,
"has_decompiled_code" => false,
@ -95,7 +95,96 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do
assert ^correct_response = json_response(request, 200)
end
test "get contract info", %{conn: conn} do
test "get EIP-1167 proxy contract info", %{conn: conn} do
implementation_contract =
insert(:smart_contract,
name: "Implementation",
external_libraries: [],
constructor_arguments: "",
abi: [
%{
"type" => "constructor",
"inputs" => [
%{"type" => "address", "name" => "_proxyStorage"},
%{"type" => "address", "name" => "_implementationAddress"}
]
},
%{
"constant" => false,
"inputs" => [%{"name" => "x", "type" => "uint256"}],
"name" => "set",
"outputs" => [],
"payable" => false,
"stateMutability" => "nonpayable",
"type" => "function"
},
%{
"constant" => true,
"inputs" => [],
"name" => "get",
"outputs" => [%{"name" => "", "type" => "uint256"}],
"payable" => false,
"stateMutability" => "view",
"type" => "function"
}
],
license_type: 9
)
implementation_contract_address_hash_string =
Base.encode16(implementation_contract.address_hash.bytes, case: :lower)
proxy_tx_input =
"0x11b804ab000000000000000000000000" <>
implementation_contract_address_hash_string <>
"000000000000000000000000000000000000000000000000000000000000006035323031313537360000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000284e159163400000000000000000000000034420c13696f4ac650b9fafe915553a1abcd7dd30000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000001c00000000000000000000000000000000000000000000000000000000000000220000000000000000000000000ff5ae9b0a7522736299d797d80b8fc6f31d61100000000000000000000000000ff5ae9b0a7522736299d797d80b8fc6f31d6110000000000000000000000000000000000000000000000000000000000000003e8000000000000000000000000000000000000000000000000000000000000000000000000000000000000000034420c13696f4ac650b9fafe915553a1abcd7dd300000000000000000000000000000000000000000000000000000000000000184f7074696d69736d2053756273637269626572204e465473000000000000000000000000000000000000000000000000000000000000000000000000000000054f504e46540000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000037697066733a2f2f516d66544e504839765651334b5952346d6b52325a6b757756424266456f5a5554545064395538666931503332752f300000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c82bbe41f2cf04e3a8efa18f7032bdd7f6d98a81000000000000000000000000efba8a2a82ec1fb1273806174f5e28fbb917cf9500000000000000000000000000000000000000000000000000000000"
proxy_deployed_bytecode =
"0x363d3d373d3d3d363d73" <> implementation_contract_address_hash_string <> "5af43d82803e903d91602b57fd5bf3"
proxy_address =
insert(:contract_address,
contract_code: proxy_deployed_bytecode
)
tx =
insert(:transaction,
created_contract_address_hash: proxy_address.hash,
input: proxy_tx_input
)
|> with_block(status: :ok)
name = implementation_contract.name
from = Address.checksum(tx.from_address_hash)
tx_hash = to_string(tx.hash)
address_hash = Address.checksum(proxy_address.hash)
insert(:proxy_implementation,
proxy_address_hash: proxy_address.hash,
proxy_type: "eip1167",
address_hashes: [implementation_contract.address_hash],
names: [name]
)
request = get(conn, "/api/v2/addresses/#{Address.checksum(proxy_address.hash)}")
assert %{
"hash" => ^address_hash,
"is_contract" => true,
"is_verified" => true,
"private_tags" => [],
"public_tags" => [],
"watchlist_names" => [],
"creator_address_hash" => ^from,
"creation_tx_hash" => ^tx_hash,
"implementation_address" => "0x" <> ^implementation_contract_address_hash_string,
"implementations" => [
%{"address" => "0x" <> ^implementation_contract_address_hash_string, "name" => ^name}
]
} = json_response(request, 200)
end
test "get EIP-1967 proxy contract info", %{conn: conn} do
smart_contract = insert(:smart_contract)
tx =

Loading…
Cancel
Save