Added names to smart-contract's outputs

pull/4452/head
nikitosing 3 years ago committed by Viktor Baranov
parent 648f9d3ead
commit 7baa78841c
  1. 2
      .dialyzer-ignore
  2. 1
      CHANGELOG.md
  3. 10
      apps/block_scout_web/lib/block_scout_web/controllers/smart_contract_controller.ex
  4. 10
      apps/block_scout_web/lib/block_scout_web/templates/smart_contract/_function_response.html.eex
  5. 83
      apps/block_scout_web/lib/block_scout_web/views/smart_contract_view.ex
  6. 58
      apps/explorer/lib/explorer/smart_contract/reader.ex

@ -18,7 +18,7 @@ lib/block_scout_web/views/layout_view.ex:145: The call 'Elixir.Poison.Parser':'p
lib/block_scout_web/views/layout_view.ex:237: The call 'Elixir.Poison.Parser':'parse!'
lib/block_scout_web/controllers/api/rpc/transaction_controller.ex:21
lib/block_scout_web/controllers/api/rpc/transaction_controller.ex:22
lib/explorer/smart_contract/reader.ex:348
lib/explorer/smart_contract/reader.ex:344
lib/indexer/fetcher/token_total_supply_on_demand.ex:16
lib/explorer/exchange_rates/source.ex:110
lib/explorer/exchange_rates/source.ex:113

