diff --git a/.dialyzer-ignore b/.dialyzer-ignore
index 9a1636eee1..271ef7d322 100644
--- a/.dialyzer-ignore
+++ b/.dialyzer-ignore
@@ -23,8 +23,8 @@ lib/indexer/fetcher/token_total_supply_on_demand.ex:16
lib/explorer/exchange_rates/source.ex:110
lib/explorer/exchange_rates/source.ex:113
lib/explorer/smart_contract/solidity/verifier.ex:162
-lib/block_scout_web/templates/address_contract/index.html.eex:159
-lib/block_scout_web/templates/address_contract/index.html.eex:196
+lib/block_scout_web/templates/address_contract/index.html.eex:162
+lib/block_scout_web/templates/address_contract/index.html.eex:199
lib/explorer/staking/stake_snapshotting.ex:15: Function do_snapshotting/7 has no local return
lib/explorer/staking/stake_snapshotting.ex:147
lib/explorer/third_party_integrations/sourcify.ex:70
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8c2748c95f..01f64b844a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,7 @@
## Current
### Features
+- [#4924](https://github.com/blockscout/blockscout/pull/4924) - Add daily bytecode verifcation to prevent metamorphic contracts vulnerablity
- [#4908](https://github.com/blockscout/blockscout/pull/4908) - Add verification via standard JSON input
- [#5004](https://github.com/blockscout/blockscout/pull/5004) - Add ability to set up a separate DB endpoint for the API endpoints
- [#4989](https://github.com/blockscout/blockscout/pull/4989), [#4991](https://github.com/blockscout/blockscout/pull/4991) - Bridged tokens list API endpoint
diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address_contract/index.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address_contract/index.html.eex
index 5b24e21d97..2a371a92f4 100644
--- a/apps/block_scout_web/lib/block_scout_web/templates/address_contract/index.html.eex
+++ b/apps/block_scout_web/lib/block_scout_web/templates/address_contract/index.html.eex
@@ -31,6 +31,9 @@
<% end %>
<% end %>
<% end %>
+ <%= if smart_contract_verified && @address.smart_contract.is_changed_bytecode do %>
+ <%= render BlockScoutWeb.CommonComponentsView, "_changed_bytecode_warning.html" %>
+ <% end %>
<%= if smart_contract_verified || (!smart_contract_verified && metadata_for_verification) do %>
<% target_contract = if smart_contract_verified, do: @address.smart_contract, else: metadata_for_verification %>
<%= if @address.smart_contract.partially_verified && smart_contract_verified do %>
diff --git a/apps/block_scout_web/lib/block_scout_web/templates/common_components/_changed_bytecode_warning.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/common_components/_changed_bytecode_warning.html.eex
new file mode 100644
index 0000000000..665733f715
--- /dev/null
+++ b/apps/block_scout_web/lib/block_scout_web/templates/common_components/_changed_bytecode_warning.html.eex
@@ -0,0 +1,4 @@
+
+ <%= render BlockScoutWeb.CommonComponentsView, "_info.html" %>
+ <%= gettext("Warning! Contract bytecode has been changed and doesn't match the verified one. Therefore, interaction with this smart contract may be risky.") %>
+
\ No newline at end of file
diff --git a/apps/block_scout_web/lib/block_scout_web/templates/smart_contract/_functions.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/smart_contract/_functions.html.eex
index 21125a712b..4dd73b65f9 100644
--- a/apps/block_scout_web/lib/block_scout_web/templates/smart_contract/_functions.html.eex
+++ b/apps/block_scout_web/lib/block_scout_web/templates/smart_contract/_functions.html.eex
@@ -1,6 +1,7 @@
<% minimal_proxy_template = Chain.get_minimal_proxy_template(@address.hash) %>
<% metadata_for_verification = minimal_proxy_template || Chain.get_address_verified_twin_contract(@address.hash).verified_contract %>
-<%= unless BlockScoutWeb.AddressView.smart_contract_verified?(@address) do %>
+<% smart_contract_verified = BlockScoutWeb.AddressView.smart_contract_verified?(@address) %>
+<%= unless smart_contract_verified do %>
<%= if metadata_for_verification do %>
<%= if minimal_proxy_template do %>
<%= render BlockScoutWeb.CommonComponentsView, "_minimal_proxy_pattern.html", address_hash: metadata_for_verification.address_hash, conn: @conn %>
@@ -17,6 +18,9 @@
<% end %>
<% end %>
<% end %>
+<%= if smart_contract_verified && @address.smart_contract.is_changed_bytecode do %>
+ <%= render BlockScoutWeb.CommonComponentsView, "_changed_bytecode_warning.html" %>
+<% end %>
<%= if @action == "write" or (@read_functions_required_wallet && @read_functions_required_wallet != []) do %>
<%= render BlockScoutWeb.SmartContractView, "_connect_container.html" %>
<% end %>
diff --git a/apps/block_scout_web/priv/gettext/default.pot b/apps/block_scout_web/priv/gettext/default.pot
index a8c608569d..df65d5a225 100644
--- a/apps/block_scout_web/priv/gettext/default.pot
+++ b/apps/block_scout_web/priv/gettext/default.pot
@@ -215,7 +215,7 @@ msgid "All"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:12
+#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:13
msgid "All functions displayed below are from ABI of that contract. In order to verify current contract, proceed with"
msgstr ""
@@ -582,7 +582,7 @@ msgid "Compiler"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/address_contract/index.html.eex:62
+#: lib/block_scout_web/templates/address_contract/index.html.eex:65
msgid "Compiler version"
msgstr ""
@@ -633,7 +633,7 @@ msgid "Connection Lost, click to load newer validations"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/address_contract/index.html.eex:87
+#: lib/block_scout_web/templates/address_contract/index.html.eex:90
msgid "Constructor Arguments"
msgstr ""
@@ -643,7 +643,7 @@ msgid "Contract"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/address_contract/index.html.eex:123
+#: lib/block_scout_web/templates/address_contract/index.html.eex:126
msgid "Contract ABI"
msgstr ""
@@ -673,8 +673,8 @@ msgid "Contract Creation"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/address_contract/index.html.eex:139
-#: lib/block_scout_web/templates/address_contract/index.html.eex:154
+#: lib/block_scout_web/templates/address_contract/index.html.eex:142
+#: lib/block_scout_web/templates/address_contract/index.html.eex:157
msgid "Contract Creation Code"
msgstr ""
@@ -692,22 +692,22 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_contract/index.html.eex:25
-#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:10
+#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:11
msgid "Contract is not verified. However, we found a verified contract with the same bytecode in Blockscout DB"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/address_contract/index.html.eex:54
+#: lib/block_scout_web/templates/address_contract/index.html.eex:57
msgid "Contract name:"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/address_contract/index.html.eex:97
+#: lib/block_scout_web/templates/address_contract/index.html.eex:100
msgid "Contract source code"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/address_contract/index.html.eex:145
+#: lib/block_scout_web/templates/address_contract/index.html.eex:148
msgid "Contracts that self destruct in their constructors have no contract code published and cannot be verified."
msgstr ""
@@ -717,7 +717,7 @@ msgid "Contribute"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/address_contract/index.html.eex:125
+#: lib/block_scout_web/templates/address_contract/index.html.eex:128
msgid "Copy ABI"
msgstr ""
@@ -730,8 +730,8 @@ msgid "Copy Address"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/address_contract/index.html.eex:141
-#: lib/block_scout_web/templates/address_contract/index.html.eex:157
+#: lib/block_scout_web/templates/address_contract/index.html.eex:144
+#: lib/block_scout_web/templates/address_contract/index.html.eex:160
msgid "Copy Contract Creation Code"
msgstr ""
@@ -741,8 +741,8 @@ msgid "Copy Decompiled Contract Code"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/address_contract/index.html.eex:184
-#: lib/block_scout_web/templates/address_contract/index.html.eex:194
+#: lib/block_scout_web/templates/address_contract/index.html.eex:187
+#: lib/block_scout_web/templates/address_contract/index.html.eex:197
msgid "Copy Deployed ByteCode"
msgstr ""
@@ -776,8 +776,8 @@ msgid "Copy Raw Trace"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/address_contract/index.html.eex:99
-#: lib/block_scout_web/templates/address_contract/index.html.eex:112
+#: lib/block_scout_web/templates/address_contract/index.html.eex:102
+#: lib/block_scout_web/templates/address_contract/index.html.eex:115
msgid "Copy Source Code"
msgstr ""
@@ -947,8 +947,8 @@ msgid "Delegators’ Staked Amount"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/address_contract/index.html.eex:182
-#: lib/block_scout_web/templates/address_contract/index.html.eex:190
+#: lib/block_scout_web/templates/address_contract/index.html.eex:185
+#: lib/block_scout_web/templates/address_contract/index.html.eex:193
msgid "Deployed ByteCode"
msgstr ""
@@ -971,7 +971,7 @@ msgid "Difficulty"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/address_contract/index.html.eex:146
+#: lib/block_scout_web/templates/address_contract/index.html.eex:149
msgid "Displaying the init data provided of the creating transaction."
msgstr ""
@@ -1017,8 +1017,8 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_token/overview.html.eex:1
-#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:89 lib/block_scout_web/templates/smart_contract/_functions.html.eex:89
-#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:128
+#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:93 lib/block_scout_web/templates/smart_contract/_functions.html.eex:93
+#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:132
msgid "ETH"
msgstr ""
@@ -1028,7 +1028,7 @@ msgid "ETH RPC API Documentation"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/address_contract/index.html.eex:73
+#: lib/block_scout_web/templates/address_contract/index.html.eex:76
#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:40
msgid "EVM Version"
msgstr ""
@@ -1138,7 +1138,7 @@ msgid "Export Data"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/address_contract/index.html.eex:225
+#: lib/block_scout_web/templates/address_contract/index.html.eex:228
msgid "External libraries"
msgstr ""
@@ -1706,12 +1706,12 @@ msgid "OUT"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/address_contract/index.html.eex:58
+#: lib/block_scout_web/templates/address_contract/index.html.eex:61
msgid "Optimization enabled"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/address_contract/index.html.eex:67
+#: lib/block_scout_web/templates/address_contract/index.html.eex:70
#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:72
msgid "Optimization runs"
msgstr ""
@@ -1864,7 +1864,7 @@ msgid "QR Code"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:94
+#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:98
msgid "Query"
msgstr ""
@@ -2265,7 +2265,7 @@ msgid "The block height of a particular block is defined as the number of blocks
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:37
+#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:41
msgid "The fallback function is executed on a call to the contract if none of the other functions match the given function signature, or if no data was supplied at all and there is no receive Ether function. The fallback function always receives data, but in order to also receive Ether it must be marked payable."
msgstr ""
@@ -2315,7 +2315,7 @@ msgid "The percentage of stake in a single pool relative to the total amount sta
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:39
+#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:43
msgid "The receive function is executed on a call to the contract with empty calldata. This is the function that is executed on plain Ether transfers (e.g. via .send() or .transfer()). If no such function exists, but a payable fallback function exists, the fallback function will be called on a plain Ether transfer. If neither a receive Ether nor a payable fallback function is present, the contract cannot receive Ether through regular transactions and throws an exception."
msgstr ""
@@ -2467,12 +2467,12 @@ msgid "This block has not been processed yet."
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/address_contract/index.html.eex:38
+#: lib/block_scout_web/templates/address_contract/index.html.eex:41
msgid "This contract has been partially verified via Sourcify."
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/address_contract/index.html.eex:42
+#: lib/block_scout_web/templates/address_contract/index.html.eex:45
msgid "This contract has been verified via Sourcify."
msgstr ""
@@ -2911,15 +2911,15 @@ msgid "Value sent in the native token (and USD) if applicable."
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/address_contract/index.html.eex:79
+#: lib/block_scout_web/templates/address_contract/index.html.eex:82
msgid "Verified at"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_contract/index.html.eex:27
-#: lib/block_scout_web/templates/address_contract/index.html.eex:29 lib/block_scout_web/templates/address_contract/index.html.eex:161
-#: lib/block_scout_web/templates/address_contract/index.html.eex:167 lib/block_scout_web/templates/address_contract/index.html.eex:198
-#: lib/block_scout_web/templates/address_contract/index.html.eex:204 lib/block_scout_web/templates/smart_contract/_functions.html.eex:13
+#: lib/block_scout_web/templates/address_contract/index.html.eex:29 lib/block_scout_web/templates/address_contract/index.html.eex:164
+#: lib/block_scout_web/templates/address_contract/index.html.eex:170 lib/block_scout_web/templates/address_contract/index.html.eex:201
+#: lib/block_scout_web/templates/address_contract/index.html.eex:207 lib/block_scout_web/templates/smart_contract/_functions.html.eex:14
msgid "Verify & Publish"
msgstr ""
@@ -3004,7 +3004,7 @@ msgid "Vyper contract"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:127
+#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:131
msgid "WEI"
msgstr ""
@@ -3018,6 +3018,11 @@ msgstr ""
msgid "Wallet addresses"
msgstr ""
+#, elixir-format
+#: lib/block_scout_web/templates/common_components/_changed_bytecode_warning.html.eex:3
+msgid "Warning! Contract bytecode has been changed and doesn't match the verified one. Therefore, interaction with this smart contract may be risky."
+msgstr ""
+
#, elixir-format
#: lib/block_scout_web/templates/stakes/_stakes_modal_claim_reward_content.html.eex:2
msgid "We found the following pools you can claim reward from:"
@@ -3054,7 +3059,7 @@ msgid "Working Stake Amount"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:94
+#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:98
msgid "Write"
msgstr ""
@@ -3184,7 +3189,7 @@ msgid "custom RPC"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:37
+#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:41
msgid "fallback"
msgstr ""
@@ -3220,7 +3225,7 @@ msgid "of"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:15
+#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:16
msgid "page"
msgstr ""
@@ -3230,7 +3235,7 @@ msgid "pool owner"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:39
+#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:43
msgid "receive"
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 a8c608569d..df65d5a225 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
@@ -215,7 +215,7 @@ msgid "All"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:12
+#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:13
msgid "All functions displayed below are from ABI of that contract. In order to verify current contract, proceed with"
msgstr ""
@@ -582,7 +582,7 @@ msgid "Compiler"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/address_contract/index.html.eex:62
+#: lib/block_scout_web/templates/address_contract/index.html.eex:65
msgid "Compiler version"
msgstr ""
@@ -633,7 +633,7 @@ msgid "Connection Lost, click to load newer validations"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/address_contract/index.html.eex:87
+#: lib/block_scout_web/templates/address_contract/index.html.eex:90
msgid "Constructor Arguments"
msgstr ""
@@ -643,7 +643,7 @@ msgid "Contract"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/address_contract/index.html.eex:123
+#: lib/block_scout_web/templates/address_contract/index.html.eex:126
msgid "Contract ABI"
msgstr ""
@@ -673,8 +673,8 @@ msgid "Contract Creation"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/address_contract/index.html.eex:139
-#: lib/block_scout_web/templates/address_contract/index.html.eex:154
+#: lib/block_scout_web/templates/address_contract/index.html.eex:142
+#: lib/block_scout_web/templates/address_contract/index.html.eex:157
msgid "Contract Creation Code"
msgstr ""
@@ -692,22 +692,22 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_contract/index.html.eex:25
-#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:10
+#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:11
msgid "Contract is not verified. However, we found a verified contract with the same bytecode in Blockscout DB"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/address_contract/index.html.eex:54
+#: lib/block_scout_web/templates/address_contract/index.html.eex:57
msgid "Contract name:"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/address_contract/index.html.eex:97
+#: lib/block_scout_web/templates/address_contract/index.html.eex:100
msgid "Contract source code"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/address_contract/index.html.eex:145
+#: lib/block_scout_web/templates/address_contract/index.html.eex:148
msgid "Contracts that self destruct in their constructors have no contract code published and cannot be verified."
msgstr ""
@@ -717,7 +717,7 @@ msgid "Contribute"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/address_contract/index.html.eex:125
+#: lib/block_scout_web/templates/address_contract/index.html.eex:128
msgid "Copy ABI"
msgstr ""
@@ -730,8 +730,8 @@ msgid "Copy Address"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/address_contract/index.html.eex:141
-#: lib/block_scout_web/templates/address_contract/index.html.eex:157
+#: lib/block_scout_web/templates/address_contract/index.html.eex:144
+#: lib/block_scout_web/templates/address_contract/index.html.eex:160
msgid "Copy Contract Creation Code"
msgstr ""
@@ -741,8 +741,8 @@ msgid "Copy Decompiled Contract Code"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/address_contract/index.html.eex:184
-#: lib/block_scout_web/templates/address_contract/index.html.eex:194
+#: lib/block_scout_web/templates/address_contract/index.html.eex:187
+#: lib/block_scout_web/templates/address_contract/index.html.eex:197
msgid "Copy Deployed ByteCode"
msgstr ""
@@ -776,8 +776,8 @@ msgid "Copy Raw Trace"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/address_contract/index.html.eex:99
-#: lib/block_scout_web/templates/address_contract/index.html.eex:112
+#: lib/block_scout_web/templates/address_contract/index.html.eex:102
+#: lib/block_scout_web/templates/address_contract/index.html.eex:115
msgid "Copy Source Code"
msgstr ""
@@ -947,8 +947,8 @@ msgid "Delegators’ Staked Amount"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/address_contract/index.html.eex:182
-#: lib/block_scout_web/templates/address_contract/index.html.eex:190
+#: lib/block_scout_web/templates/address_contract/index.html.eex:185
+#: lib/block_scout_web/templates/address_contract/index.html.eex:193
msgid "Deployed ByteCode"
msgstr ""
@@ -971,7 +971,7 @@ msgid "Difficulty"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/address_contract/index.html.eex:146
+#: lib/block_scout_web/templates/address_contract/index.html.eex:149
msgid "Displaying the init data provided of the creating transaction."
msgstr ""
@@ -1017,8 +1017,8 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_token/overview.html.eex:1
-#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:89 lib/block_scout_web/templates/smart_contract/_functions.html.eex:89
-#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:128
+#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:93 lib/block_scout_web/templates/smart_contract/_functions.html.eex:93
+#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:132
msgid "ETH"
msgstr ""
@@ -1028,7 +1028,7 @@ msgid "ETH RPC API Documentation"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/address_contract/index.html.eex:73
+#: lib/block_scout_web/templates/address_contract/index.html.eex:76
#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:40
msgid "EVM Version"
msgstr ""
@@ -1138,7 +1138,7 @@ msgid "Export Data"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/address_contract/index.html.eex:225
+#: lib/block_scout_web/templates/address_contract/index.html.eex:228
msgid "External libraries"
msgstr ""
@@ -1706,12 +1706,12 @@ msgid "OUT"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/address_contract/index.html.eex:58
+#: lib/block_scout_web/templates/address_contract/index.html.eex:61
msgid "Optimization enabled"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/address_contract/index.html.eex:67
+#: lib/block_scout_web/templates/address_contract/index.html.eex:70
#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:72
msgid "Optimization runs"
msgstr ""
@@ -1864,7 +1864,7 @@ msgid "QR Code"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:94
+#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:98
msgid "Query"
msgstr ""
@@ -2265,7 +2265,7 @@ msgid "The block height of a particular block is defined as the number of blocks
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:37
+#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:41
msgid "The fallback function is executed on a call to the contract if none of the other functions match the given function signature, or if no data was supplied at all and there is no receive Ether function. The fallback function always receives data, but in order to also receive Ether it must be marked payable."
msgstr ""
@@ -2315,7 +2315,7 @@ msgid "The percentage of stake in a single pool relative to the total amount sta
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:39
+#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:43
msgid "The receive function is executed on a call to the contract with empty calldata. This is the function that is executed on plain Ether transfers (e.g. via .send() or .transfer()). If no such function exists, but a payable fallback function exists, the fallback function will be called on a plain Ether transfer. If neither a receive Ether nor a payable fallback function is present, the contract cannot receive Ether through regular transactions and throws an exception."
msgstr ""
@@ -2467,12 +2467,12 @@ msgid "This block has not been processed yet."
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/address_contract/index.html.eex:38
+#: lib/block_scout_web/templates/address_contract/index.html.eex:41
msgid "This contract has been partially verified via Sourcify."
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/address_contract/index.html.eex:42
+#: lib/block_scout_web/templates/address_contract/index.html.eex:45
msgid "This contract has been verified via Sourcify."
msgstr ""
@@ -2911,15 +2911,15 @@ msgid "Value sent in the native token (and USD) if applicable."
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/address_contract/index.html.eex:79
+#: lib/block_scout_web/templates/address_contract/index.html.eex:82
msgid "Verified at"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_contract/index.html.eex:27
-#: lib/block_scout_web/templates/address_contract/index.html.eex:29 lib/block_scout_web/templates/address_contract/index.html.eex:161
-#: lib/block_scout_web/templates/address_contract/index.html.eex:167 lib/block_scout_web/templates/address_contract/index.html.eex:198
-#: lib/block_scout_web/templates/address_contract/index.html.eex:204 lib/block_scout_web/templates/smart_contract/_functions.html.eex:13
+#: lib/block_scout_web/templates/address_contract/index.html.eex:29 lib/block_scout_web/templates/address_contract/index.html.eex:164
+#: lib/block_scout_web/templates/address_contract/index.html.eex:170 lib/block_scout_web/templates/address_contract/index.html.eex:201
+#: lib/block_scout_web/templates/address_contract/index.html.eex:207 lib/block_scout_web/templates/smart_contract/_functions.html.eex:14
msgid "Verify & Publish"
msgstr ""
@@ -3004,7 +3004,7 @@ msgid "Vyper contract"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:127
+#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:131
msgid "WEI"
msgstr ""
@@ -3018,6 +3018,11 @@ msgstr ""
msgid "Wallet addresses"
msgstr ""
+#, elixir-format
+#: lib/block_scout_web/templates/common_components/_changed_bytecode_warning.html.eex:3
+msgid "Warning! Contract bytecode has been changed and doesn't match the verified one. Therefore, interaction with this smart contract may be risky."
+msgstr ""
+
#, elixir-format
#: lib/block_scout_web/templates/stakes/_stakes_modal_claim_reward_content.html.eex:2
msgid "We found the following pools you can claim reward from:"
@@ -3054,7 +3059,7 @@ msgid "Working Stake Amount"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:94
+#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:98
msgid "Write"
msgstr ""
@@ -3184,7 +3189,7 @@ msgid "custom RPC"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:37
+#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:41
msgid "fallback"
msgstr ""
@@ -3220,7 +3225,7 @@ msgid "of"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:15
+#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:16
msgid "page"
msgstr ""
@@ -3230,7 +3235,7 @@ msgid "pool owner"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:39
+#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:43
msgid "receive"
msgstr ""
diff --git a/apps/block_scout_web/test/block_scout_web/controllers/address_read_contract_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/address_read_contract_controller_test.exs
index 102df87ebe..e13af34d94 100644
--- a/apps/block_scout_web/test/block_scout_web/controllers/address_read_contract_controller_test.exs
+++ b/apps/block_scout_web/test/block_scout_web/controllers/address_read_contract_controller_test.exs
@@ -85,6 +85,12 @@ defmodule BlockScoutWeb.AddressReadContractControllerTest do
def get_eip1967_implementation do
EthereumJSONRPC.Mox
+ |> expect(
+ :json_rpc,
+ fn [%{id: id, method: "eth_getCode", params: [_, _]}], _options ->
+ {:ok, [%{id: id, jsonrpc: "2.0", result: "0x0"}]}
+ end
+ )
|> expect(:json_rpc, fn %{
id: 0,
method: "eth_getStorageAt",
diff --git a/apps/block_scout_web/test/block_scout_web/controllers/address_read_proxy_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/address_read_proxy_controller_test.exs
index 2c2075461a..dd8781f993 100644
--- a/apps/block_scout_web/test/block_scout_web/controllers/address_read_proxy_controller_test.exs
+++ b/apps/block_scout_web/test/block_scout_web/controllers/address_read_proxy_controller_test.exs
@@ -82,6 +82,12 @@ defmodule BlockScoutWeb.AddressReadProxyControllerTest do
def get_eip1967_implementation do
EthereumJSONRPC.Mox
+ |> expect(
+ :json_rpc,
+ fn [%{id: id, method: "eth_getCode", params: [_, _]}], _options ->
+ {:ok, [%{id: id, jsonrpc: "2.0", result: "0x0"}]}
+ end
+ )
|> expect(:json_rpc, fn %{
id: 0,
method: "eth_getStorageAt",
diff --git a/apps/block_scout_web/test/block_scout_web/controllers/address_write_contract_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/address_write_contract_controller_test.exs
index 62a3b4d0a6..06a0c070f1 100644
--- a/apps/block_scout_web/test/block_scout_web/controllers/address_write_contract_controller_test.exs
+++ b/apps/block_scout_web/test/block_scout_web/controllers/address_write_contract_controller_test.exs
@@ -86,6 +86,12 @@ defmodule BlockScoutWeb.AddressWriteContractControllerTest do
def get_eip1967_implementation do
EthereumJSONRPC.Mox
+ |> expect(
+ :json_rpc,
+ fn [%{id: id, method: "eth_getCode", params: [_, _]}], _options ->
+ {:ok, [%{id: id, jsonrpc: "2.0", result: "0x0"}]}
+ end
+ )
|> expect(:json_rpc, fn %{
id: 0,
method: "eth_getStorageAt",
diff --git a/apps/block_scout_web/test/block_scout_web/controllers/address_write_proxy_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/address_write_proxy_controller_test.exs
index 481ecfcadb..17dfcdd982 100644
--- a/apps/block_scout_web/test/block_scout_web/controllers/address_write_proxy_controller_test.exs
+++ b/apps/block_scout_web/test/block_scout_web/controllers/address_write_proxy_controller_test.exs
@@ -84,6 +84,12 @@ defmodule BlockScoutWeb.AddressWriteProxyControllerTest do
def get_eip1967_implementation do
EthereumJSONRPC.Mox
+ |> expect(
+ :json_rpc,
+ fn [%{id: id, method: "eth_getCode", params: [_, _]}], _options ->
+ {:ok, [%{id: id, jsonrpc: "2.0", result: "0x0"}]}
+ end
+ )
|> expect(:json_rpc, fn %{
id: 0,
method: "eth_getStorageAt",
diff --git a/apps/block_scout_web/test/block_scout_web/controllers/smart_contract_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/smart_contract_controller_test.exs
index 2dc0ac8a39..07e1124311 100644
--- a/apps/block_scout_web/test/block_scout_web/controllers/smart_contract_controller_test.exs
+++ b/apps/block_scout_web/test/block_scout_web/controllers/smart_contract_controller_test.exs
@@ -47,6 +47,7 @@ defmodule BlockScoutWeb.SmartContractControllerTest do
insert(:smart_contract, address_hash: token_contract_address.hash)
+ blockchain_get_code_mock()
blockchain_get_function_mock()
path =
@@ -83,6 +84,8 @@ defmodule BlockScoutWeb.SmartContractControllerTest do
]
)
+ blockchain_get_code_mock()
+
path =
smart_contract_path(BlockScoutWeb.Endpoint, :index,
hash: token_contract_address.hash,
@@ -117,6 +120,7 @@ defmodule BlockScoutWeb.SmartContractControllerTest do
]
)
+ blockchain_get_code_mock()
blockchain_get_implementation_mock()
path =
@@ -153,6 +157,7 @@ defmodule BlockScoutWeb.SmartContractControllerTest do
]
)
+ blockchain_get_code_mock()
blockchain_get_implementation_mock_2()
path =
@@ -232,6 +237,7 @@ defmodule BlockScoutWeb.SmartContractControllerTest do
address = insert(:contract_address)
smart_contract = insert(:smart_contract, address_hash: address.hash)
+ blockchain_get_code_mock()
blockchain_get_function_mock()
path =
@@ -269,6 +275,16 @@ defmodule BlockScoutWeb.SmartContractControllerTest do
)
end
+ defp blockchain_get_code_mock do
+ expect(
+ EthereumJSONRPC.Mox,
+ :json_rpc,
+ fn [%{id: id, method: "eth_getCode", params: [_, _]}], _options ->
+ {:ok, [%{id: id, jsonrpc: "2.0", result: "0x0"}]}
+ end
+ )
+ end
+
defp blockchain_get_implementation_mock do
expect(
EthereumJSONRPC.Mox,
diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex
index 3c3a8893f7..cd74312f35 100644
--- a/apps/explorer/lib/explorer/chain.ex
+++ b/apps/explorer/lib/explorer/chain.ex
@@ -101,6 +101,9 @@ defmodule Explorer.Chain do
@burn_address_hash_str "0x0000000000000000000000000000000000000000"
+ # seconds
+ @check_bytecode_interval 86_400
+
@typedoc """
The name of an association on the `t:Ecto.Schema.t/0`
"""
@@ -1700,7 +1703,7 @@ defmodule Explorer.Chain do
case address_result do
%{smart_contract: smart_contract} ->
if smart_contract do
- address_result
+ check_bytecode_matching(address_result)
else
address_verified_twin_contract =
Chain.get_minimal_proxy_template(hash) ||
@@ -1730,6 +1733,46 @@ defmodule Explorer.Chain do
end
end
+ defp check_bytecode_matching(address) do
+ now = DateTime.utc_now()
+ json_rpc_named_arguments = Application.get_env(:explorer, :json_rpc_named_arguments)
+
+ if !address.smart_contract.is_changed_bytecode and
+ address.smart_contract.bytecode_checked_at
+ |> DateTime.add(@check_bytecode_interval, :second)
+ |> DateTime.compare(now) != :gt do
+ case EthereumJSONRPC.fetch_codes(
+ [%{block_quantity: "latest", address: address.smart_contract.address_hash}],
+ json_rpc_named_arguments
+ ) do
+ {:ok, %EthereumJSONRPC.FetchedCodes{params_list: fetched_codes}} ->
+ bytecode_from_node = fetched_codes |> List.first() |> Map.get(:code)
+ bytecode_from_db = "0x" <> (address.contract_code.bytes |> Base.encode16(case: :lower))
+
+ if bytecode_from_node == bytecode_from_db do
+ {:ok, smart_contract} =
+ address.smart_contract
+ |> Changeset.change(%{bytecode_checked_at: now})
+ |> Repo.update()
+
+ %{address | smart_contract: smart_contract}
+ else
+ {:ok, smart_contract} =
+ address.smart_contract
+ |> Changeset.change(%{bytecode_checked_at: now, is_changed_bytecode: true})
+ |> Repo.update()
+
+ %{address | smart_contract: smart_contract}
+ end
+
+ _ ->
+ address
+ end
+ else
+ address
+ end
+ end
+
@spec find_decompiled_contract_address(Hash.Address.t()) :: {:ok, Address.t()} | {:error, :not_found}
def find_decompiled_contract_address(%Hash{byte_count: unquote(Hash.Address.byte_count())} = hash) do
query =
diff --git a/apps/explorer/lib/explorer/chain/smart_contract.ex b/apps/explorer/lib/explorer/chain/smart_contract.ex
index 5c52bd5a06..502a7e8088 100644
--- a/apps/explorer/lib/explorer/chain/smart_contract.ex
+++ b/apps/explorer/lib/explorer/chain/smart_contract.ex
@@ -195,6 +195,9 @@ defmodule Explorer.Chain.SmartContract do
* `verified_via_sourcify` - whether contract verified through Sourcify utility or not.
* `partially_verified` - whether contract verified using partial matched source code or not.
* `is_vyper_contract` - boolean flag, determines if contract is Vyper or not
+ * `file_path` - show the filename or path to the file of the contract source file
+ * `is_changed_bytecode` - boolean flag, determines if contract's bytecode was modified
+ * `bytecode_checked_at` - timestamp of the last check of contract's bytecode matching (DB and BlockChain)
"""
@type t :: %Explorer.Chain.SmartContract{
@@ -209,7 +212,9 @@ defmodule Explorer.Chain.SmartContract do
verified_via_sourcify: boolean | nil,
partially_verified: boolean | nil,
file_path: String.t(),
- is_vyper_contract: boolean | nil
+ is_vyper_contract: boolean | nil,
+ is_changed_bytecode: boolean,
+ bytecode_checked_at: DateTime.t()
}
schema "smart_contracts" do
@@ -226,6 +231,8 @@ defmodule Explorer.Chain.SmartContract do
field(:partially_verified, :boolean)
field(:file_path, :string)
field(:is_vyper_contract, :boolean)
+ field(:is_changed_bytecode, :boolean, default: false)
+ field(:bytecode_checked_at, :utc_datetime_usec, default: DateTime.add(DateTime.utc_now(), -86400, :second))
has_many(
:decompiled_smart_contracts,
@@ -263,7 +270,9 @@ defmodule Explorer.Chain.SmartContract do
:verified_via_sourcify,
:partially_verified,
:file_path,
- :is_vyper_contract
+ :is_vyper_contract,
+ :is_changed_bytecode,
+ :bytecode_checked_at
])
|> validate_required([:name, :compiler_version, :optimization, :contract_source_code, :abi, :address_hash])
|> unique_constraint(:address_hash)
@@ -291,7 +300,9 @@ defmodule Explorer.Chain.SmartContract do
:verified_via_sourcify,
:partially_verified,
:file_path,
- :is_vyper_contract
+ :is_vyper_contract,
+ :is_changed_bytecode,
+ :bytecode_checked_at
])
|> (&if(json_verification,
do: &1,
diff --git a/apps/explorer/priv/repo/migrations/20211115164817_add_check_for_bytecode_actuality.exs b/apps/explorer/priv/repo/migrations/20211115164817_add_check_for_bytecode_actuality.exs
new file mode 100644
index 0000000000..dfb86074be
--- /dev/null
+++ b/apps/explorer/priv/repo/migrations/20211115164817_add_check_for_bytecode_actuality.exs
@@ -0,0 +1,13 @@
+defmodule Explorer.Repo.Migrations.AddCheckForBytecodeActuality do
+ use Ecto.Migration
+
+ def change do
+ alter table(:smart_contracts) do
+ add(:is_changed_bytecode, :boolean, default: false)
+ # subtracting 1 day to perform first check
+ add(:bytecode_checked_at, :"timestamp without time zone",
+ default: fragment("(NOW() AT TIME ZONE 'utc') - INTERVAL '1 DAY'")
+ )
+ end
+ end
+end
diff --git a/apps/explorer/test/explorer/chain/log_test.exs b/apps/explorer/test/explorer/chain/log_test.exs
index f670c83873..8a6fecb492 100644
--- a/apps/explorer/test/explorer/chain/log_test.exs
+++ b/apps/explorer/test/explorer/chain/log_test.exs
@@ -1,6 +1,8 @@
defmodule Explorer.Chain.LogTest do
use Explorer.DataCase
+ import Mox
+
alias Ecto.Changeset
alias Explorer.Chain.{Log, SmartContract}
alias Explorer.Repo
@@ -99,6 +101,8 @@ defmodule Explorer.Chain.LogTest do
data: data
)
+ blockchain_get_code_mock()
+
assert Log.decode(log, transaction) ==
{:ok, "eb9b3c4c", "WantsPets(string indexed _from_human, uint256 _number, bool indexed _belly)",
[
@@ -167,4 +171,14 @@ defmodule Explorer.Chain.LogTest do
]}
end
end
+
+ defp blockchain_get_code_mock do
+ expect(
+ EthereumJSONRPC.Mox,
+ :json_rpc,
+ fn [%{id: id, method: "eth_getCode", params: [_, _]}], _options ->
+ {:ok, [%{id: id, jsonrpc: "2.0", result: "0x0"}]}
+ end
+ )
+ end
end