Merge pull request #1410 from poanetwork/ab-ab-verify-smart-contract-with-ext-libs

verify smart contracts with external libraries
pull/1425/head
Ayrat Badykov 6 years ago committed by GitHub
commit ff95eb41b6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 12
      apps/block_scout_web/lib/block_scout_web/controllers/address_contract_verification_controller.ex
  2. 52
      apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification/new.html.eex
  3. 63
      apps/block_scout_web/priv/gettext/default.pot
  4. 63
      apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po
  5. 25
      apps/explorer/lib/explorer/smart_contract/publisher.ex
  6. 12
      apps/explorer/lib/explorer/smart_contract/verifier.ex
  7. 39
      apps/explorer/test/explorer/smart_contract/publisher_test.exs
  8. 28
      apps/explorer/test/explorer/smart_contract/verifier_test.exs

@ -16,11 +16,15 @@ defmodule BlockScoutWeb.AddressContractVerificationController do
render(conn, "new.html", changeset: changeset, compiler_versions: compiler_versions) render(conn, "new.html", changeset: changeset, compiler_versions: compiler_versions)
end end
def create(conn, %{ def create(
conn,
%{
"address_id" => address_hash_string, "address_id" => address_hash_string,
"smart_contract" => smart_contract "smart_contract" => smart_contract,
}) do "external_libraries" => external_libraries
case Publisher.publish(address_hash_string, smart_contract) do }
) do
case Publisher.publish(address_hash_string, smart_contract, external_libraries) do
{:ok, _smart_contract} -> {:ok, _smart_contract} ->
redirect(conn, to: address_contract_path(conn, :index, address_hash_string)) redirect(conn, to: address_contract_path(conn, :index, address_hash_string))

@ -49,6 +49,58 @@
<%= error_tag f, :contract_source_code, id: "contract-source-code-help-block", class: "text-danger", "data-test": "contract-source-code-error" %> <%= error_tag f, :contract_source_code, id: "contract-source-code-help-block", class: "text-danger", "data-test": "contract-source-code-error" %>
</div> </div>
<h3 class="card-title"><%= gettext "Contract Libraries" %></h3>
<div class="form-group">
<%= label :external_libraries, :library1, gettext("1 Library Name") %>
<%= text_input :external_libraries, :library1_name, class: "form-control", "aria-describedby": "contract-name-help-block" %>
</div>
<div class="form-group">
<%= label :external_libraries, :library1, gettext("1 Library Address") %>
<%= text_input :external_libraries, :library1_address, class: "form-control", "aria-describedby": "contract-name-help-block" %>
</div>
<div class="form-group">
<%= label :external_libraries, :library2, gettext("2 Library Name") %>
<%= text_input :external_libraries, :library2_name, class: "form-control", "aria-describedby": "contract-name-help-block" %>
</div>
<div class="form-group">
<%= label :external_libraries, :library2, gettext("2 Library Address") %>
<%= text_input :external_libraries, :library2_address, class: "form-control", "aria-describedby": "contract-name-help-block" %>
</div>
<div class="form-group">
<%= label :external_libraries, :library3, gettext("3 Library Name") %>
<%= text_input :external_libraries, :library3_name, class: "form-control", "aria-describedby": "contract-name-help-block" %>
</div>
<div class="form-group">
<%= label :external_libraries, :library3, gettext("3 Library Address") %>
<%= text_input :external_libraries, :library3_address, class: "form-control", "aria-describedby": "contract-name-help-block" %>
</div>
<div class="form-group">
<%= label :external_libraries, :library4, gettext("4 Library Name") %>
<%= text_input :external_libraries, :library4_name, class: "form-control", "aria-describedby": "contract-name-help-block" %>
</div>
<div class="form-group">
<%= label :external_libraries, :library4, gettext("4 Library Address") %>
<%= text_input :external_libraries, :library4_address, class: "form-control", "aria-describedby": "contract-name-help-block" %>
</div>
<div class="form-group">
<%= label :external_libraries, :library5, gettext("5 Library Name") %>
<%= text_input :external_libraries, :library5_name, class: "form-control", "aria-describedby": "contract-name-help-block" %>
</div>
<div class="form-group">
<%= label f, :library5, gettext("5 Library Address") %>
<%= text_input f, :library5_address, class: "form-control", "aria-describedby": "contract-name-help-block" %>
</div>
<button <button
type="button" type="button"
name="button" name="button"

