parent
1908097a9b
commit
48f363e0c6
@ -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 %> |
@ -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 |
@ -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 |
Loading…
Reference in new issue