Add verification via standard JSON input

pull/4908/head
nikitosing 3 years ago
parent bef614825d
commit 9afeaf2708
  1. 6
      .dialyzer-ignore
  2. 2
      .github/workflows/config.yml
  3. 1
      CHANGELOG.md
  4. 77
      apps/block_scout_web/assets/js/pages/verification_form.js
  5. 33
      apps/block_scout_web/lib/block_scout_web/controllers/address_contract_verification_controller.ex
  6. 40
      apps/block_scout_web/lib/block_scout_web/controllers/address_contract_verification_via_standard_json_input_controller.ex
  7. 54
      apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/contract_controller.ex
  8. 127
      apps/block_scout_web/lib/block_scout_web/etherscan.ex
  9. 50
      apps/block_scout_web/lib/block_scout_web/notifier.ex
  10. 39
      apps/block_scout_web/lib/block_scout_web/templates/address_contract/index.html.eex
  11. 18
      apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification/new.html.eex
  12. 10
      apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification_common_fields/_compiler_field.html.eex
  13. 10
      apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification_common_fields/_constructor_args.html.eex
  14. 9
      apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification_common_fields/_contract_address_field.html.eex
  15. 10
      apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification_common_fields/_contract_name_field.html.eex
  16. 20
      apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification_common_fields/_fetch_constructor_args.html.eex
  17. 21
      apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification_common_fields/_include_nightly_builds_field.html.eex
  18. 76
      apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex
  19. 70
      apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification_via_standard_json_input/new.html.eex
  20. 3
      apps/block_scout_web/lib/block_scout_web/templates/layout/app.html.eex
  21. 3
      apps/block_scout_web/lib/block_scout_web/views/address_contract_verification_common_fields_view.ex
  22. 4
      apps/block_scout_web/lib/block_scout_web/views/address_contract_verification_via_standard_json_input_view.ex
  23. 4
      apps/block_scout_web/lib/block_scout_web/views/api/rpc/contract_view.ex
  24. 7
      apps/block_scout_web/lib/block_scout_web/web_router.ex
  25. 3240
      apps/block_scout_web/priv/gettext/default.pot
  26. 3240
      apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po
  27. 16
      apps/explorer/lib/explorer/chain.ex
  28. 20
      apps/explorer/lib/explorer/chain/smart_contract.ex
  29. 133
      apps/explorer/lib/explorer/chain/smart_contract/verification_status.ex
  30. 77
      apps/explorer/lib/explorer/smart_contract/solidity/code_compiler.ex
  31. 31
      apps/explorer/lib/explorer/smart_contract/solidity/publisher.ex
  32. 26
      apps/explorer/lib/explorer/smart_contract/solidity/publisher_worker.ex
  33. 94
      apps/explorer/lib/explorer/smart_contract/solidity/verifier.ex
  34. 15
      apps/explorer/priv/compile_solc_standard_json_input.js
  35. 13
      apps/explorer/priv/repo/migrations/20211203115010_add_contract_verification_status_table.exs

@ -22,9 +22,9 @@ lib/explorer/smart_contract/reader.ex:461
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:89
lib/block_scout_web/templates/address_contract/index.html.eex:156
lib/block_scout_web/templates/address_contract/index.html.eex:193
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/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

