feat: decoding candidates for unverified contracts

pull/1466/head
zachdaniel 6 years ago
parent 1908097a9b
commit 48f363e0c6
  1. 63
      apps/block_scout_web/lib/block_scout_web/templates/transaction/_decoded_input.html.eex
  2. 47
      apps/block_scout_web/lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex
  3. 2
      apps/block_scout_web/lib/block_scout_web/templates/transaction/overview.html.eex
  4. 2
      apps/block_scout_web/lib/block_scout_web/views/transaction_view.ex
  5. 39
      apps/block_scout_web/priv/gettext/default.pot
  6. 39
      apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po
  7. 74
      apps/explorer/lib/explorer/chain/contract_method.ex
  8. 36
      apps/explorer/lib/explorer/chain/method_identifier.ex
  9. 21
      apps/explorer/lib/explorer/chain/smart_contract.ex
  10. 35
      apps/explorer/lib/explorer/chain/transaction.ex
  11. 15
      apps/explorer/priv/repo/migrations/20181221145054_add_contract_methods.exs
  12. 22
      apps/explorer/test/explorer/chain/transaction_test.exs
  13. 28
      apps/explorer/test/explorer/smart_contract/publisher_test.exs
  14. 19
      apps/explorer/test/support/factory.ex

@ -3,9 +3,9 @@
<h1 class="card-title"><%= gettext "Input" %> </h1> <h1 class="card-title"><%= gettext "Input" %> </h1>
<!-- Input --> <!-- Input -->
<%= case @decoded_input_data do %> <%= case @decoded_input_data do %>
<% {:error, :contract_not_verified} -> %> <% {:error, :contract_not_verified, candidates} -> %>
<div class="alert alert-info"> <div class="alert alert-info">
<%= gettext "To see decoded input data, the contract must be verified." %> <%= gettext "To see accurate decoded input data, the contract must be verified." %>
<%= case @transaction do %> <%= case @transaction do %>
<% %{to_address: %{hash: hash}} -> %> <% %{to_address: %{hash: hash}} -> %>
<%= gettext "Verify the contract " %><a href="<%= address_verify_contract_path(@conn, :new, hash)%>"><%= gettext "here" %></a> <%= gettext "Verify the contract " %><a href="<%= address_verify_contract_path(@conn, :new, hash)%>"><%= gettext "here" %></a>
@ -13,57 +13,24 @@
<%= nil %> <%= nil %>
<% end %> <% end %>
</div> </div>
<% {:ok, method_id, text, mapping} -> %> <%= unless Enum.empty?(candidates) do %>
<table summary="<%= gettext "Transaction Info" %>" class="table thead-light table-bordered table-responsive transaction-info-table"> <h3><%= gettext "Potential matches from our contract method database:" %></h3>
<tr> <%= gettext "IMPORTANT: This information is a best guess based on similar functions from other verified contracts." %>
<td><%= gettext "Method Id" %></td> <%= gettext "To have guaranteed accuracy, use the link above to verify the contract's source code." %>
<td colspan="3"><code>0x<%= method_id %></code></td>
</tr>
<tr>
<td>Call</td>
<td colspan="3"><code><%= text %></code></td>
</tr>
</table>
<table summary="<%= gettext "Transaction Inputs" %>" class="table thead-light table-bordered table-responsive"> <%= for {:ok, method_id, text, mapping} <- candidates do %>
<tr> <hr>
<th scope="col"></th> <h3><%= text %>: </h3>
<th scope="col"><%= gettext "Name" %></th>
<th scope="col"><%= gettext "Type" %></th>
<th scope="col"><%= gettext "Data" %></th>
<tr>
<%= for {name, type, value} <- mapping do %>
<tr>
<th scope="row">
<%= case BlockScoutWeb.ABIEncodedValueView.copy_text(type, value) do %>
<% :error -> %>
<%= nil %>
<% copy_text -> %>
<button type="button" class="copy icon-link" data-toggle="tooltip" data-placement="top" data-clipboard-text="<%= copy_text %>" aria-label="<%= gettext "Copy Value" %>">
<i class="fas fa-clone"></i>
</button>
<% end %>
</th>
<td><%= name %></td>
<td><%= type %></td>
<td>
<%= case BlockScoutWeb.ABIEncodedValueView.value_html(type, value) do %>
<% :error -> %>
<div class="alert alert-danger">
<%= gettext "Error rendering value" %>
</div>
<% value -> %>
<pre class="transaction-input-text tile"><code><%= value %></code></pre>
<% end %>
</td> <%= render(BlockScoutWeb.TransactionView, "_decoded_input_body.html", method_id: method_id, text: text, mapping: mapping) %>
</tr> <% end %>
<% end %> <% end %>
</table> <% {:ok, method_id, text, mapping} -> %>
<%= render(BlockScoutWeb.TransactionView, "_decoded_input_body.html", method_id: method_id, text: text, mapping: mapping) %>
<% _ -> %> <% _ -> %>
<div class="alert alert-danger"> <div class="alert alert-danger">
<%= gettext "Failed to decode input data." %> <%= gettext "Failed to decode input data." %>
</div> </div>
<% end %> <% end %>
</div> </div>
</div> </div>

