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. 239
      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
### 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
### Chore

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

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

@ -2,6 +2,7 @@ defmodule BlockScoutWeb.SmartContractView do
use BlockScoutWeb, :view
alias Explorer.Chain
alias Explorer.Chain.Hash.Address
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?(_), do: false
def values(addresses, type) when is_list(addresses) and type == "address[]" do
addresses
|> Enum.map(&values(&1, "address"))
def values_with_type(value, type, components \\ nil)
def values_with_type(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_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
def values_with_type(value, type, _components) when is_tuple(value) do
values =
value
|> tuple_to_array(type)
|> Enum.join(", ")
render_type_value(type, values)
end
def values_with_type(value, type, _components) when type in ["address", "address payable"] do
{:ok, address} = Address.cast(value)
render_type_value("address", to_string(address))
end
def values(values, type) when is_list(values) and type == "tuple[]" do
array_from_tuple = tuple_array_to_array(values)
def values_with_type(value, "string", _components), do: render_type_value("string", value)
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)
array_from_tuple
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
def values(value, _type) when is_tuple(value) do
tuple_to_array(value)
def values_only(value, type, _components) when is_tuple(value) do
values =
value
|> tuple_to_array(type)
|> Enum.join(", ")
values
end
def values(value, type) when type in ["address", "address payable"] do
{:ok, address} = Explorer.Chain.Hash.Address.cast(value)
def values_only(value, type, _components) when type in ["address", "address payable"] do
{:ok, address} = Address.cast(value)
to_string(address)
end
def values(values, _) when is_list(values), do: Enum.join(values, ",")
def values(value, _), do: value
def values_only(value, "string", _components), do: value
defp tuple_array_to_array(values) do
values
|> Enum.map(fn value ->
tuple_to_array(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
|> Enum.map(fn item ->
tuple_to_array(item, type)
end)
end
defp tuple_to_array(value) do
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()
|> Enum.map(&binary_to_utf_string(&1))
|> Enum.join(",")
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
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

@ -240,21 +240,23 @@ defmodule BlockScoutWeb.SmartContractViewTest do
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
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
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>>
assert SmartContractView.values(value, "address") == "0x5f26097334b6a32b7951df61fd0c5803ec5d8354"
assert SmartContractView.values_only(value, "address", nil) == "0x5f26097334b6a32b7951df61fd0c5803ec5d8354"
end
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>>
assert SmartContractView.values(value, "address payable") == "0x5f26097334b6a32b7951df61fd0c5803ec5d8354"
assert SmartContractView.values_only(value, "address payable", nil) ==
"0x5f26097334b6a32b7951df61fd0c5803ec5d8354"
end
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>>
]
assert SmartContractView.values(value, "address[]") ==
"0x5f26097334b6a32b7951df61fd0c5803ec5d8354, 0xcf260ea317555637c55f70e55dba8d5ad8414cb0"
assert SmartContractView.values_only(value, "address[]", nil) ==
"[0x5f26097334b6a32b7951df61fd0c5803ec5d8354, 0xcf260ea317555637c55f70e55dba8d5ad8414cb0]"
end
test "returns the value when the type is neither 'address' nor 'address payable'" do
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

Loading…
Cancel
Save