feat: save smart-contract proxy type in the DB (#10033)

* feat: save smart-contract proxy type in the DB

* Add tests

* Allow null proxy type in DB

* Fix tests
docker-compose-changing-fe-port
Victor Baranov 7 months ago committed by GitHub
parent 141a85a9f0
commit a16afd14b3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 14
      apps/block_scout_web/test/block_scout_web/views/api/v2/address_view_test.exs
  2. 65
      apps/explorer/lib/explorer/chain/smart_contract/proxy.ex
  3. 41
      apps/explorer/lib/explorer/chain/smart_contract/proxy/models/implementation.ex
  4. 4
      apps/explorer/priv/repo/migrations/20240419101821_migrate_proxy_implementations.exs
  5. 16
      apps/explorer/priv/repo/migrations/20240425091614_add_proxy_type_column.exs
  6. 5
      apps/explorer/test/explorer/chain/smart_contract/proxy/models/implementation_test.exs
  7. 2
      apps/explorer/test/explorer/chain/smart_contract/proxy_test.exs

@ -14,11 +14,15 @@ defmodule BlockScoutWeb.API.V2.AddressViewTest do
contract_code_md5: "123"
)
insert(:proxy_implementation,
proxy_address_hash: proxy_address.hash,
address_hashes: [implementation_address.hash],
names: []
)
implementation =
insert(:proxy_implementation,
proxy_address_hash: proxy_address.hash,
proxy_type: "eip1967",
address_hashes: [implementation_address.hash],
names: []
)
assert implementation.proxy_type == :eip1967
TestHelper.get_eip1967_implementation_zero_addresses()