@ -0,0 +1,47 @@
<table summary="<%= gettext "Transaction Info" %>" class="table thead-light table-bordered table-responsive transaction-info-table">
<tr>
<td><%= gettext "Method Id" %></td>
<td colspan="3"><code>0x<%= @method_id %></code></td>
</tr>
<tr>
<td>Call</td>
<td colspan="3"><code><%= @text %></code></td>
</tr>
</table>
<%= unless Enum.empty?(@mapping) do %>
<table summary="<%= gettext "Transaction Inputs" %>" class="table thead-light table-bordered table-responsive">
<tr>
<th scope="col"></th>
<th scope="col"><%= gettext "Name" %></th>
<th scope="col"><%= gettext "Type" %></th>
<th scope="col"><%= gettext "Data" %></th>
<tr>
<%= for {name, type, value} <- @mapping do %>
<tr>
<th scope="row">
<%= case BlockScoutWeb.ABIEncodedValueView.copy_text(type, value) do %>
<% :error -> %>
<%= nil %>
<% copy_text -> %>
<button type="button" class="copy icon-link" data-toggle="tooltip" data-placement="top" data-clipboard-text="<%= copy_text %>" aria-label="<%= gettext "Copy Value" %>">
<i class="fas fa-clone"></i>
</button>
<% end %>
</th>
<td><%= name %></td>
<td><%= type %></td>
<td>
<%= case BlockScoutWeb.ABIEncodedValueView.value_html(type, value) do %>
<% :error -> %>
<div class="alert alert-danger">
<%= gettext "Error rendering value" %>
</div>
<% value -> %>
<pre class="transaction-input-text tile"><code><%= value %></code></pre>
<% end %>
</td>
</tr>
<% end %>
</table>
<% end %>

@ -172,7 +172,7 @@
</div> </div>
</div> </div>
<%= unless should_decode?(@transaction) do %> <%= unless skip_decoding?(@transaction) do %>
<div class="row"> <div class="row">
<div class="col-md-12"> <div class="col-md-12">
<%= render BlockScoutWeb.TransactionView, "_decoded_input.html", Map.put(assigns, :decoded_input_data, decoded_input_data) %> <%= render BlockScoutWeb.TransactionView, "_decoded_input.html", Map.put(assigns, :decoded_input_data, decoded_input_data) %>

@ -138,7 +138,7 @@ defmodule BlockScoutWeb.TransactionView do
Cldr.Number.to_string!(gas) Cldr.Number.to_string!(gas)
end end
def should_decode?(transaction) do def skip_decoding?(transaction) do
contract_creation?(transaction) || value_transfer?(transaction) contract_creation?(transaction) || value_transfer?(transaction)
end end