@ -154,7 +154,7 @@ jobs:
id: dialyzer-cache
with:
path: priv/plts
key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-dialyzer-mixlockhash_5-${{ hashFiles('mix.lock') }}
key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-dialyzer-mixlockhash_6-${{ hashFiles('mix.lock') }}
restore-keys: |
${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-dialyzer-"

@ -1,6 +1,7 @@
## Current
### Features
- [#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
- [#4931](https://github.com/blockscout/blockscout/pull/4931) - Web3 modal with Wallet Connect for Write contract page and Staking Dapp

@ -142,24 +142,54 @@ if ($contractVerificationPage.length) {
})
$(function () {
if ($('#metadata-json-dropzone').length) {
var dropzone = new Dropzone('#metadata-json-dropzone', {
function standardJSONBehavior () {
$('#json-dropzone-form').removeClass('dz-clickable')
this.on('addedfile', function (_file) {
$('#verify-via-standart-json-input-submit').prop('disabled', false)
$('#file-help-block').text('')
$('#dropzone-previews').addClass('dz-started')
})
this.on('removedfile', function (_file) {
if (this.files.length === 0) {
$('#verify-via-standart-json-input-submit').prop('disabled', true)
$('#dropzone-previews').removeClass('dz-started')
}
})
}
function metadataJSONBehavior () {
this.on('addedfile', function (_file) {
changeVisibilityOfVerifyButton(this.files.length)
$('#file-help-block').text('')
})
this.on('removedfile', function (_file) {
changeVisibilityOfVerifyButton(this.files.length)
})
}
const $jsonDropzoneMetadata = $('#metadata-json-dropzone')
const $jsonDropzoneStandardInput = $('#json-dropzone-form')
if ($jsonDropzoneMetadata.length || $jsonDropzoneStandardInput.length) {
const func = $jsonDropzoneMetadata.length ? metadataJSONBehavior : standardJSONBehavior
const maxFiles = $jsonDropzoneMetadata.length ? 100 : 1
const acceptedFiles = $jsonDropzoneMetadata.length ? 'text/plain,application/json,.sol,.json' : 'text/plain,application/json,.json'
const tag = $jsonDropzoneMetadata.length ? '#metadata-json-dropzone' : '#json-dropzone-form'
const previewsContainer = $jsonDropzoneMetadata.length ? undefined : '#dropzone-previews'
var dropzone = new Dropzone(tag, {
autoProcessQueue: false,
acceptedFiles: 'text/plain,application/json,.sol,.json',
acceptedFiles: acceptedFiles,
parallelUploads: 100,
uploadMultiple: true,
addRemoveLinks: true,
maxFilesize: 10,
maxFiles: maxFiles,
previewsContainer: previewsContainer,
params: { address_hash: $('#smart_contract_address_hash').val() },
init: function () {
this.on('addedfile', function (_file) {
changeVisibilityOfVerifyButton(this.files.length)
$('#file-help-block').text('')
})
this.on('removedfile', function (_file) {
changeVisibilityOfVerifyButton(this.files.length)
})
}
init: func
})
}
@ -224,6 +254,15 @@ if ($contractVerificationPage.length) {
}
})
$('#verify-via-standart-json-input-submit').on('click', (event) => {
event.preventDefault()
if (dropzone.files.length > 0) {
dropzone.processQueue()
} else {
$('#loading').addClass('d-none')
}
})
$('#verify-via-json-submit').on('click', function () {
if (dropzone.files.length > 0) {
dropzone.processQueue()
@ -238,6 +277,7 @@ if ($contractVerificationPage.length) {
$('#verify_via_flattened_code_button').show()
$('#verify_via_sourcify_button').hide()
$('#verify_vyper_contract_button').hide()
$('#verify_via_standard_json_input').hide()
}
})
@ -246,6 +286,7 @@ if ($contractVerificationPage.length) {
$('#verify_via_flattened_code_button').hide()
$('#verify_via_sourcify_button').show()
$('#verify_vyper_contract_button').hide()
$('#verify_via_standard_json_input').hide()
}
})
@ -254,6 +295,16 @@ if ($contractVerificationPage.length) {
$('#verify_via_flattened_code_button').hide()
$('#verify_via_sourcify_button').hide()
$('#verify_vyper_contract_button').show()
$('#verify_via_standard_json_input').hide()
}
})
$('.verify-via-standard-json-input').on('click', function () {
if ($(this).prop('checked')) {
$('#verify_via_flattened_code_button').hide()
$('#verify_via_sourcify_button').hide()
$('#verify_vyper_contract_button').hide()
$('#verify_via_standard_json_input').show()
}
})
}

@ -57,6 +57,27 @@ defmodule BlockScoutWeb.AddressContractVerificationController do
send_resp(conn, 204, "")
end
# sobelow_skip ["Traversal.FileModule"]
def create(
conn,
%{
"smart_contract" => smart_contract,
"file" => files
}
) do
files_array = prepare_files_array(files)
with %Plug.Upload{path: path} <- get_one_json(files_array),
{:ok, json_input} <- File.read(path) do
Que.add(SolidityPublisherWorker, {smart_contract, json_input, conn})
else
_ ->
nil
end
send_resp(conn, 204, "")
end
def create(
conn,
%{
@ -77,11 +98,7 @@ defmodule BlockScoutWeb.AddressContractVerificationController do
) do
files_array = prepare_files_array(files)
json_files =
files_array
|> Enum.filter(fn file -> file.content_type == "application/json" end)
json_file = json_files |> Enum.at(0)
json_file = get_one_json(files_array)
if json_file do
if Chain.smart_contract_fully_verified?(address_hash_string) do
@ -188,6 +205,12 @@ defmodule BlockScoutWeb.AddressContractVerificationController do
if is_map(files), do: Enum.map(files, fn {_, file} -> file end), else: []
end
defp get_one_json(files_array) do
files_array
|> Enum.filter(fn file -> file.content_type == "application/json" end)
|> Enum.at(0)
end
defp prepare_verification_error(msg, address_hash_string, conn) do
[
{:contract_verification_result,

@ -0,0 +1,40 @@
defmodule BlockScoutWeb.AddressContractVerificationViaStandardJsonInputController do
use BlockScoutWeb, :controller
alias BlockScoutWeb.Controller
alias Explorer.Chain
alias Explorer.Chain.SmartContract
alias Explorer.SmartContract.CompilerVersion
def new(conn, %{"address_id" => address_hash_string}) do
if Chain.smart_contract_fully_verified?(address_hash_string) do
address_path =
conn
|> address_path(:show, address_hash_string)
|> Controller.full_path()
redirect(conn, to: address_path)
else
changeset =
SmartContract.changeset(
%SmartContract{address_hash: address_hash_string},
%{}
)
compiler_versions =
case CompilerVersion.fetch_versions(:solc) do
{:ok, compiler_versions} ->
compiler_versions
{:error, _} ->
[]
end
render(conn, "new.html",
changeset: changeset,
address_hash: address_hash_string,
compiler_versions: compiler_versions
)
end
end
end

@ -7,7 +7,9 @@ defmodule BlockScoutWeb.API.RPC.ContractController do
alias Explorer.Chain.Events.Publisher, as: EventsPublisher
alias Explorer.Chain.{Hash, SmartContract}
alias Explorer.Etherscan.Contracts
alias Explorer.Chain.SmartContract.VerificationStatus
alias Explorer.SmartContract.Solidity.Publisher
alias Explorer.SmartContract.Solidity.PublisherWorker, as: SolidityPublisherWorker
alias Explorer.SmartContract.Vyper.Publisher, as: VyperPublisher
alias Explorer.ThirdPartyIntegrations.Sourcify
@ -77,6 +79,49 @@ defmodule BlockScoutWeb.API.RPC.ContractController do
end
end
def verifysourcecode(
conn,
%{
"codeformat" => "solidity-standard-json-input",
"contractaddress" => address_hash,
"sourceCode" => json_input
} = params
) do
with {:format, {:ok, _casted_address_hash}} <- to_address_hash(address_hash),
{:params, {:ok, fetched_params}} <- {:params, fetch_verifysourcecode_params(params)},
uid <- VerificationStatus.generate_uid(address_hash) do
Que.add(SolidityPublisherWorker, {fetched_params, json_input, uid})
render(conn, :show, %{result: uid})
else
{:format, :error} ->
render(conn, :error, error: "Invalid address hash")
{:params, {:error, error}} ->
render(conn, :error, error: error)
end
end
def verifysourcecode(conn, %{"codeformat" => "solidity-standard-json-input"}) do
render(conn, :error, error: "Missing sourceCode or contractaddress fields")
end
def checkverifystatus(conn, %{"guid" => guid}) do
case VerificationStatus.fetch_status(guid) do
:pending ->
render(conn, :show, %{result: "Pending in queue"})
:pass ->
render(conn, :show, %{result: "Pass - Verified"})
:fail ->
render(conn, :show, %{result: "Fail - Unable to verify"})
:unknown_uid ->
render(conn, :show, %{result: "Unknown UID"})
end
end
defp prepare_params(files) when is_struct(files) do
{:error, "Invalid args format"}
end
@ -461,6 +506,15 @@ defmodule BlockScoutWeb.API.RPC.ContractController do
|> optional_param(params, "constructorArguments", "constructor_arguments")
end
defp fetch_verifysourcecode_params(params) do
{:ok, %{}}
|> required_param(params, "contractaddress", "address_hash")
|> required_param(params, "contractname", "name")
|> required_param(params, "compilerversion", "compiler_version")
|> optional_param(params, "constructorArguements", "constructor_arguments")
|> optional_param(params, "constructorArguments", "constructor_arguments")
end
defp parse_optimization_runs({:ok, %{"optimization_runs" => runs} = opts}) when is_bitstring(runs) do
case Integer.parse(runs) do
{runs_int, _} ->

@ -501,6 +501,18 @@ defmodule BlockScoutWeb.Etherscan do
"result" => nil
}
@contract_verifysourcecode_example_value %{
"message" => "OK",
"result" => "b080b96bd06ad1c9341c2afb7e3730311388544961acde94",
"status" => "1"
}
@contract_checkverifystatus_example_value %{
"message" => "OK",
"result" => "Pending in queue",
"status" => "1"
}
@contract_getsourcecode_example_value %{
"status" => "1",
"message" => "OK",
@ -1082,6 +1094,28 @@ defmodule BlockScoutWeb.Etherscan do
}
}
@uid_response_model %{
name: "UID",
fields: %{
"UID" => %{
type: "string",
definition: "Unique identifier of the verification attempt",
example: "b080b96bd06ad1c9341c2afb7e3730311388544961acde94"
}
}
}
@status_response_model %{
name: "Status",
fields: %{
"status" => %{
type: "string",
definition: "Current status of the verification attempt",
example: "`Pending in queue` | `Pass - Verified` | `Fail - Unable to verify` | `Unknown UID`"
}
}
}
@contract_source_code_type %{
type: "contract source code",
definition: "The contract's source code.",
@ -2684,6 +2718,95 @@ defmodule BlockScoutWeb.Etherscan do
}
]
}
@contract_verifysourcecode_action %{
name: "verifysourcecode",
description: """
Verify a contract with Standard input JSON file. Its interface the same as <a href="https://docs.etherscan.io/tutorials/verifying-contracts-programmatically">Etherscan</a>'s API endpoint
<br/>
<br/>
""",
required_params: [
%{
name: "solidity-standard-json-input",
key: "codeformat",
placeholder: "solidity-standard-json-input",
type: "string",
description: "Format of sourceCode(supported only \"solidity-standard-json-input\")"
},
%{
key: "contractaddress",
placeholder: "contractaddress",
type: "string",
description: "The address of the contract."
},
%{
key: "contractname",
placeholder: "contractname",
type: "string",
description:
"The name of the contract. It could be empty string(\"\"), just contract name(\"ContractName\"), or filename and contract name(\"contracts/contract_1.sol:ContractName\")"
},
%{
key: "compilerversion",
placeholder: "compilerversion",
type: "string",
description: "The compiler version for the contract."
},
%{
key: "sourceCode",
placeholder: "sourceCode",
type: "string",
description: "Standard input json"
}
],
optional_params: [
%{
key: "constructorArguements",
type: "string",
description: "The constructor argument data provided."
},
%{
key: "autodetectConstructorArguments",
placeholder: false,
type: "boolean",
description: "Whether or not automatically detect constructor argument."
}
],
responses: [
%{
code: "200",
description: "successful operation",
example_value: Jason.encode!(@contract_verifysourcecode_example_value),
type: "model",
model: @uid_response_model
}
]
}
@contract_checkverifystatus_action %{
name: "checkverifystatus",
description: "Return status of the verification attempt (works in addition to verifysourcecode method)",
required_params: [
%{
key: "guid",
placeholder: "identifierString",
type: "string",
description: "A string used for identifying verification attempt"
}
],
optional_params: [],
responses: [
%{
code: "200",
description: "successful operation",
example_value: Jason.encode!(@contract_checkverifystatus_example_value),
type: "model",
model: @status_response_model
}
]
}
@contract_getabi_action %{
name: "getabi",
description: "Get ABI for verified contract. Also available through a GraphQL 'addresses' query.",
@ -2931,7 +3054,9 @@ defmodule BlockScoutWeb.Etherscan do
@contract_getsourcecode_action,
@contract_verify_action,
@contract_verify_via_sourcify_action,
@contract_verify_vyper_contract_action
@contract_verify_vyper_contract_action,
@contract_verifysourcecode_action,
@contract_checkverifystatus_action
]
}