@ -197,7 +197,7 @@ msgid "Blocks Validated"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:67 #: lib/block_scout_web/templates/address_contract_verification/new.html.eex:119
#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:24 #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:24
msgid "Cancel" msgid "Cancel"
msgstr "" msgstr ""
@ -714,7 +714,7 @@ msgid "Request URL"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:65 #: lib/block_scout_web/templates/address_contract_verification/new.html.eex:117
msgid "Reset" msgid "Reset"
msgstr "" msgstr ""
@ -1031,7 +1031,7 @@ msgid "Verify & Publish"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:64 #: lib/block_scout_web/templates/address_contract_verification/new.html.eex:116
msgid "Verify & publish" msgid "Verify & publish"
msgstr "" msgstr ""
@ -1172,7 +1172,7 @@ msgid "Loading..."
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:62 #: lib/block_scout_web/templates/address_contract_verification/new.html.eex:114
msgid "Loading...." msgid "Loading...."
msgstr "" msgstr ""
@ -1568,3 +1568,58 @@ msgstr ""
#: lib/block_scout_web/templates/block/overview.html.eex:13 #: lib/block_scout_web/templates/block/overview.html.eex:13
msgid "Genesis Block" msgid "Genesis Block"
msgstr "" msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:60
msgid "1 Library Address"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:55
msgid "1 Library Name"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:70
msgid "2 Library Address"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:65
msgid "2 Library Name"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:80
msgid "3 Library Address"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:75
msgid "3 Library Name"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:90
msgid "4 Library Address"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:85
msgid "4 Library Name"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:100
msgid "5 Library Address"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:95
msgid "5 Library Name"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:52
msgid "Contract Libraries"
msgstr ""

@ -197,7 +197,7 @@ msgid "Blocks Validated"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:67 #: lib/block_scout_web/templates/address_contract_verification/new.html.eex:119
#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:24 #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:24
msgid "Cancel" msgid "Cancel"
msgstr "" msgstr ""
@ -714,7 +714,7 @@ msgid "Request URL"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:65 #: lib/block_scout_web/templates/address_contract_verification/new.html.eex:117
msgid "Reset" msgid "Reset"
msgstr "" msgstr ""
@ -1031,7 +1031,7 @@ msgid "Verify & Publish"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:64 #: lib/block_scout_web/templates/address_contract_verification/new.html.eex:116
msgid "Verify & publish" msgid "Verify & publish"
msgstr "" msgstr ""
@ -1172,7 +1172,7 @@ msgid "Loading..."
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:62 #: lib/block_scout_web/templates/address_contract_verification/new.html.eex:114
msgid "Loading...." msgid "Loading...."
msgstr "" msgstr ""
@ -1568,3 +1568,58 @@ msgstr ""
#: lib/block_scout_web/templates/block/overview.html.eex:13 #: lib/block_scout_web/templates/block/overview.html.eex:13
msgid "Genesis Block" msgid "Genesis Block"
msgstr "" msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:60
msgid "1 Library Address"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:55
msgid "1 Library Name"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:70
msgid "2 Library Address"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:65
msgid "2 Library Name"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:80
msgid "3 Library Address"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:75
msgid "3 Library Name"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:90
msgid "4 Library Address"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:85
msgid "4 Library Name"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:100
msgid "5 Library Address"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:95
msgid "5 Library Name"
msgstr ""
#, elixir-format, fuzzy
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:52
msgid "Contract Libraries"
msgstr ""

@ -23,8 +23,10 @@ defmodule Explorer.SmartContract.Publisher do
#=> {:ok, %Explorer.Chain.SmartContract{}} #=> {:ok, %Explorer.Chain.SmartContract{}}
""" """
def publish(address_hash, params) do def publish(address_hash, params, external_libraries \\ %{}) do
case Verifier.evaluate_authenticity(address_hash, params) do params_with_external_libaries = add_external_libraries(params, external_libraries)
case Verifier.evaluate_authenticity(address_hash, params_with_external_libaries) do
{:ok, %{abi: abi}} -> {:ok, %{abi: abi}} ->
publish_smart_contract(address_hash, params, abi) publish_smart_contract(address_hash, params, abi)
@ -62,4 +64,23 @@ defmodule Explorer.SmartContract.Publisher do
abi: abi abi: abi
} }
end end
defp add_external_libraries(params, external_libraries) do
clean_external_libraries =
Enum.reduce(1..5, %{}, fn number, acc ->
address_key = "library#{number}_address"
name_key = "library#{number}_name"
address = external_libraries[address_key]
name = external_libraries[name_key]
if is_nil(address) || address == "" || is_nil(name) || name == "" do
acc
else
Map.put(acc, name, address)
end
end)
Map.put(params, "external_libraries", clean_external_libraries)
end
end end