@ -338,7 +338,7 @@ msgid "Curl"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:33 #: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:18
#: lib/block_scout_web/templates/transaction_log/index.html.eex:60 #: lib/block_scout_web/templates/transaction_log/index.html.eex:60
#: lib/block_scout_web/templates/transaction_log/index.html.eex:118 #: lib/block_scout_web/templates/transaction_log/index.html.eex:118
msgid "Data" msgid "Data"
@ -574,7 +574,7 @@ msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:29 #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:29
#: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:31 #: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:16
#: lib/block_scout_web/templates/transaction_log/index.html.eex:57 #: lib/block_scout_web/templates/transaction_log/index.html.eex:57
msgid "Name" msgid "Name"
msgstr "" msgstr ""
@ -1305,29 +1305,28 @@ msgid "Indexed?"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:32 #: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:17
#: lib/block_scout_web/templates/transaction_log/index.html.eex:58 #: lib/block_scout_web/templates/transaction_log/index.html.eex:58
msgid "Type" msgid "Type"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:19 #: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:3
msgid "Method Id" msgid "Method Id"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:8
#: lib/block_scout_web/templates/transaction_log/index.html.eex:31 #: lib/block_scout_web/templates/transaction_log/index.html.eex:31
msgid "To see decoded input data, the contract must be verified." msgid "To see decoded input data, the contract must be verified."
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:17 #: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:1
msgid "Transaction Info" msgid "Transaction Info"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:28 #: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:13
msgid "Transaction Inputs" msgid "Transaction Inputs"
msgstr "" msgstr ""
@ -1344,17 +1343,17 @@ msgid "here"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:65 #: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:32
msgid "Failed to decode input data." msgid "Failed to decode input data."
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:53 #: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:38
msgid "Error rendering value" msgid "Error rendering value"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:42 #: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:27
#: lib/block_scout_web/templates/transaction_log/index.html.eex:69 #: lib/block_scout_web/templates/transaction_log/index.html.eex:69
msgid "Copy Value" msgid "Copy Value"
msgstr "" msgstr ""
@ -1492,6 +1491,26 @@ msgstr ""
msgid "Uncle Reward" msgid "Uncle Reward"
msgstr "" msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:18
msgid "IMPORTANT: This information is a best guess based on similar functions from other verified contracts."
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:17
msgid "Potential matches from our contract method database:"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:19
msgid "To have guaranteed accuracy, use the link above to verify the contract's source code."
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:8
msgid "To see accurate decoded input data, the contract must be verified."
msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/transaction/_emission_reward_tile.html.eex:5 #: lib/block_scout_web/templates/transaction/_emission_reward_tile.html.eex:5
msgid "Emission Contract" msgid "Emission Contract"

