Improve working with contracts implementations (#5561)
* Fix error on invalid response from node while fetching implementation address; Improve storing the implementation name * Add time out on implementation fetching and storing implementation info in DB * Add tests for contracts implementation fetching; Fixes for SmartContract module * Fixes after rebase * Refactorpull/6519/head
parent
8fdc845e48
commit
708a6b0036
@ -0,0 +1,10 @@ |
|||||||
|
defmodule Explorer.Repo.Migrations.AddImplementationFields do |
||||||
|
use Ecto.Migration |
||||||
|
|
||||||
|
def change do |
||||||
|
alter table(:smart_contracts) do |
||||||
|
add(:implementation_address_hash, :bytea, null: true) |
||||||
|
add(:implementation_fetched_at, :"timestamp without time zone", null: true) |
||||||
|
end |
||||||
|
end |
||||||
|
end |
@ -0,0 +1,237 @@ |
|||||||
|
defmodule Explorer.Chain.SmartContractTest do |
||||||
|
use Explorer.DataCase, async: false |
||||||
|
|
||||||
|
import Mox |
||||||
|
alias Explorer.Chain |
||||||
|
alias Explorer.Chain.SmartContract |
||||||
|
|
||||||
|
doctest Explorer.Chain.SmartContract |
||||||
|
|
||||||
|
setup :verify_on_exit! |
||||||
|
setup :set_mox_global |
||||||
|
|
||||||
|
describe "test fetching implementation" do |
||||||
|
test "check proxy_contract/1 function" do |
||||||
|
smart_contract = insert(:smart_contract) |
||||||
|
|
||||||
|
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)) |
||||||
|
|
||||||
|
refute smart_contract.implementation_fetched_at |
||||||
|
|
||||||
|
# fetch nil implementation and save it to db |
||||||
|
get_eip1967_implementation_zero_addresses() |
||||||
|
refute SmartContract.proxy_contract?(smart_contract) |
||||||
|
verify!(EthereumJSONRPC.Mox) |
||||||
|
assert_empty_implementation(smart_contract.address_hash) |
||||||
|
# extract proxy info from db |
||||||
|
refute SmartContract.proxy_contract?(smart_contract) |
||||||
|
verify!(EthereumJSONRPC.Mox) |
||||||
|
assert_empty_implementation(smart_contract.address_hash) |
||||||
|
|
||||||
|
Application.put_env(:explorer, :fallback_ttl_cached_implementation_data_of_proxy, 0) |
||||||
|
|
||||||
|
get_eip1967_implementation_error_response() |
||||||
|
refute SmartContract.proxy_contract?(smart_contract) |
||||||
|
verify!(EthereumJSONRPC.Mox) |
||||||
|
|
||||||
|
get_eip1967_implementation_non_zero_address() |
||||||
|
assert SmartContract.proxy_contract?(smart_contract) |
||||||
|
verify!(EthereumJSONRPC.Mox) |
||||||
|
assert_implementation_address(smart_contract.address_hash) |
||||||
|
|
||||||
|
get_eip1967_implementation_non_zero_address() |
||||||
|
assert SmartContract.proxy_contract?(smart_contract) |
||||||
|
verify!(EthereumJSONRPC.Mox) |
||||||
|
assert_implementation_address(smart_contract.address_hash) |
||||||
|
|
||||||
|
Application.put_env(:explorer, :fallback_ttl_cached_implementation_data_of_proxy, :timer.seconds(20)) |
||||||
|
assert SmartContract.proxy_contract?(smart_contract) |
||||||
|
|
||||||
|
Application.put_env(:explorer, :fallback_ttl_cached_implementation_data_of_proxy, 0) |
||||||
|
get_eip1967_implementation_non_zero_address() |
||||||
|
assert SmartContract.proxy_contract?(smart_contract) |
||||||
|
verify!(EthereumJSONRPC.Mox) |
||||||
|
|
||||||
|
get_eip1967_implementation_error_response() |
||||||
|
assert SmartContract.proxy_contract?(smart_contract) |
||||||
|
verify!(EthereumJSONRPC.Mox) |
||||||
|
end |
||||||
|
|
||||||
|
test "test get_implementation_adddress_hash/1" do |
||||||
|
smart_contract = insert(: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)) |
||||||
|
|
||||||
|
refute smart_contract.implementation_fetched_at |
||||||
|
|
||||||
|
# fetch nil implementation and save it to db |
||||||
|
get_eip1967_implementation_zero_addresses() |
||||||
|
assert {nil, nil} = SmartContract.get_implementation_address_hash(smart_contract) |
||||||
|
verify!(EthereumJSONRPC.Mox) |
||||||
|
assert_empty_implementation(smart_contract.address_hash) |
||||||
|
|
||||||
|
# extract proxy info from db |
||||||
|
assert {nil, nil} = SmartContract.get_implementation_address_hash(smart_contract) |
||||||
|
assert_empty_implementation(smart_contract.address_hash) |
||||||
|
|
||||||
|
Application.put_env(:explorer, :fallback_ttl_cached_implementation_data_of_proxy, 0) |
||||||
|
|
||||||
|
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(smart_contract) |
||||||
|
|
||||||
|
verify!(EthereumJSONRPC.Mox) |
||||||
|
|
||||||
|
assert_exact_name_and_address( |
||||||
|
smart_contract.address_hash, |
||||||
|
implementation_smart_contract.address_hash, |
||||||
|
implementation_smart_contract.name |
||||||
|
) |
||||||
|
|
||||||
|
get_eip1967_implementation_error_response() |
||||||
|
|
||||||
|
assert {^string_implementation_address_hash, "proxy"} = |
||||||
|
SmartContract.get_implementation_address_hash(smart_contract) |
||||||
|
|
||||||
|
verify!(EthereumJSONRPC.Mox) |
||||||
|
|
||||||
|
assert_exact_name_and_address( |
||||||
|
smart_contract.address_hash, |
||||||
|
implementation_smart_contract.address_hash, |
||||||
|
implementation_smart_contract.name |
||||||
|
) |
||||||
|
|
||||||
|
contract_1 = Chain.address_hash_to_smart_contract(smart_contract.address_hash) |
||||||
|
|
||||||
|
Application.put_env(:explorer, :fallback_ttl_cached_implementation_data_of_proxy, :timer.seconds(20)) |
||||||
|
|
||||||
|
assert {^string_implementation_address_hash, "proxy"} = |
||||||
|
SmartContract.get_implementation_address_hash(smart_contract) |
||||||
|
|
||||||
|
contract_2 = Chain.address_hash_to_smart_contract(smart_contract.address_hash) |
||||||
|
|
||||||
|
assert contract_1.implementation_fetched_at == contract_2.implementation_fetched_at && |
||||||
|
contract_1.updated_at == contract_2.updated_at |
||||||
|
|
||||||
|
Application.put_env(:explorer, :fallback_ttl_cached_implementation_data_of_proxy, 0) |
||||||
|
get_eip1967_implementation_zero_addresses() |
||||||
|
assert {nil, nil} = SmartContract.get_implementation_address_hash(smart_contract) |
||||||
|
verify!(EthereumJSONRPC.Mox) |
||||||
|
assert_empty_implementation(smart_contract.address_hash) |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
def get_eip1967_implementation_zero_addresses do |
||||||
|
EthereumJSONRPC.Mox |
||||||
|
|> expect(:json_rpc, fn %{ |
||||||
|
id: 0, |
||||||
|
method: "eth_getStorageAt", |
||||||
|
params: [ |
||||||
|
_, |
||||||
|
"0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc", |
||||||
|
"latest" |
||||||
|
] |
||||||
|
}, |
||||||
|
_options -> |
||||||
|
{:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"} |
||||||
|
end) |
||||||
|
|> expect(:json_rpc, fn %{ |
||||||
|
id: 0, |
||||||
|
method: "eth_getStorageAt", |
||||||
|
params: [ |
||||||
|
_, |
||||||
|
"0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50", |
||||||
|
"latest" |
||||||
|
] |
||||||
|
}, |
||||||
|
_options -> |
||||||
|
{:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"} |
||||||
|
end) |
||||||
|
|> expect(:json_rpc, fn %{ |
||||||
|
id: 0, |
||||||
|
method: "eth_getStorageAt", |
||||||
|
params: [ |
||||||
|
_, |
||||||
|
"0x7050c9e0f4ca769c69bd3a8ef740bc37934f8e2c036e5a723fd8ee048ed3f8c3", |
||||||
|
"latest" |
||||||
|
] |
||||||
|
}, |
||||||
|
_options -> |
||||||
|
{:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"} |
||||||
|
end) |
||||||
|
end |
||||||
|
|
||||||
|
def get_eip1967_implementation_non_zero_address do |
||||||
|
expect(EthereumJSONRPC.Mox, :json_rpc, fn %{ |
||||||
|
id: 0, |
||||||
|
method: "eth_getStorageAt", |
||||||
|
params: [ |
||||||
|
_, |
||||||
|
"0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc", |
||||||
|
"latest" |
||||||
|
] |
||||||
|
}, |
||||||
|
_options -> |
||||||
|
{:ok, "0x0000000000000000000000000000000000000000000000000000000000000001"} |
||||||
|
end) |
||||||
|
end |
||||||
|
|
||||||
|
def get_eip1967_implementation_error_response do |
||||||
|
EthereumJSONRPC.Mox |
||||||
|
|> expect(:json_rpc, fn %{ |
||||||
|
id: 0, |
||||||
|
method: "eth_getStorageAt", |
||||||
|
params: [ |
||||||
|
_, |
||||||
|
"0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc", |
||||||
|
"latest" |
||||||
|
] |
||||||
|
}, |
||||||
|
_options -> |
||||||
|
{:error, "error"} |
||||||
|
end) |
||||||
|
end |
||||||
|
|
||||||
|
def assert_empty_implementation(address_hash) do |
||||||
|
contract = Chain.address_hash_to_smart_contract(address_hash) |
||||||
|
assert 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 |
||||||
|
assert contract.implementation_address_hash |
||||||
|
end |
||||||
|
|
||||||
|
def assert_implementation_name(address_hash) do |
||||||
|
contract = Chain.address_hash_to_smart_contract(address_hash) |
||||||
|
assert contract.implementation_fetched_at |
||||||
|
assert contract.implementation_name |
||||||
|
end |
||||||
|
|
||||||
|
def assert_exact_name_and_address(address_hash, implementation_address_hash, implementation_name) do |
||||||
|
contract = Chain.address_hash_to_smart_contract(address_hash) |
||||||
|
assert contract.implementation_fetched_at |
||||||
|
assert contract.implementation_name == implementation_name |
||||||
|
assert to_string(contract.implementation_address_hash) == to_string(implementation_address_hash) |
||||||
|
end |
||||||
|
end |
Loading…
Reference in new issue