@ -8,6 +8,7 @@ defmodule BlockScoutWeb.Notifier do
alias BlockScoutWeb.{
AddressContractVerificationViaFlattenedCodeView,
AddressContractVerificationViaJsonView,
AddressContractVerificationViaStandardJsonInputView,
AddressContractVerificationVyperView,
Endpoint
}
@ -47,9 +48,7 @@ defmodule BlockScoutWeb.Notifier do
def handle_event(
{:chain_event, :contract_verification_result, :on_demand, {address_hash, contract_verification_result, conn}}
) do
verification_from_json_upload? = Map.has_key?(conn.params, "file")
verification_from_flattened_source? = Map.has_key?(conn.params, "external_libraries")
compiler = if verification_from_flattened_source?, do: :solc, else: :vyper
%{view: view, compiler: compiler} = select_contract_type_and_form_view(conn.params)
contract_verification_result =
case contract_verification_result do
@ -57,21 +56,7 @@ defmodule BlockScoutWeb.Notifier do
result
{:error, changeset} ->
compiler_versions =
case CompilerVersion.fetch_versions(compiler) do
{:ok, compiler_versions} ->
compiler_versions
{:error, _} ->
[]
end
view =
cond do
verification_from_json_upload? -> AddressContractVerificationViaJsonView
verification_from_flattened_source? -> AddressContractVerificationViaFlattenedCodeView
true -> AddressContractVerificationVyperView
end
compiler_versions = fetch_compiler_version(compiler)
result =
view
@ -201,6 +186,35 @@ defmodule BlockScoutWeb.Notifier do
def handle_event(_), do: nil
def fetch_compiler_version(compiler) do
case CompilerVersion.fetch_versions(compiler) do
{:ok, compiler_versions} ->
compiler_versions
{:error, _} ->
[]
end
end
def select_contract_type_and_form_view(params) do
verification_from_json_upload? = Map.has_key?(params, "file")
verification_from_flattened_source? = Map.has_key?(params, "external_libraries")
verification_from_standard_json_input? = verification_from_json_upload? && Map.has_key?(params, "smart_contract")
compiler = if verification_from_flattened_source? || verification_from_standard_json_input?, do: :solc, else: :vyper
view =
cond do
verification_from_standard_json_input? -> AddressContractVerificationViaStandardJsonInputView
verification_from_json_upload? -> AddressContractVerificationViaJsonView
verification_from_flattened_source? -> AddressContractVerificationViaFlattenedCodeView
true -> AddressContractVerificationVyperView
end
%{view: view, compiler: compiler}
end
@doc """
Broadcast the percentage of blocks indexed so far.
"""

