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