Merge pull request #4218 from blockscout/np-spoiler-first-task

Hide long arrays in smart-contract's output
pull/4231/head
Victor Baranov 4 years ago committed by GitHub
commit 058b596b0c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      CHANGELOG.md
  2. 3
      apps/block_scout_web/config/config.exs
  3. 42
      apps/block_scout_web/lib/block_scout_web/templates/smart_contract/_functions.html.eex
  4. 37
      apps/block_scout_web/lib/block_scout_web/views/smart_contract_view.ex
  5. 48
      apps/block_scout_web/test/block_scout_web/views/tokens/smart_contract_view_test.exs

@ -1,6 +1,7 @@
## Current ## Current
### Features ### Features
- [#4218](https://github.com/blockscout/blockscout/pull/4218) - Hide long arrays in smart-contract's
- [#4205](https://github.com/blockscout/blockscout/pull/4205) - Total transactions fees per day API endpoint - [#4205](https://github.com/blockscout/blockscout/pull/4205) - Total transactions fees per day API endpoint
- [#4158](https://github.com/blockscout/blockscout/pull/4158) - Calculate total fee per day - [#4158](https://github.com/blockscout/blockscout/pull/4158) - Calculate total fee per day
- [#4067](https://github.com/blockscout/blockscout/pull/4067) - Display LP tokens USD value and custom metadata in tokens dropdown at address page - [#4067](https://github.com/blockscout/blockscout/pull/4067) - Display LP tokens USD value and custom metadata in tokens dropdown at address page

@ -48,7 +48,8 @@ config :block_scout_web,
dark_forest_addresses: System.get_env("CUSTOM_CONTRACT_ADDRESSES_DARK_FOREST"), dark_forest_addresses: System.get_env("CUSTOM_CONTRACT_ADDRESSES_DARK_FOREST"),
dark_forest_addresses_v_0_5: System.get_env("CUSTOM_CONTRACT_ADDRESSES_DARK_FOREST_V_0_5"), dark_forest_addresses_v_0_5: System.get_env("CUSTOM_CONTRACT_ADDRESSES_DARK_FOREST_V_0_5"),
circles_addresses: System.get_env("CUSTOM_CONTRACT_ADDRESSES_CIRCLES"), circles_addresses: System.get_env("CUSTOM_CONTRACT_ADDRESSES_CIRCLES"),
test_tokens_addresses: System.get_env("CUSTOM_CONTRACT_ADDRESSES_TEST_TOKEN") test_tokens_addresses: System.get_env("CUSTOM_CONTRACT_ADDRESSES_TEST_TOKEN"),
max_size_to_show_array_as_is: Integer.parse(System.get_env("MAX_SIZE_UNLESS_HIDE_ARRAY", "50"))
config :block_scout_web, BlockScoutWeb.Counters.BlocksIndexedCounter, enabled: true config :block_scout_web, BlockScoutWeb.Counters.BlocksIndexedCounter, enabled: true

@ -98,35 +98,35 @@
<% end %> <% end %>
</div> </div>
<% end %> <% end %>
<div data-function-response></div> <div data-function-response></div>
</div> </div>
<% else %> <% else %>
<span class="py-2 word-break-all"> <%= if outputs?(function["outputs"]) do %>
<%= if outputs?(function["outputs"]) do %> <%= for output <- function["outputs"] do %>
<%= for output <- function["outputs"] do %> <%= if address?(output["type"]) do %>
<%= if address?(output["type"]) do %> <div class="py-2 word-break-all">
<%= link( <%= link(
output["value"], output["value"],
to: address_path(@conn, :show, output["value"]) to: address_path(@conn, :show, output["value"])) %>
) %> </div>
<% else %>
<%= if output["type"] == "uint256" do %>
<div class="py-2 word-break-all">
<div data-wei-ether-converter>
<span data-conversion-unit data-original-value="<%= output["value"] %>"><%= output["value"] %></span>
<span class="py-2 px-2">
<input class="wei-ether" type="checkbox" autocomplete="off">
<span class="d-inline-block" data-conversion-text-wei><%= gettext("WEI")%></span>
<span class="d-none" data-conversion-text-eth><%= gettext("ETH")%></span>
</span>
</div>
</div>
<% else %> <% else %>
<%= if output["type"] == "uint256" do %> <%= raw(values_only(output["value"], output["type"], output["components"])) %>
<div data-wei-ether-converter>
<span data-conversion-unit data-original-value="<%= output["value"] %>"><%= output["value"] %></span>
<span class="py-2 px-2">
<input class="wei-ether" type="checkbox" autocomplete="off">
<span class="d-inline-block" data-conversion-text-wei><%= gettext("WEI")%></span>
<span class="d-none" data-conversion-text-eth><%= gettext("ETH")%></span>
</span>
</div>
<% else %>
<%= raw(values_only(output["value"], output["type"], output["components"])) %>
<% end %>
<% end %> <% end %>
<% end %> <% end %>
<% end %> <% end %>
</span> <% end %>
<% end %> <% end %>
</div> </div>
<% end %> <% end %>

@ -105,6 +105,9 @@ defmodule BlockScoutWeb.SmartContractView do
end end
def values_only(value, type, components) when is_list(value) do def values_only(value, type, components) when is_list(value) do
max_size = Enum.at(Tuple.to_list(Application.get_env(:block_scout_web, :max_size_to_show_array_as_is)), 0)
is_too_long = length(value) > max_size
cond do cond do
String.starts_with?(type, "tuple") -> String.starts_with?(type, "tuple") ->
tuple_types = tuple_types =
@ -117,7 +120,7 @@ defmodule BlockScoutWeb.SmartContractView do
|> tuple_array_to_array(tuple_types) |> tuple_array_to_array(tuple_types)
|> Enum.join(", ") |> Enum.join(", ")
render_array_value(values) wrap_output(render_array_value(values), is_too_long)
String.starts_with?(type, "address") -> String.starts_with?(type, "address") ->
values = values =
@ -125,7 +128,7 @@ defmodule BlockScoutWeb.SmartContractView do
|> Enum.map(&binary_to_utf_string(&1)) |> Enum.map(&binary_to_utf_string(&1))
|> Enum.join(", ") |> Enum.join(", ")
render_array_value(values) wrap_output(render_array_value(values), is_too_long)
String.starts_with?(type, "bytes") -> String.starts_with?(type, "bytes") ->
values = values =
@ -133,14 +136,14 @@ defmodule BlockScoutWeb.SmartContractView do
|> Enum.map(&binary_to_utf_string(&1)) |> Enum.map(&binary_to_utf_string(&1))
|> Enum.join(", ") |> Enum.join(", ")
render_array_value(values) wrap_output(render_array_value(values), is_too_long)
true -> true ->
values = values =
value value
|> Enum.join(", ") |> Enum.join(", ")
render_array_value(values) wrap_output(render_array_value(values), is_too_long)
end end
end end
@ -150,26 +153,36 @@ defmodule BlockScoutWeb.SmartContractView do
|> tuple_to_array(type) |> tuple_to_array(type)
|> Enum.join(", ") |> Enum.join(", ")
values max_size = Enum.at(Tuple.to_list(Application.get_env(:block_scout_web, :max_size_to_show_array_as_is)), 0)
wrap_output(values, tuple_size(value) > max_size)
end end
def values_only(value, type, _components) when type in [:address, "address", "address payable"] do def values_only(value, type, _components) when type in [:address, "address", "address payable"] do
{:ok, address} = HashAddress.cast(value) {:ok, address} = HashAddress.cast(value)
to_string(address) wrap_output(to_string(address))
end end
def values_only(value, "string", _components), do: value def values_only(value, "string", _components), do: wrap_output(value)
def values_only(value, :string, _components), do: value def values_only(value, :string, _components), do: wrap_output(value)
def values_only(value, :bytes, _components), do: value def values_only(value, :bytes, _components), do: wrap_output(value)
def values_only(value, "bool", _components), do: to_string(value) def values_only(value, "bool", _components), do: wrap_output(to_string(value))
def values_only(value, :bool, _components), do: to_string(value) def values_only(value, :bool, _components), do: wrap_output(to_string(value))
def values_only(value, _type, _components) do def values_only(value, _type, _components) do
binary_to_utf_string(value) wrap_output(binary_to_utf_string(value))
end
def wrap_output(value, is_too_long \\ false) do
if is_too_long do
"<details class=\"py-2 word-break-all\"><summary>Click to view</summary>#{value}</details>"
else
"<div class=\"py-2 word-break-all\">#{value}</div>"
end
end end
defp tuple_array_to_array(value, type) do defp tuple_array_to_array(value, type) do

@ -1,6 +1,8 @@
defmodule BlockScoutWeb.SmartContractViewTest do defmodule BlockScoutWeb.SmartContractViewTest do
use BlockScoutWeb.ConnCase, async: true use BlockScoutWeb.ConnCase, async: true
@max_size Enum.at(Tuple.to_list(Application.get_env(:block_scout_web, :max_size_to_show_array_as_is)), 0)
alias BlockScoutWeb.SmartContractView alias BlockScoutWeb.SmartContractView
describe "queryable?" do describe "queryable?" do
@ -129,28 +131,39 @@ defmodule BlockScoutWeb.SmartContractViewTest do
end end
end end
defp wrap_it(output, length \\ -1) do
if length > @max_size do
"<details class=\"py-2 word-break-all\"><summary>Click to view</summary>#{output}</details>"
else
"<div class=\"py-2 word-break-all\">#{output}</div>"
end
end
describe "values_only/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_only(values, "type", nil) == wrap_it("[8, 6, 9, 2, 2, 37]", length(values))
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_only(value, "address", nil) == "0x5f26097334b6a32b7951df61fd0c5803ec5d8354"
assert SmartContractView.values_only(value, "address", nil) ==
wrap_it("0x5f26097334b6a32b7951df61fd0c5803ec5d8354")
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_only(value, :address, nil) == "0x5f26097334b6a32b7951df61fd0c5803ec5d8354"
assert SmartContractView.values_only(value, :address, nil) ==
wrap_it("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_only(value, "address payable", nil) == assert SmartContractView.values_only(value, "address payable", nil) ==
"0x5f26097334b6a32b7951df61fd0c5803ec5d8354" wrap_it("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
@ -160,19 +173,22 @@ defmodule BlockScoutWeb.SmartContractViewTest do
] ]
assert SmartContractView.values_only(value, "address[]", nil) == assert SmartContractView.values_only(value, "address[]", nil) ==
"[0x5f26097334b6a32b7951df61fd0c5803ec5d8354, 0xcf260ea317555637c55f70e55dba8d5ad8414cb0]" wrap_it(
"[0x5f26097334b6a32b7951df61fd0c5803ec5d8354, 0xcf260ea317555637c55f70e55dba8d5ad8414cb0]",
length(value)
)
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_only(value, "string", nil) == "POA" assert SmartContractView.values_only(value, "string", nil) == wrap_it("POA")
end end
test "returns the value when the type is :string" do test "returns the value when the type is :string" do
value = "POA" value = "POA"
assert SmartContractView.values_only(value, :string, nil) == "POA" assert SmartContractView.values_only(value, :string, nil) == wrap_it("POA")
end end
test "returns the value when the type is :bytes" do test "returns the value when the type is :bytes" do
@ -180,25 +196,27 @@ defmodule BlockScoutWeb.SmartContractViewTest do
"0x00050000a7823d6f1e31569f51861e345b30c6bebf70ebe700000000000019f2f6a78083ca3e2a662d6dd1703c939c8ace2e268d88ad09518695c6c3712ac10a214be5109a65567100061a800101806401125e4cfb0000000000000000000000000ae055097c6d159879521c384f1d2123d1f195e60000000000000000000000004c26ca0dc82a6e7bb00b8815a65985b67c0d30d3000000000000000000000000000000000000000000000002b5598f488fb733c9" "0x00050000a7823d6f1e31569f51861e345b30c6bebf70ebe700000000000019f2f6a78083ca3e2a662d6dd1703c939c8ace2e268d88ad09518695c6c3712ac10a214be5109a65567100061a800101806401125e4cfb0000000000000000000000000ae055097c6d159879521c384f1d2123d1f195e60000000000000000000000004c26ca0dc82a6e7bb00b8815a65985b67c0d30d3000000000000000000000000000000000000000000000002b5598f488fb733c9"
assert SmartContractView.values_only(value, :bytes, nil) == assert SmartContractView.values_only(value, :bytes, nil) ==
"0x00050000a7823d6f1e31569f51861e345b30c6bebf70ebe700000000000019f2f6a78083ca3e2a662d6dd1703c939c8ace2e268d88ad09518695c6c3712ac10a214be5109a65567100061a800101806401125e4cfb0000000000000000000000000ae055097c6d159879521c384f1d2123d1f195e60000000000000000000000004c26ca0dc82a6e7bb00b8815a65985b67c0d30d3000000000000000000000000000000000000000000000002b5598f488fb733c9" wrap_it(
"0x00050000a7823d6f1e31569f51861e345b30c6bebf70ebe700000000000019f2f6a78083ca3e2a662d6dd1703c939c8ace2e268d88ad09518695c6c3712ac10a214be5109a65567100061a800101806401125e4cfb0000000000000000000000000ae055097c6d159879521c384f1d2123d1f195e60000000000000000000000004c26ca0dc82a6e7bb00b8815a65985b67c0d30d3000000000000000000000000000000000000000000000002b5598f488fb733c9"
)
end end
test "returns the value when the type is boolean" do test "returns the value when the type is boolean" do
value = "true" value = "true"
assert SmartContractView.values_only(value, "bool", nil) == "true" assert SmartContractView.values_only(value, "bool", nil) == wrap_it("true")
end end
test "returns the value when the type is :bool" do test "returns the value when the type is :bool" do
value = "true" value = "true"
assert SmartContractView.values_only(value, :bool, nil) == "true" assert SmartContractView.values_only(value, :bool, nil) == wrap_it("true")
end end
test "returns the value when the type is bytes4" do test "returns the value when the type is bytes4" do
value = <<228, 184, 12, 77>> value = <<228, 184, 12, 77>>
assert SmartContractView.values_only(value, "bytes4", nil) == "0xe4b80c4d" assert SmartContractView.values_only(value, "bytes4", nil) == wrap_it("0xe4b80c4d")
end end
test "returns the value when the type is bytes32" do test "returns the value when the type is bytes32" do
@ -207,19 +225,19 @@ defmodule BlockScoutWeb.SmartContractViewTest do
211, 212, 230, 127, 179, 214, 249, 38>> 211, 212, 230, 127, 179, 214, 249, 38>>
assert SmartContractView.values_only(value, "bytes32", nil) == assert SmartContractView.values_only(value, "bytes32", nil) ==
"0x9cd14677f9aa5569b3bbb351fcd67d1115aa563ae1624276d3d4e67fb3d6f926" wrap_it("0x9cd14677f9aa5569b3bbb351fcd67d1115aa563ae1624276d3d4e67fb3d6f926")
end end
test "returns the value when the type is uint(n) and value is 0" do test "returns the value when the type is uint(n) and value is 0" do
value = "0" value = "0"
assert SmartContractView.values_only(value, "uint64", nil) == "0" assert SmartContractView.values_only(value, "uint64", nil) == wrap_it("0")
end end
test "returns the value when the type is int(n) and value is 0" do test "returns the value when the type is int(n) and value is 0" do
value = "0" value = "0"
assert SmartContractView.values_only(value, "int64", nil) == "0" assert SmartContractView.values_only(value, "int64", nil) == wrap_it("0")
end end
end end
end end

Loading…
Cancel
Save