From b4b8ecf4a5e1b4a3f3158c8c0ddb7e3edfeb5c5f Mon Sep 17 00:00:00 2001 From: Victor Baranov Date: Wed, 28 Aug 2024 15:23:50 +0400 Subject: [PATCH] feature: Detect Diamond proxy pattern on unverified proxy smart-contract (#10665) --- CHANGELOG.md | 1 + .../api/v2/smart_contract_controller_test.exs | 14 +-- .../explorer/chain/smart_contract/proxy.ex | 94 ++++++++++++++++--- .../chain/smart_contract/proxy/basic.ex | 17 +++- .../chain/smart_contract/proxy/eip_2535.ex | 5 +- .../proxy/models/implementation.ex | 17 +++- apps/explorer/lib/test_helper.ex | 39 ++++++++ 7 files changed, 159 insertions(+), 28 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3f6d967ab1..036e872976 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ### 🚀 Features +- Detect Diamond proxy pattern on unverified proxy smart-contract ([#10665](https://github.com/blockscout/blockscout/pull/10665)) - Support smart-contract verification in zkSync ([#10500](https://github.com/blockscout/blockscout/issues/10500)) - Add icon for secondary coin ([#10241](https://github.com/blockscout/blockscout/issues/10241)) - Integrate Cryptorank API ([#10550](https://github.com/blockscout/blockscout/issues/10550)) diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/smart_contract_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/smart_contract_controller_test.exs index 03534093c5..082f8db55d 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/smart_contract_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/smart_contract_controller_test.exs @@ -954,7 +954,7 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do assert response == %{ - "proxy_type" => nil, + "proxy_type" => "unknown", "implementations" => [], "has_custom_methods_read" => false, "has_custom_methods_write" => false, @@ -1159,8 +1159,8 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do assert response == %{ - "proxy_type" => nil, - "implementations" => [], + "proxy_type" => "eip1967", + "implementations" => [%{"address" => formatted_implementation_address_hash_string, "name" => nil}], "has_custom_methods_read" => false, "has_custom_methods_write" => false, "is_self_destructed" => false, @@ -1284,8 +1284,8 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do assert response == %{ - "proxy_type" => nil, - "implementations" => [], + "proxy_type" => "eip1967", + "implementations" => [%{"address" => formatted_implementation_address_hash_string, "name" => nil}], "has_custom_methods_read" => false, "has_custom_methods_write" => false, "is_self_destructed" => false, @@ -1409,8 +1409,8 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do assert response == %{ - "proxy_type" => nil, - "implementations" => [], + "proxy_type" => "eip1967", + "implementations" => [%{"address" => formatted_implementation_address_hash_string, "name" => nil}], "has_custom_methods_read" => false, "has_custom_methods_write" => false, "is_self_destructed" => false, diff --git a/apps/explorer/lib/explorer/chain/smart_contract/proxy.ex b/apps/explorer/lib/explorer/chain/smart_contract/proxy.ex index f4ea719eb7..4d9a8bd7cf 100644 --- a/apps/explorer/lib/explorer/chain/smart_contract/proxy.ex +++ b/apps/explorer/lib/explorer/chain/smart_contract/proxy.ex @@ -292,14 +292,64 @@ defmodule Explorer.Chain.SmartContract.Proxy do 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, :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? + ], + :get_implementation_address_hash_string_eip2535 + ) + end + + @doc """ + Returns EIP-2535 implementation address or tries next proxy pattern + """ + @spec get_implementation_address_hash_string_eip2535(Hash.Address.t(), any(), bool()) :: %{ + implementation_address_hash_strings: [String.t() | :error | nil], + proxy_type: atom() + } + def get_implementation_address_hash_string_eip2535(proxy_address_hash, proxy_abi, go_to_fallback?) do + get_implementation_address_hash_string_by_module(EIP2535, :eip2535, [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, + args, next_func \\ :fallback_proxy_detection + ) + + defp get_implementation_address_hash_string_by_module( + EIP2535 = module, + :eip2535 = proxy_type, + [proxy_address_hash, proxy_abi, go_to_fallback?] = args, + next_func + ) do + implementation_address_hash_strings = module.get_implementation_address_hash_strings(proxy_address_hash) + + if !is_nil(implementation_address_hash_strings) && implementation_address_hash_strings !== [] && + implementation_address_hash_strings !== :error do + %{implementation_address_hash_strings: implementation_address_hash_strings, proxy_type: proxy_type} + else + do_get_implementation_address_hash_string_by_module( + implementation_address_hash_strings, + proxy_address_hash, + proxy_abi, + go_to_fallback?, + next_func, + args + ) + end + end + + defp get_implementation_address_hash_string_by_module( + module, + proxy_type, + [proxy_address_hash, proxy_abi, go_to_fallback?] = args, + next_func ) do implementation_address_hash_string = module.get_implementation_address_hash_string(proxy_address_hash) @@ -307,23 +357,41 @@ defmodule Explorer.Chain.SmartContract.Proxy do implementation_address_hash_string !== :error do %{implementation_address_hash_strings: [implementation_address_hash_string], proxy_type: proxy_type} else - cond do - next_func !== :fallback_proxy_detection -> - apply(__MODULE__, next_func, args) + do_get_implementation_address_hash_string_by_module( + implementation_address_hash_string, + proxy_address_hash, + proxy_abi, + go_to_fallback?, + next_func, + args + ) + end + end - go_to_fallback? && next_func == :fallback_proxy_detection -> - fallback_value = implementation_fallback_value(implementation_address_hash_string) + defp do_get_implementation_address_hash_string_by_module( + implementation_value, + proxy_address_hash, + proxy_abi, + go_to_fallback?, + next_func, + args + ) do + cond do + next_func !== :fallback_proxy_detection -> + apply(__MODULE__, next_func, args) - apply(__MODULE__, :fallback_proxy_detection, [proxy_address_hash, proxy_abi, fallback_value]) + go_to_fallback? && next_func == :fallback_proxy_detection -> + fallback_value = implementation_fallback_value(implementation_value) - true -> - implementation_fallback_value(implementation_address_hash_string) - end + apply(__MODULE__, :fallback_proxy_detection, [proxy_address_hash, proxy_abi, fallback_value]) + + true -> + implementation_fallback_value(implementation_value) end end - defp implementation_fallback_value(implementation_address_hash_string) do - value = if implementation_address_hash_string == :error, do: :error, else: [] + defp implementation_fallback_value(implementation_value) do + value = if implementation_value == :error, do: :error, else: [] %{implementation_address_hash_strings: value, proxy_type: :unknown} end diff --git a/apps/explorer/lib/explorer/chain/smart_contract/proxy/basic.ex b/apps/explorer/lib/explorer/chain/smart_contract/proxy/basic.ex index d0d6a4fb54..aff7b417fe 100644 --- a/apps/explorer/lib/explorer/chain/smart_contract/proxy/basic.ex +++ b/apps/explorer/lib/explorer/chain/smart_contract/proxy/basic.ex @@ -9,7 +9,8 @@ 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() | [binary()] + @spec get_implementation_address_hash_string(binary, binary, SmartContract.abi()) :: + nil | :error | binary() | [binary()] def get_implementation_address_hash_string(signature, proxy_address_hash, abi) do implementation_address = case Reader.query_contract( @@ -20,8 +21,14 @@ defmodule Explorer.Chain.SmartContract.Proxy.Basic do }, false ) do - %{^signature => {:ok, [result]}} -> result - _ -> nil + %{^signature => {:ok, [result]}} -> + result + + %{^signature => {:error, _}} -> + :error + + _ -> + nil end adds_0x_to_address(implementation_address) @@ -30,9 +37,11 @@ defmodule Explorer.Chain.SmartContract.Proxy.Basic do @doc """ Adds 0x to address at the beginning """ - @spec adds_0x_to_address(nil | binary()) :: nil | binary() | [binary()] + @spec adds_0x_to_address(nil | :error | binary()) :: nil | :error | binary() | [binary()] def adds_0x_to_address(nil), do: nil + def adds_0x_to_address(:error), do: :error + def adds_0x_to_address(addresses) when is_list(addresses) do addresses |> Enum.map(fn address -> adds_0x_to_address(address) end) diff --git a/apps/explorer/lib/explorer/chain/smart_contract/proxy/eip_2535.ex b/apps/explorer/lib/explorer/chain/smart_contract/proxy/eip_2535.ex index cc0d374e60..4e9aaf9313 100644 --- a/apps/explorer/lib/explorer/chain/smart_contract/proxy/eip_2535.ex +++ b/apps/explorer/lib/explorer/chain/smart_contract/proxy/eip_2535.ex @@ -19,7 +19,7 @@ defmodule Explorer.Chain.SmartContract.Proxy.EIP2535 do } ] - @spec get_implementation_address_hash_strings(Hash.Address.t()) :: nil | [binary] + @spec get_implementation_address_hash_strings(Hash.Address.t()) :: nil | :error | [binary] def get_implementation_address_hash_strings(proxy_address_hash) do case @facet_addresses_signature |> Basic.get_implementation_address_hash_string( @@ -29,6 +29,9 @@ defmodule Explorer.Chain.SmartContract.Proxy.EIP2535 do implementation_addresses when is_list(implementation_addresses) -> implementation_addresses + :error -> + :error + _ -> nil end diff --git a/apps/explorer/lib/explorer/chain/smart_contract/proxy/models/implementation.ex b/apps/explorer/lib/explorer/chain/smart_contract/proxy/models/implementation.ex index f6e4d9c501..4b78a0d005 100644 --- a/apps/explorer/lib/explorer/chain/smart_contract/proxy/models/implementation.ex +++ b/apps/explorer/lib/explorer/chain/smart_contract/proxy/models/implementation.ex @@ -94,7 +94,7 @@ defmodule Explorer.Chain.SmartContract.Proxy.Models.Implementation do @doc """ 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() + @spec get_proxy_implementation_updated_at(Hash.Address.t() | nil, Keyword.t()) :: DateTime.t() | nil def get_proxy_implementation_updated_at(proxy_address_hash, options) do proxy_address_hash |> get_proxy_implementations_query() @@ -140,9 +140,20 @@ defmodule Explorer.Chain.SmartContract.Proxy.Models.Implementation do {updated_smart_contract, implementation_address_fetched?} = if check_implementation_refetch_necessity(implementation_updated_at) do - SmartContract.address_hash_to_smart_contract_with_bytecode_twin(address_hash, options) + {smart_contract_with_bytecode_twin, implementation_address_fetched?} = + SmartContract.address_hash_to_smart_contract_with_bytecode_twin(address_hash, options) + + if smart_contract_with_bytecode_twin do + {smart_contract_with_bytecode_twin, implementation_address_fetched?} + else + {smart_contract, implementation_address_fetched?} + end else - {smart_contract, false} + if implementation_updated_at do + {smart_contract, true} + else + {smart_contract, false} + end end get_implementation( diff --git a/apps/explorer/lib/test_helper.ex b/apps/explorer/lib/test_helper.ex index e82a742b83..bc674636f8 100644 --- a/apps/explorer/lib/test_helper.ex +++ b/apps/explorer/lib/test_helper.ex @@ -89,6 +89,43 @@ defmodule Explorer.TestHelper do end) end + def mock_eip_2535_storage_pointer_request( + mox, + error?, + resp \\ "0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000" + ) do + response = + if error?, + do: {:error, "error"}, + else: + {:ok, + [ + %{ + id: 0, + jsonrpc: "2.0", + result: resp + } + ]} + + expect(mox, :json_rpc, fn [ + %{ + id: 0, + jsonrpc: "2.0", + method: "eth_call", + params: [ + %{ + data: "0x52ef6b2c", + to: _ + }, + "latest" + ] + } + ], + _options -> + response + end) + end + def get_eip1967_implementation_non_zero_address(address_hash_string) do EthereumJSONRPC.Mox |> mock_logic_storage_pointer_request(false) @@ -102,6 +139,7 @@ defmodule Explorer.TestHelper do |> mock_beacon_storage_pointer_request(false) |> mock_oz_storage_pointer_request(false) |> mock_eip_1822_storage_pointer_request(false) + |> mock_eip_2535_storage_pointer_request(false) end def get_eip1967_implementation_error_response do @@ -110,6 +148,7 @@ defmodule Explorer.TestHelper do |> mock_beacon_storage_pointer_request(true) |> mock_oz_storage_pointer_request(true) |> mock_eip_1822_storage_pointer_request(true) + |> mock_eip_2535_storage_pointer_request(true) end def fetch_token_uri_mock(url, token_contract_address_hash_string) do