Skip to content

Commit

Permalink
Merge pull request blockscout#6549 from blockscout/np-fix-proxy-bug
Browse files Browse the repository at this point in the history
Fix bug with proxy for twins
  • Loading branch information
vbaranov authored Dec 5, 2022
2 parents 9df316a + 373527d commit 8167d6e
Show file tree
Hide file tree
Showing 5 changed files with 216 additions and 26 deletions.
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
### Features

- [#6544](https://github.com/blockscout/blockscout/pull/6544) - API improvements
- [#5561](https://github.com/blockscout/blockscout/pull/5561), [#6523](https://github.com/blockscout/blockscout/pull/6523) - Improve working with contracts implementations
- [#5561](https://github.com/blockscout/blockscout/pull/5561), [#6523](https://github.com/blockscout/blockscout/pull/6523), [#6549](https://github.com/blockscout/blockscout/pull/6549) - Improve working with contracts implementations
- [#6401](https://github.com/blockscout/blockscout/pull/6401) - Add Sol2Uml contract visualization
- [#6481](https://github.com/blockscout/blockscout/pull/6481) - Smart contract verification improvements
- [#6444](https://github.com/blockscout/blockscout/pull/6444) - Add support for yul verification via rust microservice
Expand Down
17 changes: 12 additions & 5 deletions apps/explorer/lib/explorer/chain.ex
Original file line number Diff line number Diff line change
Expand Up @@ -1384,7 +1384,10 @@ defmodule Explorer.Chain do
address_verified_twin_contract_updated =
address_verified_twin_contract
|> Map.put(:address_hash, hash)
|> Map.put_new(:metadata_from_verified_twin, true)
|> Map.put(:metadata_from_verified_twin, true)
|> Map.put(:implementation_address_hash, nil)
|> Map.put(:implementation_name, nil)
|> Map.put(:implementation_fetched_at, nil)

address_result
|> Map.put(:smart_contract, address_verified_twin_contract_updated)
Expand Down Expand Up @@ -1902,7 +1905,10 @@ defmodule Explorer.Chain do
address_verified_twin_contract_updated =
address_verified_twin_contract
|> Map.put(:address_hash, hash)
|> Map.put_new(:metadata_from_verified_twin, true)
|> Map.put(:metadata_from_verified_twin, true)
|> Map.put(:implementation_address_hash, nil)
|> Map.put(:implementation_name, nil)
|> Map.put(:implementation_fetched_at, nil)

address_result
|> Map.put(:smart_contract, address_verified_twin_contract_updated)
Expand Down Expand Up @@ -4349,9 +4355,10 @@ defmodule Explorer.Chain do
if address_verified_twin_contract do
address_verified_twin_contract
|> Map.put(:address_hash, address_hash)
|> Map.put(:implementation_address_hash, current_smart_contract.implementation_address_hash)
|> Map.put(:implementation_name, current_smart_contract.implementation_name)
|> Map.put(:implementation_fetched_at, current_smart_contract.implementation_fetched_at)
|> Map.put(:metadata_from_verified_twin, true)
|> Map.put(:implementation_address_hash, nil)
|> Map.put(:implementation_name, nil)
|> Map.put(:implementation_fetched_at, nil)
else
current_smart_contract
end
Expand Down
54 changes: 35 additions & 19 deletions apps/explorer/lib/explorer/chain/smart_contract.ex
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,7 @@ defmodule Explorer.Chain.SmartContract do
field(:implementation_address_hash, Hash.Address, default: nil)
field(:autodetect_constructor_args, :boolean, virtual: true)
field(:is_yul, :boolean, virtual: true)
field(:metadata_from_verified_twin, :boolean, virtual: true)

has_many(
:decompiled_smart_contracts,
Expand Down Expand Up @@ -529,7 +530,11 @@ defmodule Explorer.Chain.SmartContract do

def proxy_contract?(_), do: false

def get_implementation_address_hash(%__MODULE__{abi: nil}), do: false
def get_implementation_address_hash(%__MODULE__{abi: nil}), do: {nil, nil}

def get_implementation_address_hash(%__MODULE__{metadata_from_verified_twin: true} = smart_contract) do
get_implementation_address_hash({:updated, smart_contract})
end

def get_implementation_address_hash(
%__MODULE__{
Expand All @@ -540,7 +545,7 @@ defmodule Explorer.Chain.SmartContract do
updated_smart_contract =
if Application.get_env(:explorer, :enable_caching_implementation_data_of_proxy) &&
check_implementation_refetch_neccessity(implementation_fetched_at) do
Chain.address_hash_to_smart_contract(address_hash)
Chain.address_hash_to_smart_contract_without_twin(address_hash)
else
smart_contract
end
Expand All @@ -555,11 +560,13 @@ defmodule Explorer.Chain.SmartContract do
abi: abi,
implementation_address_hash: implementation_address_hash_from_db,
implementation_name: implementation_name_from_db,
implementation_fetched_at: implementation_fetched_at
implementation_fetched_at: implementation_fetched_at,
metadata_from_verified_twin: metadata_from_verified_twin
}}
) do
if check_implementation_refetch_neccessity(implementation_fetched_at) do
get_implementation_address_hash_task = Task.async(fn -> get_implementation_address_hash(address_hash, abi) end)
get_implementation_address_hash_task =
Task.async(fn -> get_implementation_address_hash(address_hash, abi, metadata_from_verified_twin) end)

timeout = Application.get_env(:explorer, :implementation_data_fetching_timeout)

Expand Down Expand Up @@ -624,8 +631,9 @@ defmodule Explorer.Chain.SmartContract do
end
end

@spec get_implementation_address_hash(Hash.Address.t(), list()) :: {String.t() | nil, String.t() | nil}
defp get_implementation_address_hash(proxy_address_hash, abi)
@spec get_implementation_address_hash(Hash.Address.t(), list(), boolean() | nil) ::
{String.t() | nil, String.t() | nil}
defp get_implementation_address_hash(proxy_address_hash, abi, metadata_from_verified_twin)
when not is_nil(proxy_address_hash) and not is_nil(abi) do
implementation_method_abi =
abi
Expand All @@ -651,10 +659,10 @@ defmodule Explorer.Chain.SmartContract do
get_implementation_address_hash_eip_1967(proxy_address_hash)
end

save_implementation_data(implementation_address, proxy_address_hash)
save_implementation_data(implementation_address, proxy_address_hash, metadata_from_verified_twin)
end

defp get_implementation_address_hash(proxy_address_hash, abi) when is_nil(proxy_address_hash) or is_nil(abi) do
defp get_implementation_address_hash(_proxy_address_hash, _abi, _) do
{nil, nil}
end

Expand Down Expand Up @@ -794,28 +802,30 @@ defmodule Explorer.Chain.SmartContract do
abi_decode_address_output(implementation_address)
end

defp save_implementation_data(nil, _), do: {nil, nil}
defp save_implementation_data(nil, _, _), do: {nil, nil}

defp save_implementation_data(empty_address_hash_string, proxy_address_hash)
defp save_implementation_data(empty_address_hash_string, proxy_address_hash, metadata_from_verified_twin)
when empty_address_hash_string in [
"0x",
"0x0",
"0x0000000000000000000000000000000000000000000000000000000000000000",
@burn_address_hash_str
] do
proxy_address_hash
|> Chain.address_hash_to_smart_contract_without_twin()
|> changeset(%{
implementation_name: nil,
implementation_address_hash: nil,
implementation_fetched_at: DateTime.utc_now()
})
|> Repo.update()
if is_nil(metadata_from_verified_twin) or !metadata_from_verified_twin do
proxy_address_hash
|> Chain.address_hash_to_smart_contract_without_twin()
|> changeset(%{
implementation_name: nil,
implementation_address_hash: nil,
implementation_fetched_at: DateTime.utc_now()
})
|> Repo.update()
end

{:empty, :empty}
end

defp save_implementation_data(implementation_address_hash_string, proxy_address_hash)
defp save_implementation_data(implementation_address_hash_string, proxy_address_hash, _)
when is_binary(implementation_address_hash_string) do
with {:ok, address_hash} <- Chain.string_to_address_hash(implementation_address_hash_string),
proxy_contract <- Chain.address_hash_to_smart_contract_without_twin(proxy_address_hash),
Expand Down Expand Up @@ -845,6 +855,12 @@ defmodule Explorer.Chain.SmartContract do

{implementation_address_hash_string, nil}

true ->
{:ok, address_hash} = Chain.string_to_address_hash(implementation_address_hash_string)
smart_contract = Chain.address_hash_to_smart_contract(address_hash)

{implementation_address_hash_string, smart_contract && smart_contract.name}

_ ->
{implementation_address_hash_string, nil}
end
Expand Down
166 changes: 166 additions & 0 deletions apps/explorer/test/explorer/chain/smart_contract_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,165 @@ defmodule Explorer.Chain.SmartContractTest do
verify!(EthereumJSONRPC.Mox)
assert_empty_implementation(smart_contract.address_hash)
end

test "test get_implementation_adddress_hash/1 for twins contract" do
# return nils for nil
assert {nil, nil} = SmartContract.get_implementation_address_hash(nil)
smart_contract = insert(:smart_contract)
another_address = insert(:contract_address)

twin = Chain.address_hash_to_smart_contract(another_address.hash)
implementation_smart_contract = insert(:smart_contract, name: "proxy")

Application.put_env(:explorer, :fallback_ttl_cached_implementation_data_of_proxy, :timer.seconds(20))
Application.put_env(:explorer, :implementation_data_fetching_timeout, :timer.seconds(20))

# fetch nil implementation
get_eip1967_implementation_zero_addresses()
assert {nil, nil} = SmartContract.get_implementation_address_hash(twin)
verify!(EthereumJSONRPC.Mox)
assert_implementation_never_fetched(smart_contract.address_hash)

get_eip1967_implementation_zero_addresses()
assert {nil, nil} = SmartContract.get_implementation_address_hash(twin)
verify!(EthereumJSONRPC.Mox)
assert_implementation_never_fetched(smart_contract.address_hash)

string_implementation_address_hash = to_string(implementation_smart_contract.address_hash)

expect(EthereumJSONRPC.Mox, :json_rpc, fn %{
id: 0,
method: "eth_getStorageAt",
params: [
_,
"0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc",
"latest"
]
},
_options ->
{:ok, string_implementation_address_hash}
end)

assert {^string_implementation_address_hash, "proxy"} = SmartContract.get_implementation_address_hash(twin)

verify!(EthereumJSONRPC.Mox)

assert_implementation_never_fetched(smart_contract.address_hash)

get_eip1967_implementation_error_response()

assert {nil, nil} = SmartContract.get_implementation_address_hash(twin)

verify!(EthereumJSONRPC.Mox)

assert_implementation_never_fetched(smart_contract.address_hash)

{:ok, addr} = Chain.hash_to_address(another_address.hash)
twin = addr.smart_contract

implementation_smart_contract = insert(:smart_contract, name: "proxy")

Application.put_env(:explorer, :fallback_ttl_cached_implementation_data_of_proxy, :timer.seconds(20))
Application.put_env(:explorer, :implementation_data_fetching_timeout, :timer.seconds(20))

# fetch nil implementation
get_eip1967_implementation_zero_addresses()
assert {nil, nil} = SmartContract.get_implementation_address_hash(twin)
verify!(EthereumJSONRPC.Mox)
assert_implementation_never_fetched(smart_contract.address_hash)

get_eip1967_implementation_zero_addresses()
assert {nil, nil} = SmartContract.get_implementation_address_hash(twin)
verify!(EthereumJSONRPC.Mox)
assert_implementation_never_fetched(smart_contract.address_hash)

string_implementation_address_hash = to_string(implementation_smart_contract.address_hash)

expect(EthereumJSONRPC.Mox, :json_rpc, fn %{
id: 0,
method: "eth_getStorageAt",
params: [
_,
"0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc",
"latest"
]
},
_options ->
{:ok, string_implementation_address_hash}
end)

assert {^string_implementation_address_hash, "proxy"} = SmartContract.get_implementation_address_hash(twin)

verify!(EthereumJSONRPC.Mox)

assert_implementation_never_fetched(smart_contract.address_hash)

get_eip1967_implementation_error_response()

assert {nil, nil} = SmartContract.get_implementation_address_hash(twin)

verify!(EthereumJSONRPC.Mox)

assert_implementation_never_fetched(smart_contract.address_hash)

{:ok, addr} =
Chain.find_contract_address(
another_address.hash,
[
necessity_by_association: %{
:smart_contract => :optional
}
],
true
)

twin = addr.smart_contract

implementation_smart_contract = insert(:smart_contract, name: "proxy")

Application.put_env(:explorer, :fallback_ttl_cached_implementation_data_of_proxy, :timer.seconds(20))
Application.put_env(:explorer, :implementation_data_fetching_timeout, :timer.seconds(20))

# fetch nil implementation
get_eip1967_implementation_zero_addresses()
assert {nil, nil} = SmartContract.get_implementation_address_hash(twin)
verify!(EthereumJSONRPC.Mox)
assert_implementation_never_fetched(smart_contract.address_hash)

get_eip1967_implementation_zero_addresses()
assert {nil, nil} = SmartContract.get_implementation_address_hash(twin)
verify!(EthereumJSONRPC.Mox)
assert_implementation_never_fetched(smart_contract.address_hash)

string_implementation_address_hash = to_string(implementation_smart_contract.address_hash)

expect(EthereumJSONRPC.Mox, :json_rpc, fn %{
id: 0,
method: "eth_getStorageAt",
params: [
_,
"0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc",
"latest"
]
},
_options ->
{:ok, string_implementation_address_hash}
end)

assert {^string_implementation_address_hash, "proxy"} = SmartContract.get_implementation_address_hash(twin)

verify!(EthereumJSONRPC.Mox)

assert_implementation_never_fetched(smart_contract.address_hash)

get_eip1967_implementation_error_response()

assert {nil, nil} = SmartContract.get_implementation_address_hash(twin)

verify!(EthereumJSONRPC.Mox)

assert_implementation_never_fetched(smart_contract.address_hash)
end
end

def get_eip1967_implementation_zero_addresses do
Expand Down Expand Up @@ -216,6 +375,13 @@ defmodule Explorer.Chain.SmartContractTest do
refute contract.implementation_address_hash
end

def assert_implementation_never_fetched(address_hash) do
contract = Chain.address_hash_to_smart_contract(address_hash)
refute contract.implementation_fetched_at
refute contract.implementation_name
refute contract.implementation_address_hash
end

def assert_implementation_address(address_hash) do
contract = Chain.address_hash_to_smart_contract(address_hash)
assert contract.implementation_fetched_at
Expand Down
3 changes: 2 additions & 1 deletion apps/explorer/test/support/factory.ex
Original file line number Diff line number Diff line change
Expand Up @@ -807,7 +807,8 @@ defmodule Explorer.Factory do
def smart_contract_factory do
contract_code_info = contract_code_info()

bytecode_md5 = Helper.contract_code_md5(contract_code_info.bytecode)
{:ok, data} = Explorer.Chain.Data.cast(contract_code_info.bytecode)
bytecode_md5 = Helper.contract_code_md5(data.bytes)

%SmartContract{
address_hash: insert(:address, contract_code: contract_code_info.bytecode, verified: true).hash,
Expand Down

0 comments on commit 8167d6e

Please sign in to comment.