@ -2,6 +2,7 @@
### Features
- [#4475](https://github.com/blockscout/blockscout/pull/4475) - Tx page facelifting
- [#4452](https://github.com/blockscout/blockscout/pull/4452) - Add names for smart-conrtact's function response
### Fixes
- [#4483](https://github.com/blockscout/blockscout/pull/4483) - Fix copy-paste typo in `token_transfers_counter.ex`

@ -93,11 +93,12 @@ defmodule BlockScoutWeb.SmartContractController do
{:ok, address} <- Chain.find_contract_address(address_hash, address_options, true) do
contract_type = if Chain.proxy_contract?(address.hash, address.smart_contract.abi), do: :proxy, else: :regular
outputs =
Reader.query_function(
%{output: outputs, names: names} =
Reader.query_function_with_names(
address_hash,
%{method_id: params["method_id"], args: params["args"]},
contract_type
contract_type,
params["function_name"]
)
conn
@ -107,7 +108,8 @@ defmodule BlockScoutWeb.SmartContractController do
"_function_response.html",
function_name: params["function_name"],
method_id: params["method_id"],
outputs: outputs
outputs: outputs,
names: names
)
else
:error ->

@ -3,12 +3,12 @@
[ <strong><%= @function_name %></strong> method Response ]
<%= case @outputs do %>
<% {:error, message} -> %>
<span class="text-muted"><%=raw(values_with_type(message, :error))%></span>
<span class="text-muted"><%=raw(values_with_type(message, :error, nil, 0))%></span>
<% _ -> %>
[<%= for item <- @outputs do %>
<%= if named_argument?(item) do %><span class="function-response-item"><%= item["name"] %></span><% end %>
<span class="text-muted"><%= raw(values_with_type(item["value"], item["type"])) %></span>
<% end %>]
[<%= for {item, index} <- Enum.with_index(@outputs) do %>
<%= if named_argument?(item) do %><span class="function-response-item"><%= item["name"] %></span><% end %>
<span class="text-muted"><%= raw(values_with_type(item["value"], item["type"], fetch_name(@names, index), 0)) %></span>
<% end %>]
<% end %>
</pre>
</div>

@ -34,9 +34,9 @@ defmodule BlockScoutWeb.SmartContractView do
def named_argument?(%{"name" => _}), do: true
def named_argument?(_), do: false
def values_with_type(value, type, components \\ nil)
def values_with_type(value, type, names, index, components \\ nil)
def values_with_type(value, type, components) when is_list(value) do
def values_with_type(value, type, names, index, components) when is_list(value) do
cond do
String.starts_with?(type, "tuple") ->
tuple_types =
@ -46,10 +46,10 @@ defmodule BlockScoutWeb.SmartContractView do
values =
value
|> tuple_array_to_array(tuple_types)
|> Enum.join(", ")
|> tuple_array_to_array(tuple_types, fetch_name(names, index + 1))
|> Enum.join("),\n(")
render_array_type_value(type, values)
render_array_type_value(type, "(\n" <> values <> ")", fetch_name(names, index))
String.starts_with?(type, "address") ->
values =
@ -57,7 +57,7 @@ defmodule BlockScoutWeb.SmartContractView do
|> Enum.map(&binary_to_utf_string(&1))
|> Enum.join(", ")
render_array_type_value(type, values)
render_array_type_value(type, values, fetch_name(names, index))
String.starts_with?(type, "bytes") ->
values =
@ -65,50 +65,66 @@ defmodule BlockScoutWeb.SmartContractView do
|> Enum.map(&binary_to_utf_string(&1))
|> Enum.join(", ")
render_array_type_value(type, values)
render_array_type_value(type, values, fetch_name(names, index))
true ->
values =
value
|> Enum.join(", ")
|> Enum.join("),\n(")
render_array_type_value(type, values)
render_array_type_value(type, "(\n" <> values <> ")", fetch_name(names, index))
end
end
def values_with_type(value, type, _components) when is_tuple(value) do
def values_with_type(value, type, names, index, _components) when is_tuple(value) do
values =
value
|> tuple_to_array(type)
|> Enum.join(", ")
|> tuple_to_array(type, fetch_name(names, index + 1))
|> Enum.join("")
render_type_value(type, values)
render_type_value(type, values, fetch_name(names, index))
end
def values_with_type(value, type, _components) when type in [:address, "address", "address payable"] do
def values_with_type(value, type, names, index, _components) when type in [:address, "address", "address payable"] do
case HashAddress.cast(value) do
{:ok, address} ->
render_type_value("address", to_string(address))
render_type_value("address", to_string(address), fetch_name(names, index))
_ ->
""
end
end
def values_with_type(value, "string", _components), do: render_type_value("string", value)
def values_with_type(value, "string", names, index, _components),
do: render_type_value("string", value, fetch_name(names, index))
def values_with_type(value, :string, names, index, _components),
do: render_type_value("string", value, fetch_name(names, index))
def values_with_type(value, :bytes, names, index, _components),
do: render_type_value("bytes", value, fetch_name(names, index))
def values_with_type(value, :string, _components), do: render_type_value("string", value)
def values_with_type(value, "bool", names, index, _components),
do: render_type_value("bool", to_string(value), fetch_name(names, index))
def values_with_type(value, :bytes, _components), do: render_type_value("bytes", value)
def values_with_type(value, :bool, names, index, _components),
do: render_type_value("bool", to_string(value), fetch_name(names, index))
def values_with_type(value, "bool", _components), do: render_type_value("bool", to_string(value))
def values_with_type(value, type, names, index, _components),
do: render_type_value(type, binary_to_utf_string(value), fetch_name(names, index))
def values_with_type(value, :bool, _components), do: render_type_value("bool", to_string(value))
def values_with_type(value, :error, _components), do: render_type_value("error", value, nil)
def values_with_type(value, :error, _components), do: render_type_value("error", value)
defp fetch_name(nil, _index), do: nil
def values_with_type(value, type, _components) do
render_type_value(type, binary_to_utf_string(value))
defp fetch_name([], _index), do: nil
defp fetch_name(names, index) when is_list(names) do
Enum.at(names, index)
end
defp fetch_name(name, _index) when is_binary(name) do
name
end
def values_only(value, type, components) when is_list(value) do
@ -192,14 +208,14 @@ defmodule BlockScoutWeb.SmartContractView do
end
end
defp tuple_array_to_array(value, type) do
defp tuple_array_to_array(value, type, names \\ []) do
value
|> Enum.map(fn item ->
tuple_to_array(item, type)
tuple_to_array(item, type, names)
end)
end
defp tuple_to_array(value, type) do
defp tuple_to_array(value, type, names \\ []) do
types_string =
type
|> String.slice(6..-2)
@ -243,8 +259,9 @@ defmodule BlockScoutWeb.SmartContractView do
values_types_list = Enum.zip(tuple_types, values_list)
values_types_list
|> Enum.map(fn {type, value} ->
values_with_type(value, type)
|> Enum.with_index()
|> Enum.map(fn {{type, value}, index} ->
values_with_type(value, type, fetch_name(names, index), 0)
end)
end
@ -286,14 +303,18 @@ defmodule BlockScoutWeb.SmartContractView do
end
end
defp render_type_value(type, value) do
defp render_type_value(type, value, type) do
"<div style=\"padding-left: 20px\">(#{type}) : #{value}</div>"
end
defp render_array_type_value(type, values) do
defp render_type_value(type, value, name) do
"<div style=\"padding-left: 20px\"><span style=\"color: black\">#{name}</span> (#{type}) : #{value}</div>"
end
defp render_array_type_value(type, values, name) do
value_to_display = "[" <> values <> "]"
render_type_value(type, value_to_display)
render_type_value(type, value_to_display, name)
end
defp render_array_value(values) do

@ -317,6 +317,12 @@ defmodule Explorer.SmartContract.Reader do
Map.replace!(function, "outputs", values)
end
def query_function_with_names(contract_address_hash, %{method_id: method_id, args: args}, type, function_name) do
outputs = query_function(contract_address_hash, %{method_id: method_id, args: args}, type)
names = parse_names_from_abi(get_abi(contract_address_hash, type), function_name)
%{output: outputs, names: names}
end
@doc """
Fetches the blockchain value of a function that requires arguments.
"""
@ -327,20 +333,10 @@ defmodule Explorer.SmartContract.Reader do
@spec query_function(Hash.t(), %{method_id: String.t(), args: [term()]}, atom()) :: [%{}]
def query_function(contract_address_hash, %{method_id: method_id, args: args}, type) do
abi =
contract_address_hash
|> Chain.address_hash_to_smart_contract()
|> Map.get(:abi)
final_abi =
if type == :proxy do
Chain.get_implementation_abi_from_proxy(contract_address_hash, abi)
else
abi
end
abi = get_abi(contract_address_hash, type)
parsed_final_abi =
final_abi
abi
|> ABI.parse_specification()
%{outputs: outputs, method_id: method_id} =
@ -359,10 +355,46 @@ defmodule Explorer.SmartContract.Reader do
end
contract_address_hash
|> query_verified_contract(%{method_id => normalize_args(args)}, final_abi)
|> query_verified_contract(%{method_id => normalize_args(args)}, abi)
|> link_outputs_and_values(outputs, method_id)
end
defp get_abi(contract_address_hash, type) do
abi =
contract_address_hash
|> Chain.address_hash_to_smart_contract()
|> Map.get(:abi)
if type == :proxy do
Chain.get_implementation_abi_from_proxy(contract_address_hash, abi)
else
abi
end
end
defp parse_names_from_abi(abi, function_name) do
function = Enum.find(abi, fn el -> el["type"] == "function" and el["name"] == function_name end)
outputs_to_list(function["outputs"])
end
defp outputs_to_list(nil), do: []
defp outputs_to_list([]), do: []
defp outputs_to_list(outputs) do
for el <- outputs do
name = if validate_name(el["name"]), do: el["name"], else: el["internalType"]
if Map.has_key?(el, "components") do
[name] ++ [outputs_to_list(el["components"])]
else
name
end
end
end
defp validate_name(name), do: not is_nil(name) and String.length(name) > 0
defp find_function_by_method(parsed_abi, method_id) do
parsed_abi
|> Enum.filter(fn %ABI.FunctionSelector{method_id: find_method_id} ->

Loading…
Cancel
Save