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