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. 5
      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. 40
      apps/explorer/test/explorer/chain_test.exs
  20. 6
      config/runtime.exs

@ -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

@ -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

@ -124,8 +124,8 @@
<% end %>
<!-- Implementation -->
<%= if @is_proxy do %>
<% {implementation_address, name} = Chain.get_implementation_address_hash(@address.hash, @address.smart_contract.abi) || "0x0000000000000000000000000000000000000000" %>
<%= if implementation_address do %>
<% {implementation_address_, name} = SmartContract.get_implementation_address_hash(@address.smart_contract) %>
<% implementation_address = implementation_address_ || "0x0000000000000000000000000000000000000000" %>
<dl class="row">
<dt class="col-sm-4 col-md-4 col-lg-3 text-muted">
<%= render BlockScoutWeb.CommonComponentsView, "_i_tooltip_2.html",
@ -142,7 +142,6 @@
</dd>
</dl>
<% end %>
<% end %>
<!-- Balance -->
<dl class="row">
<dt class="col-sm-4 col-md-4 col-lg-3 text-muted">

@ -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

@ -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

@ -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 ""

@ -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 ""

@ -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

@ -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

@ -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,

@ -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
<<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
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} =

@ -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),

@ -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
<<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

@ -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),

@ -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()
)

@ -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

@ -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,25 +5936,35 @@ 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)
smart_contract =
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
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)
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,25 +6003,35 @@ 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)
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(proxy_contract_address, @proxy_abi) == []
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)
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,13 +6060,15 @@ 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)
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

@ -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"),

Loading…
Cancel
Save