Skip to content

Commit

Permalink
Sanitize values of getters on smart contract read page
Browse files Browse the repository at this point in the history
  • Loading branch information
vbaranov committed Jun 15, 2022
1 parent 39ffea3 commit fccfee1
Show file tree
Hide file tree
Showing 4 changed files with 24 additions and 212 deletions.
107 changes: 8 additions & 99 deletions apps/block_scout_web/lib/block_scout_web/views/smart_contract_view.ex
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ defmodule BlockScoutWeb.SmartContractView do
alias Explorer.Chain.Hash.Address, as: HashAddress
alias Explorer.SmartContract.Helper

@tab "    "

def queryable?(inputs) when not is_nil(inputs), do: Enum.any?(inputs)

def queryable?(inputs) when is_nil(inputs), do: false
Expand Down Expand Up @@ -57,6 +55,7 @@ defmodule BlockScoutWeb.SmartContractView do

values =
value
|> Helper.sanitize_input()
|> tuple_array_to_array(tuple_types, fetch_name(names, index + 1))
|> Enum.join("),\n(")

Expand All @@ -65,20 +64,23 @@ defmodule BlockScoutWeb.SmartContractView do
String.starts_with?(type, "address") ->
values =
value
|> Helper.sanitize_input()
|> Enum.map_join(", ", &binary_to_utf_string(&1))

render_array_type_value(type, values, fetch_name(names, index))

String.starts_with?(type, "bytes") ->
values =
value
|> Helper.sanitize_input()
|> Enum.map_join(", ", &binary_to_utf_string(&1))

render_array_type_value(type, values, fetch_name(names, index))

true ->
values =
value
|> Helper.sanitize_input()
|> Enum.join("),\n(")

render_array_type_value(type, "(\n" <> values <> ")", fetch_name(names, index))
Expand Down Expand Up @@ -136,87 +138,6 @@ defmodule BlockScoutWeb.SmartContractView do
name
end

def values_only(value, type, components, nested_index \\ 1)

def values_only(value, type, components, nested_index) 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
String.ends_with?(type, "[][]") ->
values =
value
|> Enum.map(&values_only(&1, String.slice(type, 0..-3), components, nested_index + 1))
|> Enum.map_join(",</br>", &(String.duplicate(@tab, nested_index) <> &1))

wrap_output(render_nested_array_value(values, nested_index - 1), is_too_long)

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(", ")

wrap_output(render_array_value(values), is_too_long)

String.starts_with?(type, "address") ->
values =
value
|> Enum.map_join(", ", &binary_to_utf_string(&1))

wrap_output(render_array_value(values), is_too_long)

String.starts_with?(type, "bytes") ->
values =
value
|> Enum.map_join(", ", &binary_to_utf_string(&1))

wrap_output(render_array_value(values), is_too_long)

true ->
values =
value
|> Enum.join(", ")

wrap_output(render_array_value(values), is_too_long)
end
end

def values_only(value, type, _components, _nested_index) when is_tuple(value) do
values =
value
|> tuple_to_array(type)
|> Enum.join(", ")

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

def values_only(value, type, _components, _nested_index) when type in [:address, "address", "address payable"] do
{:ok, address} = HashAddress.cast(value)
wrap_output(to_string(address))
end

def values_only(value, "string", _components, _nested_index), do: wrap_output(value)

def values_only(value, :string, _components, _nested_index), do: wrap_output(value)

def values_only(value, :bytes, _components, _nested_index), do: wrap_output(value)

def values_only(value, "bool", _components, _nested_index), do: wrap_output(to_string(value))

def values_only(value, :bool, _components, _nested_index), do: wrap_output(to_string(value))

def values_only(value, _type, _components, _nested_index) do
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>"
Expand All @@ -225,14 +146,14 @@ defmodule BlockScoutWeb.SmartContractView do
end
end

defp tuple_array_to_array(value, type, names \\ []) do
defp tuple_array_to_array(value, type, names) do
value
|> Enum.map(fn item ->
tuple_to_array(item, type, names)
end)
end

defp tuple_to_array(value, type, names \\ []) do
defp tuple_to_array(value, type, names) do
types_string =
type
|> String.slice(6..-2)
Expand Down Expand Up @@ -321,11 +242,11 @@ defmodule BlockScoutWeb.SmartContractView do
end

defp render_type_value(type, value, type) do
"<div class=\"pl-3\"><i>(#{type})</i> : #{value}</div>"
"<div class=\"pl-3\"><i>(#{type})</i> : #{value |> Helper.sanitize_input()}</div>"
end

defp render_type_value(type, value, name) do
"<div class=\"pl-3\"><i><span style=\"color: black\">#{name}</span> (#{type})</i> : #{value}</div>"
"<div class=\"pl-3\"><i><span style=\"color: black\">#{name}</span> (#{type})</i> : #{value |> Helper.sanitize_input()}</div>"
end

defp render_array_type_value(type, values, name) do
Expand All @@ -334,18 +255,6 @@ defmodule BlockScoutWeb.SmartContractView do
render_type_value(type, value_to_display, name)
end

