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
pull/6519/head
nikitosing 2 years ago committed by GitHub
parent 8fdc845e48
commit 708a6b0036
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      CHANGELOG.md
  2. 5
      apps/block_scout_web/lib/block_scout_web/controllers/smart_contract_controller.ex
  3. 35
      apps/block_scout_web/lib/block_scout_web/templates/address/overview.html.eex
  4. 4
      apps/block_scout_web/lib/block_scout_web/views/address_view.ex
  5. 4
      apps/block_scout_web/lib/block_scout_web/views/tokens/overview_view.ex
  6. 98
      apps/block_scout_web/priv/gettext/default.pot
  7. 98
      apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po
  8. 14
      apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/http.ex
  9. 2
      apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/request_coordinator.ex
  10. 6
      apps/explorer/config/test.exs
  11. 264
      apps/explorer/lib/explorer/chain.ex
  12. 4
      apps/explorer/lib/explorer/chain/log.ex
  13. 384
      apps/explorer/lib/explorer/chain/smart_contract.ex
  14. 13
      apps/explorer/lib/explorer/chain/transaction.ex
  15. 6
      apps/explorer/lib/explorer/etherscan/contracts.ex
  16. 9
      apps/explorer/lib/explorer/smart_contract/reader.ex
  17. 10
      apps/explorer/priv/repo/migrations/20220527131249_add_implementation_fields.exs
  18. 237
      apps/explorer/test/explorer/chain/smart_contract_test.exs
  19. 50
      apps/explorer/test/explorer/chain_test.exs
  20. 6
      config/runtime.exs

