pull/3157/head
Victor Baranov 4 years ago
parent 8aab411505
commit 7adf3f4220
  1. 1
      CHANGELOG.md
  2. 3
      apps/block_scout_web/assets/js/lib/smart_contract/read_only_functions.js
  3. 1
      apps/block_scout_web/lib/block_scout_web/controllers/address_read_contract_controller.ex
  4. 38
      apps/block_scout_web/lib/block_scout_web/controllers/address_read_proxy_controller.ex
  5. 9
      apps/block_scout_web/lib/block_scout_web/controllers/smart_contract_controller.ex
  6. 7
      apps/block_scout_web/lib/block_scout_web/templates/address/_tabs.html.eex
  7. 2
      apps/block_scout_web/lib/block_scout_web/templates/address_read_contract/index.html.eex
  8. 1
      apps/block_scout_web/lib/block_scout_web/templates/address_read_proxy/_metatags.html.eex
  9. 19
      apps/block_scout_web/lib/block_scout_web/templates/address_read_proxy/index.html.eex
  10. 7
      apps/block_scout_web/lib/block_scout_web/views/address_read_proxy_view.ex
  11. 8
      apps/block_scout_web/lib/block_scout_web/views/address_view.ex
  12. 7
      apps/block_scout_web/lib/block_scout_web/web_router.ex
  13. 37
      apps/block_scout_web/priv/gettext/default.pot
  14. 37
      apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po
  15. 81
      apps/block_scout_web/test/block_scout_web/controllers/address_read_proxy_controller_test.exs
  16. 20
      apps/block_scout_web/test/block_scout_web/controllers/smart_contract_controller_test.exs
  17. 19
      apps/block_scout_web/test/block_scout_web/views/address_read_proxy_view_test.exs
  18. 101
      apps/explorer/lib/explorer/chain.ex
  19. 19
      apps/explorer/lib/explorer/smart_contract/reader.ex
  20. 56
      apps/explorer/test/explorer/chain_test.exs
  21. 93
      apps/explorer/test/explorer/smart_contract/reader_test.exs

