parent
bef614825d
commit
9afeaf2708
@ -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 |
@ -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> |
@ -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> |
@ -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 |
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -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 |
@ -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…
Reference in new issue