@ -22,7 +22,7 @@ defmodule Explorer.Chain.SmartContract.Proxy do
is_burn_signature: 1,
get_implementation: 2,
get_proxy_implementations: 1,
save_implementation_data: 3
save_implementation_data: 4
]
# supported signatures:
@ -44,7 +44,7 @@ defmodule Explorer.Chain.SmartContract.Proxy do
{String.t() | nil | :empty, String.t() | nil | :empty}
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: implementation_address_hash_string, proxy_type: proxy_type} =
if options[:unverified_proxy_only?] do
get_implementation_address_hash_string_for_non_verified_proxy(proxy_address_hash)
else
@ -55,6 +55,7 @@ defmodule Explorer.Chain.SmartContract.Proxy do
save_implementation_data(
implementation_address_hash_string,
proxy_address_hash,
proxy_type,
options
)
else
@ -203,10 +204,12 @@ defmodule Explorer.Chain.SmartContract.Proxy do
@doc """
Returns EIP-1167 implementation address or tries next proxy pattern
"""
@spec get_implementation_address_hash_string_eip1167(Hash.Address.t(), any(), bool()) :: String.t() | :error | nil
@spec get_implementation_address_hash_string_eip1167(Hash.Address.t(), any(), bool()) ::
%{implementation_address_hash_string: 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,
:eip1167,
[
proxy_address_hash,
proxy_abi,
@ -219,10 +222,14 @@ defmodule Explorer.Chain.SmartContract.Proxy do
@doc """
Returns EIP-1967 implementation address or tries next proxy pattern
"""
@spec get_implementation_address_hash_string_eip1967(Hash.Address.t(), any(), bool()) :: String.t() | :error | nil
@spec get_implementation_address_hash_string_eip1967(Hash.Address.t(), any(), bool()) :: %{
implementation_address_hash_string: String.t() | :error | nil,
proxy_type: atom()
}
def get_implementation_address_hash_string_eip1967(proxy_address_hash, proxy_abi, go_to_fallback?) do
get_implementation_address_hash_string_by_module(
EIP1967,
:eip1967,
[
proxy_address_hash,
proxy_abi,
@ -235,13 +242,17 @@ defmodule Explorer.Chain.SmartContract.Proxy do
@doc """
Returns EIP-1822 implementation address or tries next proxy pattern
"""
@spec get_implementation_address_hash_string_eip1822(Hash.Address.t(), any(), bool()) :: String.t() | :error | nil
@spec get_implementation_address_hash_string_eip1822(Hash.Address.t(), any(), bool()) :: %{
implementation_address_hash_string: String.t() | :error | nil,
proxy_type: atom()
}
def get_implementation_address_hash_string_eip1822(proxy_address_hash, proxy_abi, go_to_fallback?) do
get_implementation_address_hash_string_by_module(EIP1822, [proxy_address_hash, proxy_abi, go_to_fallback?])
get_implementation_address_hash_string_by_module(EIP1822, :eip1822, [proxy_address_hash, proxy_abi, go_to_fallback?])
end
defp get_implementation_address_hash_string_by_module(
module,
proxy_type,
[proxy_address_hash, proxy_abi, go_to_fallback?] = args,
next_func \\ :fallback_proxy_detection
) do
@ -249,7 +260,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: implementation_address_hash_string, proxy_type: proxy_type}
else
cond do
next_func !== :fallback_proxy_detection ->
@ -267,10 +278,15 @@ defmodule Explorer.Chain.SmartContract.Proxy do
end
defp implementation_fallback_value(implementation_address_hash_string) do
if implementation_address_hash_string == :error, do: :error, else: nil
value = if implementation_address_hash_string == :error, do: :error, else: nil
%{implementation_address_hash_string: value, proxy_type: :unknown}
end
@spec fallback_proxy_detection(Hash.Address.t(), any(), :error | nil) :: String.t() | :error | nil
@spec fallback_proxy_detection(Hash.Address.t(), any(), :error | nil) :: %{
implementation_address_hash_string: String.t() | :error | nil,
proxy_type: atom()
}
def fallback_proxy_detection(proxy_address_hash, proxy_abi, fallback_value \\ nil) do
implementation_method_abi = get_naive_implementation_abi(proxy_abi, "implementation")
@ -284,23 +300,36 @@ defmodule Explorer.Chain.SmartContract.Proxy do
cond do
implementation_method_abi ->
Basic.get_implementation_address_hash_string(@implementation_signature, proxy_address_hash, proxy_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}
get_implementation_method_abi ->
Basic.get_implementation_address_hash_string(@get_implementation_signature, proxy_address_hash, proxy_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}
master_copy_method_abi ->
MasterCopy.get_implementation_address_hash_string(proxy_address_hash)
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}
comptroller_implementation_method_abi ->
Basic.get_implementation_address_hash_string(
@comptroller_implementation_signature,
proxy_address_hash,
proxy_abi
)
implementation_address_hash_string =
Basic.get_implementation_address_hash_string(
@comptroller_implementation_signature,
proxy_address_hash,
proxy_abi
)
%{implementation_address_hash_string: implementation_address_hash_string, proxy_type: :comptroller}
get_address_method_abi ->
EIP930.get_implementation_address_hash_string(@get_address_signature, proxy_address_hash, proxy_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}
true ->
fallback_value

@ -22,12 +22,29 @@ defmodule Explorer.Chain.SmartContract.Proxy.Models.Implementation do
@typedoc """
* `proxy_address_hash` - proxy `smart_contract` address hash.
* `proxy_type` - type of the proxy.
* `address_hashes` - array of implementation `smart_contract` address hashes. Proxy can contain multiple implementations at once.
* `names` - array of implementation `smart_contract` names.
"""
@primary_key false
typed_schema "proxy_implementations" do
field(:proxy_address_hash, Hash.Address, primary_key: true, null: false)
field(:proxy_type, Ecto.Enum,
values: [
:eip1167,
:eip1967,
:eip1822,
:eip930,
:master_copy,
:basic_implementation,
:basic_get_implementation,
:comptroller,
:unknown
],
null: true
)
field(:address_hashes, {:array, Hash.Address}, null: false)
field(:names, {:array, :string}, null: false)
@ -38,10 +55,11 @@ defmodule Explorer.Chain.SmartContract.Proxy.Models.Implementation do
proxy_implementation
|> cast(attrs, [
:proxy_address_hash,
:proxy_type,
:address_hashes,
:names
])
|> validate_required([:proxy_address_hash, :address_hashes, :names])
|> validate_required([:proxy_address_hash, :proxy_type, :address_hashes, :names])
|> unique_constraint([:proxy_address_hash])
end
@ -255,15 +273,16 @@ 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(), Keyword.t()) ::
@spec save_implementation_data(String.t() | nil, Hash.Address.t(), atom() | nil, Keyword.t()) ::
{nil, nil} | {String.t(), String.t() | nil}
def save_implementation_data(
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, nil, nil, options)
upsert_implementation(proxy_address_hash, proxy_type, nil, nil, options)
{:empty, :empty}
end
@ -271,6 +290,7 @@ defmodule Explorer.Chain.SmartContract.Proxy.Models.Implementation do
def save_implementation_data(
implementation_address_hash_string,
proxy_address_hash,
proxy_type,
options
)
when is_binary(implementation_address_hash_string) do
@ -278,7 +298,7 @@ defmodule Explorer.Chain.SmartContract.Proxy.Models.Implementation do
{: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, implementation_address_hash_string, name, options)
upsert_implementation(proxy_address_hash, proxy_type, implementation_address_hash_string, name, options)
{implementation_address_hash_string, name}
else
@ -288,6 +308,7 @@ defmodule Explorer.Chain.SmartContract.Proxy.Models.Implementation do
{:implementation, _} ->
upsert_implementation(
proxy_address_hash,
proxy_type,
implementation_address_hash_string,
nil,
options
@ -297,20 +318,21 @@ defmodule Explorer.Chain.SmartContract.Proxy.Models.Implementation do
end
end
defp upsert_implementation(proxy_address_hash, implementation_address_hash_string, name, options) do
defp upsert_implementation(proxy_address_hash, proxy_type, implementation_address_hash_string, name, options) do
proxy = get_proxy_implementations(proxy_address_hash, options)
if proxy do
update_implementation(proxy, implementation_address_hash_string, name)
update_implementation(proxy, proxy_type, implementation_address_hash_string, name)
else
insert_implementation(proxy_address_hash, implementation_address_hash_string, name)
insert_implementation(proxy_address_hash, proxy_type, implementation_address_hash_string, name)
end
end
defp insert_implementation(proxy_address_hash, implementation_address_hash_string, name)
defp insert_implementation(proxy_address_hash, proxy_type, implementation_address_hash_string, name)
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]) || []
}
@ -320,9 +342,10 @@ defmodule Explorer.Chain.SmartContract.Proxy.Models.Implementation do
|> Repo.insert()
end
defp update_implementation(proxy, implementation_address_hash_string, name) do
defp update_implementation(proxy, proxy_type, implementation_address_hash_string, name) do
proxy
|> changeset(%{
proxy_type: proxy_type,
address_hashes: (implementation_address_hash_string && [implementation_address_hash_string]) || [],
names: (name && [name]) || []
})