@ -18,10 +18,15 @@
<%= render BlockScoutWeb.CommonComponentsView, "_minimal_proxy_pattern.html", address_hash: metadata_for_verification.address_hash, conn: @conn %>
<% else %>
<%= if metadata_for_verification do %>
<% path = address_verify_contract_path(@conn, :new, @address.hash) %>
<div class="mb-4">
<%= render BlockScoutWeb.CommonComponentsView, "_info.html" %><span> <%= gettext("Contract is not verified. However, we found a verified contract with the same bytecode in Blockscout DB") %> <%= link(
metadata_for_verification.address_hash,
to: address_contract_path(@conn, :index, metadata_for_verification.address_hash)) %>.<br/> <%= gettext("All metadata displayed below is from that contract. In order to verify current contract, click") %> <i><%= gettext("Verify & Publish") %></i> <%= gettext("button") %></span>
<div style="display: inline-block;">
<%= render BlockScoutWeb.CommonComponentsView, "_info.html" %>
<span> <%= gettext("Contract is not verified. However, we found a verified contract with the same bytecode in Blockscout DB") %> <%= link(
metadata_for_verification.address_hash,
to: address_contract_path(@conn, :index, metadata_for_verification.address_hash)) %>.<br/> <%= gettext("All metadata displayed below is from that contract. In order to verify current contract, click") %> <i><%= gettext("Verify & Publish") %></i> <%= gettext("button") %></span>
</div>
<%= link(gettext("Verify & Publish"), to: path, class: "button button-primary button-sm float-right ml-3", "data-test": "verify_and_publish") %>
</div>
<% end %>
<% end %>
@ -99,21 +104,19 @@
</div>
</section>
<%= if target_contract.verified_via_sourcify do %>
<%= Enum.map(additional_sources, fn additional_source -> %>
<section>
<div class="d-flex justify-content-between align-items-baseline">
<h3><%= additional_source.file_name %></h3>
<button type="button" class="btn-line" id="button" data-toggle="tooltip" data-placement="top" data-clipboard-text="<%= additional_source.contract_source_code %>" aria-label="Copy Contract Source Code">
<%= gettext "Copy Source Code" %>
</button>
</div>
<div class="tile tile-muted mb-4">
<pre class="pre-scrollable line-numbers" data-activate-highlight><code class="solidity"><%= for {line, number} <- contract_lines_with_index(additional_source.contract_source_code) do %><div data-line-number="<%= number %>"><%= line %></div><% end %></code></pre>
</div>
</section>
<% end)%>
<% end %>
<%= Enum.map(additional_sources, fn additional_source -> %>
<section>
<div class="d-flex justify-content-between align-items-baseline">
<h3><%= additional_source.file_name %></h3>
<button type="button" class="btn-line" id="button" data-toggle="tooltip" data-placement="top" data-clipboard-text="<%= additional_source.contract_source_code %>" aria-label="Copy Contract Source Code">
<%= gettext "Copy Source Code" %>
</button>
</div>
<div class="tile tile-muted mb-4">
<pre class="pre-scrollable line-numbers" data-activate-highlight><code class="solidity"><%= for {line, number} <- contract_lines_with_index(additional_source.contract_source_code) do %><div data-line-number="<%= number %>"><%= line %></div><% end %></code></pre>
</div>
</section>
<% end)%>
<section>
<div class="d-flex justify-content-between align-items-baseline">

