Merge pull request #1414 from poanetwork/ab-smart-contract-constructor-arguments-

smart contract constructor arguments verification
pull/1375/head
Andrew Cravenho 6 years ago committed by GitHub
commit 9128a50959
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 6
      apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification/new.html.eex
  2. 35
      apps/block_scout_web/priv/gettext/default.pot
  3. 37
      apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po
  4. 29
      apps/explorer/lib/explorer/chain.ex
  5. 12
      apps/explorer/lib/explorer/chain/smart_contract.ex
  6. 10
      apps/explorer/lib/explorer/smart_contract/publisher.ex
  7. 49
      apps/explorer/lib/explorer/smart_contract/verifier.ex
  8. 20
      apps/explorer/lib/explorer/smart_contract/verifier/constructor_arguments.ex
  9. 9
      apps/explorer/priv/repo/migrations/20190207105501_add_constructor_arguments_to_smart_contracts.exs
  10. 54
      apps/explorer/test/explorer/chain_test.exs
  11. 29
      apps/explorer/test/explorer/smart_contract/publisher_test.exs
  12. 3
      apps/explorer/test/explorer/smart_contract/solidity/code_compiler_test.exs
  13. 44
      apps/explorer/test/explorer/smart_contract/verifier/constructor_arguments_test.exs
  14. 51
      apps/explorer/test/explorer/smart_contract/verifier_test.exs

@ -49,6 +49,12 @@
<%= error_tag f, :contract_source_code, id: "contract-source-code-help-block", class: "text-danger", "data-test": "contract-source-code-error" %>
</div>
<div class="form-group mb-4">
<%= label f, :contructor_arguments, gettext("Enter contructor arguments if the contract had any") %>
<%= textarea f, :constructor_arguments, class: "form-control monospace", rows: 3, "aria-describedby": "contract-constructor-arguments-help-block" %>
<%= error_tag f, :constructor_arguments, id: "contract-constructor-arguments-help-block", class: "text-danger", "data-test": "contract-constructor-arguments-error" %>
</div>
<h3 class="card-title"><%= gettext "Contract Libraries" %></h3>
<div class="form-group">

@ -197,7 +197,7 @@ msgid "Blocks Validated"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:119
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:125
#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:24
msgid "Cancel"
msgstr ""
@ -714,7 +714,7 @@ msgid "Request URL"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:117
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:123
msgid "Reset"
msgstr ""
@ -1031,7 +1031,7 @@ msgid "Verify & Publish"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:116
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:122
msgid "Verify & publish"
msgstr ""
@ -1172,7 +1172,7 @@ msgid "Loading..."
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:114
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:120
msgid "Loading...."
msgstr ""
@ -1570,56 +1570,61 @@ msgid "Genesis Block"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:60
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:66
msgid "1 Library Address"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:55
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:61
msgid "1 Library Name"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:70
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:76
msgid "2 Library Address"
msgstr ""
#, 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:71
msgid "2 Library Name"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:80
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:86
msgid "3 Library Address"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:75
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:81
msgid "3 Library Name"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:90
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:96
msgid "4 Library Address"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:85
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:91
msgid "4 Library Name"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:100
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:106
msgid "5 Library Address"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:95
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:101
msgid "5 Library Name"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:52
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:58
msgid "Contract Libraries"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:53
msgid "Enter contructor arguments if the contract had any"
msgstr ""

@ -197,7 +197,7 @@ msgid "Blocks Validated"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:119
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:125
#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:24
msgid "Cancel"
msgstr ""
@ -714,7 +714,7 @@ msgid "Request URL"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:117
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:123
msgid "Reset"
msgstr ""
@ -1031,7 +1031,7 @@ msgid "Verify & Publish"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:116
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:122
msgid "Verify & publish"
msgstr ""
@ -1172,7 +1172,7 @@ msgid "Loading..."
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:114
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:120
msgid "Loading...."
msgstr ""
@ -1570,56 +1570,61 @@ msgid "Genesis Block"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:60
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:66
msgid "1 Library Address"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:55
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:61
msgid "1 Library Name"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:70
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:76
msgid "2 Library Address"
msgstr ""
#, 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:71
msgid "2 Library Name"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:80
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:86
msgid "3 Library Address"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:75
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:81
msgid "3 Library Name"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:90
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:96
msgid "4 Library Address"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:85
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:91
msgid "4 Library Name"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:100
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:106
msgid "5 Library Address"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:95
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:101
msgid "5 Library Name"
msgstr ""
#, elixir-format, fuzzy
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:52
#, elixir-format
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:58
msgid "Contract Libraries"
msgstr ""
#, elixir-format, fuzzy
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:53
msgid "Enter contructor arguments if the contract had any"
msgstr ""

