feature: Detect Diamond proxy pattern on unverified proxy smart-contract (#10665)

pull/10676/head v6.8.0-beta
Victor Baranov 3 months ago committed by GitHub
parent 2c72aee5db
commit b4b8ecf4a5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 1
      CHANGELOG.md
  2. 14
      apps/block_scout_web/test/block_scout_web/controllers/api/v2/smart_contract_controller_test.exs
  3. 82
      apps/explorer/lib/explorer/chain/smart_contract/proxy.ex
  4. 17
      apps/explorer/lib/explorer/chain/smart_contract/proxy/basic.ex
  5. 5
      apps/explorer/lib/explorer/chain/smart_contract/proxy/eip_2535.ex
  6. 13
      apps/explorer/lib/explorer/chain/smart_contract/proxy/models/implementation.ex
  7. 39
      apps/explorer/lib/test_helper.ex

@ -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))

@ -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,

@ -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
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
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)
go_to_fallback? && next_func == :fallback_proxy_detection ->
fallback_value = implementation_fallback_value(implementation_address_hash_string)
fallback_value = implementation_fallback_value(implementation_value)
apply(__MODULE__, :fallback_proxy_detection, [proxy_address_hash, proxy_abi, fallback_value])
true ->
implementation_fallback_value(implementation_address_hash_string)
end
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

@ -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)

@ -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

@ -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,10 +140,21 @@ defmodule Explorer.Chain.SmartContract.Proxy.Models.Implementation do
{updated_smart_contract, implementation_address_fetched?} =
if check_implementation_refetch_necessity(implementation_updated_at) do
{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
if implementation_updated_at do
{smart_contract, true}
else
{smart_contract, false}
end
end
get_implementation(
%{

@ -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

Loading…
Cancel
Save