@ -2,6 +2,7 @@
### Features ### 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 - [#6401](https://github.com/blockscout/blockscout/pull/6401) - Add Sol2Uml contract visualization
- [#6481](https://github.com/blockscout/blockscout/pull/6481) - Smart contract verification improvements - [#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 - [#6444](https://github.com/blockscout/blockscout/pull/6444) - Add support for yul verification via rust microservice

@ -3,6 +3,7 @@ defmodule BlockScoutWeb.SmartContractController do
alias BlockScoutWeb.AddressView alias BlockScoutWeb.AddressView
alias Explorer.Chain alias Explorer.Chain
alias Explorer.Chain.SmartContract
alias Explorer.SmartContract.{Reader, Writer} alias Explorer.SmartContract.{Reader, Writer}
import Explorer.SmartContract.Solidity.Verifier, only: [parse_boolean: 1] 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 {:ok, address} <- Chain.find_contract_address(address_hash, address_options, true) do
implementation_address_hash_string = implementation_address_hash_string =
if contract_type == "proxy" do if contract_type == "proxy" do
address.hash address.smart_contract
|> Chain.get_implementation_address_hash(address.smart_contract.abi) |> SmartContract.get_implementation_address_hash()
|> Tuple.to_list() |> Tuple.to_list()
|> List.first() || @burn_address |> List.first() || @burn_address
else else

@ -124,24 +124,23 @@
<% end %> <% end %>
<!-- Implementation --> <!-- Implementation -->
<%= if @is_proxy do %> <%= if @is_proxy do %>
<% {implementation_address, name} = Chain.get_implementation_address_hash(@address.hash, @address.smart_contract.abi) || "0x0000000000000000000000000000000000000000" %> <% {implementation_address_, name} = SmartContract.get_implementation_address_hash(@address.smart_contract) %>
<%= if implementation_address do %> <% implementation_address = implementation_address_ || "0x0000000000000000000000000000000000000000" %>
<dl class="row"> <dl class="row">
<dt class="col-sm-4 col-md-4 col-lg-3 text-muted"> <dt class="col-sm-4 col-md-4 col-lg-3 text-muted">
<%= render BlockScoutWeb.CommonComponentsView, "_i_tooltip_2.html", <%= render BlockScoutWeb.CommonComponentsView, "_i_tooltip_2.html",
text: gettext("Implementation address of the proxy contract.") %> text: gettext("Implementation address of the proxy contract.") %>
<%= gettext("Implementation") %> <%= gettext("Implementation") %>
</dt> </dt>
<dd class="col-sm-8 col-md-8 col-lg-9" data-test="address_contract_implementation"> <dd class="col-sm-8 col-md-8 col-lg-9" data-test="address_contract_implementation">
<%= link( <%= link(
(if name, do: name <> " | " <> implementation_address, else: implementation_address), (if name, do: name <> " | " <> implementation_address, else: implementation_address),
to: address_path(@conn, :show, implementation_address), to: address_path(@conn, :show, implementation_address),
class: "contract-address" class: "contract-address"
) )
%> %>
</dd> </dd>
</dl> </dl>
<% end %>
<% end %> <% end %>
<!-- Balance --> <!-- Balance -->
<dl class="row"> <dl class="row">

@ -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 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 def smart_contract_is_proxy?(%Address{smart_contract: %SmartContract{} = smart_contract}) do
Chain.proxy_contract?(address.hash, address.smart_contract.abi) SmartContract.proxy_contract?(smart_contract)
end end
def smart_contract_is_proxy?(%Address{smart_contract: nil}), do: false def smart_contract_is_proxy?(%Address{smart_contract: nil}), do: false

@ -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_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 def smart_contract_is_proxy?(%Token{contract_address: %Address{smart_contract: %SmartContract{} = smart_contract}}) do
Chain.proxy_contract?(address.hash, address.smart_contract.abi) SmartContract.proxy_contract?(smart_contract)
end end
def smart_contract_is_proxy?(%Token{contract_address: %Address{smart_contract: nil}}), do: false def smart_contract_is_proxy?(%Token{contract_address: %Address{smart_contract: nil}}), do: false

@ -256,7 +256,7 @@ msgstr ""
msgid "Address Tags" msgid "Address Tags"
msgstr "" 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 #, elixir-autogen, elixir-format
msgid "Address balance in" msgid "Address balance in"
msgstr "" msgstr ""
@ -304,7 +304,7 @@ msgstr ""
msgid "All metadata displayed below is from that contract. In order to verify current contract, click" msgid "All metadata displayed below is from that contract. In order to verify current contract, click"
msgstr "" 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 #, elixir-autogen, elixir-format
msgid "All tokens in the account and total value." msgid "All tokens in the account and total value."
msgstr "" msgstr ""
@ -371,7 +371,7 @@ msgid "Back to Watch list (Cancel)"
msgstr "" msgstr ""
#: lib/block_scout_web/templates/account/watchlist/show.html.eex:24 #: 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/address_token/overview.html.eex:51
#: lib/block_scout_web/templates/verified_contracts/index.html.eex:57 #: lib/block_scout_web/templates/verified_contracts/index.html.eex:57
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
@ -463,7 +463,7 @@ msgstr ""
msgid "Block number containing the transaction." msgid "Block number containing the transaction."
msgstr "" 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 #, elixir-autogen, elixir-format
msgid "Block number in which the address was updated." msgid "Block number in which the address was updated."
msgstr "" msgstr ""
@ -491,7 +491,7 @@ msgid "Blocks Indexed"
msgstr "" msgstr ""
#: lib/block_scout_web/templates/address/_tabs.html.eex:48 #: 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/templates/address_validation/index.html.eex:11
#: lib/block_scout_web/views/address_view.ex:381 #: lib/block_scout_web/views/address_view.ex:381
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
@ -620,6 +620,11 @@ msgstr ""
msgid "Compiler" msgid "Compiler"
msgstr "" 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 #: lib/block_scout_web/templates/address_contract/index.html.eex:66
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Compiler version" msgid "Compiler version"
@ -805,6 +810,11 @@ msgstr ""
msgid "Copy Address" msgid "Copy Address"
msgstr "" 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
#: 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 #, elixir-autogen, elixir-format
@ -1281,7 +1291,7 @@ msgstr ""
msgid "Fast" msgid "Fast"
msgstr "" 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 #, elixir-autogen, elixir-format
msgid "Fetching gas used..." msgid "Fetching gas used..."
msgstr "" msgstr ""
@ -1296,14 +1306,14 @@ msgstr ""
msgid "Fetching tokens..." msgid "Fetching tokens..."
msgstr "" msgstr ""
#: lib/block_scout_web/templates/address/overview.html.eex:195 #: lib/block_scout_web/templates/address/overview.html.eex:194
#: lib/block_scout_web/templates/address/overview.html.eex:203 #: lib/block_scout_web/templates/address/overview.html.eex:202
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Fetching transactions..." msgid "Fetching transactions..."
msgstr "" msgstr ""
#: lib/block_scout_web/templates/address/overview.html.eex:222 #: lib/block_scout_web/templates/address/overview.html.eex:221
#: lib/block_scout_web/templates/address/overview.html.eex:230 #: lib/block_scout_web/templates/address/overview.html.eex:229
#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:123 #: lib/block_scout_web/templates/tokens/overview/_details.html.eex:123
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Fetching transfers..." msgid "Fetching transfers..."
@ -1362,7 +1372,7 @@ msgstr ""
msgid "Gas Price" msgid "Gas Price"
msgstr "" 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/_tile.html.eex:73
#: lib/block_scout_web/templates/block/overview.html.eex:178 #: lib/block_scout_web/templates/block/overview.html.eex:178
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
@ -1380,7 +1390,7 @@ msgstr ""
msgid "Gas tracker" msgid "Gas tracker"
msgstr "" 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 #, elixir-autogen, elixir-format
msgid "Gas used by the address." msgid "Gas used by the address."
msgstr "" msgstr ""
@ -1474,6 +1484,11 @@ msgstr ""
msgid "Implementation address of the proxy contract." msgid "Implementation address of the proxy contract."
msgstr "" 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:30
#: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:43 #: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:43
#: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:56 #: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:56
@ -1549,7 +1564,7 @@ msgstr ""
msgid "Last 24h" msgid "Last 24h"
msgstr "" 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 #, elixir-autogen, elixir-format
msgid "Last Balance Update" msgid "Last Balance Update"
msgstr "" msgstr ""
@ -1874,7 +1889,7 @@ msgstr ""
msgid "Number of accounts holding the token" msgid "Number of accounts holding the token"
msgstr "" 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 #, elixir-autogen, elixir-format
msgid "Number of blocks validated by this validator." msgid "Number of blocks validated by this validator."
msgstr "" msgstr ""
@ -1884,7 +1899,7 @@ msgstr ""
msgid "Number of digits that come after the decimal place when displaying token value" msgid "Number of digits that come after the decimal place when displaying token value"
msgstr "" 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 #, elixir-autogen, elixir-format
msgid "Number of transactions related to this address." msgid "Number of transactions related to this address."
msgstr "" msgstr ""
@ -1894,7 +1909,7 @@ msgstr ""
msgid "Number of transfers for the token" msgid "Number of transfers for the token"
msgstr "" 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 #, elixir-autogen, elixir-format
msgid "Number of transfers to/from this address." msgid "Number of transfers to/from this address."
msgstr "" msgstr ""
@ -2240,6 +2255,11 @@ msgstr ""
msgid "Select Yes if you want to vefify Yul contract." msgid "Select Yes if you want to vefify Yul contract."
msgstr "" 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 #: lib/block_scout_web/views/internal_transaction_view.ex:27
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Self-Destruct" msgid "Self-Destruct"
@ -2776,7 +2796,7 @@ msgid "Token type"
msgstr "" msgstr ""
#: lib/block_scout_web/templates/address/_tabs.html.eex:21 #: 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/overview.html.eex:58
#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:13 #: lib/block_scout_web/templates/address_token_transfer/index.html.eex:13
#: lib/block_scout_web/templates/layout/_topnav.html.eex:78 #: lib/block_scout_web/templates/layout/_topnav.html.eex:78
@ -2935,9 +2955,9 @@ msgid "Transaction type, introduced in EIP-2718."
msgstr "" msgstr ""
#: lib/block_scout_web/templates/address/_tabs.html.eex:7 #: 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:186
#: lib/block_scout_web/templates/address/overview.html.eex:193 #: lib/block_scout_web/templates/address/overview.html.eex:192
#: lib/block_scout_web/templates/address/overview.html.eex:201 #: 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/address_transaction/index.html.eex:13
#: lib/block_scout_web/templates/block/overview.html.eex:80 #: lib/block_scout_web/templates/block/overview.html.eex:80
#: lib/block_scout_web/templates/block_transaction/index.html.eex:10 #: lib/block_scout_web/templates/block_transaction/index.html.eex:10
@ -2958,9 +2978,9 @@ msgstr ""
msgid "Transactions sent" msgid "Transactions sent"
msgstr "" msgstr ""
#: lib/block_scout_web/templates/address/overview.html.eex:214 #: lib/block_scout_web/templates/address/overview.html.eex:213
#: lib/block_scout_web/templates/address/overview.html.eex:220 #: lib/block_scout_web/templates/address/overview.html.eex:219
#: lib/block_scout_web/templates/address/overview.html.eex:228 #: 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/instance/overview/_details.html.eex:50
#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:119 #: lib/block_scout_web/templates/tokens/overview/_details.html.eex:119
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
@ -2973,6 +2993,11 @@ msgstr ""
msgid "Try it out" msgid "Try it out"
msgstr "" 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 #: lib/block_scout_web/templates/layout/_footer.html.eex:27
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Twitter" msgid "Twitter"
@ -3383,7 +3408,7 @@ msgstr ""
msgid "custom RPC" msgid "custom RPC"
msgstr "" 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 #, elixir-autogen, elixir-format
msgid "doesn't include ERC20, ERC721, ERC1155 tokens)." msgid "doesn't include ERC20, ERC721, ERC1155 tokens)."
msgstr "" msgstr ""
@ -3463,28 +3488,3 @@ msgstr ""
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "truffle flattener" msgid "truffle flattener"
msgstr "" 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 ""

@ -256,7 +256,7 @@ msgstr ""
msgid "Address Tags" msgid "Address Tags"
msgstr "" 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 #, elixir-autogen, elixir-format
msgid "Address balance in" msgid "Address balance in"
msgstr "" msgstr ""
@ -304,7 +304,7 @@ msgstr ""
msgid "All metadata displayed below is from that contract. In order to verify current contract, click" msgid "All metadata displayed below is from that contract. In order to verify current contract, click"
msgstr "" 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 #, elixir-autogen, elixir-format
msgid "All tokens in the account and total value." msgid "All tokens in the account and total value."
msgstr "" msgstr ""
@ -371,7 +371,7 @@ msgid "Back to Watch list (Cancel)"
msgstr "" msgstr ""
#: lib/block_scout_web/templates/account/watchlist/show.html.eex:24 #: 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/address_token/overview.html.eex:51
#: lib/block_scout_web/templates/verified_contracts/index.html.eex:57 #: lib/block_scout_web/templates/verified_contracts/index.html.eex:57
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
@ -463,7 +463,7 @@ msgstr ""
msgid "Block number containing the transaction." msgid "Block number containing the transaction."
msgstr "" 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 #, elixir-autogen, elixir-format
msgid "Block number in which the address was updated." msgid "Block number in which the address was updated."
msgstr "" msgstr ""
@ -491,7 +491,7 @@ msgid "Blocks Indexed"
msgstr "" msgstr ""
#: lib/block_scout_web/templates/address/_tabs.html.eex:48 #: 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/templates/address_validation/index.html.eex:11
#: lib/block_scout_web/views/address_view.ex:381 #: lib/block_scout_web/views/address_view.ex:381
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
@ -620,6 +620,11 @@ msgstr ""
msgid "Compiler" msgid "Compiler"
msgstr "" 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 #: lib/block_scout_web/templates/address_contract/index.html.eex:66
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Compiler version" msgid "Compiler version"
@ -805,6 +810,11 @@ msgstr ""
msgid "Copy Address" msgid "Copy Address"
msgstr "" 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
#: 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 #, elixir-autogen, elixir-format
@ -1281,7 +1291,7 @@ msgstr ""
msgid "Fast" msgid "Fast"
msgstr "" 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 #, elixir-autogen, elixir-format
msgid "Fetching gas used..." msgid "Fetching gas used..."
msgstr "" msgstr ""
@ -1296,14 +1306,14 @@ msgstr ""
msgid "Fetching tokens..." msgid "Fetching tokens..."
msgstr "" msgstr ""
#: lib/block_scout_web/templates/address/overview.html.eex:195 #: lib/block_scout_web/templates/address/overview.html.eex:194
#: lib/block_scout_web/templates/address/overview.html.eex:203 #: lib/block_scout_web/templates/address/overview.html.eex:202
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Fetching transactions..." msgid "Fetching transactions..."
msgstr "" msgstr ""
#: lib/block_scout_web/templates/address/overview.html.eex:222 #: lib/block_scout_web/templates/address/overview.html.eex:221
#: lib/block_scout_web/templates/address/overview.html.eex:230 #: lib/block_scout_web/templates/address/overview.html.eex:229
#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:123 #: lib/block_scout_web/templates/tokens/overview/_details.html.eex:123
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Fetching transfers..." msgid "Fetching transfers..."
@ -1362,7 +1372,7 @@ msgstr ""
msgid "Gas Price" msgid "Gas Price"
msgstr "" 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/_tile.html.eex:73
#: lib/block_scout_web/templates/block/overview.html.eex:178 #: lib/block_scout_web/templates/block/overview.html.eex:178
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
@ -1380,7 +1390,7 @@ msgstr ""
msgid "Gas tracker" msgid "Gas tracker"
msgstr "" 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 #, elixir-autogen, elixir-format
msgid "Gas used by the address." msgid "Gas used by the address."
msgstr "" msgstr ""
@ -1474,6 +1484,11 @@ msgstr ""
msgid "Implementation address of the proxy contract." msgid "Implementation address of the proxy contract."
msgstr "" 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:30
#: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:43 #: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:43
#: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:56 #: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:56
@ -1549,7 +1564,7 @@ msgstr ""
msgid "Last 24h" msgid "Last 24h"
msgstr "" 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 #, elixir-autogen, elixir-format
msgid "Last Balance Update" msgid "Last Balance Update"
msgstr "" msgstr ""
@ -1874,7 +1889,7 @@ msgstr ""
msgid "Number of accounts holding the token" msgid "Number of accounts holding the token"
msgstr "" 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 #, elixir-autogen, elixir-format
msgid "Number of blocks validated by this validator." msgid "Number of blocks validated by this validator."
msgstr "" msgstr ""
@ -1884,7 +1899,7 @@ msgstr ""
msgid "Number of digits that come after the decimal place when displaying token value" msgid "Number of digits that come after the decimal place when displaying token value"
msgstr "" 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 #, elixir-autogen, elixir-format
msgid "Number of transactions related to this address." msgid "Number of transactions related to this address."
msgstr "" msgstr ""
@ -1894,7 +1909,7 @@ msgstr ""
msgid "Number of transfers for the token" msgid "Number of transfers for the token"
msgstr "" 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 #, elixir-autogen, elixir-format
msgid "Number of transfers to/from this address." msgid "Number of transfers to/from this address."
msgstr "" msgstr ""
@ -2240,6 +2255,11 @@ msgstr ""
msgid "Select Yes if you want to vefify Yul contract." msgid "Select Yes if you want to vefify Yul contract."
msgstr "" 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 #: lib/block_scout_web/views/internal_transaction_view.ex:27
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Self-Destruct" msgid "Self-Destruct"
@ -2776,7 +2796,7 @@ msgid "Token type"
msgstr "" msgstr ""
#: lib/block_scout_web/templates/address/_tabs.html.eex:21 #: 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/overview.html.eex:58
#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:13 #: lib/block_scout_web/templates/address_token_transfer/index.html.eex:13
#: lib/block_scout_web/templates/layout/_topnav.html.eex:78 #: lib/block_scout_web/templates/layout/_topnav.html.eex:78
@ -2935,9 +2955,9 @@ msgid "Transaction type, introduced in EIP-2718."
msgstr "" msgstr ""
#: lib/block_scout_web/templates/address/_tabs.html.eex:7 #: 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:186
#: lib/block_scout_web/templates/address/overview.html.eex:193 #: lib/block_scout_web/templates/address/overview.html.eex:192
#: lib/block_scout_web/templates/address/overview.html.eex:201 #: 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/address_transaction/index.html.eex:13
#: lib/block_scout_web/templates/block/overview.html.eex:80 #: lib/block_scout_web/templates/block/overview.html.eex:80
#: lib/block_scout_web/templates/block_transaction/index.html.eex:10 #: lib/block_scout_web/templates/block_transaction/index.html.eex:10
@ -2958,9 +2978,9 @@ msgstr ""
msgid "Transactions sent" msgid "Transactions sent"
msgstr "" msgstr ""
#: lib/block_scout_web/templates/address/overview.html.eex:214 #: lib/block_scout_web/templates/address/overview.html.eex:213
#: lib/block_scout_web/templates/address/overview.html.eex:220 #: lib/block_scout_web/templates/address/overview.html.eex:219
#: lib/block_scout_web/templates/address/overview.html.eex:228 #: 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/instance/overview/_details.html.eex:50
#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:119 #: lib/block_scout_web/templates/tokens/overview/_details.html.eex:119
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
@ -2973,6 +2993,11 @@ msgstr ""
msgid "Try it out" msgid "Try it out"
msgstr "" 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 #: lib/block_scout_web/templates/layout/_footer.html.eex:27
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Twitter" msgid "Twitter"
@ -3383,7 +3408,7 @@ msgstr ""
msgid "custom RPC" msgid "custom RPC"
msgstr "" 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 #, elixir-autogen, elixir-format
msgid "doesn't include ERC20, ERC721, ERC1155 tokens)." msgid "doesn't include ERC20, ERC721, ERC1155 tokens)."
msgstr "" msgstr ""
@ -3463,28 +3488,3 @@ msgstr ""
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "truffle flattener" msgid "truffle flattener"
msgstr "" 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 ""

@ -3,7 +3,7 @@ defmodule EthereumJSONRPC.HTTP do
JSONRPC over HTTP JSONRPC over HTTP
""" """
alias EthereumJSONRPC.Transport alias EthereumJSONRPC.{DecodeError, Transport}
require Logger require Logger
@ -114,7 +114,17 @@ defmodule EthereumJSONRPC.HTTP do
{:error, {:bad_gateway, request_url}} {: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 end
end end

@ -109,7 +109,7 @@ defmodule EthereumJSONRPC.RequestCoordinator do
defp trace_request(_, fun), do: fun.() 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) RollingWindow.inc(table(), @error_key)
inc_throttle_table() inc_throttle_table()
error error

@ -22,7 +22,11 @@ config :explorer, Explorer.Repo.Replica1,
# Default of `5_000` was too low for `BlockFetcher` test # Default of `5_000` was too low for `BlockFetcher` test
ownership_timeout: :timer.minutes(1), ownership_timeout: :timer.minutes(1),
timeout: :timer.seconds(60), 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 # Configure API database
config :explorer, Explorer.Repo.Account, config :explorer, Explorer.Repo.Account,

@ -21,7 +21,6 @@ defmodule Explorer.Chain do
select: 3, select: 3,
subquery: 1, subquery: 1,
union: 2, union: 2,
update: 2,
where: 2, where: 2,
where: 3 where: 3
] ]
@ -33,7 +32,6 @@ defmodule Explorer.Chain do
alias ABI.TypeDecoder alias ABI.TypeDecoder
alias Ecto.{Changeset, Multi} alias Ecto.{Changeset, Multi}
alias EthereumJSONRPC.Contract
alias EthereumJSONRPC.Transaction, as: EthereumJSONRPCTransaction alias EthereumJSONRPC.Transaction, as: EthereumJSONRPCTransaction
alias Explorer.Counters.{LastFetchedCounter, TokenHoldersCounter, TokenTransfersCounter} alias Explorer.Counters.{LastFetchedCounter, TokenHoldersCounter, TokenTransfersCounter}
@ -91,7 +89,7 @@ defmodule Explorer.Chain do
alias Explorer.Market.MarketHistoryCache alias Explorer.Market.MarketHistoryCache
alias Explorer.{PagingOptions, Repo} alias Explorer.{PagingOptions, Repo}
alias Explorer.SmartContract.{Helper, Reader} alias Explorer.SmartContract.Helper
alias Dataloader.Ecto, as: DataloaderEcto alias Dataloader.Ecto, as: DataloaderEcto
@ -4334,13 +4332,28 @@ defmodule Explorer.Chain do
Chain.get_address_verified_twin_contract(address_hash).verified_contract Chain.get_address_verified_twin_contract(address_hash).verified_contract
if address_verified_twin_contract do 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 else
current_smart_contract current_smart_contract
end end
end 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 def smart_contract_fully_verified?(address_hash_str) when is_binary(address_hash_str) do
case string_to_address_hash(address_hash_str) do case string_to_address_hash(address_hash_str) do
{:ok, address_hash} -> {:ok, address_hash} ->
@ -5844,32 +5857,16 @@ defmodule Explorer.Chain do
end end
end end
def combine_proxy_implementation_abi(proxy_address_hash, abi) when not is_nil(abi) do def combine_proxy_implementation_abi(%SmartContract{abi: abi} = smart_contract) when not is_nil(abi) do
implementation_abi = get_implementation_abi_from_proxy(proxy_address_hash, abi) implementation_abi = get_implementation_abi_from_proxy(smart_contract)
if Enum.empty?(implementation_abi), do: abi, else: implementation_abi ++ abi if Enum.empty?(implementation_abi), do: abi, else: implementation_abi ++ abi
end end
def combine_proxy_implementation_abi(_, abi) when is_nil(abi) do def combine_proxy_implementation_abi(_) do
[] []
end 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 def gnosis_safe_contract?(abi) when not is_nil(abi) do
implementation_method_abi = implementation_method_abi =
abi abi
@ -5882,167 +5879,7 @@ defmodule Explorer.Chain do
def gnosis_safe_contract?(abi) when is_nil(abi), do: false 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 master_copy_pattern?(method) do
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
<<implementation_address::binary-size(42)>> ->
{: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
Map.get(method, "type") == "constructor" && Map.get(method, "type") == "constructor" &&
method method
|> Enum.find(fn item -> |> Enum.find(fn item ->
@ -6063,57 +5900,6 @@ defmodule Explorer.Chain do
end) end)
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 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 case Chain.string_to_address_hash(implementation_address_hash_string) do
{:ok, implementation_address_hash} -> {:ok, implementation_address_hash} ->
@ -6137,15 +5923,13 @@ defmodule Explorer.Chain do
[] []
end 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 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) get_implementation_abi(implementation_address_hash_string)
end end
def get_implementation_abi_from_proxy(proxy_address_hash, abi) when is_nil(proxy_address_hash) or is_nil(abi) do def get_implementation_abi_from_proxy(_), do: []
[]
end
defp format_tx_first_trace(first_trace, block_hash, json_rpc_named_arguments) do defp format_tx_first_trace(first_trace, block_hash, json_rpc_named_arguments) do
{:ok, to_address_hash} = {:ok, to_address_hash} =