@ -30,6 +30,11 @@
<div class="radio-icon"></div>
<%= label :verify_via, :true, gettext("Via flattened source code"), class: "radio-text" %>
</div>
<div class="radio-big">
<%= radio_button f, :verify_via, false, class: "form-check-input verify-via-standard-json-input" %>
<div class="radio-icon"></div>
<%= label :verify_via, :false, gettext("Via Standard Input JSON"), class: "radio-text" %>
</div>
<%= if Application.get_env(:explorer, Explorer.ThirdPartyIntegrations.Sourcify)[:enabled] do %>
<div class="radio-big">
<%= radio_button f, :verify_via, false, class: "form-check-input verify-via-sourcify" %>
@ -53,10 +58,11 @@
data-template="<div class='tooltip' role='tooltip'><div class='arrow'></div><div class='tooltip-inner'></div></div>"
title="If using a flat file for contract verification, you can use the POA Solidity flattener or the Truffle flattener">
<i style="color: #f7b32b;" class="fa fa-info-circle ml-1" data-test="token-bridge-supply"></i></span><br/>
2. Verification through <a href="https://sourcify.dev">Sourcify</a>.<br/>
2. Verification using <a href="https://docs.soliditylang.org/en/latest/using-the-compiler.html#input-description">Standard input JSON</a> file.<br/>
3. Verification through <a href="https://sourcify.dev">Sourcify</a>.<br/>
a) if smart-contract already verified on Sourcify, it will automatically fetch the data from the <a href="https://repo.sourcify.dev">repo</a><br/>
b) otherwise you will be asked to upload source files and JSON metadata file(s).<br/>
3. Verification of Vyper contract.
4. Verification of Vyper contract.
</div>
</div>
@ -95,6 +101,14 @@
style: "display: none;",
"data-button-loading": "animation"
) %>
<%= link(
gettext("Next"),
to: address_verify_contract_via_standard_json_input_path(@conn, :new, @address_hash),
id: "verify_via_standard_json_input",
class: "btn-full-primary mr-2",
style: "display: none;",
"data-button-loading": "animation"
) %>
<%=
link(
gettext("Cancel"),

@ -0,0 +1,10 @@
<div class="smart-contract-form-group">
<div class="smart-contract-form-group-inner-wrapper">
<%= label @f, :compiler_version, gettext("Compiler") %>
<div class="center-column">
<%= select @f, :compiler_version, @compiler_versions, class: "form-control border-rounded", selected: @compiler_version, "aria-describedby": "compiler-help-block", id: "smart_contract_compiler_version" %>
<%= error_tag @f, :compiler_version, id: "compiler-help-block", class: "text-danger form-error" %>
</div>
<div class="smart-contract-form-group-tooltip">The compiler version is specified in <span class="tooltip-quote">pragma solidity X.X.X</span>. Use the compiler version rather than the nightly build. If using the Solidity compiler, run <span class="tooltip-quote">solc —version</span> to check.</div>
</div>
</div>

@ -0,0 +1,10 @@
<div class="smart-contract-form-group constructor-arguments" style="display: <%= @display_constructor_arguments_text_area %>">
<div class="smart-contract-form-group-inner-wrapper">
<%= label @f, :constructor_arguments, gettext("ABI-encoded Constructor Arguments (if required by the contract)") %>
<div class="center-column">
<%= textarea @f, :constructor_arguments, class: "form-control border-rounded monospace", rows: 3, "aria-describedby": "contract-constructor-arguments-help-block" %>
<%= error_tag @f, :constructor_arguments, id: "contract-constructor-arguments-help-block", class: "text-danger form-error", "data-test": "contract-constructor-arguments-error" %>
</div>
<div class="smart-contract-form-group-tooltip">Add arguments in <a href="https://solidity.readthedocs.io/en/develop/abi-spec.html" target="_blank">ABI hex encoded form</a>. Constructor arguments are written right to left, and will be found at the end of the input created bytecode. They may also be <a href="https://abi.hashex.org/" target="_blank">parsed here.</a></div>
</div>
</div>

@ -0,0 +1,9 @@
<div class="smart-contract-form-group">
<div class="smart-contract-form-group-inner-wrapper">
<label for="smart_contract_address_hash"><%= gettext("Contract Address") %></label>
<div class="center-column">
<input aria-describedby="contract-address-help-block" class="form-control border-rounded" id="smart_contract_address_hash" name="smart_contract[address_hash]" type="text" value=<%= @address_hash %> readonly="">
</div>
<div class="smart-contract-form-group-tooltip">The 0x address supplied on contract creation.</div>
</div>
</div>

@ -0,0 +1,10 @@
<div class="smart-contract-form-group">
<div class="smart-contract-form-group-inner-wrapper">
<%= label @f, :name, gettext("Contract Name") %>
<div class="center-column">
<%= text_input @f, :name, class: "form-control border-rounded", "aria-describedby": "contract-name-help-block", "data-test": "contract_name", value: @contract_name_value %>
<%= error_tag @f, :name, id: "contract-name-help-block", class: "text-danger form-error" %>
</div>
<div class="smart-contract-form-group-tooltip"><%= raw @tooltip %></div>
</div>
</div>

@ -0,0 +1,20 @@
<div class="smart-contract-form-group">
<div class="smart-contract-form-group-inner-wrapper">
<%= label @f, "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, checked: !@fetch_constructor_arguments_automatically, class: "form-check-input autodetectfalse" %>
<div class="radio-icon"></div>
<%= label :autodetect_constructor_args, :false, gettext("No"), class: "radio-text" %>
</div>
<div class="radio-big">
<%= radio_button @f, :autodetect_constructor_args, true, checked: @fetch_constructor_arguments_automatically, 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" %>
</div>
</div>
<%= error_tag @f, :autodetect_constructor_args, id: "autodetect_constructor_args-help-block", class: "text-danger form-error" %>
</div>
</div>
</div>

@ -0,0 +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>
<div class="smart-contract-form-group-tooltip">Select yes if you want to show nightly builds.</div>
</div>
</div>

@ -29,49 +29,11 @@
</div>
</div>
<div class="smart-contract-form-group">
<div class="smart-contract-form-group-inner-wrapper">
<%= label f, :name, gettext("Contract Name") %>
<div class="center-column">
<%= text_input f, :name, class: "form-control border-rounded", "aria-describedby": "contract-name-help-block", "data-test": "contract_name", value: contract_name_value %>
<%= error_tag f, :name, id: "contract-name-help-block", class: "text-danger form-error" %>
</div>
<div class="smart-contract-form-group-tooltip">Must match the name specified in the code. For example, in <span class="tooltip-quote">contract MyContract {..}</span> <strong>MyContract</strong> is the contract name.</div>
</div>
</div>
<%= render BlockScoutWeb.AddressContractVerificationCommonFieldsView, "_contract_name_field.html", f: f, tooltip: "Must match the name specified in the code. For example, in <span class=\"tooltip-quote\">contract MyContract {..}</span> <strong>MyContract</strong> is the contract name.", contract_name_value: contract_name_value %>
<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>
<div class="smart-contract-form-group-tooltip">Select yes if you want to show nightly builds.</div>
</div>
</div>
<%= render BlockScoutWeb.AddressContractVerificationCommonFieldsView, "_include_nightly_builds_field.html", f: f %>
<div class="smart-contract-form-group">
<div class="smart-contract-form-group-inner-wrapper">
<%= label f, :compiler_version, gettext("Compiler") %>
<div class="center-column">
<%= select f, :compiler_version, @compiler_versions, class: "form-control border-rounded", selected: compiler_version, "aria-describedby": "compiler-help-block" %>
<%= error_tag f, :compiler_version, id: "compiler-help-block", class: "text-danger form-error" %>
</div>
<div class="smart-contract-form-group-tooltip">The compiler version is specified in <span class="tooltip-quote">pragma solidity X.X.X</span>. Use the compiler version rather than the nightly build. If using the Solidity compiler, run <span class="tooltip-quote">solc —version</span> to check.</div>
</div>
</div>
<%= render BlockScoutWeb.AddressContractVerificationCommonFieldsView, "_compiler_field.html", f: f, compiler_version: compiler_version, compiler_versions: @compiler_versions %>
<div class="smart-contract-form-group">
<div class="smart-contract-form-group-inner-wrapper">
@ -126,37 +88,9 @@
</div>
</div>
<div class="smart-contract-form-group">
<div class="smart-contract-form-group-inner-wrapper">
<%= label f, "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, checked: !fetch_constructor_arguments_automatically, class: "form-check-input autodetectfalse" %>
<div class="radio-icon"></div>
<%= label :autodetect_constructor_args, :false, gettext("No"), class: "radio-text" %>
</div>
<div class="radio-big">
<%= radio_button f, :autodetect_constructor_args, true, checked: fetch_constructor_arguments_automatically, 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" %>
</div>
</div>
<%= error_tag f, :autodetect_constructor_args, id: "autodetect_constructor_args-help-block", class: "text-danger form-error" %>
</div>
</div>
</div>
<%= render BlockScoutWeb.AddressContractVerificationCommonFieldsView, "_fetch_constructor_args.html", f: f, fetch_constructor_arguments_automatically: fetch_constructor_arguments_automatically %>
<div class="smart-contract-form-group constructor-arguments" style="display: <%= display_constructor_arguments_text_area %>">
<div class="smart-contract-form-group-inner-wrapper">
<%= label f, :constructor_arguments, gettext("ABI-encoded Constructor Arguments (if required by the contract)") %>
<div class="center-column">
<%= textarea f, :constructor_arguments, class: "form-control border-rounded monospace", rows: 3, "aria-describedby": "contract-constructor-arguments-help-block" %>
<%= error_tag f, :constructor_arguments, id: "contract-constructor-arguments-help-block", class: "text-danger form-error", "data-test": "contract-constructor-arguments-error" %>
</div>
<div class="smart-contract-form-group-tooltip">Add arguments in <a href="https://solidity.readthedocs.io/en/develop/abi-spec.html" target="_blank">ABI hex encoded form</a>. Constructor arguments are written right to left, and will be found at the end of the input created bytecode. They may also be <a href="https://abi.hashex.org/" target="_blank">parsed here.</a></div>
</div>
</div>
<%= render BlockScoutWeb.AddressContractVerificationCommonFieldsView, "_constructor_args.html", f: f, display_constructor_arguments_text_area: display_constructor_arguments_text_area %>
<div class="add-contract-libraries-wrapper">
<span class="btn-line js-btn-add-contract-libraries">Add Contract Libraries</span>

@ -0,0 +1,70 @@
<% metadata_for_verification = Chain.get_address_verified_twin_contract(@address_hash).verified_contract %>
<% contract_name_value = if metadata_for_verification, do: metadata_for_verification.name, else: "" %>
<% compiler_version = if metadata_for_verification, do: metadata_for_verification.compiler_version, else: "latest" %>
<% fetch_constructor_arguments_automatically = if metadata_for_verification, do: true, else: false %>
<% 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">
<div data-selector="channel-disconnected-message" class="d-none">
<div data-selector="reload-button" class="alert alert-danger">
<a href="#" class="alert-link"><%= gettext "Connection Lost" %></a>
</div>
</div>
<div class="new-smart-contract-form">
<h1 class="smart-contract-title"><%= gettext "New Smart Contract Verification" %></h1>
<%= form_for @changeset,
address_contract_verification_path(@conn, :create),
[id: "json-dropzone-form"],
fn f -> %>
<%= render BlockScoutWeb.AddressContractVerificationCommonFieldsView, "_contract_address_field.html", address_hash: @address_hash, f: f %>
<%= render BlockScoutWeb.AddressContractVerificationCommonFieldsView, "_contract_name_field.html", f: f, tooltip: "Must match the name specified in the code. For example, in <span class=\"tooltip-quote\">contract MyContract {..}</span> <strong>MyContract</strong> is the contract name. Also contract name could be: <span class=\"tooltip-quote\"><strong>path/to/file.sol:MyContract</strong></span>", contract_name_value: contract_name_value %>
<%= render BlockScoutWeb.AddressContractVerificationCommonFieldsView, "_include_nightly_builds_field.html", f: f %>
<%= render BlockScoutWeb.AddressContractVerificationCommonFieldsView, "_compiler_field.html", f: f, compiler_version: compiler_version, compiler_versions: @compiler_versions %>
<div class="smart-contract-form-group">
<div class="smart-contract-form-group-inner-wrapper">
<label for="smart_contract_metadata_json"><%= gettext("Standard Input JSON") %></label>
<div class="center-column">
<div class="dropzone-1 dropzone-previews" style="display: flex; margin: 0 auto;", id="dropzone-previews">
<div style="text-align: center;">
<span class="dz-message btn-full-primary" id="dz-button-message"><%= gettext("Drop the standard input JSON file or click here") %></span>
<%= error_tag f, :file, id: "file-help-block", class: "text-danger form-error", style: "max-width: 600px;" %>
</div>
</div>
</div>
<div class="smart-contract-form-group-tooltip">Drop the standard input JSON file created during contract compilation into the drop zone.</div>
</div>
</div>
<%= render BlockScoutWeb.AddressContractVerificationCommonFieldsView, "_fetch_constructor_args.html", f: f, fetch_constructor_arguments_automatically: fetch_constructor_arguments_automatically %>
<%= render BlockScoutWeb.AddressContractVerificationCommonFieldsView, "_constructor_args.html", f: f, display_constructor_arguments_text_area: display_constructor_arguments_text_area %>
<div class="smart-contract-form-buttons">
<button
class="position-absolute w-118 btn-full-primary d-none mr-2"
disabled="true"
id="loading"
name="button"
type="button"
>
<%= render BlockScoutWeb.CommonComponentsView, "_loading_spinner.html", loading_text: gettext("Loading...") %>
</button>
<button id="verify-via-standart-json-input-submit" class="btn-full-primary mr-2" disabled data-button-loading="animation"><%= gettext("Verify & publish") %></button>
<%= reset gettext("Reset"), class: "btn-line mr-2 js-smart-contract-form-reset" %>
<%=
link(
gettext("Cancel"),
class: "btn-no-border",
to: address_contract_path(@conn, :index, @address_hash)
)
%>
</div>
<% end %>
</div>
<script defer data-cfasync="false" src="<%= static_path(@conn, "/js/verification-form.js") %>"></script>
<script defer data-cfasync="false" src="<%= static_path(@conn, "/js/dropzone.min.js") %>"></script>
</section>

@ -231,7 +231,8 @@
@view_module != Elixir.BlockScoutWeb.Tokens.Instance.TransferView &&
@view_module != Elixir.BlockScoutWeb.APIDocsView &&
@view_module != Elixir.BlockScoutWeb.Admin.DashboardView &&
@view_module != Elixir.BlockScoutWeb.SearchView
@view_module != Elixir.BlockScoutWeb.SearchView &&
@view_module != Elixir.BlockScoutWeb.AddressContractVerificationViaStandardJsonInputView
) do %>
<script defer data-cfasync="false" src="<%= static_path(@conn, "/js/app.js") %>"></script>
<% end %>

