Skip to content

Commit

Permalink
Smart contract verification improvements (blockscout#6481)
Browse files Browse the repository at this point in the history
* Refactor smart contract verification form; Add support for smart contract verification without creation bytecode

* Add compiler settings for smart contracts verified via standard JSON input or Sourcify

* Add verified twin name as fallback name for unverified contract; Fix compiler label
  • Loading branch information
nikitosing authored and cedricfung committed Dec 2, 2022
1 parent a7e2ade commit 2f6d37b
Show file tree
Hide file tree
Showing 25 changed files with 287 additions and 150 deletions.
2 changes: 1 addition & 1 deletion .dialyzer-ignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ lib/explorer/smart_contract/reader.ex:435
lib/indexer/fetcher/token_total_supply_on_demand.ex:16
lib/explorer/exchange_rates/source.ex:116
lib/explorer/exchange_rates/source.ex:119
lib/explorer/smart_contract/solidity/verifier.ex:316
lib/explorer/smart_contract/solidity/verifier.ex:317
lib/block_scout_web/templates/address_contract/index.html.eex:158
lib/block_scout_web/templates/address_contract/index.html.eex:195
lib/explorer/third_party_integrations/sourcify.ex:120
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
### Features

- [#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
- [#6440](https://github.com/blockscout/blockscout/pull/6440) - Add support for base64 encoded NFT metadata
- [#6407](https://github.com/blockscout/blockscout/pull/6407) - Indexed ratio for int txs fetching stage
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,22 @@
</section>
<% end)%>

<%= if !is_nil(target_contract.abi) do %>>
<%= if !is_nil(target_contract.compiler_settings) do %>
<section>
<div class="d-flex justify-content-between align-items-baseline">
<h3><%= gettext "Compiler Settings" %></h3>
<button type="button" class="btn-line" id="button" data-clipboard-text="<%= format_smart_contract_abi(target_contract.compiler_settings) %>" aria-label="Copy Compiler Settings">
<%= gettext "Copy Compiler Settings" %>
</button>
</div>
<div class="tile tile-muted mb-4">
<pre class="pre-wrap pre-scrollable"><code class="nohighlight"><%= format_smart_contract_abi(target_contract.compiler_settings) %></code>
</pre>
</div>
</section>
<% end %>

<%= if !is_nil(target_contract.abi) do %>
<section>
<div class="d-flex justify-content-between align-items-baseline">
<h3><%= gettext "Contract ABI" %></h3>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<div class="smart-contract-form-group">
<div class="smart-contract-form-group-inner-wrapper">
<%= label @f, :compiler_version, gettext("Compiler") %>
<%= label :smart_contract, :compiler_version, gettext("Compiler") %>
<div class="center-column">
<%= select @f, :compiler_version, @compiler_versions, class: "form-control border-rounded", "aria-describedby": "compiler-help-block", id: "smart_contract_compiler_version" %>
<%= error_tag @f, :compiler_version, id: "compiler-help-block", class: "text-danger form-error" %>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
<div class="smart-contract-form-group">
<div class="smart-contract-form-group-inner-wrapper">
<%= label @f, "Try to fetch constructor arguments automatically" %>
<%= label @f, :autodetect_constructor_args, gettext("Try to fetch constructor arguments automatically") %>
<div class="center-column">
<div class="form-radios-group">
<div class="radio-big">
<%= radio_button @f, :autodetect_constructor_args, false, class: "form-check-input autodetectfalse" %>
<div class="radio-icon"></div>
<%= label :autodetect_constructor_args, :false, gettext("No"), class: "radio-text" %>
<%= label @f, :autodetect_constructor_args_false, gettext("No"), class: "radio-text" %>
</div>
<div class="radio-big">
<%= radio_button @f, :autodetect_constructor_args, true, class: "form-check-input autodetecttrue", "aria-describedby": "autodetect_constructor_args-help-block" %>
<div class="radio-icon"></div>
<%= label :autodetect_constructor_args, :true, gettext("Yes"), class: "radio-text" %>
<%= label @f, :autodetect_constructor_args_true, gettext("Yes"), class: "radio-text" %>
</div>
</div>
<%= error_tag @f, :autodetect_constructor_args, id: "autodetect_constructor_args-help-block", class: "text-danger form-error" %>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
<div class="smart-contract-form-group">
<div class="smart-contract-form-group-inner-wrapper">
<%= label @f, "Include nightly builds" %>
<div class="center-column">
<div class="form-radios-group">
<div class="radio-big">
<%= radio_button @f, :nightly_builds, false, checked: true, class: "form-check-input nightly-builds-false" %>
<div class="radio-icon"></div>
<%= label :nightly_builds, :false, gettext("No"), class: "radio-text" %>
</div>
<div class="radio-big">
<%= radio_button @f, :nightly_builds, true, class: "form-check-input nightly-builds-true", "aria-describedby": "nightly_builds-help-block" %>
<div class="radio-icon"></div>
<%= label :nightly_builds, :true, gettext("Yes"), class: "radio-text" %>
</div>
</div>
<%= error_tag @f, :nightly_builds, id: "nightly_builds-help-block", class: "text-danger form-error" %>
<div class="smart-contract-form-group-inner-wrapper">
<%= label @f, :nightly_builds, gettext("Include nightly builds") %>
<div class="center-column">
<div class="form-radios-group">
<div class="radio-big">
<%= radio_button @f, :nightly_builds, false, checked: true, class: "form-check-input nightly-builds-false" %>
<div class="radio-icon"></div>
<%= label @f, :nightly_builds_false, gettext("No"), class: "radio-text" %>
</div>
<div class="radio-big">
<%= radio_button @f, :nightly_builds, true, class: "form-check-input nightly-builds-true", "aria-describedby": "nightly_builds-help-block" %>
<div class="radio-icon"></div>
<%= label @f, :nightly_builds_true, gettext("Yes"), class: "radio-text" %>
</div>
<div class="smart-contract-form-group-tooltip">Select yes if you want to show nightly builds.</div>
</div>
<%= error_tag @f, :nightly_builds, id: "nightly_builds-help-block", class: "text-danger form-error" %>
</div>
<div class="smart-contract-form-group-tooltip"><%= gettext("Select yes if you want to show nightly builds.") %></div>
</div>
</div>
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
<%= for library_index <- 2..Application.get_env(:block_scout_web, :verification_max_libraries) do %>
<% library = "library" <> to_string(library_index) |> String.to_atom() %>
<div class="contract-library-form-group js-contract-library-form-group">
<%= render BlockScoutWeb.AddressContractVerificationCommonFieldsView, "_library_name.html", library: library, index: library_index %>
<div class="contract-library-form-group js-contract-library-form-group">
<%= render BlockScoutWeb.AddressContractVerificationCommonFieldsView, "_library_name.html", index: library_index %>

<%= render BlockScoutWeb.AddressContractVerificationCommonFieldsView, "_library_address.html", library: library, index: library_index %>
</div>
<%= render BlockScoutWeb.AddressContractVerificationCommonFieldsView, "_library_address.html", index: library_index %>
</div>
<% end %>
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
<% library_address = "library" <> to_string(@index) <> "_address" |> String.to_atom() %>
<div class="smart-contract-form-group">
<div class="smart-contract-form-group-inner-wrapper">
<%= label :external_libraries, @library, gettext("Library") <> " " <> to_string(@index) <> " " <> gettext("Address") %>
<div class="smart-contract-form-group-inner-wrapper">
<%= label :external_libraries, library_address, gettext("Library") <> " " <> to_string(@index) <> " " <> gettext("Address") %>
<div class="center-column">
<%= text_input :external_libraries, library_address, class: "form-control border-rounded", "aria-describedby": "contract-name-help-block" %>
<%= text_input :external_libraries, library_address, class: "form-control border-rounded", "aria-describedby": "contract-name-help-block" %>
</div>
<div class="smart-contract-form-group-tooltip"><%= if assigns[:tooltip_text] do @tooltip_text end %></div>
</div>
</div>
</div>
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
<div class="contract-library-form-group js-contract-library-form-group active">
<%= render BlockScoutWeb.AddressContractVerificationCommonFieldsView, "_library_name.html",
library: :library1,
index: 1,
tooltip_text: gettext("A library name called in the .sol file. Multiple libraries (up to ") <> to_string(Application.get_env(:block_scout_web, :verification_max_libraries)) <> gettext(") may be added for each contract. Click the Add Library button to add an additional one.")
%>
<%= render BlockScoutWeb.AddressContractVerificationCommonFieldsView, "_library_name.html",
index: 1,
tooltip_text: gettext("A library name called in the .sol file. Multiple libraries (up to ") <> to_string(Application.get_env(:block_scout_web, :verification_max_libraries)) <> gettext(") may be added for each contract. Click the Add Library button to add an additional one.")
%>

<%= render BlockScoutWeb.AddressContractVerificationCommonFieldsView, "_library_address.html",
library: :library1,
index: 1,
tooltip_text: gettext "The 0x library address. This can be found in the generated json file or Truffle output (if using truffle)."
%>
<%= render BlockScoutWeb.AddressContractVerificationCommonFieldsView, "_library_address.html",
index: 1,
tooltip_text: gettext "The 0x library address. This can be found in the generated json file or Truffle output (if using truffle)."
%>
</div>
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
<% library_name = "library" <> to_string(@index) <> "_name" |> String.to_atom() %>
<div class="smart-contract-form-group">
<div class="smart-contract-form-group-inner-wrapper">
<%= label :external_libraries, @library, gettext("Library") <> " " <> to_string(@index) <> " " <> gettext("Name") %>
<div class="center-column">
<%= text_input :external_libraries, library_name, class: "form-control border-rounded", "aria-describedby": "contract-name-help-block" %>
</div>
<div class="smart-contract-form-group-tooltip"><%= if assigns[:tooltip_text] do @tooltip_text end %></div>
</div>
<div class="smart-contract-form-group-inner-wrapper">
<%= label :external_libraries, library_name, gettext("Library") <> " " <> to_string(@index) <> " " <> gettext("Name") %>
<div class="center-column">
<%= text_input :external_libraries, library_name, class: "form-control border-rounded", "aria-describedby": "contract-name-help-block" %>
</div>
<div class="smart-contract-form-group-tooltip"><%= if assigns[:tooltip_text] do @tooltip_text end %></div>
</div>
</div>
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@
<div class="radio-big">
<%= radio_button @f, :is_yul, false, class: "form-check-input autodetectfalse" %>
<div class="radio-icon"></div>
<%= label :is_yul, :false, gettext("No"), class: "radio-text" %>
<%= label @f, :is_yul_false, gettext("No"), class: "radio-text" %>
</div>
<div class="radio-big">
<%= radio_button @f, :is_yul, true, class: "form-check-input autodetecttrue", "aria-describedby": "is_yul-help-block" %>
<div class="radio-icon"></div>
<%= label :is_yul, :true, gettext("Yes"), class: "radio-text" %>
<%= label @f, :is_yul_true, gettext("Yes"), class: "radio-text" %>
</div>
</div>
<%= error_tag @f, :is_yul, id: "is_yul-help-block", class: "text-danger form-error" %>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<% metadata_for_verification = if assigns[:retrying], do: nil, else: Chain.get_address_verified_twin_contract(@address_hash).verified_contract %>
<% changeset = (if assigns[:retrying], do: @changeset, else: SmartContract.merge_twin_contract_with_changeset(metadata_for_verification, @changeset)) |> SmartContract.address_to_checksum_address() %>
<% fetch_constructor_arguments_automatically = if metadata_for_verification, do: true, else: changeset.changes.autodetect_constructor_args %>
<% fetch_constructor_arguments_automatically = if metadata_for_verification, do: true, else: changeset.changes[:autodetect_constructor_args] || true %>
<% display_constructor_arguments_text_area = if fetch_constructor_arguments_automatically, do: "none", else: "block" %>
<section data-page="contract-verification" class="container new-smart-contract-container">
<%= render BlockScoutWeb.CommonComponentsView, "_channel_disconnected_message.html", text: gettext("Connection Lost") %>
Expand All @@ -27,7 +27,7 @@

<div class="smart-contract-form-group">
<div class="smart-contract-form-group-inner-wrapper">
<%= label :evm_version, :evm_version, gettext("EVM Version") %>
<%= label f, :evm_version, gettext("EVM Version") %>
<div class="center-column">
<%= select f, :evm_version, @evm_versions, class: "form-control border-rounded", "aria-describedby": "evm-version-help-block" %>
</div>
Expand All @@ -37,7 +37,7 @@

<div class="smart-contract-form-group">
<div class="smart-contract-form-group-inner-wrapper">
<%= label f, "Optimization" %>
<%= label f, :optimization, gettext("Optimization") %>
<div class="center-column">
<div class="form-radios-group">
<div class="radio-big">
Expand All @@ -59,7 +59,7 @@

<div class="smart-contract-form-group optimization-runs" style="<%= if !changeset.changes.optimization, do: "display: none;"%>">
<div class="smart-contract-form-group-inner-wrapper">
<%= label f, :name, gettext("Optimization runs") %>
<%= label f, :optimization_runs, gettext("Optimization runs") %>
<div class="center-column">
<%= text_input f, :optimization_runs, class: "form-control border-rounded", "aria-describedby": "optimization-runs-help-block", "data-test": "optimization-runs" %>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@

<div class="smart-contract-form-group">
<div class="smart-contract-form-group-inner-wrapper">
<%= label :evm_version, :evm_version, gettext("EVM Version") %>
<%= label f, :evm_version, gettext("EVM Version") %>
<div class="center-column">
<%= select f, :evm_version, @evm_versions, class: "form-control border-rounded", "aria-describedby": "evm-version-help-block" %>
</div>
Expand All @@ -29,18 +29,18 @@

<div class="smart-contract-form-group">
<div class="smart-contract-form-group-inner-wrapper">
<%= label f, "Optimization" %>
<%= label f, :optimization, gettext("Optimization") %>
<div class="center-column">
<div class="form-radios-group">
<div class="radio-big">
<%= radio_button f, :optimization, false, class: "form-check-input optimization-false" %>
<div class="radio-icon"></div>
<%= label :smart_contract_optimization, :false, gettext("No"), class: "radio-text" %>
<%= label f, :optimization_false, gettext("No"), class: "radio-text" %>
</div>
<div class="radio-big">
<%= radio_button f, :optimization, true, class: "form-check-input optimization-true", "aria-describedby": "optimization-help-block" %>
<div class="radio-icon"></div>
<%= label :smart_contract_optimization, :true, gettext("Yes"), class: "radio-text" %>
<%= label f, :optimization_true, gettext("Yes"), class: "radio-text" %>
</div>
</div>
<%= error_tag f, :optimization, id: "optimization-help-block", class: "text-danger form-error" %>
Expand All @@ -51,7 +51,7 @@

<div class="smart-contract-form-group optimization-runs" style="<%= if !changeset.changes.optimization, do: "display: none;"%>">
<div class="smart-contract-form-group-inner-wrapper">
<%= label f, :name, gettext("Optimization runs") %>
<%= label f, :optimization_runs, gettext("Optimization runs") %>
<div class="center-column">
<%= text_input f, :optimization_runs, class: "form-control border-rounded", "aria-describedby": "optimization-runs-help-block", "data-test": "optimization-runs" %>
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<% metadata_for_verification = Chain.get_address_verified_twin_contract(@address_hash).verified_contract %>
<% changeset = (if assigns[:retrying], do: @changeset, else: SmartContract.merge_twin_contract_with_changeset(metadata_for_verification, @changeset)) |> SmartContract.address_to_checksum_address() %>
<% fetch_constructor_arguments_automatically = if metadata_for_verification, do: true, else: changeset.changes.autodetect_constructor_args %>
<% fetch_constructor_arguments_automatically = if metadata_for_verification, do: true, else: changeset.changes[:autodetect_constructor_args] || true %>
<% display_constructor_arguments_text_area = if fetch_constructor_arguments_automatically, do: "none", else: "block" %>
<section data-page="contract-verification" class="container new-smart-contract-container">
<%= render BlockScoutWeb.CommonComponentsView, "_channel_disconnected_message.html", text: gettext("Connection Lost") %>
Expand Down
19 changes: 13 additions & 6 deletions apps/block_scout_web/lib/block_scout_web/views/address_view.ex
Original file line number Diff line number Diff line change
Expand Up @@ -181,9 +181,7 @@ defmodule BlockScoutWeb.AddressView do
@doc """
Returns the primary name of an address if available. If there is no names on address function performs preload of names association.
"""
def primary_name(_, second_time? \\ false)

def primary_name(%Address{names: [_ | _] = address_names}, _second_time?) do
def primary_name(%Address{names: [_ | _] = address_names}) do
case Enum.find(address_names, &(&1.primary == true)) do
nil ->
%Address.Name{name: name} = Enum.at(address_names, 0)
Expand All @@ -194,11 +192,20 @@ defmodule BlockScoutWeb.AddressView do
end
end

def primary_name(%Address{names: _} = address, false) do
primary_name(Repo.preload(address, [:names]), true)
def primary_name(%Address{names: %Ecto.Association.NotLoaded{}} = address) do
primary_name(Repo.preload(address, [:names]))
end

def primary_name(%Address{names: _}, true), do: nil
def primary_name(%Address{names: _} = address) do
with false <- is_nil(address.contract_code),
twin <- Chain.get_verified_twin_contract(address),
false <- is_nil(twin) do
twin.name
else
_ ->
nil
end
end

def implementation_name(%Address{smart_contract: %{implementation_name: implementation_name}}),
do: implementation_name
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,18 @@ defmodule BlockScoutWeb.API.RPC.ContractView do
|> set_external_libraries(contract)
|> set_verified_contract_data(contract, address, optimization)
|> set_proxy_info(contract)
|> set_compiler_settings(contract)
end

defp set_compiler_settings(contract_output, contract) when contract == %{}, do: contract_output

defp set_compiler_settings(contract_output, contract) do
if is_nil(contract.compiler_settings) do
contract_output
else
contract_output
|> Map.put(:CompilerSettings, contract.compiler_settings)
end
end

defp set_proxy_info(contract_output, contract) when contract == %{} do
Expand Down
Loading

0 comments on commit 2f6d37b

Please sign in to comment.