@ -128,8 +128,8 @@ defmodule Explorer.Chain.Log do
] ]
case Chain.find_contract_address(log.address_hash, address_options, true) do case Chain.find_contract_address(log.address_hash, address_options, true) do
{:ok, %{smart_contract: %{abi: abi}}} -> {:ok, %{smart_contract: smart_contract}} ->
full_abi = Chain.combine_proxy_implementation_abi(log.address_hash, abi) full_abi = Chain.combine_proxy_implementation_abi(smart_contract)
with {:ok, selector, mapping} <- find_and_decode(full_abi, log, transaction), with {:ok, selector, mapping} <- find_and_decode(full_abi, log, transaction),
identifier <- Base.encode16(selector.method_id, case: :lower), identifier <- Base.encode16(selector.method_id, case: :lower),

@ -13,9 +13,15 @@ defmodule Explorer.Chain.SmartContract do
use Explorer.Schema use Explorer.Schema
alias Ecto.Changeset alias Ecto.Changeset
alias EthereumJSONRPC.Contract
alias Explorer.Counters.AverageBlockTime
alias Explorer.{Chain, Repo} alias Explorer.{Chain, Repo}
alias Explorer.Chain.{Address, ContractMethod, DecompiledSmartContract, Hash} alias Explorer.Chain.{Address, ContractMethod, DecompiledSmartContract, Hash}
alias Explorer.Chain.SmartContract.ExternalLibrary alias Explorer.Chain.SmartContract.ExternalLibrary
alias Explorer.SmartContract.Reader
alias Timex.Duration
@burn_address_hash_str "0x0000000000000000000000000000000000000000"
@typedoc """ @typedoc """
The name of a parameter to a function or event. 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) * `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`) * `contract_code_md5` - md5(`t:Explorer.Chain.Address.t/0` `contract_code`)
* `implementation_name` - name of the proxy implementation * `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 * `autodetect_constructor_args` - field was added for storing user's choice
* `is_yul` - 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(), contract_code_md5: String.t(),
implementation_name: String.t() | nil, implementation_name: String.t() | nil,
compiler_settings: map() | nil, compiler_settings: map() | nil,
implementation_fetched_at: DateTime.t(),
implementation_address_hash: Hash.Address.t(),
autodetect_constructor_args: boolean | nil, autodetect_constructor_args: boolean | nil,
is_yul: boolean | nil is_yul: boolean | nil
} }
@ -247,6 +257,8 @@ defmodule Explorer.Chain.SmartContract do
field(:contract_code_md5, :string) field(:contract_code_md5, :string)
field(:implementation_name, :string) field(:implementation_name, :string)
field(:compiler_settings, :map) 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(:autodetect_constructor_args, :boolean, virtual: true)
field(:is_yul, :boolean, virtual: true) field(:is_yul, :boolean, virtual: true)
@ -291,7 +303,9 @@ defmodule Explorer.Chain.SmartContract do
:bytecode_checked_at, :bytecode_checked_at,
:contract_code_md5, :contract_code_md5,
:implementation_name, :implementation_name,
:compiler_settings :compiler_settings,
:implementation_address_hash,
:implementation_fetched_at
]) ])
|> validate_required([ |> validate_required([
:name, :name,
@ -493,4 +507,370 @@ defmodule Explorer.Chain.SmartContract do
end end
defp to_address_hash(address_hash), do: address_hash 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
<<implementation_address::binary-size(42)>> ->
{: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 end

@ -23,6 +23,7 @@ defmodule Explorer.Chain.Transaction do
Hash, Hash,
InternalTransaction, InternalTransaction,
Log, Log,
SmartContract,
TokenTransfer, TokenTransfer,
Transaction, Transaction,
Wei Wei
@ -493,7 +494,7 @@ defmodule Explorer.Chain.Transaction do
candidates_query candidates_query
|> Repo.all() |> Repo.all()
|> Enum.flat_map(fn candidate -> |> 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] {:ok, _, _, _} = decoded -> [decoded]
_ -> [] _ -> []
end end
@ -508,11 +509,11 @@ defmodule Explorer.Chain.Transaction do
def decoded_input_data(%__MODULE__{ def decoded_input_data(%__MODULE__{
input: %{bytes: data}, input: %{bytes: data},
to_address: %{smart_contract: %{abi: abi, address_hash: address_hash}}, to_address: %{smart_contract: smart_contract},
hash: hash hash: hash
}) do }) do
case do_decoded_input_data(data, abi, address_hash, hash) do case do_decoded_input_data(data, smart_contract, 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 # 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} -> {:error, :could_not_decode} ->
case decoded_input_data(%__MODULE__{ case decoded_input_data(%__MODULE__{
to_address: %{smart_contract: nil}, to_address: %{smart_contract: nil},
@ -534,8 +535,8 @@ defmodule Explorer.Chain.Transaction do
end end
end end
defp do_decoded_input_data(data, abi, address_hash, hash) do defp do_decoded_input_data(data, smart_contract, hash) do
full_abi = Chain.combine_proxy_implementation_abi(address_hash, abi) full_abi = Chain.combine_proxy_implementation_abi(smart_contract)
with {:ok, {selector, values}} <- find_and_decode(full_abi, data, hash), with {:ok, {selector, values}} <- find_and_decode(full_abi, data, hash),
{:ok, mapping} <- selector_mapping(selector, values, hash), {:ok, mapping} <- selector_mapping(selector, values, hash),

@ -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 def append_proxy_info(%Address{smart_contract: smart_contract} = address) when not is_nil(smart_contract) do
updated_smart_contract = updated_smart_contract =
if Chain.proxy_contract?(address.hash, smart_contract.abi) do if SmartContract.proxy_contract?(smart_contract) do
smart_contract smart_contract
|> Map.put(:is_proxy, true) |> Map.put(:is_proxy, true)
|> Map.put( |> Map.put(
:implementation_address_hash_string, :implementation_address_hash_string,
address.hash smart_contract
|> Chain.get_implementation_address_hash(smart_contract.abi) |> SmartContract.get_implementation_address_hash()
|> Tuple.to_list() |> Tuple.to_list()
|> List.first() |> List.first()
) )

@ -526,15 +526,12 @@ defmodule Explorer.SmartContract.Reader do
end end
defp get_abi(contract_address_hash, type) do defp get_abi(contract_address_hash, type) do
abi = contract = Chain.address_hash_to_smart_contract(contract_address_hash)
contract_address_hash
|> Chain.address_hash_to_smart_contract()
|> Map.get(:abi)
if type == :proxy do if type == :proxy do
Chain.get_implementation_abi_from_proxy(contract_address_hash, abi) Chain.get_implementation_abi_from_proxy(contract)
else else
abi contract.abi
end end
end end

@ -0,0 +1,10 @@
defmodule Explorer.Repo.Migrations.AddImplementationFields do
use Ecto.Migration
def change do
alter table(:smart_contracts) do
add(:implementation_address_hash, :bytea, null: true)
add(:implementation_fetched_at, :"timestamp without time zone", null: true)
end
end
end

@ -0,0 +1,237 @@
defmodule Explorer.Chain.SmartContractTest do
use Explorer.DataCase, async: false
import Mox
alias Explorer.Chain
alias Explorer.Chain.SmartContract
doctest Explorer.Chain.SmartContract
setup :verify_on_exit!
setup :set_mox_global
describe "test fetching implementation" do
test "check proxy_contract/1 function" do
smart_contract = insert(:smart_contract)
Application.put_env(:explorer, :fallback_ttl_cached_implementation_data_of_proxy, :timer.seconds(20))
Application.put_env(:explorer, :implementation_data_fetching_timeout, :timer.seconds(20))
refute smart_contract.implementation_fetched_at
# fetch nil implementation and save it to db
get_eip1967_implementation_zero_addresses()
refute SmartContract.proxy_contract?(smart_contract)
verify!(EthereumJSONRPC.Mox)
assert_empty_implementation(smart_contract.address_hash)
# extract proxy info from db
refute SmartContract.proxy_contract?(smart_contract)
verify!(EthereumJSONRPC.Mox)
assert_empty_implementation(smart_contract.address_hash)
Application.put_env(:explorer, :fallback_ttl_cached_implementation_data_of_proxy, 0)
get_eip1967_implementation_error_response()
refute SmartContract.proxy_contract?(smart_contract)
verify!(EthereumJSONRPC.Mox)
get_eip1967_implementation_non_zero_address()
assert SmartContract.proxy_contract?(smart_contract)
verify!(EthereumJSONRPC.Mox)
assert_implementation_address(smart_contract.address_hash)
get_eip1967_implementation_non_zero_address()
assert SmartContract.proxy_contract?(smart_contract)
verify!(EthereumJSONRPC.Mox)
assert_implementation_address(smart_contract.address_hash)
Application.put_env(:explorer, :fallback_ttl_cached_implementation_data_of_proxy, :timer.seconds(20))
assert SmartContract.proxy_contract?(smart_contract)
Application.put_env(:explorer, :fallback_ttl_cached_implementation_data_of_proxy, 0)
get_eip1967_implementation_non_zero_address()
assert SmartContract.proxy_contract?(smart_contract)
verify!(EthereumJSONRPC.Mox)
get_eip1967_implementation_error_response()
assert SmartContract.proxy_contract?(smart_contract)
verify!(EthereumJSONRPC.Mox)
end
test "test get_implementation_adddress_hash/1" do
smart_contract = insert(:smart_contract)
implementation_smart_contract = insert(:smart_contract, name: "proxy")
Application.put_env(:explorer, :fallback_ttl_cached_implementation_data_of_proxy, :timer.seconds(20))
Application.put_env(:explorer, :implementation_data_fetching_timeout, :timer.seconds(20))
refute smart_contract.implementation_fetched_at
# fetch nil implementation and save it to db
get_eip1967_implementation_zero_addresses()
assert {nil, nil} = SmartContract.get_implementation_address_hash(smart_contract)
verify!(EthereumJSONRPC.Mox)
assert_empty_implementation(smart_contract.address_hash)
# extract proxy info from db
assert {nil, nil} = SmartContract.get_implementation_address_hash(smart_contract)
assert_empty_implementation(smart_contract.address_hash)
Application.put_env(:explorer, :fallback_ttl_cached_implementation_data_of_proxy, 0)
string_implementation_address_hash = to_string(implementation_smart_contract.address_hash)
expect(EthereumJSONRPC.Mox, :json_rpc, fn %{
id: 0,
method: "eth_getStorageAt",
params: [
_,
"0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc",
"latest"
]
},
_options ->
{:ok, string_implementation_address_hash}
end)
assert {^string_implementation_address_hash, "proxy"} =
SmartContract.get_implementation_address_hash(smart_contract)
verify!(EthereumJSONRPC.Mox)
assert_exact_name_and_address(
smart_contract.address_hash,
implementation_smart_contract.address_hash,
implementation_smart_contract.name
)
get_eip1967_implementation_error_response()
assert {^string_implementation_address_hash, "proxy"} =
SmartContract.get_implementation_address_hash(smart_contract)
verify!(EthereumJSONRPC.Mox)
assert_exact_name_and_address(
smart_contract.address_hash,
implementation_smart_contract.address_hash,
implementation_smart_contract.name
)
contract_1 = Chain.address_hash_to_smart_contract(smart_contract.address_hash)
Application.put_env(:explorer, :fallback_ttl_cached_implementation_data_of_proxy, :timer.seconds(20))
assert {^string_implementation_address_hash, "proxy"} =
SmartContract.get_implementation_address_hash(smart_contract)
contract_2 = Chain.address_hash_to_smart_contract(smart_contract.address_hash)
assert contract_1.implementation_fetched_at == contract_2.implementation_fetched_at &&
contract_1.updated_at == contract_2.updated_at
Application.put_env(:explorer, :fallback_ttl_cached_implementation_data_of_proxy, 0)
get_eip1967_implementation_zero_addresses()
assert {nil, nil} = SmartContract.get_implementation_address_hash(smart_contract)
verify!(EthereumJSONRPC.Mox)
assert_empty_implementation(smart_contract.address_hash)
end
end
def get_eip1967_implementation_zero_addresses do
EthereumJSONRPC.Mox
|> expect(:json_rpc, fn %{
id: 0,
method: "eth_getStorageAt",
params: [
_,
"0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc",
"latest"
]
},
_options ->
{:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"}
end)
|> expect(:json_rpc, fn %{
id: 0,
method: "eth_getStorageAt",
params: [
_,
"0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50",
"latest"
]
},
_options ->
{:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"}
end)
|> expect(:json_rpc, fn %{
id: 0,
method: "eth_getStorageAt",
params: [
_,
"0x7050c9e0f4ca769c69bd3a8ef740bc37934f8e2c036e5a723fd8ee048ed3f8c3",
"latest"
]
},
_options ->
{:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"}
end)
end
def get_eip1967_implementation_non_zero_address do
expect(EthereumJSONRPC.Mox, :json_rpc, fn %{
id: 0,
method: "eth_getStorageAt",
params: [
_,
"0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc",
"latest"
]
},
_options ->
{:ok, "0x0000000000000000000000000000000000000000000000000000000000000001"}
end)
end
def get_eip1967_implementation_error_response do
EthereumJSONRPC.Mox
|> expect(:json_rpc, fn %{
id: 0,
method: "eth_getStorageAt",
params: [
_,
"0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc",
"latest"
]
},
_options ->
{:error, "error"}
end)
end
def assert_empty_implementation(address_hash) do
contract = Chain.address_hash_to_smart_contract(address_hash)
assert contract.implementation_fetched_at
refute contract.implementation_name
refute contract.implementation_address_hash
end
def assert_implementation_address(address_hash) do
contract = Chain.address_hash_to_smart_contract(address_hash)
assert contract.implementation_fetched_at
assert contract.implementation_address_hash
end
def assert_implementation_name(address_hash) do
contract = Chain.address_hash_to_smart_contract(address_hash)
assert contract.implementation_fetched_at
assert contract.implementation_name
end
def assert_exact_name_and_address(address_hash, implementation_address_hash, implementation_name) do
contract = Chain.address_hash_to_smart_contract(address_hash)
assert contract.implementation_fetched_at
assert contract.implementation_name == implementation_name
assert to_string(contract.implementation_address_hash) == to_string(implementation_address_hash)
end
end

@ -5936,26 +5936,36 @@ defmodule Explorer.ChainTest do
test "combine_proxy_implementation_abi/2 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) 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 end
test "combine_proxy_implementation_abi/2 returns [] abi for unverified proxy" do test "combine_proxy_implementation_abi/2 returns [] abi for unverified proxy" do
proxy_contract_address = insert(:contract_address) 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() get_eip1967_implementation()
assert Chain.combine_proxy_implementation_abi(proxy_contract_address, []) == [] assert Chain.combine_proxy_implementation_abi(smart_contract) == []
end end
test "combine_proxy_implementation_abi/2 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) 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 end
test "combine_proxy_implementation_abi/2 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) 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) implementation_contract_address = insert(:contract_address)
@ -5983,7 +5993,7 @@ defmodule Explorer.ChainTest do
end 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, 0) end) == false
assert Enum.any?(@proxy_abi, fn el -> el == Enum.at(@implementation_abi, 1) 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 test "get_implementation_abi_from_proxy/2 returns empty [] abi if proxy abi is null" do
proxy_contract_address = insert(:contract_address) 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 end
test "get_implementation_abi_from_proxy/2 returns [] abi for unverified proxy" do test "get_implementation_abi_from_proxy/2 returns [] abi for unverified proxy" do
proxy_contract_address = insert(:contract_address) 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() get_eip1967_implementation()
assert Chain.combine_proxy_implementation_abi(proxy_contract_address, []) == [] assert Chain.combine_proxy_implementation_abi(smart_contract) == []
end end
test "get_implementation_abi_from_proxy/2 returns [] if implementation is not verified" do test "get_implementation_abi_from_proxy/2 returns [] if implementation is not verified" do
proxy_contract_address = insert(:contract_address) 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 end
test "get_implementation_abi_from_proxy/2 returns implementation abi if implementation is verified" do test "get_implementation_abi_from_proxy/2 returns implementation abi if implementation is verified" do
proxy_contract_address = insert(:contract_address) 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) implementation_contract_address = insert(:contract_address)
@ -6040,14 +6060,16 @@ defmodule Explorer.ChainTest do
end 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 assert implementation_abi == @implementation_abi
end end
test "get_implementation_abi_from_proxy/2 returns implementation abi in case of EIP-1967 proxy pattern" do test "get_implementation_abi_from_proxy/2 returns implementation abi in case of EIP-1967 proxy pattern" do
proxy_contract_address = insert(:contract_address) 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) implementation_contract_address = insert(:contract_address)
@ -6077,7 +6099,7 @@ defmodule Explorer.ChainTest do
end 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 assert implementation_abi == @implementation_abi
end end

@ -213,7 +213,11 @@ config :explorer,
if(disable_webapp != "true", if(disable_webapp != "true",
do: Explorer.Chain.Events.SimpleSender, do: Explorer.Chain.Events.SimpleSender,
else: Explorer.Chain.Events.DBSender 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, config :explorer, Explorer.Visualize.Sol2uml,
service_url: System.get_env("VISUALIZE_SOL2UML_SERVICE_URL"), service_url: System.get_env("VISUALIZE_SOL2UML_SERVICE_URL"),

Loading…
Cancel
Save