@ -15,6 +15,18 @@ defmodule Explorer.SmartContract.Verifier do
def evaluate_authenticity(_, %{"contract_source_code" => ""}), def evaluate_authenticity(_, %{"contract_source_code" => ""}),
do: {:error, :contract_source_code} do: {:error, :contract_source_code}
def evaluate_authenticity(address_hash, %{
"name" => name,
"contract_source_code" => contract_source_code,
"optimization" => optimization,
"compiler_version" => compiler_version,
"external_libraries" => external_libraries
}) do
solc_output = CodeCompiler.run(name, compiler_version, contract_source_code, optimization, external_libraries)
compare_bytecodes(solc_output, address_hash)
end
def evaluate_authenticity(address_hash, %{ def evaluate_authenticity(address_hash, %{
"name" => name, "name" => name,
"contract_source_code" => contract_source_code, "contract_source_code" => contract_source_code,

@ -45,5 +45,44 @@ defmodule Explorer.SmartContract.PublisherTest do
assert {:error, %Ecto.Changeset{}} = Publisher.publish(address_hash, invalid_attrs) assert {:error, %Ecto.Changeset{}} = Publisher.publish(address_hash, invalid_attrs)
end end
test "validates and creates smart contract with external libraries" do
contract_data =
"#{System.cwd!()}/test/support/fixture/smart_contract/compiler_tests.json"
|> File.read!()
|> Jason.decode!()
|> List.first()
compiler_version = contract_data["compiler_version"]
external_libraries = contract_data["external_libraries"]
name = contract_data["name"]
optimize = contract_data["optimize"]
contract = contract_data["contract"]
expected_bytecode = contract_data["expected_bytecode"]
contract_address = insert(:contract_address, contract_code: "0x" <> expected_bytecode)
params = %{
"contract_source_code" => contract,
"compiler_version" => compiler_version,
"name" => name,
"optimization" => optimize
}
external_libraries_form_params =
external_libraries
|> Enum.with_index()
|> Enum.reduce(%{}, fn {{name, address}, index}, acc ->
name_key = "library#{index + 1}_name"
address_key = "library#{index + 1}_address"
acc
|> Map.put(name_key, name)
|> Map.put(address_key, address)
end)
response = Publisher.publish(contract_address.hash, params, external_libraries_form_params)
assert {:ok, %SmartContract{} = smart_contract} = response
end
end end
end end

@ -28,6 +28,34 @@ defmodule Explorer.SmartContract.VerifierTest do
assert abi != nil assert abi != nil
end end
test "verifies the generated bytecode with external libraries" do
contract_data =
"#{System.cwd!()}/test/support/fixture/smart_contract/compiler_tests.json"
|> File.read!()
|> Jason.decode!()
|> List.first()
compiler_version = contract_data["compiler_version"]
external_libraries = contract_data["external_libraries"]
name = contract_data["name"]
optimize = contract_data["optimize"]
contract = contract_data["contract"]
expected_bytecode = contract_data["expected_bytecode"]
contract_address = insert(:contract_address, contract_code: "0x" <> expected_bytecode)
params = %{
"contract_source_code" => contract,
"compiler_version" => compiler_version,
"name" => name,
"optimization" => optimize,
"external_libraries" => external_libraries
}
assert {:ok, %{abi: abi}} = Verifier.evaluate_authenticity(contract_address.hash, params)
assert abi != nil
end
test "returns error when bytecode doesn't match", %{contract_code_info: contract_code_info} do test "returns error when bytecode doesn't match", %{contract_code_info: contract_code_info} do
contract_address = insert(:contract_address, contract_code: contract_code_info.bytecode) contract_address = insert(:contract_address, contract_code: contract_code_info.bytecode)

Loading…
Cancel
Save