defp render_nested_array_value(values, nested_index) do
value_to_display = "[</br>" <> values <> "</br>" <> String.duplicate(@tab, nested_index) <> "]"

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 =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,106 +138,4 @@ defmodule BlockScoutWeb.Tokens.SmartContractViewTest do
"<span class=\"word-break-all\" style=\"line-height: 3;\">#{output}</span>"
end
end

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_only(values, "type", nil) == wrap_it("[8, 6, 9, 2, 2, 37]", length(values))
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_only(value, "address", nil) ==
wrap_it("0x5f26097334b6a32b7951df61fd0c5803ec5d8354")
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_only(value, :address, nil) ==
wrap_it("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_only(value, "address payable", nil) ==
wrap_it("0x5f26097334b6a32b7951df61fd0c5803ec5d8354")
end

test "convert each value to string and join them when receiving 'address[]' as the type" do
value = [
<<95, 38, 9, 115, 52, 182, 163, 43, 121, 81, 223, 97, 253, 12, 88, 3, 236, 93, 131, 84>>,
<<207, 38, 14, 163, 23, 85, 86, 55, 197, 95, 112, 229, 93, 186, 141, 90, 216, 65, 76, 176>>
]

assert SmartContractView.values_only(value, "address[]", nil) ==
wrap_it(
"[0x5f26097334b6a32b7951df61fd0c5803ec5d8354, 0xcf260ea317555637c55f70e55dba8d5ad8414cb0]",
length(value)
)
end

test "returns the value when the type is neither 'address' nor 'address payable'" do
value = "POA"

assert SmartContractView.values_only(value, "string", nil) == wrap_it("POA")
end

test "returns the value when the type is :string" do
value = "POA"

assert SmartContractView.values_only(value, :string, nil) == wrap_it("POA")
end

test "returns the value when the type is :bytes" do
value =
"0x00050000a7823d6f1e31569f51861e345b30c6bebf70ebe700000000000019f2f6a78083ca3e2a662d6dd1703c939c8ace2e268d88ad09518695c6c3712ac10a214be5109a65567100061a800101806401125e4cfb0000000000000000000000000ae055097c6d159879521c384f1d2123d1f195e60000000000000000000000004c26ca0dc82a6e7bb00b8815a65985b67c0d30d3000000000000000000000000000000000000000000000002b5598f488fb733c9"

assert SmartContractView.values_only(value, :bytes, nil) ==
wrap_it(
"0x00050000a7823d6f1e31569f51861e345b30c6bebf70ebe700000000000019f2f6a78083ca3e2a662d6dd1703c939c8ace2e268d88ad09518695c6c3712ac10a214be5109a65567100061a800101806401125e4cfb0000000000000000000000000ae055097c6d159879521c384f1d2123d1f195e60000000000000000000000004c26ca0dc82a6e7bb00b8815a65985b67c0d30d3000000000000000000000000000000000000000000000002b5598f488fb733c9"
)
end

test "returns the value when the type is boolean" do
value = "true"

assert SmartContractView.values_only(value, "bool", nil) == wrap_it("true")
end

test "returns the value when the type is :bool" do
value = "true"

assert SmartContractView.values_only(value, :bool, nil) == wrap_it("true")
end

test "returns the value when the type is bytes4" do
value = <<228, 184, 12, 77>>

assert SmartContractView.values_only(value, "bytes4", nil) == wrap_it("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) ==
wrap_it("0x9cd14677f9aa5569b3bbb351fcd67d1115aa563ae1624276d3d4e67fb3d6f926")
end

test "returns the value when the type is uint(n) and value is 0" do
value = "0"

assert SmartContractView.values_only(value, "uint64", nil) == wrap_it("0")
end

test "returns the value when the type is int(n) and value is 0" do
value = "0"

assert SmartContractView.values_only(value, "int64", nil) == wrap_it("0")
end
end
end
17 changes: 6 additions & 11 deletions apps/explorer/lib/explorer/chain/token.ex
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ defmodule Explorer.Chain.Token do

alias Ecto.Changeset
alias Explorer.Chain.{Address, Hash, Token}
alias Phoenix.HTML
alias Explorer.SmartContract.Helper

@typedoc """
* `name` - Name of the token
Expand Down Expand Up @@ -103,8 +103,8 @@ defmodule Explorer.Chain.Token do
|> validate_required(@required_attrs)
|> foreign_key_constraint(:contract_address)
|> trim_name()
|> sanitize_input(:name)
|> sanitize_input(:symbol)
|> sanitize_token_input(:name)
|> sanitize_token_input(:symbol)
|> unique_constraint(:contract_address_hash)
end

Expand All @@ -117,20 +117,15 @@ defmodule Explorer.Chain.Token do
end
end

defp sanitize_input(%Changeset{valid?: false} = changeset, _), do: changeset
defp sanitize_token_input(%Changeset{valid?: false} = changeset, _), do: changeset

defp sanitize_input(%Changeset{valid?: true} = changeset, key) do
defp sanitize_token_input(%Changeset{valid?: true} = changeset, key) do
case get_change(changeset, key) do
nil ->
changeset

property ->
safe_property =
property
|> HTML.html_escape()
|> HTML.safe_to_string()

put_change(changeset, key, String.trim(safe_property))
put_change(changeset, key, Helper.sanitize_input(property))
end
end

Expand Down
10 changes: 10 additions & 0 deletions apps/explorer/lib/explorer/smart_contract/helper.ex
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ defmodule Explorer.SmartContract.Helper do
"""

alias Explorer.Chain
alias Phoenix.HTML

def queriable_method?(method) do
method["constant"] || method["stateMutability"] == "view" || method["stateMutability"] == "pure"
Expand Down Expand Up @@ -71,4 +72,13 @@ defmodule Explorer.SmartContract.Helper do
|> :crypto.hash(bytes)
|> Base.encode16(case: :lower)
end

def sanitize_input(nil), do: nil

def sanitize_input(input) do
input
|> HTML.html_escape()
|> HTML.safe_to_string()
|> String.trim()
end
end

0 comments on commit fccfee1

Please sign in to comment.