@ -1,6 +1,7 @@
## Current
### Features
- [#3157](https://github.com/poanetwork/blockscout/pull/3157) - Read methods of implementation on proxy contract
### Fixes

@ -4,10 +4,11 @@ const loadFunctions = (element) => {
const $element = $(element)
const url = $element.data('url')
const hash = $element.data('hash')
const type = $element.data('type')
$.get(
url,
{ hash: hash },
{ hash: hash, type: type },
response => $element.html(response)
)
.done(function () {

@ -31,6 +31,7 @@ defmodule BlockScoutWeb.AddressReadContractController do
conn,
"index.html",
address: address,
type: :regular,
coin_balance_status: CoinBalanceOnDemand.trigger_fetch(address),
exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(),
counters_path: address_path(conn, :address_counters, %{"id" => Address.checksum(address_hash)})

@ -0,0 +1,38 @@
# credo:disable-for-this-file
defmodule BlockScoutWeb.AddressReadProxyController do
use BlockScoutWeb, :controller
alias Explorer.{Chain, Market}
alias Explorer.Chain.Address
alias Explorer.ExchangeRates.Token
alias Indexer.Fetcher.CoinBalanceOnDemand
def index(conn, %{"address_id" => address_hash_string}) do
address_options = [
necessity_by_association: %{
:contracts_creation_internal_transaction => :optional,
:names => :optional,
:smart_contract => :optional,
:token => :optional,
:contracts_creation_transaction => :optional
}
]
with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string),
{:ok, address} <- Chain.find_contract_address(address_hash, address_options, true),
false <- is_nil(address.smart_contract) do
render(
conn,
"index.html",
address: address,
type: :proxy,
coin_balance_status: CoinBalanceOnDemand.trigger_fetch(address),
exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(),
counters_path: address_path(conn, :address_counters, %{"id" => Address.checksum(address_hash)})
)
else
_ ->
not_found(conn)
end
end
end

@ -4,11 +4,16 @@ defmodule BlockScoutWeb.SmartContractController do
alias Explorer.Chain
alias Explorer.SmartContract.Reader
def index(conn, %{"hash" => address_hash_string}) do
def index(conn, %{"hash" => address_hash_string, "type" => contract_type}) do
with true <- ajax?(conn),
{:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string),
{:ok, address} <- Chain.find_contract_address(address_hash) do
read_only_functions = Reader.read_only_functions(address_hash)
read_only_functions =
if contract_type == "proxy" do
Reader.read_only_functions_proxy(address_hash)
else
Reader.read_only_functions(address_hash)
end
conn
|> put_status(200)

@ -64,4 +64,11 @@
class: "card-tab #{tab_status("read_contract", @conn.request_path)}")
%>
<% end %>
<%= if smart_contract_is_proxy?(@address) do %>
<%= link(
gettext("Read Proxy"),
to: address_read_proxy_path(@conn, :index, @address.hash),
class: "card-tab #{tab_status("read_proxy", @conn.request_path)}")
%>
<% end %>
</div>

@ -5,7 +5,7 @@
<div class="card">
<%= render BlockScoutWeb.AddressView, "_tabs.html", assigns %>
<!-- loaded via AJAX -->
<div class="card-body" data-smart-contract-functions data-hash="<%= to_string(@address.hash) %>" data-url="<%= smart_contract_path(@conn, :index) %>">
<div class="card-body" data-smart-contract-functions data-hash="<%= to_string(@address.hash) %>" data-type="<%= @type %>" data-url="<%= smart_contract_path(@conn, :index) %>">
<div>
<span class="loading-spinner-small mr-2">
<span class="loading-spinner-block-1"></span>

@ -0,0 +1 @@
<%= render BlockScoutWeb.AddressView, "_metatags.html", conn: @conn, address: @address %>

@ -0,0 +1,19 @@
<section class="container">
<%= render BlockScoutWeb.AddressView, "overview.html", assigns %>
<div class="card">
<%= render BlockScoutWeb.AddressView, "_tabs.html", assigns %>
<!-- loaded via AJAX -->
<div class="card-body" data-smart-contract-functions data-hash="<%= to_string(@address.hash) %>" data-type="<%= @type %>" data-url="<%= smart_contract_path(@conn, :index) %>">
<div>
<span class="loading-spinner-small mr-2">
<span class="loading-spinner-block-1"></span>
<span class="loading-spinner-block-2"></span>
</span>
<%= gettext("Loading...") %>
</div>
</div>
</div>
<script defer data-cfasync="false" src="<%= static_path(@conn, "/js/smart-contract-helpers.js") %>"></script>
</section>

@ -0,0 +1,7 @@
defmodule BlockScoutWeb.AddressReadProxyView do
use BlockScoutWeb, :view
def queryable?(inputs), do: Enum.any?(inputs)
def address?(type), do: type == "address"
end

@ -18,6 +18,7 @@ defmodule BlockScoutWeb.AddressView do
"internal_transactions",
"token_transfers",
"read_contract",
"read_proxy",
"tokens",
"transactions",
"validations"
@ -228,6 +229,12 @@ defmodule BlockScoutWeb.AddressView do
def smart_contract_with_read_only_functions?(%Address{smart_contract: nil}), do: false
def smart_contract_is_proxy?(%Address{smart_contract: %SmartContract{}} = address) do
Chain.is_proxy_contract?(address.smart_contract.abi)
end
def smart_contract_is_proxy?(%Address{smart_contract: nil}), do: false
def has_decompiled_code?(address) do
address.has_decompiled_code? ||
(Ecto.assoc_loaded?(address.decompiled_smart_contracts) && Enum.count(address.decompiled_smart_contracts) > 0)
@ -326,6 +333,7 @@ defmodule BlockScoutWeb.AddressView do
defp tab_name(["contracts"]), do: gettext("Code")
defp tab_name(["decompiled_contracts"]), do: gettext("Decompiled Code")
defp tab_name(["read_contract"]), do: gettext("Read Contract")
defp tab_name(["read_proxy"]), do: gettext("Read Proxy")
defp tab_name(["coin_balances"]), do: gettext("Coin Balance History")
defp tab_name(["validations"]), do: gettext("Blocks Validated")
defp tab_name(["logs"]), do: gettext("Logs")

@ -125,6 +125,13 @@ defmodule BlockScoutWeb.WebRouter do
as: :read_contract
)
resources(
"/read_proxy",
AddressReadProxyController,
only: [:index, :show],
as: :read_proxy
)
resources(
"/token_transfers",
AddressTokenTransferController,

@ -124,7 +124,7 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/_validator_metadata_modal.html.eex:16
#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:19
#: lib/block_scout_web/views/address_view.ex:100
#: lib/block_scout_web/views/address_view.ex:101
msgid "Address"
msgstr ""
@ -338,14 +338,14 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:18
#: lib/block_scout_web/views/address_view.ex:98
#: lib/block_scout_web/views/address_view.ex:99
msgid "Contract Address"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/transaction/_pending_tile.html.eex:16
#: lib/block_scout_web/views/address_view.ex:38
#: lib/block_scout_web/views/address_view.ex:72
#: lib/block_scout_web/views/address_view.ex:39
#: lib/block_scout_web/views/address_view.ex:73
msgid "Contract Address Pending"
msgstr ""
@ -903,7 +903,7 @@ msgstr ""
#: lib/block_scout_web/templates/tokens/transfer/index.html.eex:14
#: lib/block_scout_web/templates/transaction/_tabs.html.eex:4
#: lib/block_scout_web/templates/transaction_token_transfer/index.html.eex:7
#: lib/block_scout_web/views/address_view.ex:325
#: lib/block_scout_web/views/address_view.ex:332
#: lib/block_scout_web/views/tokens/instance/overview_view.ex:98
#: lib/block_scout_web/views/tokens/overview_view.ex:35
#: lib/block_scout_web/views/transaction_view.ex:394
@ -1011,6 +1011,7 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_read_contract/index.html.eex:14
#: lib/block_scout_web/templates/address_read_proxy/index.html.eex:14
#: lib/block_scout_web/templates/tokens/read_contract/index.html.eex:20
msgid "Loading..."
msgstr ""
@ -1041,7 +1042,7 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/chain/show.html.eex:52
#: lib/block_scout_web/templates/layout/app.html.eex:30
#: lib/block_scout_web/views/address_view.ex:138
#: lib/block_scout_web/views/address_view.ex:139
msgid "Market Cap"
msgstr ""
@ -1807,7 +1808,7 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/_tabs.html.eex:37
#: lib/block_scout_web/templates/address_validation/index.html.eex:13
#: lib/block_scout_web/views/address_view.ex:330
#: lib/block_scout_web/views/address_view.ex:338
msgid "Blocks Validated"
msgstr ""
@ -1817,18 +1818,18 @@ msgstr ""
#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:187
#: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:126
#: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:149
#: lib/block_scout_web/views/address_view.ex:326
#: lib/block_scout_web/views/address_view.ex:333
msgid "Code"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/_tabs.html.eex:26
#: lib/block_scout_web/views/address_view.ex:329
#: lib/block_scout_web/views/address_view.ex:337
msgid "Coin Balance History"
msgstr ""
#, elixir-format
#: lib/block_scout_web/views/address_view.ex:327
#: lib/block_scout_web/views/address_view.ex:334
msgid "Decompiled Code"
msgstr ""
@ -1837,7 +1838,7 @@ msgstr ""
#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:19
#: lib/block_scout_web/templates/transaction/_tabs.html.eex:11
#: lib/block_scout_web/templates/transaction_internal_transaction/index.html.eex:6
#: lib/block_scout_web/views/address_view.ex:324
#: lib/block_scout_web/views/address_view.ex:331
#: lib/block_scout_web/views/transaction_view.ex:395
msgid "Internal Transactions"
msgstr ""
@ -1847,7 +1848,7 @@ msgstr ""
#: lib/block_scout_web/templates/address_logs/index.html.eex:8
#: lib/block_scout_web/templates/transaction/_tabs.html.eex:17
#: lib/block_scout_web/templates/transaction_log/index.html.eex:8
#: lib/block_scout_web/views/address_view.ex:331
#: lib/block_scout_web/views/address_view.ex:339
#: lib/block_scout_web/views/transaction_view.ex:396
msgid "Logs"
msgstr ""
@ -1855,7 +1856,7 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/_tabs.html.eex:62
#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:25
#: lib/block_scout_web/views/address_view.ex:328
#: lib/block_scout_web/views/address_view.ex:335
#: lib/block_scout_web/views/tokens/overview_view.ex:37
msgid "Read Contract"
msgstr ""
@ -1864,7 +1865,7 @@ msgstr ""
#: lib/block_scout_web/templates/address/_tabs.html.eex:14
#: lib/block_scout_web/templates/address_token/index.html.eex:8
#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:11
#: lib/block_scout_web/views/address_view.ex:322
#: lib/block_scout_web/views/address_view.ex:329
msgid "Tokens"
msgstr ""
@ -1876,7 +1877,7 @@ msgstr ""
#: lib/block_scout_web/templates/block_transaction/index.html.eex:18
#: lib/block_scout_web/templates/chain/show.html.eex:184
#: lib/block_scout_web/templates/layout/_topnav.html.eex:50
#: lib/block_scout_web/views/address_view.ex:323
#: lib/block_scout_web/views/address_view.ex:330
msgid "Transactions"
msgstr ""
@ -1913,3 +1914,9 @@ msgstr ""
#: lib/block_scout_web/templates/transaction/overview.html.eex:58
msgid "Revert reason"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/_tabs.html.eex:69
#: lib/block_scout_web/views/address_view.ex:336
msgid "Read Proxy"
msgstr ""

@ -124,7 +124,7 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/_validator_metadata_modal.html.eex:16
#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:19
#: lib/block_scout_web/views/address_view.ex:100
#: lib/block_scout_web/views/address_view.ex:101
msgid "Address"
msgstr ""
@ -338,14 +338,14 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:18
#: lib/block_scout_web/views/address_view.ex:98
#: lib/block_scout_web/views/address_view.ex:99
msgid "Contract Address"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/transaction/_pending_tile.html.eex:16
#: lib/block_scout_web/views/address_view.ex:38
#: lib/block_scout_web/views/address_view.ex:72
#: lib/block_scout_web/views/address_view.ex:39
#: lib/block_scout_web/views/address_view.ex:73
msgid "Contract Address Pending"
msgstr ""
@ -903,7 +903,7 @@ msgstr ""
#: lib/block_scout_web/templates/tokens/transfer/index.html.eex:14
#: lib/block_scout_web/templates/transaction/_tabs.html.eex:4
#: lib/block_scout_web/templates/transaction_token_transfer/index.html.eex:7
#: lib/block_scout_web/views/address_view.ex:325
#: lib/block_scout_web/views/address_view.ex:332
#: lib/block_scout_web/views/tokens/instance/overview_view.ex:98
#: lib/block_scout_web/views/tokens/overview_view.ex:35
#: lib/block_scout_web/views/transaction_view.ex:394
@ -1011,6 +1011,7 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_read_contract/index.html.eex:14
#: lib/block_scout_web/templates/address_read_proxy/index.html.eex:14
#: lib/block_scout_web/templates/tokens/read_contract/index.html.eex:20
msgid "Loading..."
msgstr ""
@ -1041,7 +1042,7 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/chain/show.html.eex:52
#: lib/block_scout_web/templates/layout/app.html.eex:30
#: lib/block_scout_web/views/address_view.ex:138
#: lib/block_scout_web/views/address_view.ex:139
msgid "Market Cap"
msgstr ""
@ -1807,7 +1808,7 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/_tabs.html.eex:37
#: lib/block_scout_web/templates/address_validation/index.html.eex:13
#: lib/block_scout_web/views/address_view.ex:330
#: lib/block_scout_web/views/address_view.ex:338
msgid "Blocks Validated"
msgstr ""
@ -1817,18 +1818,18 @@ msgstr ""
#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:187
#: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:126
#: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:149
#: lib/block_scout_web/views/address_view.ex:326
#: lib/block_scout_web/views/address_view.ex:333
msgid "Code"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/_tabs.html.eex:26
#: lib/block_scout_web/views/address_view.ex:329
#: lib/block_scout_web/views/address_view.ex:337
msgid "Coin Balance History"
msgstr ""
#, elixir-format
#: lib/block_scout_web/views/address_view.ex:327
#: lib/block_scout_web/views/address_view.ex:334
msgid "Decompiled Code"
msgstr ""
@ -1837,7 +1838,7 @@ msgstr ""
#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:19
#: lib/block_scout_web/templates/transaction/_tabs.html.eex:11
#: lib/block_scout_web/templates/transaction_internal_transaction/index.html.eex:6
#: lib/block_scout_web/views/address_view.ex:324
#: lib/block_scout_web/views/address_view.ex:331
#: lib/block_scout_web/views/transaction_view.ex:395
msgid "Internal Transactions"
msgstr ""
@ -1847,7 +1848,7 @@ msgstr ""
#: lib/block_scout_web/templates/address_logs/index.html.eex:8
#: lib/block_scout_web/templates/transaction/_tabs.html.eex:17
#: lib/block_scout_web/templates/transaction_log/index.html.eex:8
#: lib/block_scout_web/views/address_view.ex:331
#: lib/block_scout_web/views/address_view.ex:339
#: lib/block_scout_web/views/transaction_view.ex:396
msgid "Logs"
msgstr ""
@ -1855,7 +1856,7 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/_tabs.html.eex:62
#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:25
#: lib/block_scout_web/views/address_view.ex:328
#: lib/block_scout_web/views/address_view.ex:335
#: lib/block_scout_web/views/tokens/overview_view.ex:37
msgid "Read Contract"
msgstr ""
@ -1864,7 +1865,7 @@ msgstr ""
#: lib/block_scout_web/templates/address/_tabs.html.eex:14
#: lib/block_scout_web/templates/address_token/index.html.eex:8
#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:11
#: lib/block_scout_web/views/address_view.ex:322
#: lib/block_scout_web/views/address_view.ex:329
msgid "Tokens"
msgstr ""
@ -1876,7 +1877,7 @@ msgstr ""
#: lib/block_scout_web/templates/block_transaction/index.html.eex:18
#: lib/block_scout_web/templates/chain/show.html.eex:184
#: lib/block_scout_web/templates/layout/_topnav.html.eex:50
#: lib/block_scout_web/views/address_view.ex:323
#: lib/block_scout_web/views/address_view.ex:330
msgid "Transactions"
msgstr ""
@ -1913,3 +1914,9 @@ msgstr ""
#: lib/block_scout_web/templates/transaction/overview.html.eex:58
msgid "Revert reason"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/_tabs.html.eex:69
#: lib/block_scout_web/views/address_view.ex:336
msgid "Read Proxy"
msgstr ""

@ -0,0 +1,81 @@
defmodule BlockScoutWeb.AddressReadProxyControllerTest do
use BlockScoutWeb.ConnCase, async: true
alias Explorer.ExchangeRates.Token
alias Explorer.Chain.Address
import Mox
describe "GET index/3" do
setup :set_mox_global
setup do
configuration = Application.get_env(:explorer, :checksum_function)
Application.put_env(:explorer, :checksum_function, :eth)
:ok
on_exit(fn ->
Application.put_env(:explorer, :checksum_function, configuration)
end)
end
test "with invalid address hash", %{conn: conn} do
conn = get(conn, address_read_contract_path(BlockScoutWeb.Endpoint, :index, "invalid_address"))
assert html_response(conn, 404)
end
test "with valid address that is not a contract", %{conn: conn} do
address = insert(:address)
conn = get(conn, address_read_contract_path(BlockScoutWeb.Endpoint, :index, Address.checksum(address.hash)))
assert html_response(conn, 404)
end
test "successfully renders the page when the address is a contract", %{conn: conn} do
contract_address = insert(:contract_address)
transaction = insert(:transaction, from_address: contract_address) |> with_block()
insert(
:internal_transaction_create,
index: 0,
transaction: transaction,
created_contract_address: contract_address,
block_hash: transaction.block_hash,
block_index: 0
)
insert(:smart_contract, address_hash: contract_address.hash)
conn =
get(conn, address_read_contract_path(BlockScoutWeb.Endpoint, :index, Address.checksum(contract_address.hash)))
assert html_response(conn, 200)
assert contract_address.hash == conn.assigns.address.hash
assert %Token{} = conn.assigns.exchange_rate
end
test "returns not found for an unverified contract", %{conn: conn} do
contract_address = insert(:contract_address)
transaction = insert(:transaction, from_address: contract_address) |> with_block()
insert(
:internal_transaction_create,
index: 0,
transaction: transaction,
created_contract_address: contract_address,
block_hash: transaction.block_hash,
block_index: 0
)
conn =
get(conn, address_read_contract_path(BlockScoutWeb.Endpoint, :index, Address.checksum(contract_address.hash)))
assert html_response(conn, 404)
end
end
end

@ -22,7 +22,7 @@ defmodule BlockScoutWeb.SmartContractControllerTest do
end
test "error for invalid address" do
path = smart_contract_path(BlockScoutWeb.Endpoint, :index, hash: "0x00")
path = smart_contract_path(BlockScoutWeb.Endpoint, :index, hash: "0x00", type: "regular")
conn =
build_conn()
@ -49,7 +49,7 @@ defmodule BlockScoutWeb.SmartContractControllerTest do
blockchain_get_function_mock()
path = smart_contract_path(BlockScoutWeb.Endpoint, :index, hash: token_contract_address.hash)
path = smart_contract_path(BlockScoutWeb.Endpoint, :index, hash: token_contract_address.hash, type: "regular")
conn =
build_conn()
@ -59,6 +59,22 @@ defmodule BlockScoutWeb.SmartContractControllerTest do
assert conn.status == 200
refute conn.assigns.read_only_functions == []
end
test "lists [] proxy read only functions if no verified implementation" do
token_contract_address = insert(:contract_address)
insert(:smart_contract, address_hash: token_contract_address.hash)
path = smart_contract_path(BlockScoutWeb.Endpoint, :index, hash: token_contract_address.hash, type: "proxy")
conn =
build_conn()
|> put_req_header("x-requested-with", "xmlhttprequest")
|> get(path)
assert conn.status == 200
assert conn.assigns.read_only_functions == []
end
end
describe "GET show/3" do

@ -0,0 +1,19 @@
defmodule BlockScoutWeb.AddressReadProxyViewTest do
use BlockScoutWeb.ConnCase, async: true
alias BlockScoutWeb.AddressReadProxyView
describe "queryable?/1" do
test "returns true if list of inputs is not empty" do
assert AddressReadProxyView.queryable?([%{"name" => "argument_name", "type" => "uint256"}]) == true
assert AddressReadProxyView.queryable?([]) == false
end
end
describe "address?/1" do
test "returns true if type equals `address`" do
assert AddressReadProxyView.address?("address") == true
assert AddressReadProxyView.address?("uint256") == false
end
end
end

@ -4338,53 +4338,96 @@ defmodule Explorer.Chain do
end
end
def combine_proxy_implementation_abi(address_hash, abi) when not is_nil(abi) do
def combine_proxy_implementation_abi(proxy_address_hash, abi) when not is_nil(abi) do
implementation_abi = get_implementation_abi_from_proxy(proxy_address_hash, abi)
if Enum.empty?(implementation_abi), do: abi, else: implementation_abi ++ abi
end
def combine_proxy_implementation_abi(_, abi) when is_nil(abi) do
[]
end
def is_proxy_contract?(abi) when not is_nil(abi) do
implementation_method_abi =
abi
|> Enum.find(fn method ->
Map.get(method, "name") == "implementation"
end)
implementation_abi =
if implementation_method_abi do
implementation_address =
case Reader.query_contract(address_hash, abi, %{
"implementation" => []
}) do
%{"implementation" => {:ok, [result]}} -> result
_ -> nil
end
if implementation_method_abi, do: true, else: false
end
if implementation_address do
implementation_address_hash_string = "0x" <> Base.encode16(implementation_address, case: :lower)
def is_proxy_contract?(abi) when is_nil(abi) do
false
end
case Chain.string_to_address_hash(implementation_address_hash_string) do
{:ok, implementation_address_hash} ->
implementation_smart_contract =
implementation_address_hash
|> Chain.address_hash_to_smart_contract()
def get_implementation_address_hash(proxy_address_hash, abi)
when not is_nil(proxy_address_hash) and not is_nil(abi) do
implementation_address =
case Reader.query_contract(proxy_address_hash, abi, %{
"implementation" => []
}) do
%{"implementation" => {:ok, [result]}} -> result
_ -> nil
end
if implementation_address do
"0x" <> Base.encode16(implementation_address, case: :lower)
else
nil
end
end
if implementation_smart_contract do
implementation_smart_contract
|> Map.get(:abi)
else
[]
end
def get_implementation_address_hash(proxy_address_hash, abi) when is_nil(proxy_address_hash) or is_nil(abi) do
nil
end
_ ->
[]
end
defp get_implementation_abi(implementation_address_hash_string) when not is_nil(implementation_address_hash_string) do
case Chain.string_to_address_hash(implementation_address_hash_string) do
{:ok, implementation_address_hash} ->
implementation_smart_contract =
implementation_address_hash
|> Chain.address_hash_to_smart_contract()
if implementation_smart_contract do
implementation_smart_contract
|> Map.get(:abi)
else
[]
end
_ ->
[]
end
end
defp get_implementation_abi(implementation_address_hash_string) when is_nil(implementation_address_hash_string) do
[]
end
def get_implementation_abi_from_proxy(proxy_address_hash, abi)
when not is_nil(proxy_address_hash) and not is_nil(abi) do
implementation_method_abi =
abi
|> Enum.find(fn method ->
Map.get(method, "name") == "implementation"
end)
if implementation_method_abi do
implementation_address_hash_string = get_implementation_address_hash(proxy_address_hash, abi)
if implementation_address_hash_string do
get_implementation_abi(implementation_address_hash_string)
else
[]
end
if Enum.empty?(implementation_abi), do: abi, else: implementation_abi ++ abi
else
[]
end
end
def combine_proxy_implementation_abi(_, abi) when is_nil(abi) do
def get_implementation_abi_from_proxy(proxy_address_hash, abi) when is_nil(proxy_address_hash) or is_nil(abi) do
[]
end

@ -180,6 +180,25 @@ defmodule Explorer.SmartContract.Reader do
end
end
def read_only_functions_proxy(contract_address_hash) do
abi =
contract_address_hash
|> Chain.address_hash_to_smart_contract()
|> Map.get(:abi)
implementation_abi = Chain.get_implementation_abi_from_proxy(contract_address_hash, abi)
case implementation_abi do
nil ->
[]
_ ->
implementation_abi
|> Enum.filter(&(&1["constant"] || &1["stateMutability"] == "view"))
|> Enum.map(&fetch_current_value_from_blockchain(&1, implementation_abi, contract_address_hash))
end
end
defp fetch_current_value_from_blockchain(function, abi, contract_address_hash) do
values =
case function do

@ -5194,7 +5194,7 @@ defmodule Explorer.ChainTest do
end
end
describe "combine_proxy_implementation_abi/2" do
describe "proxy contracts features" do
@proxy_abi [
%{
"type" => "function",
@ -5316,23 +5316,23 @@ defmodule Explorer.ChainTest do
}
]
test "returns empty [] abi if proxy abi is null" do
test "combine_proxy_implementation_abi/2 returns empty [] abi if proxy abi is null" do
proxy_contract_address = insert(:contract_address)
assert Chain.combine_proxy_implementation_abi(proxy_contract_address, nil) == []
end
test "returns [] abi for unverified proxy" do
test "combine_proxy_implementation_abi/2 returns [] abi for unverified proxy" do
proxy_contract_address = insert(:contract_address)
assert Chain.combine_proxy_implementation_abi(proxy_contract_address, []) == []
end
test "returns proxy abi if implementation is not verified" do
test "combine_proxy_implementation_abi/2 returns proxy abi if implementation is not verified" do
proxy_contract_address = insert(:contract_address)
insert(:smart_contract, address_hash: proxy_contract_address.hash, abi: @proxy_abi)
assert Chain.combine_proxy_implementation_abi(proxy_contract_address, @proxy_abi) == @proxy_abi
end
test "returns proxy + implementation abi if implementation is verified" do
test "combine_proxy_implementation_abi/2 returns proxy + implementation abi if implementation is verified" do
proxy_contract_address = insert(:contract_address)
insert(:smart_contract, address_hash: proxy_contract_address.hash, abi: @proxy_abi)
@ -5364,5 +5364,51 @@ defmodule Explorer.ChainTest do
assert Enum.any?(combined_abi, fn el -> el == Enum.at(@implementation_abi, 0) end) == true
assert Enum.any?(combined_abi, fn el -> el == Enum.at(@implementation_abi, 1) end) == true
end
test "get_implementation_abi_from_proxy/2 returns empty [] abi if proxy abi is null" do
proxy_contract_address = insert(:contract_address)
assert Chain.get_implementation_abi_from_proxy(proxy_contract_address, nil) == []
end
test "get_implementation_abi_from_proxy/2 returns [] abi for unverified proxy" do
proxy_contract_address = insert(:contract_address)
assert Chain.combine_proxy_implementation_abi(proxy_contract_address, []) == []
end
test "get_implementation_abi_from_proxy/2 returns [] if implementation is not verified" do
proxy_contract_address = insert(:contract_address)
insert(:smart_contract, address_hash: proxy_contract_address.hash, abi: @proxy_abi)
assert Chain.get_implementation_abi_from_proxy(proxy_contract_address, @proxy_abi) == []
end
test "get_implementation_abi_from_proxy/2 returns implementation abi if implementation is verified" do
proxy_contract_address = insert(:contract_address)
insert(:smart_contract, address_hash: proxy_contract_address.hash, abi: @proxy_abi)
implementation_contract_address = insert(:contract_address)
insert(:smart_contract, address_hash: implementation_contract_address.hash, abi: @implementation_abi)
implementation_contract_address_hash_string =
Base.encode16(implementation_contract_address.hash.bytes, case: :lower)
expect(
EthereumJSONRPC.Mox,
:json_rpc,
fn [%{id: id, method: _, params: [%{data: _, to: _}, _]}], _options ->
{:ok,
[
%{
id: id,
jsonrpc: "2.0",
result: "0x000000000000000000000000" <> implementation_contract_address_hash_string
}
]}
end
)
implementation_abi = Chain.get_implementation_abi_from_proxy(proxy_contract_address.hash, @proxy_abi)
assert implementation_abi == @implementation_abi
end
end
end

@ -169,6 +169,99 @@ defmodule Explorer.SmartContract.ReaderTest do
end
end
describe "read_only_functions_proxy/1" do
test "fetches the smart contract proxy read only functions with the blockchain value" do
proxy_smart_contract =
insert(:smart_contract,
abi: [
%{
"type" => "function",
"stateMutability" => "view",
"payable" => false,
"outputs" => [
%{
"type" => "address",
"name" => ""
}
],
"name" => "implementation",
"inputs" => [],
"constant" => true
}
]
)
implementation_contract_address = insert(:contract_address)
insert(:smart_contract,
address_hash: implementation_contract_address.hash,
abi: [
%{
"constant" => true,
"inputs" => [],
"name" => "get",
"outputs" => [%{"name" => "", "type" => "uint256"}],
"payable" => false,
"stateMutability" => "view",
"type" => "function"
},
%{
"constant" => true,
"inputs" => [%{"name" => "x", "type" => "uint256"}],
"name" => "with_arguments",
"outputs" => [%{"name" => "", "type" => "bool"}],
"payable" => false,
"stateMutability" => "view",
"type" => "function"
}
]
)
implementation_contract_address_hash_string =
Base.encode16(implementation_contract_address.hash.bytes, case: :lower)
expect(
EthereumJSONRPC.Mox,
:json_rpc,
fn [%{id: id, method: _, params: [%{data: _, to: _}, _]}], _options ->
{:ok,
[
%{
id: id,
jsonrpc: "2.0",
result: "0x000000000000000000000000" <> implementation_contract_address_hash_string
}
]}
end
)
blockchain_get_function_mock()
response = Reader.read_only_functions_proxy(proxy_smart_contract.address_hash)
assert [
%{
"constant" => true,
"inputs" => [],
"name" => "get",
"outputs" => [%{"name" => "", "type" => "uint256", "value" => 0}],
"payable" => _,
"stateMutability" => _,
"type" => _
},
%{
"constant" => true,
"inputs" => [%{"name" => "x", "type" => "uint256"}],
"name" => "with_arguments",
"outputs" => [%{"name" => "", "type" => "bool", "value" => ""}],
"payable" => _,
"stateMutability" => _,
"type" => _
}
] = response
end
end
describe "query_function/2" do
test "given the arguments, fetches the function value from the blockchain" do
smart_contract = insert(:smart_contract)

Loading…
Cancel
Save