@ -338,7 +338,7 @@ msgid "Curl"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:33 #: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:18
#: lib/block_scout_web/templates/transaction_log/index.html.eex:60 #: lib/block_scout_web/templates/transaction_log/index.html.eex:60
#: lib/block_scout_web/templates/transaction_log/index.html.eex:118 #: lib/block_scout_web/templates/transaction_log/index.html.eex:118
msgid "Data" msgid "Data"
@ -574,7 +574,7 @@ msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:29 #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:29
#: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:31 #: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:16
#: lib/block_scout_web/templates/transaction_log/index.html.eex:57 #: lib/block_scout_web/templates/transaction_log/index.html.eex:57
msgid "Name" msgid "Name"
msgstr "" msgstr ""
@ -1305,29 +1305,28 @@ msgid "Indexed?"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:32 #: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:17
#: lib/block_scout_web/templates/transaction_log/index.html.eex:58 #: lib/block_scout_web/templates/transaction_log/index.html.eex:58
msgid "Type" msgid "Type"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:19 #: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:3
msgid "Method Id" msgid "Method Id"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:8
#: lib/block_scout_web/templates/transaction_log/index.html.eex:31 #: lib/block_scout_web/templates/transaction_log/index.html.eex:31
msgid "To see decoded input data, the contract must be verified." msgid "To see decoded input data, the contract must be verified."
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:17 #: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:1
msgid "Transaction Info" msgid "Transaction Info"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:28 #: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:13
msgid "Transaction Inputs" msgid "Transaction Inputs"
msgstr "" msgstr ""
@ -1344,17 +1343,17 @@ msgid "here"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:65 #: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:32
msgid "Failed to decode input data." msgid "Failed to decode input data."
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:53 #: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:38
msgid "Error rendering value" msgid "Error rendering value"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:42 #: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:27
#: lib/block_scout_web/templates/transaction_log/index.html.eex:69 #: lib/block_scout_web/templates/transaction_log/index.html.eex:69
msgid "Copy Value" msgid "Copy Value"
msgstr "" msgstr ""
@ -1492,6 +1491,26 @@ msgstr ""
msgid "Uncle Reward" msgid "Uncle Reward"
msgstr "" msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:18
msgid "IMPORTANT: This information is a best guess based on similar functions from other verified contracts."
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:17
msgid "Potential matches from our contract method database:"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:19
msgid "To have guaranteed accuracy, use the link above to verify the contract's source code."
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:8
msgid "To see accurate decoded input data, the contract must be verified."
msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/transaction/_emission_reward_tile.html.eex:5 #: lib/block_scout_web/templates/transaction/_emission_reward_tile.html.eex:5
msgid "Emission Contract" msgid "Emission Contract"

@ -0,0 +1,74 @@
defmodule Explorer.Chain.ContractMethod do
@moduledoc """
The representation of an individual item from the ABI of a verified smart contract.
"""
require Logger
use Explorer.Schema
alias Explorer.Chain.{Hash, MethodIdentifier}
alias Explorer.Repo
@type t :: %__MODULE__{
identifier: MethodIdentifier.t(),
abi: map(),
type: String.t()
}
schema "contract_methods" do
field(:identifier, MethodIdentifier)
field(:abi, :map)
field(:type, :string)
timestamps()
end
def upsert_from_abi(abi, address_hash) do
{successes, errors} =
abi
|> Enum.reject(fn selector ->
Map.get(selector, "type") in ["fallback", "constructor"]
end)
|> Enum.reduce({[], []}, fn selector, {successes, failures} ->
case abi_element_to_contract_method(selector) do
{:error, message} ->
{successes, [message | failures]}
selector ->
{[selector | successes], failures}
end
end)
unless Enum.empty?(errors) do
Logger.error(fn ->
["Error parsing some abi elements at ", Hash.to_iodata(address_hash), ": ", Enum.intersperse(errors, "\n")]
end)
end
Repo.insert_all(__MODULE__, successes, on_conflict: :nothing, conflict_target: [:identifier, :abi])
end
defp abi_element_to_contract_method(element) do
case ABI.parse_specification([element], include_events?: true) do
[selector] ->
now = DateTime.utc_now()
%{
identifier: selector.method_id,
abi: element,
type: Atom.to_string(selector.type),
inserted_at: now,
updated_at: now
}
_ ->
{:error, "Failed to parse abi row."}
end
rescue
e ->
message = Exception.format(:error, e)
{:error, message}
end
end

@ -0,0 +1,36 @@
defmodule Explorer.Chain.MethodIdentifier do
@moduledoc """
The first four bytes of the [KECCAK-256](https://en.wikipedia.org/wiki/SHA-3) hash of a contract method or event.
Represented in the database as a 4 byte integer, decodes into a 4 byte bitstring
"""
@behaviour Ecto.Type
@type t :: binary
@impl true
def type, do: :integer
@impl true
@spec load(integer) :: {:ok, t()}
def load(value) do
{:ok, <<value::integer-signed-32>>}
end
@impl true
@spec cast(binary) :: {:ok, t()} | :error
def cast(<<_::binary-size(4)>> = identifier) do
{:ok, identifier}
end
def cast(_), do: :error
@impl true
@spec dump(t()) :: {:ok, integer} | :error
def dump(<<num::integer-signed-32>>) do
{:ok, num}
end
def dump(_), do: :error
end

