From 708a6b00362689aab50db8f0cd996c2de66013ed Mon Sep 17 00:00:00 2001 From: nikitosing <32202610+nikitosing@users.noreply.github.com> Date: Mon, 28 Nov 2022 19:08:50 +0300 Subject: [PATCH] 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 * Refactor --- CHANGELOG.md | 1 + .../controllers/smart_contract_controller.ex | 5 +- .../templates/address/overview.html.eex | 35 +- .../lib/block_scout_web/views/address_view.ex | 4 +- .../views/tokens/overview_view.ex | 4 +- apps/block_scout_web/priv/gettext/default.pot | 98 ++--- .../priv/gettext/en/LC_MESSAGES/default.po | 98 ++--- .../lib/ethereum_jsonrpc/http.ex | 14 +- .../ethereum_jsonrpc/request_coordinator.ex | 2 +- apps/explorer/config/test.exs | 6 +- apps/explorer/lib/explorer/chain.ex | 264 ++---------- apps/explorer/lib/explorer/chain/log.ex | 4 +- .../lib/explorer/chain/smart_contract.ex | 384 +++++++++++++++++- .../lib/explorer/chain/transaction.ex | 13 +- .../lib/explorer/etherscan/contracts.ex | 6 +- .../lib/explorer/smart_contract/reader.ex | 9 +- ...220527131249_add_implementation_fields.exs | 10 + .../explorer/chain/smart_contract_test.exs | 237 +++++++++++ apps/explorer/test/explorer/chain_test.exs | 50 ++- config/runtime.exs | 6 +- 20 files changed, 850 insertions(+), 400 deletions(-) create mode 100644 apps/explorer/priv/repo/migrations/20220527131249_add_implementation_fields.exs create mode 100644 apps/explorer/test/explorer/chain/smart_contract_test.exs diff --git a/CHANGELOG.md b/CHANGELOG.md index 8b864ff6b0..501d655a93 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ### Features +- [#5561](https://github.com/blockscout/blockscout/pull/5561) - 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 diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/smart_contract_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/smart_contract_controller.ex index ab3819c4c8..7d9d0964a9 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/smart_contract_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/smart_contract_controller.ex @@ -3,6 +3,7 @@ defmodule BlockScoutWeb.SmartContractController do alias BlockScoutWeb.AddressView alias Explorer.Chain + alias Explorer.Chain.SmartContract alias Explorer.SmartContract.{Reader, Writer} import Explorer.SmartContract.Solidity.Verifier, only: [parse_boolean: 1] @@ -24,8 +25,8 @@ defmodule BlockScoutWeb.SmartContractController do {:ok, address} <- Chain.find_contract_address(address_hash, address_options, true) do implementation_address_hash_string = if contract_type == "proxy" do - address.hash - |> Chain.get_implementation_address_hash(address.smart_contract.abi) + address.smart_contract + |> SmartContract.get_implementation_address_hash() |> Tuple.to_list() |> List.first() || @burn_address else diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address/overview.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address/overview.html.eex index 34059b2ed9..9a8abc9dd7 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/address/overview.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/address/overview.html.eex @@ -124,24 +124,23 @@ <% end %> <%= if @is_proxy do %> - <% {implementation_address, name} = Chain.get_implementation_address_hash(@address.hash, @address.smart_contract.abi) || "0x0000000000000000000000000000000000000000" %> - <%= if implementation_address do %> -
-
- <%= render BlockScoutWeb.CommonComponentsView, "_i_tooltip_2.html", - text: gettext("Implementation address of the proxy contract.") %> - <%= gettext("Implementation") %> -
-
- <%= link( - (if name, do: name <> " | " <> implementation_address, else: implementation_address), - to: address_path(@conn, :show, implementation_address), - class: "contract-address" - ) - %> -
-
- <% end %> + <% {implementation_address_, name} = SmartContract.get_implementation_address_hash(@address.smart_contract) %> + <% implementation_address = implementation_address_ || "0x0000000000000000000000000000000000000000" %> +
+
+ <%= render BlockScoutWeb.CommonComponentsView, "_i_tooltip_2.html", + text: gettext("Implementation address of the proxy contract.") %> + <%= gettext("Implementation") %> +
+
+ <%= link( + (if name, do: name <> " | " <> implementation_address, else: implementation_address), + to: address_path(@conn, :show, implementation_address), + class: "contract-address" + ) + %> +
+
<% end %>
diff --git a/apps/block_scout_web/lib/block_scout_web/views/address_view.ex b/apps/block_scout_web/lib/block_scout_web/views/address_view.ex index b1d1849598..ac75f3e0dd 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/address_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/address_view.ex @@ -261,8 +261,8 @@ defmodule BlockScoutWeb.AddressView do def is_read_function?(function), do: Helper.queriable_method?(function) || Helper.read_with_wallet_method?(function) - def smart_contract_is_proxy?(%Address{smart_contract: %SmartContract{}} = address) do - Chain.proxy_contract?(address.hash, address.smart_contract.abi) + def smart_contract_is_proxy?(%Address{smart_contract: %SmartContract{} = smart_contract}) do + SmartContract.proxy_contract?(smart_contract) end def smart_contract_is_proxy?(%Address{smart_contract: nil}), do: false diff --git a/apps/block_scout_web/lib/block_scout_web/views/tokens/overview_view.ex b/apps/block_scout_web/lib/block_scout_web/views/tokens/overview_view.ex index 473c2df3e2..4fc1c6d28e 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/tokens/overview_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/tokens/overview_view.ex @@ -53,8 +53,8 @@ defmodule BlockScoutWeb.Tokens.OverviewView do def smart_contract_with_read_only_functions?(%Token{contract_address: %Address{smart_contract: nil}}), do: false - def smart_contract_is_proxy?(%Token{contract_address: %Address{smart_contract: %SmartContract{}} = address}) do - Chain.proxy_contract?(address.hash, address.smart_contract.abi) + def smart_contract_is_proxy?(%Token{contract_address: %Address{smart_contract: %SmartContract{} = smart_contract}}) do + SmartContract.proxy_contract?(smart_contract) end def smart_contract_is_proxy?(%Token{contract_address: %Address{smart_contract: nil}}), do: false diff --git a/apps/block_scout_web/priv/gettext/default.pot b/apps/block_scout_web/priv/gettext/default.pot index b1258df630..153f4521d2 100644 --- a/apps/block_scout_web/priv/gettext/default.pot +++ b/apps/block_scout_web/priv/gettext/default.pot @@ -256,7 +256,7 @@ msgstr "" msgid "Address Tags" msgstr "" -#: lib/block_scout_web/templates/address/overview.html.eex:150 +#: lib/block_scout_web/templates/address/overview.html.eex:149 #, elixir-autogen, elixir-format msgid "Address balance in" msgstr "" @@ -304,7 +304,7 @@ msgstr "" msgid "All metadata displayed below is from that contract. In order to verify current contract, click" msgstr "" -#: lib/block_scout_web/templates/address/overview.html.eex:175 +#: lib/block_scout_web/templates/address/overview.html.eex:174 #, elixir-autogen, elixir-format msgid "All tokens in the account and total value." msgstr "" @@ -371,7 +371,7 @@ msgid "Back to Watch list (Cancel)" msgstr "" #: lib/block_scout_web/templates/account/watchlist/show.html.eex:24 -#: lib/block_scout_web/templates/address/overview.html.eex:151 +#: lib/block_scout_web/templates/address/overview.html.eex:150 #: lib/block_scout_web/templates/address_token/overview.html.eex:51 #: lib/block_scout_web/templates/verified_contracts/index.html.eex:57 #, elixir-autogen, elixir-format @@ -463,7 +463,7 @@ msgstr "" msgid "Block number containing the transaction." msgstr "" -#: lib/block_scout_web/templates/address/overview.html.eex:258 +#: lib/block_scout_web/templates/address/overview.html.eex:257 #, elixir-autogen, elixir-format msgid "Block number in which the address was updated." msgstr "" @@ -491,7 +491,7 @@ msgid "Blocks Indexed" msgstr "" #: lib/block_scout_web/templates/address/_tabs.html.eex:48 -#: lib/block_scout_web/templates/address/overview.html.eex:276 +#: lib/block_scout_web/templates/address/overview.html.eex:275 #: lib/block_scout_web/templates/address_validation/index.html.eex:11 #: lib/block_scout_web/views/address_view.ex:381 #, elixir-autogen, elixir-format @@ -620,6 +620,11 @@ msgstr "" msgid "Compiler" msgstr "" +#: lib/block_scout_web/templates/address_contract/index.html.eex:137 +#, elixir-autogen, elixir-format +msgid "Compiler Settings" +msgstr "" + #: lib/block_scout_web/templates/address_contract/index.html.eex:66 #, elixir-autogen, elixir-format msgid "Compiler version" @@ -805,6 +810,11 @@ msgstr "" msgid "Copy Address" msgstr "" +#: lib/block_scout_web/templates/address_contract/index.html.eex:139 +#, elixir-autogen, elixir-format +msgid "Copy Compiler Settings" +msgstr "" + #: lib/block_scout_web/templates/account/custom_abi/row.html.eex:6 #: lib/block_scout_web/templates/account/custom_abi/row.html.eex:6 #, elixir-autogen, elixir-format @@ -1281,7 +1291,7 @@ msgstr "" msgid "Fast" msgstr "" -#: lib/block_scout_web/templates/address/overview.html.eex:248 +#: lib/block_scout_web/templates/address/overview.html.eex:247 #, elixir-autogen, elixir-format msgid "Fetching gas used..." msgstr "" @@ -1296,14 +1306,14 @@ msgstr "" msgid "Fetching tokens..." msgstr "" -#: lib/block_scout_web/templates/address/overview.html.eex:195 -#: lib/block_scout_web/templates/address/overview.html.eex:203 +#: lib/block_scout_web/templates/address/overview.html.eex:194 +#: lib/block_scout_web/templates/address/overview.html.eex:202 #, elixir-autogen, elixir-format msgid "Fetching transactions..." msgstr "" -#: lib/block_scout_web/templates/address/overview.html.eex:222 -#: lib/block_scout_web/templates/address/overview.html.eex:230 +#: lib/block_scout_web/templates/address/overview.html.eex:221 +#: lib/block_scout_web/templates/address/overview.html.eex:229 #: lib/block_scout_web/templates/tokens/overview/_details.html.eex:123 #, elixir-autogen, elixir-format msgid "Fetching transfers..." @@ -1362,7 +1372,7 @@ msgstr "" msgid "Gas Price" msgstr "" -#: lib/block_scout_web/templates/address/overview.html.eex:241 +#: lib/block_scout_web/templates/address/overview.html.eex:240 #: lib/block_scout_web/templates/block/_tile.html.eex:73 #: lib/block_scout_web/templates/block/overview.html.eex:178 #, elixir-autogen, elixir-format @@ -1380,7 +1390,7 @@ msgstr "" msgid "Gas tracker" msgstr "" -#: lib/block_scout_web/templates/address/overview.html.eex:240 +#: lib/block_scout_web/templates/address/overview.html.eex:239 #, elixir-autogen, elixir-format msgid "Gas used by the address." msgstr "" @@ -1474,6 +1484,11 @@ msgstr "" msgid "Implementation address of the proxy contract." msgstr "" +#: lib/block_scout_web/templates/address_contract_verification_common_fields/_include_nightly_builds_field.html.eex:3 +#, elixir-autogen, elixir-format +msgid "Include nightly builds" +msgstr "" + #: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:30 #: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:43 #: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:56 @@ -1549,7 +1564,7 @@ msgstr "" msgid "Last 24h" msgstr "" -#: lib/block_scout_web/templates/address/overview.html.eex:259 +#: lib/block_scout_web/templates/address/overview.html.eex:258 #, elixir-autogen, elixir-format msgid "Last Balance Update" msgstr "" @@ -1874,7 +1889,7 @@ msgstr "" msgid "Number of accounts holding the token" msgstr "" -#: lib/block_scout_web/templates/address/overview.html.eex:275 +#: lib/block_scout_web/templates/address/overview.html.eex:274 #, elixir-autogen, elixir-format msgid "Number of blocks validated by this validator." msgstr "" @@ -1884,7 +1899,7 @@ msgstr "" msgid "Number of digits that come after the decimal place when displaying token value" msgstr "" -#: lib/block_scout_web/templates/address/overview.html.eex:186 +#: lib/block_scout_web/templates/address/overview.html.eex:185 #, elixir-autogen, elixir-format msgid "Number of transactions related to this address." msgstr "" @@ -1894,7 +1909,7 @@ msgstr "" msgid "Number of transfers for the token" msgstr "" -#: lib/block_scout_web/templates/address/overview.html.eex:213 +#: lib/block_scout_web/templates/address/overview.html.eex:212 #, elixir-autogen, elixir-format msgid "Number of transfers to/from this address." msgstr "" @@ -2240,6 +2255,11 @@ msgstr "" msgid "Select Yes if you want to vefify Yul contract." msgstr "" +#: lib/block_scout_web/templates/address_contract_verification_common_fields/_include_nightly_builds_field.html.eex:19 +#, elixir-autogen, elixir-format +msgid "Select yes if you want to show nightly builds." +msgstr "" + #: lib/block_scout_web/views/internal_transaction_view.ex:27 #, elixir-autogen, elixir-format msgid "Self-Destruct" @@ -2776,7 +2796,7 @@ msgid "Token type" msgstr "" #: lib/block_scout_web/templates/address/_tabs.html.eex:21 -#: lib/block_scout_web/templates/address/overview.html.eex:176 +#: lib/block_scout_web/templates/address/overview.html.eex:175 #: lib/block_scout_web/templates/address_token/overview.html.eex:58 #: lib/block_scout_web/templates/address_token_transfer/index.html.eex:13 #: lib/block_scout_web/templates/layout/_topnav.html.eex:78 @@ -2935,9 +2955,9 @@ msgid "Transaction type, introduced in EIP-2718." msgstr "" #: lib/block_scout_web/templates/address/_tabs.html.eex:7 -#: lib/block_scout_web/templates/address/overview.html.eex:187 -#: lib/block_scout_web/templates/address/overview.html.eex:193 -#: lib/block_scout_web/templates/address/overview.html.eex:201 +#: lib/block_scout_web/templates/address/overview.html.eex:186 +#: lib/block_scout_web/templates/address/overview.html.eex:192 +#: lib/block_scout_web/templates/address/overview.html.eex:200 #: lib/block_scout_web/templates/address_transaction/index.html.eex:13 #: lib/block_scout_web/templates/block/overview.html.eex:80 #: lib/block_scout_web/templates/block_transaction/index.html.eex:10 @@ -2958,9 +2978,9 @@ msgstr "" msgid "Transactions sent" msgstr "" -#: lib/block_scout_web/templates/address/overview.html.eex:214 -#: lib/block_scout_web/templates/address/overview.html.eex:220 -#: lib/block_scout_web/templates/address/overview.html.eex:228 +#: lib/block_scout_web/templates/address/overview.html.eex:213 +#: lib/block_scout_web/templates/address/overview.html.eex:219 +#: lib/block_scout_web/templates/address/overview.html.eex:227 #: lib/block_scout_web/templates/tokens/instance/overview/_details.html.eex:50 #: lib/block_scout_web/templates/tokens/overview/_details.html.eex:119 #, elixir-autogen, elixir-format @@ -2973,6 +2993,11 @@ msgstr "" msgid "Try it out" msgstr "" +#: lib/block_scout_web/templates/address_contract_verification_common_fields/_fetch_constructor_args.html.eex:3 +#, elixir-autogen, elixir-format +msgid "Try to fetch constructor arguments automatically" +msgstr "" + #: lib/block_scout_web/templates/layout/_footer.html.eex:27 #, elixir-autogen, elixir-format msgid "Twitter" @@ -3383,7 +3408,7 @@ msgstr "" msgid "custom RPC" msgstr "" -#: lib/block_scout_web/templates/address/overview.html.eex:150 +#: lib/block_scout_web/templates/address/overview.html.eex:149 #, elixir-autogen, elixir-format msgid "doesn't include ERC20, ERC721, ERC1155 tokens)." msgstr "" @@ -3463,28 +3488,3 @@ msgstr "" #, elixir-autogen, elixir-format msgid "truffle flattener" msgstr "" - -#: lib/block_scout_web/templates/address_contract_verification_common_fields/_include_nightly_builds_field.html.eex:3 -#, elixir-autogen, elixir-format -msgid "Include nightly builds" -msgstr "" - -#: lib/block_scout_web/templates/address_contract_verification_common_fields/_include_nightly_builds_field.html.eex:19 -#, elixir-autogen, elixir-format -msgid "Select yes if you want to show nightly builds." -msgstr "" - -#: lib/block_scout_web/templates/address_contract_verification_common_fields/_fetch_constructor_args.html.eex:3 -#, elixir-autogen, elixir-format -msgid "Try to fetch constructor arguments automatically" -msgstr "" - -#: lib/block_scout_web/templates/address_contract/index.html.eex:137 -#, elixir-autogen, elixir-format -msgid "Compiler Settings" -msgstr "" - -#: lib/block_scout_web/templates/address_contract/index.html.eex:139 -#, elixir-autogen, elixir-format -msgid "Copy Compiler Settings" -msgstr "" diff --git a/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po b/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po index b5d53aba90..153f4521d2 100644 --- a/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po +++ b/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po @@ -256,7 +256,7 @@ msgstr "" msgid "Address Tags" msgstr "" -#: lib/block_scout_web/templates/address/overview.html.eex:150 +#: lib/block_scout_web/templates/address/overview.html.eex:149 #, elixir-autogen, elixir-format msgid "Address balance in" msgstr "" @@ -304,7 +304,7 @@ msgstr "" msgid "All metadata displayed below is from that contract. In order to verify current contract, click" msgstr "" -#: lib/block_scout_web/templates/address/overview.html.eex:175 +#: lib/block_scout_web/templates/address/overview.html.eex:174 #, elixir-autogen, elixir-format msgid "All tokens in the account and total value." msgstr "" @@ -371,7 +371,7 @@ msgid "Back to Watch list (Cancel)" msgstr "" #: lib/block_scout_web/templates/account/watchlist/show.html.eex:24 -#: lib/block_scout_web/templates/address/overview.html.eex:151 +#: lib/block_scout_web/templates/address/overview.html.eex:150 #: lib/block_scout_web/templates/address_token/overview.html.eex:51 #: lib/block_scout_web/templates/verified_contracts/index.html.eex:57 #, elixir-autogen, elixir-format @@ -463,7 +463,7 @@ msgstr "" msgid "Block number containing the transaction." msgstr "" -#: lib/block_scout_web/templates/address/overview.html.eex:258 +#: lib/block_scout_web/templates/address/overview.html.eex:257 #, elixir-autogen, elixir-format msgid "Block number in which the address was updated." msgstr "" @@ -491,7 +491,7 @@ msgid "Blocks Indexed" msgstr "" #: lib/block_scout_web/templates/address/_tabs.html.eex:48 -#: lib/block_scout_web/templates/address/overview.html.eex:276 +#: lib/block_scout_web/templates/address/overview.html.eex:275 #: lib/block_scout_web/templates/address_validation/index.html.eex:11 #: lib/block_scout_web/views/address_view.ex:381 #, elixir-autogen, elixir-format @@ -620,6 +620,11 @@ msgstr "" msgid "Compiler" msgstr "" +#: lib/block_scout_web/templates/address_contract/index.html.eex:137 +#, elixir-autogen, elixir-format +msgid "Compiler Settings" +msgstr "" + #: lib/block_scout_web/templates/address_contract/index.html.eex:66 #, elixir-autogen, elixir-format msgid "Compiler version" @@ -805,6 +810,11 @@ msgstr "" msgid "Copy Address" msgstr "" +#: lib/block_scout_web/templates/address_contract/index.html.eex:139 +#, elixir-autogen, elixir-format +msgid "Copy Compiler Settings" +msgstr "" + #: lib/block_scout_web/templates/account/custom_abi/row.html.eex:6 #: lib/block_scout_web/templates/account/custom_abi/row.html.eex:6 #, elixir-autogen, elixir-format @@ -1281,7 +1291,7 @@ msgstr "" msgid "Fast" msgstr "" -#: lib/block_scout_web/templates/address/overview.html.eex:248 +#: lib/block_scout_web/templates/address/overview.html.eex:247 #, elixir-autogen, elixir-format msgid "Fetching gas used..." msgstr "" @@ -1296,14 +1306,14 @@ msgstr "" msgid "Fetching tokens..." msgstr "" -#: lib/block_scout_web/templates/address/overview.html.eex:195 -#: lib/block_scout_web/templates/address/overview.html.eex:203 +#: lib/block_scout_web/templates/address/overview.html.eex:194 +#: lib/block_scout_web/templates/address/overview.html.eex:202 #, elixir-autogen, elixir-format msgid "Fetching transactions..." msgstr "" -#: lib/block_scout_web/templates/address/overview.html.eex:222 -#: lib/block_scout_web/templates/address/overview.html.eex:230 +#: lib/block_scout_web/templates/address/overview.html.eex:221 +#: lib/block_scout_web/templates/address/overview.html.eex:229 #: lib/block_scout_web/templates/tokens/overview/_details.html.eex:123 #, elixir-autogen, elixir-format msgid "Fetching transfers..." @@ -1362,7 +1372,7 @@ msgstr "" msgid "Gas Price" msgstr "" -#: lib/block_scout_web/templates/address/overview.html.eex:241 +#: lib/block_scout_web/templates/address/overview.html.eex:240 #: lib/block_scout_web/templates/block/_tile.html.eex:73 #: lib/block_scout_web/templates/block/overview.html.eex:178 #, elixir-autogen, elixir-format @@ -1380,7 +1390,7 @@ msgstr "" msgid "Gas tracker" msgstr "" -#: lib/block_scout_web/templates/address/overview.html.eex:240 +#: lib/block_scout_web/templates/address/overview.html.eex:239 #, elixir-autogen, elixir-format msgid "Gas used by the address." msgstr "" @@ -1474,6 +1484,11 @@ msgstr "" msgid "Implementation address of the proxy contract." msgstr "" +#: lib/block_scout_web/templates/address_contract_verification_common_fields/_include_nightly_builds_field.html.eex:3 +#, elixir-autogen, elixir-format +msgid "Include nightly builds" +msgstr "" + #: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:30 #: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:43 #: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:56 @@ -1549,7 +1564,7 @@ msgstr "" msgid "Last 24h" msgstr "" -#: lib/block_scout_web/templates/address/overview.html.eex:259 +#: lib/block_scout_web/templates/address/overview.html.eex:258 #, elixir-autogen, elixir-format msgid "Last Balance Update" msgstr "" @@ -1874,7 +1889,7 @@ msgstr "" msgid "Number of accounts holding the token" msgstr "" -#: lib/block_scout_web/templates/address/overview.html.eex:275 +#: lib/block_scout_web/templates/address/overview.html.eex:274 #, elixir-autogen, elixir-format msgid "Number of blocks validated by this validator." msgstr "" @@ -1884,7 +1899,7 @@ msgstr "" msgid "Number of digits that come after the decimal place when displaying token value" msgstr "" -#: lib/block_scout_web/templates/address/overview.html.eex:186 +#: lib/block_scout_web/templates/address/overview.html.eex:185 #, elixir-autogen, elixir-format msgid "Number of transactions related to this address." msgstr "" @@ -1894,7 +1909,7 @@ msgstr "" msgid "Number of transfers for the token" msgstr "" -#: lib/block_scout_web/templates/address/overview.html.eex:213 +#: lib/block_scout_web/templates/address/overview.html.eex:212 #, elixir-autogen, elixir-format msgid "Number of transfers to/from this address." msgstr "" @@ -2240,6 +2255,11 @@ msgstr "" msgid "Select Yes if you want to vefify Yul contract." msgstr "" +#: lib/block_scout_web/templates/address_contract_verification_common_fields/_include_nightly_builds_field.html.eex:19 +#, elixir-autogen, elixir-format +msgid "Select yes if you want to show nightly builds." +msgstr "" + #: lib/block_scout_web/views/internal_transaction_view.ex:27 #, elixir-autogen, elixir-format msgid "Self-Destruct" @@ -2776,7 +2796,7 @@ msgid "Token type" msgstr "" #: lib/block_scout_web/templates/address/_tabs.html.eex:21 -#: lib/block_scout_web/templates/address/overview.html.eex:176 +#: lib/block_scout_web/templates/address/overview.html.eex:175 #: lib/block_scout_web/templates/address_token/overview.html.eex:58 #: lib/block_scout_web/templates/address_token_transfer/index.html.eex:13 #: lib/block_scout_web/templates/layout/_topnav.html.eex:78 @@ -2935,9 +2955,9 @@ msgid "Transaction type, introduced in EIP-2718." msgstr "" #: lib/block_scout_web/templates/address/_tabs.html.eex:7 -#: lib/block_scout_web/templates/address/overview.html.eex:187 -#: lib/block_scout_web/templates/address/overview.html.eex:193 -#: lib/block_scout_web/templates/address/overview.html.eex:201 +#: lib/block_scout_web/templates/address/overview.html.eex:186 +#: lib/block_scout_web/templates/address/overview.html.eex:192 +#: lib/block_scout_web/templates/address/overview.html.eex:200 #: lib/block_scout_web/templates/address_transaction/index.html.eex:13 #: lib/block_scout_web/templates/block/overview.html.eex:80 #: lib/block_scout_web/templates/block_transaction/index.html.eex:10 @@ -2958,9 +2978,9 @@ msgstr "" msgid "Transactions sent" msgstr "" -#: lib/block_scout_web/templates/address/overview.html.eex:214 -#: lib/block_scout_web/templates/address/overview.html.eex:220 -#: lib/block_scout_web/templates/address/overview.html.eex:228 +#: lib/block_scout_web/templates/address/overview.html.eex:213 +#: lib/block_scout_web/templates/address/overview.html.eex:219 +#: lib/block_scout_web/templates/address/overview.html.eex:227 #: lib/block_scout_web/templates/tokens/instance/overview/_details.html.eex:50 #: lib/block_scout_web/templates/tokens/overview/_details.html.eex:119 #, elixir-autogen, elixir-format @@ -2973,6 +2993,11 @@ msgstr "" msgid "Try it out" msgstr "" +#: lib/block_scout_web/templates/address_contract_verification_common_fields/_fetch_constructor_args.html.eex:3 +#, elixir-autogen, elixir-format +msgid "Try to fetch constructor arguments automatically" +msgstr "" + #: lib/block_scout_web/templates/layout/_footer.html.eex:27 #, elixir-autogen, elixir-format msgid "Twitter" @@ -3383,7 +3408,7 @@ msgstr "" msgid "custom RPC" msgstr "" -#: lib/block_scout_web/templates/address/overview.html.eex:150 +#: lib/block_scout_web/templates/address/overview.html.eex:149 #, elixir-autogen, elixir-format msgid "doesn't include ERC20, ERC721, ERC1155 tokens)." msgstr "" @@ -3463,28 +3488,3 @@ msgstr "" #, elixir-autogen, elixir-format msgid "truffle flattener" msgstr "" - -#: lib/block_scout_web/templates/address_contract_verification_common_fields/_include_nightly_builds_field.html.eex:3 -#, elixir-autogen, elixir-format -msgid "Include nightly builds" -msgstr "" - -#: lib/block_scout_web/templates/address_contract_verification_common_fields/_include_nightly_builds_field.html.eex:19 -#, elixir-autogen, elixir-format -msgid "Select yes if you want to show nightly builds." -msgstr "" - -#: lib/block_scout_web/templates/address_contract_verification_common_fields/_fetch_constructor_args.html.eex:3 -#, elixir-autogen, elixir-format -msgid "Try to fetch constructor arguments automatically" -msgstr "" - -#: lib/block_scout_web/templates/address_contract/index.html.eex:137 -#, elixir-autogen, elixir-format, fuzzy -msgid "Compiler Settings" -msgstr "" - -#: lib/block_scout_web/templates/address_contract/index.html.eex:139 -#, elixir-autogen, elixir-format -msgid "Copy Compiler Settings" -msgstr "" diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/http.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/http.ex index b8222df2d1..dc66006f9e 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/http.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/http.ex @@ -3,7 +3,7 @@ defmodule EthereumJSONRPC.HTTP do JSONRPC over HTTP """ - alias EthereumJSONRPC.Transport + alias EthereumJSONRPC.{DecodeError, Transport} require Logger @@ -114,7 +114,17 @@ defmodule EthereumJSONRPC.HTTP do {:error, {:bad_gateway, request_url}} _ -> - raise EthereumJSONRPC.DecodeError, named_arguments + named_arguments + |> DecodeError.exception() + |> DecodeError.message() + |> Logger.error() + + request_url = + named_arguments + |> Keyword.fetch!(:request) + |> Keyword.fetch!(:url) + + {:error, {:bad_response, request_url}} end end end diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/request_coordinator.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/request_coordinator.ex index bd491f25fd..b63e0971a6 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/request_coordinator.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/request_coordinator.ex @@ -109,7 +109,7 @@ defmodule EthereumJSONRPC.RequestCoordinator do defp trace_request(_, fun), do: fun.() - defp handle_transport_response({:error, {:bad_gateway, _}} = error) do + defp handle_transport_response({:error, {error_type, _}} = error) when error_type in [:bad_gateway, :bad_response] do RollingWindow.inc(table(), @error_key) inc_throttle_table() error diff --git a/apps/explorer/config/test.exs b/apps/explorer/config/test.exs index e9174824ce..cf55e854ca 100644 --- a/apps/explorer/config/test.exs +++ b/apps/explorer/config/test.exs @@ -22,7 +22,11 @@ config :explorer, Explorer.Repo.Replica1, # Default of `5_000` was too low for `BlockFetcher` test ownership_timeout: :timer.minutes(1), timeout: :timer.seconds(60), - queue_target: 1000 + queue_target: 1000, + enable_caching_implementation_data_of_proxy: true, + avg_block_time_as_ttl_cached_implementation_data_of_proxy: false, + fallback_ttl_cached_implementation_data_of_proxy: :timer.seconds(20), + implementation_data_fetching_timeout: :timer.seconds(20) # Configure API database config :explorer, Explorer.Repo.Account, diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index ef481b7232..1d83d1b44e 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -21,7 +21,6 @@ defmodule Explorer.Chain do select: 3, subquery: 1, union: 2, - update: 2, where: 2, where: 3 ] @@ -33,7 +32,6 @@ defmodule Explorer.Chain do alias ABI.TypeDecoder alias Ecto.{Changeset, Multi} - alias EthereumJSONRPC.Contract alias EthereumJSONRPC.Transaction, as: EthereumJSONRPCTransaction alias Explorer.Counters.{LastFetchedCounter, TokenHoldersCounter, TokenTransfersCounter} @@ -91,7 +89,7 @@ defmodule Explorer.Chain do alias Explorer.Market.MarketHistoryCache alias Explorer.{PagingOptions, Repo} - alias Explorer.SmartContract.{Helper, Reader} + alias Explorer.SmartContract.Helper alias Dataloader.Ecto, as: DataloaderEcto @@ -4334,13 +4332,28 @@ defmodule Explorer.Chain do Chain.get_address_verified_twin_contract(address_hash).verified_contract if address_verified_twin_contract do - Map.put(address_verified_twin_contract, :address_hash, address_hash) + 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) else current_smart_contract end end end + @spec address_hash_to_smart_contract_without_twin(Hash.Address.t()) :: SmartContract.t() | nil + def address_hash_to_smart_contract_without_twin(address_hash) do + query = + from( + smart_contract in SmartContract, + where: smart_contract.address_hash == ^address_hash + ) + + Repo.one(query) + end + def smart_contract_fully_verified?(address_hash_str) when is_binary(address_hash_str) do case string_to_address_hash(address_hash_str) do {:ok, address_hash} -> @@ -5844,32 +5857,16 @@ defmodule Explorer.Chain do end end - 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) + def combine_proxy_implementation_abi(%SmartContract{abi: abi} = smart_contract) when not is_nil(abi) do + implementation_abi = get_implementation_abi_from_proxy(smart_contract) if Enum.empty?(implementation_abi), do: abi, else: implementation_abi ++ abi end - def combine_proxy_implementation_abi(_, abi) when is_nil(abi) do + def combine_proxy_implementation_abi(_) do [] end - def proxy_contract?(address_hash, abi) when not is_nil(abi) do - implementation_method_abi = - abi - |> Enum.find(fn method -> - Map.get(method, "name") == "implementation" || - master_copy_pattern?(method) - end) - - if implementation_method_abi || - get_implementation_address_hash_eip_1967(address_hash) !== "0x0000000000000000000000000000000000000000", - do: true, - else: false - end - - def proxy_contract?(_address_hash, abi) when is_nil(abi), do: false - def gnosis_safe_contract?(abi) when not is_nil(abi) do implementation_method_abi = abi @@ -5882,167 +5879,7 @@ defmodule Explorer.Chain do def gnosis_safe_contract?(abi) when is_nil(abi), do: false - @spec get_implementation_address_hash(Hash.Address.t(), list()) :: {String.t() | nil, String.t() | nil} - def get_implementation_address_hash(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" && Map.get(method, "stateMutability") == "view" - end) - - master_copy_method_abi = - abi - |> Enum.find(fn method -> - master_copy_pattern?(method) - end) - - implementation_address = - cond do - implementation_method_abi -> - get_implementation_address_hash_basic(proxy_address_hash, abi) - - master_copy_method_abi -> - get_implementation_address_hash_from_master_copy_pattern(proxy_address_hash) - - true -> - get_implementation_address_hash_eip_1967(proxy_address_hash) - end - - save_implementation_name(implementation_address, proxy_address_hash) - end - - def get_implementation_address_hash(proxy_address_hash, abi) when is_nil(proxy_address_hash) or is_nil(abi) do - {nil, nil} - end - - defp get_implementation_address_hash_eip_1967(proxy_address_hash) do - json_rpc_named_arguments = Application.get_env(:explorer, :json_rpc_named_arguments) - - # https://eips.ethereum.org/EIPS/eip-1967 - storage_slot_logic_contract_address = "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc" - - {_status, implementation_address} = - case Contract.eth_get_storage_at_request( - proxy_address_hash, - storage_slot_logic_contract_address, - nil, - json_rpc_named_arguments - ) do - {:ok, empty_address} - when empty_address in ["0x", "0x0", "0x0000000000000000000000000000000000000000000000000000000000000000"] -> - fetch_beacon_proxy_implementation(proxy_address_hash, json_rpc_named_arguments) - - {:ok, implementation_logic_address} -> - {:ok, implementation_logic_address} - - {:error, _} -> - {:ok, "0x"} - end - - abi_decode_address_output(implementation_address) - end - - # changes requested by https://github.com/blockscout/blockscout/issues/4770 - # for support BeaconProxy pattern - defp fetch_beacon_proxy_implementation(proxy_address_hash, json_rpc_named_arguments) do - # https://eips.ethereum.org/EIPS/eip-1967 - storage_slot_beacon_contract_address = "0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50" - - implementation_method_abi = [ - %{ - "type" => "function", - "stateMutability" => "view", - "outputs" => [%{"type" => "address", "name" => "", "internalType" => "address"}], - "name" => "implementation", - "inputs" => [] - } - ] - - case Contract.eth_get_storage_at_request( - proxy_address_hash, - storage_slot_beacon_contract_address, - nil, - json_rpc_named_arguments - ) do - {:ok, empty_address} - when empty_address in ["0x", "0x0", "0x0000000000000000000000000000000000000000000000000000000000000000"] -> - fetch_openzeppelin_proxy_implementation(proxy_address_hash, json_rpc_named_arguments) - - {:ok, beacon_contract_address} -> - case beacon_contract_address - |> abi_decode_address_output() - |> get_implementation_address_hash_basic(implementation_method_abi) do - <> -> - {:ok, implementation_address} - - _ -> - {:ok, beacon_contract_address} - end - - {:error, _} -> - {:ok, "0x"} - end - end - - # changes requested by https://github.com/blockscout/blockscout/issues/5292 - defp fetch_openzeppelin_proxy_implementation(proxy_address_hash, json_rpc_named_arguments) do - # This is the keccak-256 hash of "org.zeppelinos.proxy.implementation" - storage_slot_logic_contract_address = "0x7050c9e0f4ca769c69bd3a8ef740bc37934f8e2c036e5a723fd8ee048ed3f8c3" - - case Contract.eth_get_storage_at_request( - proxy_address_hash, - storage_slot_logic_contract_address, - nil, - json_rpc_named_arguments - ) do - {:ok, empty_address} - when empty_address in ["0x", "0x0", "0x0000000000000000000000000000000000000000000000000000000000000000"] -> - {:ok, "0x"} - - {:ok, logic_contract_address} -> - {:ok, logic_contract_address} - - {:error, _} -> - {:ok, "0x"} - end - end - - defp get_implementation_address_hash_basic(proxy_address_hash, abi) do - # 5c60da1b = keccak256(implementation()) - implementation_address = - case Reader.query_contract( - proxy_address_hash, - abi, - %{ - "5c60da1b" => [] - }, - false - ) do - %{"5c60da1b" => {:ok, [result]}} -> result - _ -> nil - end - - address_to_hex(implementation_address) - end - - defp get_implementation_address_hash_from_master_copy_pattern(proxy_address_hash) do - json_rpc_named_arguments = Application.get_env(:explorer, :json_rpc_named_arguments) - - master_copy_storage_pointer = "0x0" - - {:ok, implementation_address} = - Contract.eth_get_storage_at_request( - proxy_address_hash, - master_copy_storage_pointer, - nil, - json_rpc_named_arguments - ) - - abi_decode_address_output(implementation_address) - end - - defp master_copy_pattern?(method) do + def master_copy_pattern?(method) do Map.get(method, "type") == "constructor" && method |> Enum.find(fn item -> @@ -6063,57 +5900,6 @@ defmodule Explorer.Chain do end) end - defp save_implementation_name(empty_address_hash_string, _) - when empty_address_hash_string in [ - "0x", - "0x0", - "0x0000000000000000000000000000000000000000000000000000000000000000", - @burn_address_hash_str - ], - do: {empty_address_hash_string, nil} - - defp save_implementation_name(implementation_address_hash_string, proxy_address_hash) - when is_binary(implementation_address_hash_string) do - with {:ok, address_hash} <- string_to_address_hash(implementation_address_hash_string), - %SmartContract{name: name} <- address_hash_to_smart_contract(address_hash) do - SmartContract - |> where([sc], sc.address_hash == ^proxy_address_hash) - |> update(set: [implementation_name: ^name]) - |> Repo.update_all([]) - - {implementation_address_hash_string, name} - else - _ -> - {implementation_address_hash_string, nil} - end - end - - defp save_implementation_name(other, _), do: {other, nil} - - defp abi_decode_address_output(nil), do: nil - - defp abi_decode_address_output("0x"), do: @burn_address_hash_str - - defp abi_decode_address_output(address) when is_binary(address) do - if String.length(address) > 42 do - "0x" <> String.slice(address, -40, 40) - else - address - end - end - - defp abi_decode_address_output(_), do: nil - - defp address_to_hex(address) do - if address do - if String.starts_with?(address, "0x") do - address - else - "0x" <> Base.encode16(address, case: :lower) - end - end - end - def 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} -> @@ -6137,15 +5923,13 @@ defmodule Explorer.Chain do [] end - def get_implementation_abi_from_proxy(proxy_address_hash, abi) + def get_implementation_abi_from_proxy(%SmartContract{address_hash: proxy_address_hash, abi: abi} = smart_contract) when not is_nil(proxy_address_hash) and not is_nil(abi) do - {implementation_address_hash_string, _name} = get_implementation_address_hash(proxy_address_hash, abi) + {implementation_address_hash_string, _name} = SmartContract.get_implementation_address_hash(smart_contract) get_implementation_abi(implementation_address_hash_string) end - def get_implementation_abi_from_proxy(proxy_address_hash, abi) when is_nil(proxy_address_hash) or is_nil(abi) do - [] - end + def get_implementation_abi_from_proxy(_), do: [] defp format_tx_first_trace(first_trace, block_hash, json_rpc_named_arguments) do {:ok, to_address_hash} = diff --git a/apps/explorer/lib/explorer/chain/log.ex b/apps/explorer/lib/explorer/chain/log.ex index f9de1eb43d..4a23818881 100644 --- a/apps/explorer/lib/explorer/chain/log.ex +++ b/apps/explorer/lib/explorer/chain/log.ex @@ -128,8 +128,8 @@ defmodule Explorer.Chain.Log do ] case Chain.find_contract_address(log.address_hash, address_options, true) do - {:ok, %{smart_contract: %{abi: abi}}} -> - full_abi = Chain.combine_proxy_implementation_abi(log.address_hash, abi) + {:ok, %{smart_contract: smart_contract}} -> + full_abi = Chain.combine_proxy_implementation_abi(smart_contract) with {:ok, selector, mapping} <- find_and_decode(full_abi, log, transaction), identifier <- Base.encode16(selector.method_id, case: :lower), diff --git a/apps/explorer/lib/explorer/chain/smart_contract.ex b/apps/explorer/lib/explorer/chain/smart_contract.ex index 0b18c8a3e4..bd72f3ad2b 100644 --- a/apps/explorer/lib/explorer/chain/smart_contract.ex +++ b/apps/explorer/lib/explorer/chain/smart_contract.ex @@ -13,9 +13,15 @@ defmodule Explorer.Chain.SmartContract do use Explorer.Schema alias Ecto.Changeset + alias EthereumJSONRPC.Contract + alias Explorer.Counters.AverageBlockTime alias Explorer.{Chain, Repo} alias Explorer.Chain.{Address, ContractMethod, DecompiledSmartContract, Hash} alias Explorer.Chain.SmartContract.ExternalLibrary + alias Explorer.SmartContract.Reader + alias Timex.Duration + + @burn_address_hash_str "0x0000000000000000000000000000000000000000" @typedoc """ The name of a parameter to a function or event. @@ -201,7 +207,9 @@ defmodule Explorer.Chain.SmartContract do * `bytecode_checked_at` - timestamp of the last check of contract's bytecode matching (DB and BlockChain) * `contract_code_md5` - md5(`t:Explorer.Chain.Address.t/0` `contract_code`) * `implementation_name` - name of the proxy implementation - * `settings` - raw compilation parameters + * `compiler_settings` - raw compilation parameters + * `implementation_fetched_at` - timestamp of the last fetching contract's implementation info + * `implementation_address_hash` - address hash of the proxy's implementation if any * `autodetect_constructor_args` - field was added for storing user's choice * `is_yul` - field was added for storing user's choice """ @@ -224,6 +232,8 @@ defmodule Explorer.Chain.SmartContract do contract_code_md5: String.t(), implementation_name: String.t() | nil, compiler_settings: map() | nil, + implementation_fetched_at: DateTime.t(), + implementation_address_hash: Hash.Address.t(), autodetect_constructor_args: boolean | nil, is_yul: boolean | nil } @@ -247,6 +257,8 @@ defmodule Explorer.Chain.SmartContract do field(:contract_code_md5, :string) field(:implementation_name, :string) field(:compiler_settings, :map) + field(:implementation_fetched_at, :utc_datetime_usec, default: nil) + field(:implementation_address_hash, Hash.Address, default: nil) field(:autodetect_constructor_args, :boolean, virtual: true) field(:is_yul, :boolean, virtual: true) @@ -291,7 +303,9 @@ defmodule Explorer.Chain.SmartContract do :bytecode_checked_at, :contract_code_md5, :implementation_name, - :compiler_settings + :compiler_settings, + :implementation_address_hash, + :implementation_fetched_at ]) |> validate_required([ :name, @@ -493,4 +507,370 @@ defmodule Explorer.Chain.SmartContract do end defp to_address_hash(address_hash), do: address_hash + + def proxy_contract?(%__MODULE__{abi: abi} = smart_contract) when not is_nil(abi) do + implementation_method_abi = + abi + |> Enum.find(fn method -> + Map.get(method, "name") == "implementation" || + Chain.master_copy_pattern?(method) + end) + + if implementation_method_abi || + not is_nil( + smart_contract + |> get_implementation_address_hash() + |> Tuple.to_list() + |> List.first() + ), + do: true, + else: false + end + + def proxy_contract?(_), do: false + + def get_implementation_address_hash(%__MODULE__{abi: nil}), do: false + + def get_implementation_address_hash( + %__MODULE__{ + address_hash: address_hash, + implementation_fetched_at: implementation_fetched_at + } = smart_contract + ) 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) + else + smart_contract + end + + get_implementation_address_hash({:updated, updated_smart_contract}) + end + + def get_implementation_address_hash( + {:updated, + %__MODULE__{ + address_hash: address_hash, + abi: abi, + implementation_address_hash: implementation_address_hash_from_db, + implementation_name: implementation_name_from_db, + implementation_fetched_at: implementation_fetched_at + }} + ) 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) + + timeout = Application.get_env(:explorer, :implementation_data_fetching_timeout) + + case Task.yield(get_implementation_address_hash_task, timeout) || + Task.ignore(get_implementation_address_hash_task) do + {:ok, {:empty, :empty}} -> + {nil, nil} + + {:ok, {address_hash, _name} = result} when not is_nil(address_hash) -> + result + + _ -> + {db_implementation_data_converter(implementation_address_hash_from_db), + db_implementation_data_converter(implementation_name_from_db)} + end + else + {db_implementation_data_converter(implementation_address_hash_from_db), + db_implementation_data_converter(implementation_name_from_db)} + end + end + + def get_implementation_address_hash(_), do: {nil, nil} + + defp db_implementation_data_converter(nil), do: nil + defp db_implementation_data_converter(string) when is_binary(string), do: string + defp db_implementation_data_converter(other), do: to_string(other) + + defp check_implementation_refetch_neccessity(nil), do: true + + defp check_implementation_refetch_neccessity(timestamp) do + if Application.get_env(:explorer, :enable_caching_implementation_data_of_proxy) do + now = DateTime.utc_now() + + average_block_time = + if Application.get_env(:explorer, :avg_block_time_as_ttl_cached_implementation_data_of_proxy) do + case AverageBlockTime.average_block_time() do + {:error, :disabled} -> + 0 + + duration -> + duration + |> Duration.to_milliseconds() + end + else + 0 + end + + fresh_time_distance = + case average_block_time do + 0 -> + Application.get_env(:explorer, :fallback_ttl_cached_implementation_data_of_proxy) + + time -> + round(time) + end + + timestamp + |> DateTime.add(fresh_time_distance, :millisecond) + |> DateTime.compare(now) != :gt + else + true + 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) + 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" && Map.get(method, "stateMutability") == "view" + end) + + master_copy_method_abi = + abi + |> Enum.find(fn method -> + Chain.master_copy_pattern?(method) + end) + + implementation_address = + cond do + implementation_method_abi -> + get_implementation_address_hash_basic(proxy_address_hash, abi) + + master_copy_method_abi -> + get_implementation_address_hash_from_master_copy_pattern(proxy_address_hash) + + true -> + get_implementation_address_hash_eip_1967(proxy_address_hash) + end + + save_implementation_data(implementation_address, proxy_address_hash) + end + + defp get_implementation_address_hash(proxy_address_hash, abi) when is_nil(proxy_address_hash) or is_nil(abi) do + {nil, nil} + end + + defp get_implementation_address_hash_eip_1967(proxy_address_hash) do + json_rpc_named_arguments = Application.get_env(:explorer, :json_rpc_named_arguments) + + # https://eips.ethereum.org/EIPS/eip-1967 + storage_slot_logic_contract_address = "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc" + + {_status, implementation_address} = + case Contract.eth_get_storage_at_request( + proxy_address_hash, + storage_slot_logic_contract_address, + nil, + json_rpc_named_arguments + ) do + {:ok, empty_address} + when empty_address in ["0x", "0x0", "0x0000000000000000000000000000000000000000000000000000000000000000", nil] -> + fetch_beacon_proxy_implementation(proxy_address_hash, json_rpc_named_arguments) + + {:ok, implementation_logic_address} -> + {:ok, implementation_logic_address} + + _ -> + {:ok, nil} + end + + abi_decode_address_output(implementation_address) + end + + # changes requested by https://github.com/blockscout/blockscout/issues/4770 + # for support BeaconProxy pattern + defp fetch_beacon_proxy_implementation(proxy_address_hash, json_rpc_named_arguments) do + # https://eips.ethereum.org/EIPS/eip-1967 + storage_slot_beacon_contract_address = "0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50" + + implementation_method_abi = [ + %{ + "type" => "function", + "stateMutability" => "view", + "outputs" => [%{"type" => "address", "name" => "", "internalType" => "address"}], + "name" => "implementation", + "inputs" => [] + } + ] + + case Contract.eth_get_storage_at_request( + proxy_address_hash, + storage_slot_beacon_contract_address, + nil, + json_rpc_named_arguments + ) do + {:ok, empty_address} + when empty_address in ["0x", "0x0", "0x0000000000000000000000000000000000000000000000000000000000000000", nil] -> + fetch_openzeppelin_proxy_implementation(proxy_address_hash, json_rpc_named_arguments) + + {:ok, beacon_contract_address} -> + case beacon_contract_address + |> abi_decode_address_output() + |> get_implementation_address_hash_basic(implementation_method_abi) do + <> -> + {:ok, implementation_address} + + _ -> + {:ok, beacon_contract_address} + end + + _ -> + {:ok, nil} + end + end + + # changes requested by https://github.com/blockscout/blockscout/issues/5292 + defp fetch_openzeppelin_proxy_implementation(proxy_address_hash, json_rpc_named_arguments) do + # This is the keccak-256 hash of "org.zeppelinos.proxy.implementation" + storage_slot_logic_contract_address = "0x7050c9e0f4ca769c69bd3a8ef740bc37934f8e2c036e5a723fd8ee048ed3f8c3" + + case Contract.eth_get_storage_at_request( + proxy_address_hash, + storage_slot_logic_contract_address, + nil, + json_rpc_named_arguments + ) do + {:ok, empty_address} + when empty_address in ["0x", "0x0", "0x0000000000000000000000000000000000000000000000000000000000000000"] -> + {:ok, "0x"} + + {:ok, logic_contract_address} -> + {:ok, logic_contract_address} + + _ -> + {:ok, nil} + end + end + + defp get_implementation_address_hash_basic(proxy_address_hash, abi) do + # 5c60da1b = keccak256(implementation()) + implementation_address = + case Reader.query_contract( + proxy_address_hash, + abi, + %{ + "5c60da1b" => [] + }, + false + ) do + %{"5c60da1b" => {:ok, [result]}} -> result + _ -> nil + end + + address_to_hex(implementation_address) + end + + defp get_implementation_address_hash_from_master_copy_pattern(proxy_address_hash) do + json_rpc_named_arguments = Application.get_env(:explorer, :json_rpc_named_arguments) + + master_copy_storage_pointer = "0x0" + + {:ok, implementation_address} = + case Contract.eth_get_storage_at_request( + proxy_address_hash, + master_copy_storage_pointer, + nil, + json_rpc_named_arguments + ) do + {:ok, empty_address} + when empty_address in ["0x", "0x0", "0x0000000000000000000000000000000000000000000000000000000000000000"] -> + {:ok, "0x"} + + {:ok, logic_contract_address} -> + {:ok, logic_contract_address} + + _ -> + {:ok, nil} + end + + abi_decode_address_output(implementation_address) + end + + defp save_implementation_data(nil, _), do: {nil, nil} + + defp save_implementation_data(empty_address_hash_string, proxy_address_hash) + 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() + + {:empty, :empty} + end + + 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), + false <- is_nil(proxy_contract), + %{implementation: %__MODULE__{name: name}, proxy: proxy_contract} <- %{ + implementation: Chain.address_hash_to_smart_contract(address_hash), + proxy: proxy_contract + } do + proxy_contract + |> changeset(%{ + implementation_name: name, + implementation_address_hash: implementation_address_hash_string, + implementation_fetched_at: DateTime.utc_now() + }) + |> Repo.update() + + {implementation_address_hash_string, name} + else + %{implementation: _, proxy: proxy_contract} -> + proxy_contract + |> changeset(%{ + implementation_name: nil, + implementation_address_hash: implementation_address_hash_string, + implementation_fetched_at: DateTime.utc_now() + }) + |> Repo.update() + + {implementation_address_hash_string, nil} + + _ -> + {implementation_address_hash_string, nil} + end + end + + defp address_to_hex(address) do + if address do + if String.starts_with?(address, "0x") do + address + else + "0x" <> Base.encode16(address, case: :lower) + end + end + end + + defp abi_decode_address_output(nil), do: nil + + defp abi_decode_address_output("0x"), do: @burn_address_hash_str + + defp abi_decode_address_output(address) when is_binary(address) do + if String.length(address) > 42 do + "0x" <> String.slice(address, -40, 40) + else + address + end + end + + defp abi_decode_address_output(_), do: nil end diff --git a/apps/explorer/lib/explorer/chain/transaction.ex b/apps/explorer/lib/explorer/chain/transaction.ex index be55eba91d..0dccd1c90f 100644 --- a/apps/explorer/lib/explorer/chain/transaction.ex +++ b/apps/explorer/lib/explorer/chain/transaction.ex @@ -23,6 +23,7 @@ defmodule Explorer.Chain.Transaction do Hash, InternalTransaction, Log, + SmartContract, TokenTransfer, Transaction, Wei @@ -493,7 +494,7 @@ defmodule Explorer.Chain.Transaction do candidates_query |> Repo.all() |> Enum.flat_map(fn candidate -> - case do_decoded_input_data(data, [candidate.abi], nil, hash) do + case do_decoded_input_data(data, %SmartContract{abi: [candidate.abi], address_hash: nil}, hash) do {:ok, _, _, _} = decoded -> [decoded] _ -> [] end @@ -508,11 +509,11 @@ defmodule Explorer.Chain.Transaction do def decoded_input_data(%__MODULE__{ input: %{bytes: data}, - to_address: %{smart_contract: %{abi: abi, address_hash: address_hash}}, + to_address: %{smart_contract: smart_contract}, hash: hash }) do - case do_decoded_input_data(data, abi, address_hash, hash) do - # In some cases transactions use methods of some unpredictable contracts, so we can try to look up for method in a whole DB + case do_decoded_input_data(data, smart_contract, hash) do + # In some cases transactions use methods of some unpredictadle contracts, so we can try to look up for method in a whole DB {:error, :could_not_decode} -> case decoded_input_data(%__MODULE__{ to_address: %{smart_contract: nil}, @@ -534,8 +535,8 @@ defmodule Explorer.Chain.Transaction do end end - defp do_decoded_input_data(data, abi, address_hash, hash) do - full_abi = Chain.combine_proxy_implementation_abi(address_hash, abi) + defp do_decoded_input_data(data, smart_contract, hash) do + full_abi = Chain.combine_proxy_implementation_abi(smart_contract) with {:ok, {selector, values}} <- find_and_decode(full_abi, data, hash), {:ok, mapping} <- selector_mapping(selector, values, hash), diff --git a/apps/explorer/lib/explorer/etherscan/contracts.ex b/apps/explorer/lib/explorer/etherscan/contracts.ex index a02f791af7..cea04475df 100644 --- a/apps/explorer/lib/explorer/etherscan/contracts.ex +++ b/apps/explorer/lib/explorer/etherscan/contracts.ex @@ -60,13 +60,13 @@ defmodule Explorer.Etherscan.Contracts do def append_proxy_info(%Address{smart_contract: smart_contract} = address) when not is_nil(smart_contract) do updated_smart_contract = - if Chain.proxy_contract?(address.hash, smart_contract.abi) do + if SmartContract.proxy_contract?(smart_contract) do smart_contract |> Map.put(:is_proxy, true) |> Map.put( :implementation_address_hash_string, - address.hash - |> Chain.get_implementation_address_hash(smart_contract.abi) + smart_contract + |> SmartContract.get_implementation_address_hash() |> Tuple.to_list() |> List.first() ) diff --git a/apps/explorer/lib/explorer/smart_contract/reader.ex b/apps/explorer/lib/explorer/smart_contract/reader.ex index d83cdc2c4a..0d0c8d5b2d 100644 --- a/apps/explorer/lib/explorer/smart_contract/reader.ex +++ b/apps/explorer/lib/explorer/smart_contract/reader.ex @@ -526,15 +526,12 @@ defmodule Explorer.SmartContract.Reader do end defp get_abi(contract_address_hash, type) do - abi = - contract_address_hash - |> Chain.address_hash_to_smart_contract() - |> Map.get(:abi) + contract = Chain.address_hash_to_smart_contract(contract_address_hash) if type == :proxy do - Chain.get_implementation_abi_from_proxy(contract_address_hash, abi) + Chain.get_implementation_abi_from_proxy(contract) else - abi + contract.abi end end diff --git a/apps/explorer/priv/repo/migrations/20220527131249_add_implementation_fields.exs b/apps/explorer/priv/repo/migrations/20220527131249_add_implementation_fields.exs new file mode 100644 index 0000000000..5f3839dd4a --- /dev/null +++ b/apps/explorer/priv/repo/migrations/20220527131249_add_implementation_fields.exs @@ -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 diff --git a/apps/explorer/test/explorer/chain/smart_contract_test.exs b/apps/explorer/test/explorer/chain/smart_contract_test.exs new file mode 100644 index 0000000000..38ff09e3b1 --- /dev/null +++ b/apps/explorer/test/explorer/chain/smart_contract_test.exs @@ -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 diff --git a/apps/explorer/test/explorer/chain_test.exs b/apps/explorer/test/explorer/chain_test.exs index 61b42a0e60..dfad452986 100644 --- a/apps/explorer/test/explorer/chain_test.exs +++ b/apps/explorer/test/explorer/chain_test.exs @@ -5936,26 +5936,36 @@ defmodule Explorer.ChainTest 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) == [] + + assert Chain.combine_proxy_implementation_abi(%SmartContract{address_hash: proxy_contract_address.hash, abi: nil}) == + [] end test "combine_proxy_implementation_abi/2 returns [] abi for unverified proxy" do proxy_contract_address = insert(:contract_address) + smart_contract = + insert(:smart_contract, address_hash: proxy_contract_address.hash, abi: [], contract_code_md5: "123") + get_eip1967_implementation() - assert Chain.combine_proxy_implementation_abi(proxy_contract_address, []) == [] + assert Chain.combine_proxy_implementation_abi(smart_contract) == [] end 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, contract_code_md5: "123") - assert Chain.combine_proxy_implementation_abi(proxy_contract_address, @proxy_abi) == @proxy_abi + + smart_contract = + insert(:smart_contract, address_hash: proxy_contract_address.hash, abi: @proxy_abi, contract_code_md5: "123") + + assert Chain.combine_proxy_implementation_abi(smart_contract) == @proxy_abi end 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, contract_code_md5: "123") + + smart_contract = + insert(:smart_contract, address_hash: proxy_contract_address.hash, abi: @proxy_abi, contract_code_md5: "123") implementation_contract_address = insert(:contract_address) @@ -5983,7 +5993,7 @@ defmodule Explorer.ChainTest do end ) - combined_abi = Chain.combine_proxy_implementation_abi(proxy_contract_address.hash, @proxy_abi) + combined_abi = Chain.combine_proxy_implementation_abi(smart_contract) assert Enum.any?(@proxy_abi, fn el -> el == Enum.at(@implementation_abi, 0) end) == false assert Enum.any?(@proxy_abi, fn el -> el == Enum.at(@implementation_abi, 1) end) == false @@ -5993,26 +6003,36 @@ defmodule Explorer.ChainTest do 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) == [] + + assert Chain.get_implementation_abi_from_proxy(%SmartContract{address_hash: proxy_contract_address.hash, abi: nil}) == + [] end test "get_implementation_abi_from_proxy/2 returns [] abi for unverified proxy" do proxy_contract_address = insert(:contract_address) + smart_contract = + insert(:smart_contract, address_hash: proxy_contract_address.hash, abi: [], contract_code_md5: "123") + get_eip1967_implementation() - assert Chain.combine_proxy_implementation_abi(proxy_contract_address, []) == [] + assert Chain.combine_proxy_implementation_abi(smart_contract) == [] 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, contract_code_md5: "123") - assert Chain.get_implementation_abi_from_proxy(proxy_contract_address, @proxy_abi) == [] + + smart_contract = + insert(:smart_contract, address_hash: proxy_contract_address.hash, abi: @proxy_abi, contract_code_md5: "123") + + assert Chain.get_implementation_abi_from_proxy(smart_contract) == [] 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, contract_code_md5: "123") + + smart_contract = + insert(:smart_contract, address_hash: proxy_contract_address.hash, abi: @proxy_abi, contract_code_md5: "123") implementation_contract_address = insert(:contract_address) @@ -6040,14 +6060,16 @@ defmodule Explorer.ChainTest do end ) - implementation_abi = Chain.get_implementation_abi_from_proxy(proxy_contract_address.hash, @proxy_abi) + implementation_abi = Chain.get_implementation_abi_from_proxy(smart_contract) assert implementation_abi == @implementation_abi end test "get_implementation_abi_from_proxy/2 returns implementation abi in case of EIP-1967 proxy pattern" do proxy_contract_address = insert(:contract_address) - insert(:smart_contract, address_hash: proxy_contract_address.hash, abi: [], contract_code_md5: "123") + + smart_contract = + insert(:smart_contract, address_hash: proxy_contract_address.hash, abi: [], contract_code_md5: "123") implementation_contract_address = insert(:contract_address) @@ -6077,7 +6099,7 @@ defmodule Explorer.ChainTest do end ) - implementation_abi = Chain.get_implementation_abi_from_proxy(proxy_contract_address.hash, []) + implementation_abi = Chain.get_implementation_abi_from_proxy(smart_contract) assert implementation_abi == @implementation_abi end diff --git a/config/runtime.exs b/config/runtime.exs index d3b31abf7e..7b9777f13f 100644 --- a/config/runtime.exs +++ b/config/runtime.exs @@ -213,7 +213,11 @@ config :explorer, if(disable_webapp != "true", do: Explorer.Chain.Events.SimpleSender, else: Explorer.Chain.Events.DBSender - ) + ), + enable_caching_implementation_data_of_proxy: true, + avg_block_time_as_ttl_cached_implementation_data_of_proxy: true, + fallback_ttl_cached_implementation_data_of_proxy: :timer.seconds(4), + implementation_data_fetching_timeout: :timer.seconds(2) config :explorer, Explorer.Visualize.Sol2uml, service_url: System.get_env("VISUALIZE_SOL2UML_SERVICE_URL"),