@ -1868,6 +1868,35 @@ defmodule Explorer.Chain do
|> Data.to_string()
end
@doc """
Fetches contract creation input data.
"""
@spec contract_creation_input_data(String.t()) :: nil | String.t()
def contract_creation_input_data(address_hash) do
query =
from(
address in Address,
where: address.hash == ^address_hash,
preload: [:contracts_creation_internal_transaction, :contracts_creation_transaction]
)
transaction = Repo.one(query)
cond do
is_nil(transaction) ->
""
transaction.contracts_creation_internal_transaction && transaction.contracts_creation_internal_transaction.input ->
Data.to_string(transaction.contracts_creation_internal_transaction.input)
transaction.contracts_creation_transaction && transaction.contracts_creation_transaction.input ->
Data.to_string(transaction.contracts_creation_transaction.input)
true ->
""
end
end
@doc """
Inserts a `t:SmartContract.t/0`.

@ -202,6 +202,7 @@ defmodule Explorer.Chain.SmartContract do
field(:compiler_version, :string)
field(:optimization, :boolean)
field(:contract_source_code, :string)
field(:constructor_arguments, :string)
field(:abi, {:array, :map})
belongs_to(
@ -217,7 +218,15 @@ defmodule Explorer.Chain.SmartContract do
def changeset(%__MODULE__{} = smart_contract, attrs) do
smart_contract
|> cast(attrs, [:name, :compiler_version, :optimization, :contract_source_code, :address_hash, :abi])
|> cast(attrs, [
:name,
:compiler_version,
:optimization,
:contract_source_code,
:address_hash,
:abi,
:constructor_arguments
])
|> validate_required([:name, :compiler_version, :optimization, :contract_source_code, :abi, :address_hash])
|> unique_constraint(:address_hash)
end
@ -231,6 +240,7 @@ defmodule Explorer.Chain.SmartContract do
defp error_message(:compilation), do: "There was an error compiling your contract."
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(_), do: "There was an error validating your contract, please try again."
end

@ -55,12 +55,22 @@ defmodule Explorer.SmartContract.Publisher do
end
defp attributes(address_hash, params, abi \\ %{}) do
constructor_arguments = params["constructor_arguments"]
clean_constructor_arguments =
if constructor_arguments != nil && constructor_arguments != "" do
constructor_arguments
else
nil
end
%{
address_hash: address_hash,
name: params["name"],
compiler_version: params["compiler_version"],
optimization: params["optimization"],
contract_source_code: params["contract_source_code"],
constructor_arguments: clean_constructor_arguments,
abi: abi
}
end

@ -9,50 +9,47 @@ defmodule Explorer.SmartContract.Verifier do
alias Explorer.Chain
alias Explorer.SmartContract.Solidity.CodeCompiler
alias Explorer.SmartContract.Verifier.ConstructorArguments
def evaluate_authenticity(_, %{"name" => ""}), do: {:error, :name}
def evaluate_authenticity(_, %{"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, params) do
name = Map.fetch!(params, "name")
contract_source_code = Map.fetch!(params, "contract_source_code")
optimization = Map.fetch!(params, "optimization")
compiler_version = Map.fetch!(params, "compiler_version")
external_libraries = Map.get(params, "external_libraries", %{})
constructor_arguments = Map.get(params, "constructor_arguments", "")
def evaluate_authenticity(address_hash, %{
"name" => name,
"contract_source_code" => contract_source_code,
"optimization" => optimization,
"compiler_version" => compiler_version
}) do
solc_output = CodeCompiler.run(name, compiler_version, contract_source_code, optimization)
solc_output = CodeCompiler.run(name, compiler_version, contract_source_code, optimization, external_libraries)
compare_bytecodes(solc_output, address_hash)
compare_bytecodes(solc_output, address_hash, constructor_arguments)
end
defp compare_bytecodes({:error, :name}, _), do: {:error, :name}
defp compare_bytecodes({:error, _}, _), do: {:error, :compilation}
defp compare_bytecodes({:error, :name}, _, _), do: {:error, :name}
defp compare_bytecodes({:error, _}, _, _), do: {:error, :compilation}
defp compare_bytecodes({:ok, %{"abi" => abi, "bytecode" => bytecode}}, address_hash) do
defp compare_bytecodes({:ok, %{"abi" => abi, "bytecode" => bytecode}}, address_hash, arguments_data) do
generated_bytecode = extract_bytecode(bytecode)
"0x" <> blockchain_bytecode =
address_hash
|> Chain.smart_contract_bytecode()
|> extract_bytecode
if generated_bytecode == blockchain_bytecode do
{:ok, %{abi: abi}}
else
blockchain_bytecode_without_whisper = extract_bytecode(blockchain_bytecode)
cond do
generated_bytecode != blockchain_bytecode_without_whisper ->
{:error, :generated_bytecode}
!ConstructorArguments.verify(address_hash, blockchain_bytecode, arguments_data) ->
{:error, :constructor_arguments}
true ->
{:ok, %{abi: abi}}
end
end

@ -0,0 +1,20 @@
defmodule Explorer.SmartContract.Verifier.ConstructorArguments do
@moduledoc """
Smart contract contrstructor arguments verification logic.
"""
alias Explorer.Chain
def verify(address_hash, bytecode, arguments_data) do
arguments_data = String.replace(arguments_data, "0x", "")
creation_input_data = Chain.contract_creation_input_data(address_hash)
expected_arguments_data =
creation_input_data
|> String.split(bytecode)
|> List.last()
|> String.replace("0x", "")
expected_arguments_data == arguments_data
end
end

@ -0,0 +1,9 @@
defmodule Explorer.Repo.Migrations.AddConstructorArgumentsToSmartContracts do
use Ecto.Migration
def change do
alter table(:smart_contracts) do
add(:constructor_arguments, :string, null: true)
end
end
end

@ -3577,4 +3577,58 @@ defmodule Explorer.ChainTest do
assert Chain.block_combined_rewards(block) == expected_value
end
end
describe "contract_creation_input_data/1" do
test "fetches contract creation input data from contract creation transaction" do
address = insert(:address)
input = %Data{
bytes: <<1, 2, 3, 4, 5>>
}
:transaction
|> insert(created_contract_address_hash: address.hash, input: input)
|> with_block()
found_creation_data = Chain.contract_creation_input_data(address.hash)
assert found_creation_data == Data.to_string(input)
end
test "fetches contract creation input data from internal transaction" do
created_contract_address = insert(:address)
transaction =
:transaction
|> insert()
|> with_block()
input = %Data{
bytes: <<1, 2, 3, 4, 5>>
}
insert(
:internal_transaction_create,
transaction: transaction,
index: 0,
created_contract_address: created_contract_address,
block_number: transaction.block_number,
transaction_index: transaction.index,
input: input
)
assert Chain.contract_creation_input_data(created_contract_address.hash) == Data.to_string(input)
end
test "can't find address" do
hash = %Hash{
byte_count: 20,
bytes: <<0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0>>
}
found_creation_data = Chain.contract_creation_input_data(hash)
assert found_creation_data == ""
end
end
end

@ -30,9 +30,38 @@ defmodule Explorer.SmartContract.PublisherTest do
assert smart_contract.compiler_version == valid_attrs["compiler_version"]
assert smart_contract.optimization == valid_attrs["optimization"]
assert smart_contract.contract_source_code == valid_attrs["contract_source_code"]
assert is_nil(smart_contract.constructor_arguments)
assert smart_contract.abi != nil
end
test "creates a smart contract with constructor arguments" do
contract_code_info = Factory.contract_code_info()
contract_address = insert(:contract_address, contract_code: contract_code_info.bytecode)
constructor_arguments = "0102030405"
params = %{
"contract_source_code" => contract_code_info.source_code,
"compiler_version" => contract_code_info.version,
"name" => contract_code_info.name,
"optimization" => contract_code_info.optimized,
"constructor_arguments" => constructor_arguments
}
:transaction
|> insert(
created_contract_address_hash: contract_address.hash,
input: contract_code_info.bytecode <> constructor_arguments
)
|> with_block()
response = Publisher.publish(contract_address.hash, params)
assert {:ok, %SmartContract{} = smart_contract} = response
assert smart_contract.constructor_arguments == constructor_arguments
end
test "with invalid data returns error changeset" do
address_hash = ""

@ -163,9 +163,12 @@ defmodule Explorer.SmartContract.Solidity.CodeCompilerTest do
end
defp remove_init_data_and_whisper_data(code) do
{res, _} =
code
|> String.split("0029")
|> List.first()
|> String.split_at(-64)
res
end
end

@ -0,0 +1,44 @@
defmodule Explorer.SmartContract.Verifier.ConstructorArgumentsTest do
use Explorer.DataCase
import Explorer.Factory
alias Explorer.Chain.Data
alias Explorer.SmartContract.Verifier.ConstructorArguments
describe "verify/3" do
test "verifies constructor arguments" do
bytecode = "0x0102030"
constructor_arguments = "0x405"
address = insert(:address)
input = %Data{
bytes: <<1, 2, 3, 4, 5>>
}
:transaction
|> insert(created_contract_address_hash: address.hash, input: input)
|> with_block()
assert ConstructorArguments.verify(address.hash, bytecode, constructor_arguments)
end
end
test "veriies constructor constructor arguments with whisper data" do
bytecode = "0x0102035d943c575be8a2aee2bb7737a765fdd2c6e49b74cd2c92ab0fa8e4282d1a75ae0029"
constructor_arguments = "0x0405"
address = insert(:address)
input = %Data{
bytes:
<<1, 2, 3, 93, 148, 60, 87, 91, 232, 162, 174, 226, 187, 119, 55, 167, 101, 253, 210, 198, 228, 155, 116, 205,
44, 146, 171, 15, 168, 228, 40, 45, 26, 117, 174, 0, 41, 4, 5>>
}
:transaction
|> insert(created_contract_address_hash: address.hash, input: input)
|> with_block()
assert ConstructorArguments.verify(address.hash, bytecode, constructor_arguments)
end
end

@ -56,6 +56,57 @@ defmodule Explorer.SmartContract.VerifierTest do
assert abi != nil
end
test "verifies smart contract with constructor arguments", %{
contract_code_info: contract_code_info
} do
contract_address = insert(:contract_address, contract_code: contract_code_info.bytecode)
constructor_arguments = "0102030405"
params = %{
"contract_source_code" => contract_code_info.source_code,
"compiler_version" => contract_code_info.version,
"name" => contract_code_info.name,
"optimization" => contract_code_info.optimized,
"constructor_arguments" => constructor_arguments
}
:transaction
|> insert(
created_contract_address_hash: contract_address.hash,
input: contract_code_info.bytecode <> constructor_arguments
)
|> with_block()
assert {:ok, %{abi: abi}} = Verifier.evaluate_authenticity(contract_address.hash, params)
assert abi != nil
end
test "returns error when constructor arguments do not match", %{
contract_code_info: contract_code_info
} do
contract_address = insert(:contract_address, contract_code: contract_code_info.bytecode)
constructor_arguments = "0102030405"
params = %{
"contract_source_code" => contract_code_info.source_code,
"compiler_version" => contract_code_info.version,
"name" => contract_code_info.name,
"optimization" => contract_code_info.optimized,
"constructor_arguments" => constructor_arguments
}
:transaction
|> insert(
created_contract_address_hash: contract_address.hash,
input: Verifier.extract_bytecode(contract_code_info.bytecode) <> "010203"
)
|> with_block()
assert {:error, :constructor_arguments} = Verifier.evaluate_authenticity(contract_address.hash, params)
end
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)

Loading…
Cancel
Save