Merge pull request #3592 from poanetwork/vb-nested-types-fix

Contract interaction: fix nested tuples in the output view, add formatting
pull/3600/head
Victor Baranov 4 years ago committed by GitHub
commit f9b34d2546
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      CHANGELOG.md
  2. 6
      apps/block_scout_web/lib/block_scout_web/templates/smart_contract/_function_response.html.eex
  3. 2
      apps/block_scout_web/lib/block_scout_web/templates/smart_contract/_functions.html.eex
  4. 247
      apps/block_scout_web/lib/block_scout_web/views/smart_contract_view.ex
  5. 37
      apps/block_scout_web/test/block_scout_web/views/tokens/smart_contract_view_test.exs

@ -5,6 +5,7 @@
- [#3564](https://github.com/poanetwork/blockscout/pull/3564) - Staking welcome message - [#3564](https://github.com/poanetwork/blockscout/pull/3564) - Staking welcome message
### Fixes ### Fixes
- [#3592](https://github.com/poanetwork/blockscout/pull/3592) - Contract interaction: fix nested tuples in the output view, add formatting
- [#3583](https://github.com/poanetwork/blockscout/pull/3583) - Reduce RPC requests and DB changes by Staking DApp - [#3583](https://github.com/poanetwork/blockscout/pull/3583) - Reduce RPC requests and DB changes by Staking DApp
### Chore ### Chore

@ -3,8 +3,8 @@
[ <strong><%= @function_name %></strong> method Response ] [ <strong><%= @function_name %></strong> method Response ]
[<%= for item <- @outputs do %> [<%= for item <- @outputs do %>
<span class="function-response-item"><%= if named_argument?(item) do %><%= item["name"] %> <%= if named_argument?(item) do %><span class="function-response-item"><%= item["name"] %></span><% end %>
<% end %> <span class="text-muted"><%= raw(values_with_type(item["value"], item["type"])) %></span>
<span class="text-muted">(<%= item["type"] %>)</span> : <%= values(item["value"], item["type"]) %></span><% end %>] <% end %>]
</pre> </pre>
</div> </div>

@ -121,7 +121,7 @@ to: address_contract_path(@conn, :index, metadata_for_verification.address_hash)
</span> </span>
</div> </div>
<% else %> <% else %>
<%= values(output["value"], output["type"]) %> <%= raw(values_only(output["value"], output["type"], output["components"])) %>
<% end %> <% end %>
<% end %> <% end %>
<% end %> <% end %>

@ -2,6 +2,7 @@ defmodule BlockScoutWeb.SmartContractView do
use BlockScoutWeb, :view use BlockScoutWeb, :view
alias Explorer.Chain alias Explorer.Chain
alias Explorer.Chain.Hash.Address
def queryable?(inputs) when not is_nil(inputs), do: Enum.any?(inputs) def queryable?(inputs) when not is_nil(inputs), do: Enum.any?(inputs)
@ -41,46 +42,244 @@ defmodule BlockScoutWeb.SmartContractView do
def named_argument?(%{"name" => _}), do: true def named_argument?(%{"name" => _}), do: true
def named_argument?(_), do: false def named_argument?(_), do: false
def values(addresses, type) when is_list(addresses) and type == "address[]" do def values_with_type(value, type, components \\ nil)
addresses
|> Enum.map(&values(&1, "address")) def values_with_type(value, type, components) when is_list(value) do
|> Enum.join(", ") cond do
String.starts_with?(type, "tuple") ->
tuple_types =
type
|> String.slice(0..-3)
|> supplement_type_with_components(components)
values =
value
|> tuple_array_to_array(tuple_types)
|> Enum.join(", ")
render_array_type_value(type, values)
String.starts_with?(type, "address") ->
values =
value
|> Enum.map(&binary_to_utf_string(&1))
|> Enum.join(", ")
render_array_type_value(type, values)
String.starts_with?(type, "bytes") ->
values =
value
|> Enum.map(&binary_to_utf_string(&1))
|> Enum.join(", ")
render_array_type_value(type, values)
true ->
values =
value
|> Enum.join(", ")
render_array_type_value(type, values)
end
end end
def values(values, type) when is_list(values) and type == "tuple[]" do def values_with_type(value, type, _components) when is_tuple(value) do
array_from_tuple = tuple_array_to_array(values) values =
value
|> tuple_to_array(type)
|> Enum.join(", ")
array_from_tuple render_type_value(type, values)
|> Enum.join(", ")
end end
def values(value, _type) when is_tuple(value) do def values_with_type(value, type, _components) when type in ["address", "address payable"] do
tuple_to_array(value) {:ok, address} = Address.cast(value)
render_type_value("address", to_string(address))
end end
def values(value, type) when type in ["address", "address payable"] do def values_with_type(value, "string", _components), do: render_type_value("string", value)
{:ok, address} = Explorer.Chain.Hash.Address.cast(value)
to_string(address) def values_with_type(value, "bool", _components), do: render_type_value("bool", to_string(value))
def values_with_type(value, type, _components), do: render_type_value(type, binary_to_utf_string(value))
def values_only(value, type, components) when is_list(value) do
cond do
String.starts_with?(type, "tuple") ->
tuple_types =
type
|> String.slice(0..-3)
|> supplement_type_with_components(components)
values =
value
|> tuple_array_to_array(tuple_types)
|> Enum.join(", ")
render_array_value(values)
String.starts_with?(type, "address") ->
values =
value
|> Enum.map(&binary_to_utf_string(&1))
|> Enum.join(", ")
render_array_value(values)
String.starts_with?(type, "bytes") ->
values =
value
|> Enum.map(&binary_to_utf_string(&1))
|> Enum.join(", ")
render_array_value(values)
true ->
values =
value
|> Enum.join(", ")
render_array_value(values)
end
end end
def values(values, _) when is_list(values), do: Enum.join(values, ",") def values_only(value, type, _components) when is_tuple(value) do
def values(value, _), do: value values =
value
|> tuple_to_array(type)
|> Enum.join(", ")
defp tuple_array_to_array(values) do
values values
|> Enum.map(fn value ->
tuple_to_array(value)
end)
end end
defp tuple_to_array(value) do def values_only(value, type, _components) when type in ["address", "address payable"] do
{:ok, address} = Address.cast(value)
to_string(address)
end
def values_only(value, "string", _components), do: value
def values_only(value, "bool", _components), do: to_string(value)
def values_only(value, _type, _components), do: binary_to_utf_string(value)
defp tuple_array_to_array(value, type) do
value value
|> Tuple.to_list() |> Enum.map(fn item ->
|> Enum.map(&binary_to_utf_string(&1)) tuple_to_array(item, type)
|> Enum.join(",") end)
end
defp tuple_to_array(value, type) do
types_string =
type
|> String.slice(6..-2)
types =
if String.trim(types_string) == "" do
[]
else
types_string
|> String.split(",")
end
{tuple_types, _} =
types
|> Enum.reduce({[], nil}, fn val, acc ->
{arr, to_merge} = acc
if to_merge do
if count_string_symbols(val)["]"] > count_string_symbols(val)["["] do
updated_arr = update_last_list_item(arr, val)
{updated_arr, !to_merge}
else
updated_arr = update_last_list_item(arr, val)
{updated_arr, to_merge}
end
else
if count_string_symbols(val)["["] > count_string_symbols(val)["]"] do
# credo:disable-for-next-line
{arr ++ [val], !to_merge}
else
# credo:disable-for-next-line
{arr ++ [val], to_merge}
end
end
end)
values_list =
value
|> Tuple.to_list()
values_types_list = Enum.zip(tuple_types, values_list)
values_types_list
|> Enum.map(fn {type, value} ->
values_with_type(value, type)
end)
end
defp update_last_list_item(arr, new_val) do
arr
|> Enum.with_index()
|> Enum.map(fn {item, index} ->
if index == Enum.count(arr) - 1 do
item <> "," <> new_val
else
item
end
end)
end
defp count_string_symbols(str) do
str
|> String.graphemes()
|> Enum.reduce(%{"[" => 0, "]" => 0}, fn char, acc ->
Map.update(acc, char, 1, &(&1 + 1))
end)
end end
defp binary_to_utf_string(item) do defp binary_to_utf_string(item) do
if is_binary(item), do: "0x" <> Base.encode16(item, case: :lower), else: item if is_binary(item) do
if String.starts_with?(item, "0x") do
item
else
"0x" <> Base.encode16(item, case: :lower)
end
else
item
end
end
defp render_type_value(type, value) do
"<div style=\"padding-left: 20px\">(#{type}) : #{value}</div>"
end
defp render_array_type_value(type, values) do
value_to_display = "[" <> values <> "]"
render_type_value(type, value_to_display)
end
defp render_array_value(values) do
value_to_display = "[" <> values <> "]"
value_to_display
end
defp supplement_type_with_components(type, components) do
if type == "tuple" && components do
types =
components
|> Enum.map(fn component ->
Map.get(component, "type")
end)
|> Enum.join(",")
"tuple[" <> types <> "]"
else
type
end
end end
end end

@ -240,21 +240,23 @@ defmodule BlockScoutWeb.SmartContractViewTest do
end end
end end
describe "values/2" do describe "values_only/2" do
test "joins the values when it is a list of a given type" do test "joins the values when it is a list of a given type" do
values = [8, 6, 9, 2, 2, 37] values = [8, 6, 9, 2, 2, 37]
assert SmartContractView.values(values, "type") == "8,6,9,2,2,37" assert SmartContractView.values_only(values, "type", nil) == "[8, 6, 9, 2, 2, 37]"
end end
test "convert the value to string receiving a value and the 'address' type" do test "convert the value to string receiving a value and the 'address' type" do
value = <<95, 38, 9, 115, 52, 182, 163, 43, 121, 81, 223, 97, 253, 12, 88, 3, 236, 93, 131, 84>> value = <<95, 38, 9, 115, 52, 182, 163, 43, 121, 81, 223, 97, 253, 12, 88, 3, 236, 93, 131, 84>>
assert SmartContractView.values(value, "address") == "0x5f26097334b6a32b7951df61fd0c5803ec5d8354" assert SmartContractView.values_only(value, "address", nil) == "0x5f26097334b6a32b7951df61fd0c5803ec5d8354"
end end
test "convert the value to string receiving a value and the 'address payable' type" do test "convert the value to string receiving a value and the 'address payable' type" do
value = <<95, 38, 9, 115, 52, 182, 163, 43, 121, 81, 223, 97, 253, 12, 88, 3, 236, 93, 131, 84>> value = <<95, 38, 9, 115, 52, 182, 163, 43, 121, 81, 223, 97, 253, 12, 88, 3, 236, 93, 131, 84>>
assert SmartContractView.values(value, "address payable") == "0x5f26097334b6a32b7951df61fd0c5803ec5d8354"
assert SmartContractView.values_only(value, "address payable", nil) ==
"0x5f26097334b6a32b7951df61fd0c5803ec5d8354"
end end
test "convert each value to string and join them when receiving 'address[]' as the type" do test "convert each value to string and join them when receiving 'address[]' as the type" do
@ -263,14 +265,35 @@ defmodule BlockScoutWeb.SmartContractViewTest do
<<207, 38, 14, 163, 23, 85, 86, 55, 197, 95, 112, 229, 93, 186, 141, 90, 216, 65, 76, 176>> <<207, 38, 14, 163, 23, 85, 86, 55, 197, 95, 112, 229, 93, 186, 141, 90, 216, 65, 76, 176>>
] ]
assert SmartContractView.values(value, "address[]") == assert SmartContractView.values_only(value, "address[]", nil) ==
"0x5f26097334b6a32b7951df61fd0c5803ec5d8354, 0xcf260ea317555637c55f70e55dba8d5ad8414cb0" "[0x5f26097334b6a32b7951df61fd0c5803ec5d8354, 0xcf260ea317555637c55f70e55dba8d5ad8414cb0]"
end end
test "returns the value when the type is neither 'address' nor 'address payable'" do test "returns the value when the type is neither 'address' nor 'address payable'" do
value = "POA" value = "POA"
assert SmartContractView.values(value, "not address") == "POA" assert SmartContractView.values_only(value, "string", nil) == "POA"
end
test "returns the value when the type is boolean" do
value = "true"
assert SmartContractView.values_only(value, "bool", nil) == "true"
end
test "returns the value when the type is bytes4" do
value = <<228, 184, 12, 77>>
assert SmartContractView.values_only(value, "bytes4", nil) == "0xe4b80c4d"
end
test "returns the value when the type is bytes32" do
value =
<<156, 209, 70, 119, 249, 170, 85, 105, 179, 187, 179, 81, 252, 214, 125, 17, 21, 170, 86, 58, 225, 98, 66, 118,
211, 212, 230, 127, 179, 214, 249, 38>>
assert SmartContractView.values_only(value, "bytes32", nil) ==
"0x9cd14677f9aa5569b3bbb351fcd67d1115aa563ae1624276d3d4e67fb3d6f926"
end end
end end
end end

Loading…
Cancel
Save