@ -8,10 +8,12 @@ defmodule Explorer.Chain.SmartContract do
http://solidity.readthedocs.io/en/v0.4.24/introduction-to-smart-contracts.html http://solidity.readthedocs.io/en/v0.4.24/introduction-to-smart-contracts.html
""" """
alias Explorer.Chain.{Address, Hash} require Logger
use Explorer.Schema use Explorer.Schema
alias Explorer.Chain.{Address, ContractMethod, Hash}
@typedoc """ @typedoc """
The name of a parameter to a function or event. The name of a parameter to a function or event.
""" """
@ -189,6 +191,7 @@ defmodule Explorer.Chain.SmartContract do
* `abi` - The [JSON ABI specification](https://solidity.readthedocs.io/en/develop/abi-spec.html#json) for this * `abi` - The [JSON ABI specification](https://solidity.readthedocs.io/en/develop/abi-spec.html#json) for this
contract. contract.
""" """
@type t :: %Explorer.Chain.SmartContract{ @type t :: %Explorer.Chain.SmartContract{
name: String.t(), name: String.t(),
compiler_version: String.t(), compiler_version: String.t(),
@ -229,6 +232,7 @@ defmodule Explorer.Chain.SmartContract do
]) ])
|> validate_required([:name, :compiler_version, :optimization, :contract_source_code, :abi, :address_hash]) |> validate_required([:name, :compiler_version, :optimization, :contract_source_code, :abi, :address_hash])
|> unique_constraint(:address_hash) |> unique_constraint(:address_hash)
|> prepare_changes(&upsert_contract_methods/1)
end end
def invalid_contract_changeset(%__MODULE__{} = smart_contract, attrs, error) do def invalid_contract_changeset(%__MODULE__{} = smart_contract, attrs, error) do
@ -238,6 +242,21 @@ defmodule Explorer.Chain.SmartContract do
|> add_error(:contract_source_code, error_message(error)) |> add_error(:contract_source_code, error_message(error))
end end
defp upsert_contract_methods(%Ecto.Changeset{changes: %{abi: abi}} = changeset) do
ContractMethod.upsert_from_abi(abi, get_field(changeset, :address_hash))
changeset
rescue
exception ->
message = Exception.format(:error, exception, __STACKTRACE__)
Logger.error(fn -> ["Error while upserting contract methods: ", message] end)
changeset
end
defp upsert_contract_methods(changeset), do: changeset
defp error_message(:compilation), do: "There was an error compiling your contract." 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(: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(:constructor_arguments), do: "Constructor arguments do not match, please try again."

@ -14,6 +14,7 @@ defmodule Explorer.Chain.Transaction do
alias Explorer.Chain.{ alias Explorer.Chain.{
Address, Address,
Block, Block,
ContractMethod,
Data, Data,
Gas, Gas,
Hash, Hash,
@ -25,6 +26,7 @@ defmodule Explorer.Chain.Transaction do
} }
alias Explorer.Chain.Transaction.{Fork, Status} alias Explorer.Chain.Transaction.{Fork, Status}
alias Explorer.Repo
@optional_attrs ~w(block_hash block_number created_contract_address_hash cumulative_gas_used earliest_processing_start @optional_attrs ~w(block_hash block_number created_contract_address_hash cumulative_gas_used earliest_processing_start
error gas_used index internal_transactions_indexed_at created_contract_code_indexed_at status error gas_used index internal_transactions_indexed_at created_contract_code_indexed_at status
@ -466,9 +468,40 @@ defmodule Explorer.Chain.Transaction do
def decoded_input_data(%__MODULE__{to_address: nil}), do: {:error, :no_to_address} def decoded_input_data(%__MODULE__{to_address: nil}), do: {:error, :no_to_address}
def decoded_input_data(%__MODULE__{input: %{bytes: bytes}}) when bytes in [nil, <<>>], do: {:error, :no_input_data} def decoded_input_data(%__MODULE__{input: %{bytes: bytes}}) when bytes in [nil, <<>>], do: {:error, :no_input_data}
def decoded_input_data(%__MODULE__{to_address: %{contract_code: nil}}), do: {:error, :not_a_contract_call} def decoded_input_data(%__MODULE__{to_address: %{contract_code: nil}}), do: {:error, :not_a_contract_call}
def decoded_input_data(%__MODULE__{to_address: %{smart_contract: nil}}), do: {:error, :contract_not_verified}
def decoded_input_data(%__MODULE__{
to_address: %{smart_contract: nil},
input: %{bytes: <<method_id::binary-size(4), _::binary>> = data},
hash: hash
}) do
candidates_query =
from(
contract_method in ContractMethod,
where: contract_method.identifier == ^method_id
)
candidates =
candidates_query
|> Repo.all()
|> Enum.flat_map(fn candidate ->
case do_decoded_input_data(data, [candidate.abi], hash) do
{:ok, _, _, _} = decoded -> [decoded]
_ -> []
end
end)
{:error, :contract_not_verified, candidates}
end
def decoded_input_data(%__MODULE__{to_address: %{smart_contract: nil}}) do
{:error, :contract_not_verified, []}
end
def decoded_input_data(%__MODULE__{input: %{bytes: data}, to_address: %{smart_contract: %{abi: abi}}, hash: hash}) do def decoded_input_data(%__MODULE__{input: %{bytes: data}, to_address: %{smart_contract: %{abi: abi}}, hash: hash}) do
do_decoded_input_data(data, abi, hash)
end
defp do_decoded_input_data(data, abi, hash) do
with {:ok, {selector, values}} <- find_and_decode(abi, data, hash), with {:ok, {selector, values}} <- find_and_decode(abi, data, hash),
{:ok, mapping} <- selector_mapping(selector, values, hash), {:ok, mapping} <- selector_mapping(selector, values, hash),
identifier <- Base.encode16(selector.method_id, case: :lower), identifier <- Base.encode16(selector.method_id, case: :lower),

@ -0,0 +1,15 @@
defmodule Explorer.Repo.Migrations.AddContractMethods do
use Ecto.Migration
def change do
create table(:contract_methods) do
add(:identifier, :integer, null: false)
add(:abi, :map, null: false)
add(:type, :string, null: false)
timestamps()
end
create(unique_index(:contract_methods, [:identifier, :abi]))
end
end

@ -254,7 +254,7 @@ defmodule Explorer.Chain.TransactionTest do
|> insert(to_address: insert(:contract_address)) |> insert(to_address: insert(:contract_address))
|> Repo.preload(to_address: :smart_contract) |> Repo.preload(to_address: :smart_contract)
assert Transaction.decoded_input_data(transaction) == {:error, :contract_not_verified} assert Transaction.decoded_input_data(transaction) == {:error, :contract_not_verified, []}
end end
test "that a contract call transaction that has a verified contract returns the decoded input data" do test "that a contract call transaction that has a verified contract returns the decoded input data" do
@ -265,5 +265,25 @@ defmodule Explorer.Chain.TransactionTest do
assert Transaction.decoded_input_data(transaction) == {:ok, "60fe47b1", "set(uint256 x)", [{"x", "uint256", 50}]} assert Transaction.decoded_input_data(transaction) == {:ok, "60fe47b1", "set(uint256 x)", [{"x", "uint256", 50}]}
end end
test "that a contract call will look up a match in contract_methods table" do
:transaction_to_verified_contract
|> insert()
|> Repo.preload(to_address: :smart_contract)
contract = insert(:smart_contract) |> Repo.preload(:address)
input_data =
"set(uint)"
|> ABI.encode([10])
|> Base.encode16(case: :lower)
transaction =
:transaction
|> insert(to_address: contract.address, input: "0x" <> input_data)
|> Repo.preload(to_address: :smart_contract)
assert Transaction.decoded_input_data(transaction) == {:ok, "60fe47b1", "set(uint256 x)", [{"x", "uint256", 10}]}
end
end end
end end

@ -5,9 +5,9 @@ defmodule Explorer.SmartContract.PublisherTest do
doctest Explorer.SmartContract.Publisher doctest Explorer.SmartContract.Publisher
alias Explorer.Chain.SmartContract alias Explorer.Chain.{ContractMethod, SmartContract}
alias Explorer.{Factory, Repo}
alias Explorer.SmartContract.Publisher alias Explorer.SmartContract.Publisher
alias Explorer.Factory
describe "publish/2" do describe "publish/2" do
test "with valid data creates a smart_contract" do test "with valid data creates a smart_contract" do
@ -31,7 +31,29 @@ defmodule Explorer.SmartContract.PublisherTest do
assert smart_contract.optimization == valid_attrs["optimization"] assert smart_contract.optimization == valid_attrs["optimization"]
assert smart_contract.contract_source_code == valid_attrs["contract_source_code"] assert smart_contract.contract_source_code == valid_attrs["contract_source_code"]
assert is_nil(smart_contract.constructor_arguments) assert is_nil(smart_contract.constructor_arguments)
assert smart_contract.abi != nil assert smart_contract.abi == contract_code_info.abi
end
test "corresponding contract_methods are created for the abi" do
contract_code_info = Factory.contract_code_info()
contract_address = insert(:contract_address, contract_code: contract_code_info.bytecode)
valid_attrs = %{
"contract_source_code" => contract_code_info.source_code,
"compiler_version" => contract_code_info.version,
"name" => contract_code_info.name,
"optimization" => contract_code_info.optimized
}
response = Publisher.publish(contract_address.hash, valid_attrs)
assert {:ok, %SmartContract{} = smart_contract} = response
Enum.each(contract_code_info.abi, fn selector ->
[parsed] = ABI.parse_specification([selector])
assert Repo.get_by(ContractMethod, abi: selector, identifier: parsed.method_id)
end)
end end
test "creates a smart contract with constructor arguments" do test "creates a smart contract with constructor arguments" do

@ -17,6 +17,7 @@ defmodule Explorer.Factory do
Address.TokenBalance, Address.TokenBalance,
Address.CoinBalance, Address.CoinBalance,
Block, Block,
ContractMethod,
Data, Data,
Hash, Hash,
InternalTransaction, InternalTransaction,
@ -143,6 +144,22 @@ defmodule Explorer.Factory do
} }
end end
def contract_method_factory() do
%ContractMethod{
identifier: Base.decode16!("60fe47b1", case: :lower),
abi: %{
"constant" => false,
"inputs" => [%{"name" => "x", "type" => "uint256"}],
"name" => "set",
"outputs" => [],
"payable" => false,
"stateMutability" => "nonpayable",
"type" => "function"
},
type: "function"
}
end
def block_hash do def block_hash do
{:ok, block_hash} = {:ok, block_hash} =
"block_hash" "block_hash"
@ -496,7 +513,7 @@ defmodule Explorer.Factory do
contract_code_info = contract_code_info() contract_code_info = contract_code_info()
%SmartContract{ %SmartContract{
address_hash: insert(:address).hash, address_hash: insert(:address, contract_code: contract_code_info.bytecode).hash,
compiler_version: contract_code_info.version, compiler_version: contract_code_info.version,
name: contract_code_info.name, name: contract_code_info.name,
contract_source_code: contract_code_info.source_code, contract_source_code: contract_code_info.source_code,

Loading…
Cancel
Save