@ -0,0 +1,3 @@
defmodule BlockScoutWeb.AddressContractVerificationCommonFieldsView do
use BlockScoutWeb, :view
end

@ -0,0 +1,4 @@
defmodule BlockScoutWeb.AddressContractVerificationViaStandardJsonInputView do
use BlockScoutWeb, :view
alias Explorer.Chain
end

@ -30,6 +30,10 @@ defmodule BlockScoutWeb.API.RPC.ContractView do
RPCView.render("show.json", data: prepare_source_code_contract(contract))
end
def render("show.json", %{result: result}) do
RPCView.render("show.json", data: result)
end
defp prepare_source_code_contract(nil) do
%{
"Address" => "",

@ -146,6 +146,13 @@ defmodule BlockScoutWeb.WebRouter do
as: :verify_contract_via_json
)
resources(
"/verify-via-standard-json-input",
AddressContractVerificationViaStandardJsonInputController,
only: [:new],
as: :verify_contract_via_standard_json_input
)
resources(
"/verify-vyper-contract",
AddressContractVerificationVyperController,

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

@ -3254,10 +3254,24 @@ defmodule Explorer.Chain do
:ok,
%Explorer.Chain.Hash{
byte_count: 20,
bytes: <<0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed :: big-integer-size(20)-unit(8)>>
bytes: <<90, 174, 182, 5, 63, 62, 148, 201, 185, 160, 159, 51, 102, 148, 53,
231, 239, 27, 234, 237>>
}
}
iex> Explorer.Chain.string_to_address_hash("0x5aaeb6053f3e94c9b9a09f33669435e7ef1beaed")
{
:ok,
%Explorer.Chain.Hash{
byte_count: 20,
bytes: <<90, 174, 182, 5, 63, 62, 148, 201, 185, 160, 159, 51, 102, 148, 53,
231, 239, 27, 234, 237>>
}
}
iex> Base.encode16(<<90, 174, 182, 5, 63, 62, 148, 201, 185, 160, 159, 51, 102, 148, 53, 231, 239, 27, 234, 237>>, case: :lower)
"5aaeb6053f3e94c9b9a09f33669435e7ef1beaed"
`String.t` format must always have 40 hexadecimal digits after the `0x` base prefix.
iex> Explorer.Chain.string_to_address_hash("0x0")

@ -270,7 +270,13 @@ defmodule Explorer.Chain.SmartContract do
|> prepare_changes(&upsert_contract_methods/1)
end
def invalid_contract_changeset(%__MODULE__{} = smart_contract, attrs, error, error_message) do
def invalid_contract_changeset(
%__MODULE__{} = smart_contract,
attrs,
error,
error_message,
json_verification \\ false
) do
validated =
smart_contract
|> cast(attrs, [
@ -287,12 +293,17 @@ defmodule Explorer.Chain.SmartContract do
:file_path,
:is_vyper_contract
])
|> validate_required([:name, :compiler_version, :optimization, :address_hash])
|> (&if(json_verification,
do: &1,
else: validate_required(&1, [:name, :compiler_version, :optimization, :address_hash])
)).()
field_to_put_message = if json_verification, do: :file, else: :contract_source_code
if error_message do
add_error(validated, :contract_source_code, error_message(error, error_message))
add_error(validated, field_to_put_message, error_message(error, error_message))
else
add_error(validated, :contract_source_code, error_message(error))
add_error(validated, field_to_put_message, error_message(error))
end
end
@ -355,6 +366,7 @@ defmodule Explorer.Chain.SmartContract do
defp error_message(:generated_bytecode), do: "Bytecode does not match, please try again."
defp error_message(:constructor_arguments), do: "Constructor arguments do not match, please try again."
defp error_message(:name), do: "Wrong contract name, please try again."
defp error_message(:json), do: "Invalid JSON file."
defp error_message(_), do: "There was an error validating your contract, please try again."
defp error_message(:compilation, error_message), do: "There was an error compiling your contract: #{error_message}"
end