@ -3,8 +3,8 @@ defmodule Explorer.Repo.Migrations.MigrateProxyImplementations do
def change do
execute("""
INSERT INTO proxy_implementations(proxy_address_hash, address_hashes, names, updated_at)
SELECT address_hash, ARRAY [implementation_address_hash], CASE WHEN implementation_name IS NULL THEN '{}' ELSE ARRAY [implementation_name] END, implementation_fetched_at
INSERT INTO proxy_implementations(proxy_address_hash, address_hashes, names, inserted_at, updated_at)
SELECT address_hash, ARRAY [implementation_address_hash], CASE WHEN implementation_name IS NULL THEN '{}' ELSE ARRAY [implementation_name] END, implementation_fetched_at, implementation_fetched_at
FROM smart_contracts
WHERE implementation_fetched_at IS NOT NULL
AND implementation_address_hash IS NOT NULL

@ -0,0 +1,16 @@
defmodule Explorer.Repo.Migrations.AddProxyTypeColumn do
use Ecto.Migration
def change do
execute(
"CREATE TYPE proxy_type AS ENUM ('eip1167', 'eip1967', 'eip1822', 'eip930', 'master_copy', 'basic_implementation', 'basic_get_implementation', 'comptroller', 'unknown')",
"DROP TYPE proxy_type"
)
alter table(:proxy_implementations) do
add(:proxy_type, :proxy_type, null: true)
end
create(index(:proxy_implementations, [:proxy_type]))
end
end

@ -163,7 +163,7 @@ defmodule Explorer.Chain.SmartContract.Proxy.Models.Implementation.Test do
{:ok, addr} = Chain.hash_to_address(twin_address.hash)
bytecode_twin = addr.smart_contract
implementation_smart_contract = insert(:smart_contract, name: "implementation")
_implementation_smart_contract = insert(:smart_contract, name: "implementation")
# fetch nil implementation
assert {nil, nil} = Implementation.get_implementation(bytecode_twin)
@ -259,6 +259,7 @@ defmodule Explorer.Chain.SmartContract.Proxy.Models.Implementation.Test do
def assert_exact_name_and_address(address_hash, implementation_address_hash, implementation_name) do
implementation = Implementation.get_proxy_implementations(address_hash)
assert implementation.proxy_type
assert implementation.updated_at
assert implementation.names == [implementation_name]
@ -268,6 +269,7 @@ defmodule Explorer.Chain.SmartContract.Proxy.Models.Implementation.Test do
def assert_implementation_name(address_hash) do
implementation = Implementation.get_proxy_implementations(address_hash)
assert implementation.proxy_type
assert implementation.updated_at
assert implementation.names
end
@ -286,6 +288,7 @@ defmodule Explorer.Chain.SmartContract.Proxy.Models.Implementation.Test do
def assert_empty_implementation(address_hash) do
implementation = Implementation.get_proxy_implementations(address_hash)
assert implementation.proxy_type == :unknown
assert implementation.updated_at
assert implementation.names == []
assert implementation.address_hashes == []

@ -535,6 +535,7 @@ defmodule Explorer.Chain.SmartContract.ProxyTest do
def assert_implementation_address(address_hash) do
implementation = Implementation.get_proxy_implementations(address_hash)
assert implementation.proxy_type
assert implementation.updated_at
assert implementation.address_hashes
end
@ -546,6 +547,7 @@ defmodule Explorer.Chain.SmartContract.ProxyTest do
def assert_empty_implementation(address_hash) do
implementation = Implementation.get_proxy_implementations(address_hash)
assert implementation.proxy_type == :unknown
assert implementation.updated_at
assert implementation.names == []
assert implementation.address_hashes == []

Loading…
Cancel
Save