@ -0,0 +1,133 @@
defmodule Explorer.Chain.SmartContract.VerificationStatus do
@moduledoc """
Represents single verification try
"""
use Explorer.Schema
import Ecto.Changeset
alias Explorer.Chain.Hash
alias Explorer.{Chain, Repo}
@typedoc """
* `address_hash` - address of the contract which was tried to verify
* `status` - try status: :pending | :pass | :fail
* `uid` - unique verification try identifer
"""
@type t :: %__MODULE__{
uid: String.t(),
address_hash: Hash.Address.t(),
status: non_neg_integer()
}
@primary_key false
schema "contract_verification_status" do
field(:uid, :string, primary_key: true)
field(:status, :integer)
field(:address_hash, Hash.Address)
timestamps()
end
@required_fields ~w(uid status address_hash)a
def changeset(%__MODULE__{} = struct, params \\ %{}) do
casted_params = encode_status(params)
struct
|> cast(casted_params, @required_fields)
|> validate_required(@required_fields)
end
def encode_status(%{status: status} = changeset) do
case status do
:pending ->
Map.put(changeset, :status, 0)
:pass ->
Map.put(changeset, :status, 1)
:fail ->
Map.put(changeset, :status, 2)
_ ->
changeset
end
end
def encode_status(changeset), do: changeset
def decode_status(number) when number in [0, 1, 2, 3] do
case number do
0 ->
:pending
1 ->
:pass
2 ->
:fail
3 ->
:unknown_uid
end
end
def insert_status(uid, status, address_hash) do
{:ok, hash} = if is_binary(address_hash), do: Chain.string_to_address_hash(address_hash), else: address_hash
%__MODULE__{}
|> changeset(%{uid: uid, status: status, address_hash: hash})
|> Repo.insert()
end
def update_status(uid, status) do
__MODULE__
|> Repo.get_by(uid: uid)
|> changeset(%{status: status})
|> Repo.update()
end
def fetch_status(uid) do
case validate_uid(uid) do
{:ok, valid_uid} ->
__MODULE__
|> Repo.get_by(uid: valid_uid)
|> (&if(is_nil(&1), do: 3, else: Map.get(&1, :status))).()
|> decode_status()
_ ->
:unknown_uid
end
end
def generate_uid(address_hash) do
case Chain.string_to_address_hash(address_hash) do
:error ->
nil
{:ok, %Hash{byte_count: 20, bytes: address_hash}} ->
address_encoded = Base.encode16(address_hash, case: :lower)
timestamp = DateTime.utc_now() |> DateTime.to_unix() |> Integer.to_string(16) |> String.downcase()
address_encoded <> timestamp
end
end
def validate_uid(<<_address::binary-size(40), timestamp_hex::binary>> = uid) do
case Integer.parse(timestamp_hex, 16) do
{timestamp, ""} ->
if DateTime.utc_now() |> DateTime.to_unix() > timestamp do
{:ok, uid}
else
:error
end
_ ->
:error
end
end
def validate_uid(_), do: :error
end

@ -125,6 +125,80 @@ defmodule Explorer.SmartContract.Solidity.CodeCompiler do
end
end
def run(params, json_input) do
name = Keyword.fetch!(params, :name)
compiler_version = Keyword.fetch!(params, :compiler_version)
path = SolcDownloader.ensure_exists(compiler_version)
if path do
{response, _status} =
System.cmd(
"node",
[
Application.app_dir(:explorer, "priv/compile_solc_standard_json_input.js"),
create_source_file(json_input),
path
]
)
with {:ok, decoded} <- Jason.decode(response),
{:ok, contracts} <- get_contracts_standard_input_verification(decoded) do
fetch_candidates(contracts, name)
else
{:error, %Jason.DecodeError{}} ->
{:error, :compilation}
{:error, reason} when reason in [:name, :compilation] ->
{:error, reason}
error ->
error = parse_error(error)
Logger.warn(["There was an error compiling a provided contract: ", inspect(error)])
{:error, [first_error | _]} = error
%{"message" => error_message} = first_error
{:error, :compilation, error_message}
end
else
{:error, :compilation}
end
end
defp fetch_candidates(contracts, "") when is_map(contracts) do
candidates =
for {file, content} <- contracts,
{contract_name, %{"abi" => abi, "evm" => %{"bytecode" => %{"object" => bytecode}}}} <- content,
do: %{"abi" => abi, "bytecode" => bytecode, "name" => contract_name, "file_path" => file}
{:ok, candidates}
end
defp fetch_candidates(contracts, name) when is_binary(name) and is_map(contracts) do
if String.contains?(name, ":") do
[file_name, contract_name] = String.split(name, ":")
fetch_candidates(contracts, file_name, contract_name)
else
candidates =
for {file, content} <- contracts,
{contract_name, %{"abi" => abi, "evm" => %{"bytecode" => %{"object" => bytecode}}}} <- content,
contract_name == name,
do: %{"abi" => abi, "bytecode" => bytecode, "name" => contract_name, "file_path" => file}
{:ok, candidates}
end
end
defp fetch_candidates(contracts, file_name, name)
when is_binary(name) and is_binary(file_name) and is_map(contracts) do
case contracts[file_name][name] do
%{"abi" => abi, "evm" => %{"bytecode" => %{"object" => bytecode}}} ->
{:ok, [%{"abi" => abi, "bytecode" => bytecode, "name" => name, "file_path" => file_name}]}
_ ->
{:ok, []}
end
end
def allowed_evm_versions do
:explorer
|> Application.get_env(:allowed_evm_versions)
@ -158,6 +232,9 @@ defmodule Explorer.SmartContract.Solidity.CodeCompiler do
defp get_contracts(%{"contracts" => %{"" => contracts}}), do: {:ok, contracts}
defp get_contracts(response), do: {:error, response}
defp get_contracts_standard_input_verification(%{"contracts" => contracts}), do: {:ok, contracts}
defp get_contracts_standard_input_verification(response), do: {:error, response}
defp optimize_value(false), do: "0"
defp optimize_value("false"), do: "0"

@ -45,6 +45,31 @@ defmodule Explorer.SmartContract.Solidity.Publisher do
end
end
def publish_with_standart_json_input(%{"address_hash" => address_hash} = params, json_input) do
case Verifier.evaluate_authenticity_via_standard_json_input(address_hash, params, json_input) do
{:ok, %{abi: abi, constructor_arguments: constructor_arguments}, additional_params} ->
params_with_constructor_arguments =
params
|> Map.put("constructor_arguments", constructor_arguments)
|> Map.merge(additional_params)
publish_smart_contract(address_hash, params_with_constructor_arguments, abi)
{:ok, %{abi: abi}, additional_params} ->
merged_params = Map.merge(params, additional_params)
publish_smart_contract(address_hash, merged_params, abi)
{:error, error} ->
{:error, unverified_smart_contract(address_hash, params, error, nil, true)}
{:error, error, error_message} ->
{:error, unverified_smart_contract(address_hash, params, error, error_message, true)}
_ ->
{:error, unverified_smart_contract(address_hash, params, "Failed to verify", nil, true)}
end
end
def publish_smart_contract(address_hash, params, abi) do
attrs = address_hash |> attributes(params, abi)
@ -65,7 +90,7 @@ defmodule Explorer.SmartContract.Solidity.Publisher do
end
end
defp unverified_smart_contract(address_hash, params, error, error_message) do
defp unverified_smart_contract(address_hash, params, error, error_message, json_verification \\ false) do
attrs = attributes(address_hash, params)
changeset =
@ -73,7 +98,8 @@ defmodule Explorer.SmartContract.Solidity.Publisher do
%SmartContract{address_hash: address_hash},
attrs,
error,
error_message
error_message,
json_verification
)
%{changeset | action: :insert}
@ -100,6 +126,7 @@ defmodule Explorer.SmartContract.Solidity.Publisher do
%{
address_hash: address_hash,
name: params["name"],
file_path: params["file_path"],
compiler_version: compiler_version,
evm_version: params["evm_version"],
optimization_runs: params["optimization_runs"],

@ -6,6 +6,7 @@ defmodule Explorer.SmartContract.Solidity.PublisherWorker do
use Que.Worker, concurrency: 5
alias Explorer.Chain.Events.Publisher, as: EventsPublisher
alias Explorer.Chain.SmartContract.VerificationStatus
alias Explorer.SmartContract.Solidity.Publisher
def perform({address_hash, params, external_libraries, conn}) do
@ -20,4 +21,29 @@ defmodule Explorer.SmartContract.Solidity.PublisherWorker do
EventsPublisher.broadcast([{:contract_verification_result, {address_hash, result, conn}}], :on_demand)
end
def perform({%{"address_hash" => address_hash} = params, json_input, uid}) when is_binary(uid) do
VerificationStatus.insert_status(uid, :pending, address_hash)
case Publisher.publish_with_standart_json_input(params, json_input) do
{:ok, _contract} ->
VerificationStatus.update_status(uid, :pass)
{:error, _changeset} ->
VerificationStatus.update_status(uid, :fail)
end
end
def perform({%{"address_hash" => address_hash} = params, json_input, conn}) do
result =
case Publisher.publish_with_standart_json_input(params, json_input) do
{:ok, _contract} = result ->
result
{:error, changeset} ->
{:error, changeset}
end
EventsPublisher.broadcast([{:contract_verification_result, {address_hash, result, conn}}], :on_demand)
end
end

@ -51,6 +51,79 @@ defmodule Explorer.SmartContract.Solidity.Verifier do
end)
end
def evaluate_authenticity_via_standard_json_input(address_hash, params, json_input) do
verify(address_hash, params, json_input)
end
defp verify(address_hash, params, json_input) do
name = Map.get(params, "name", "")
compiler_version = Map.fetch!(params, "compiler_version")
constructor_arguments = Map.get(params, "constructor_arguments", "")
autodetect_constructor_arguments = params |> Map.get("autodetect_constructor_args", "false") |> parse_boolean()
solc_output =
CodeCompiler.run(
[
name: name,
compiler_version: compiler_version
],
json_input
)
case solc_output do
{:ok, candidates} ->
case Jason.decode(json_input) do
{:ok, map_json_input} ->
Enum.reduce_while(candidates, %{}, fn candidate, _acc ->
file_path = candidate["file_path"]
source_code = map_json_input["sources"][file_path]["content"]
contract_name = candidate["name"]
case compare_bytecodes(
candidate,
address_hash,
constructor_arguments,
autodetect_constructor_arguments,
source_code,
contract_name
) do
{:ok, verified_data} ->
secondary_sources =
for {file, %{"content" => source}} <- map_json_input["sources"],
file != file_path,
do: %{"file_name" => file, "contract_source_code" => source, "address_hash" => address_hash}
additional_params =
map_json_input
|> extract_settings_from_json()
|> Map.put("contract_source_code", source_code)
|> Map.put("file_path", file_path)
|> Map.put("name", contract_name)
|> Map.put("secondary_sources", secondary_sources)
{:halt, {:ok, verified_data, additional_params}}
err ->
{:cont, {:error, err}}
end
end)
_ ->
{:error, :json}
end
error_response ->
error_response
end
end
defp extract_settings_from_json(json_input) when is_map(json_input) do
%{"enabled" => optimization, "runs" => optimization_runs} = json_input["settings"]["optimizer"]
%{"optimization" => optimization}
|> (&if(parse_boolean(optimization), do: Map.put(&1, "optimization_runs", optimization_runs), else: &1)).()
end
defp verify(address_hash, params) do
name = Map.fetch!(params, "name")
contract_source_code = Map.fetch!(params, "contract_source_code")
@ -90,6 +163,24 @@ defmodule Explorer.SmartContract.Solidity.Verifier do
{:error, :compilation, error_message}
end
defp compare_bytecodes(
%{"abi" => abi, "bytecode" => bytecode},
address_hash,
arguments_data,
autodetect_constructor_arguments,
contract_source_code,
contract_name
),
do:
compare_bytecodes(
{:ok, %{"abi" => abi, "bytecode" => bytecode}},
address_hash,
arguments_data,
autodetect_constructor_arguments,
contract_source_code,
contract_name
)
# credo:disable-for-next-line /Complexity/
defp compare_bytecodes(
{:ok, %{"abi" => abi, "bytecode" => bytecode}},
@ -360,4 +451,7 @@ defmodule Explorer.SmartContract.Solidity.Verifier do
defp parse_boolean("true"), do: true
defp parse_boolean("false"), do: false
defp parse_boolean(true), do: true
defp parse_boolean(false), do: false
end

@ -0,0 +1,15 @@
#!/usr/bin/env node
var inputJSONFilePath = process.argv[2];
var compilerVersionPath = process.argv[3];
var solc = require('solc')
var compilerSnapshot = require(compilerVersionPath);
var solc = solc.setupMethods(compilerSnapshot);
var fs = require('fs');
var input = fs.readFileSync(inputJSONFilePath, 'utf8');
const output = JSON.parse(solc.compile(input))
console.log(JSON.stringify(output));

@ -0,0 +1,13 @@
defmodule Explorer.Repo.Migrations.AddContractVerificationStatusTable do
use Ecto.Migration
def change do
create table("contract_verification_status", primary_key: false) do
add(:uid, :string, size: 64, primary_key: true)
add(:status, :int2, null: false)
add(:address_hash, :bytea, null: false)
timestamps()
end
end
end
Loading…
Cancel
Save