parent
4432ba4222
commit
21a1c1403c
@ -1,9 +1,26 @@ |
|||||||
.raw-transaction-input{ |
.raw-transaction-input{ |
||||||
display: none; |
display: none; |
||||||
|
} |
||||||
|
|
||||||
|
.raw-transaction-log-topics{ |
||||||
|
display: none; |
||||||
|
} |
||||||
|
|
||||||
|
.raw-transaction-log-data{ |
||||||
|
display: none; |
||||||
} |
} |
||||||
|
|
||||||
.transaction-input-text{ |
.transaction-input-text{ |
||||||
resize: vertical; |
white-space: pre; |
||||||
overflow: auto; |
color: black; |
||||||
word-break: break-all; |
|
||||||
|
pre{ |
||||||
|
code{ |
||||||
|
color: black; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
.transaction-input-table{ |
||||||
|
overflow-x: scroll; |
||||||
} |
} |
||||||
|
@ -0,0 +1,108 @@ |
|||||||
|
defmodule BlockScoutWeb.ABIEncodedValueView do |
||||||
|
@moduledoc """ |
||||||
|
Renders a decoded value that is encoded according to an ABI. |
||||||
|
|
||||||
|
Does not leverage an eex template because it renders formatted |
||||||
|
values via `<pre>` tags, and that is hard to do in an eex template. |
||||||
|
""" |
||||||
|
use BlockScoutWeb, :view |
||||||
|
|
||||||
|
require Logger |
||||||
|
|
||||||
|
def value_html(type, value) do |
||||||
|
decoded_type = ABI.FunctionSelector.decode_type(type) |
||||||
|
|
||||||
|
do_value_html(decoded_type, value) |
||||||
|
rescue |
||||||
|
exception -> |
||||||
|
Logger.warn(fn -> |
||||||
|
["Error determining value html for #{inspect(type)}: ", Exception.format(:error, exception)] |
||||||
|
end) |
||||||
|
end |
||||||
|
|
||||||
|
def copy_text(type, value) do |
||||||
|
decoded_type = ABI.FunctionSelector.decode_type(type) |
||||||
|
|
||||||
|
do_copy_text(decoded_type, value) |
||||||
|
rescue |
||||||
|
exception -> |
||||||
|
Logger.warn(fn -> |
||||||
|
["Error determining copy text for #{inspect(type)}: ", Exception.format(:error, exception)] |
||||||
|
end) |
||||||
|
end |
||||||
|
|
||||||
|
def do_copy_text({:bytes, _type}, value) do |
||||||
|
hex(value) |
||||||
|
end |
||||||
|
|
||||||
|
def do_copy_text({:array, type, _}, value) do |
||||||
|
do_copy_text({:array, type}, value) |
||||||
|
end |
||||||
|
|
||||||
|
def do_copy_text({:array, type}, value) do |
||||||
|
values = |
||||||
|
value |
||||||
|
|> Enum.map(&do_copy_text(type, &1)) |
||||||
|
|> Enum.intersperse(", ") |
||||||
|
|
||||||
|
~E|[<%= values %>]| |
||||||
|
end |
||||||
|
|
||||||
|
def do_copy_text(_, {:dynamic, value}) do |
||||||
|
hex(value) |
||||||
|
end |
||||||
|
|
||||||
|
def do_copy_text(type, value) when type in [:bytes, :address] do |
||||||
|
hex(value) |
||||||
|
end |
||||||
|
|
||||||
|
def do_copy_text(_type, value) do |
||||||
|
to_string(value) |
||||||
|
end |
||||||
|
|
||||||
|
defp do_value_html(type, value, depth \\ 0) |
||||||
|
|
||||||
|
defp do_value_html({:bytes, _}, value, depth) do |
||||||
|
do_value_html(:bytes, value, depth) |
||||||
|
end |
||||||
|
|
||||||
|
defp do_value_html({:array, type, _}, value, depth) do |
||||||
|
do_value_html({:array, type}, value, depth) |
||||||
|
end |
||||||
|
|
||||||
|
defp do_value_html({:array, type}, value, depth) do |
||||||
|
values = |
||||||
|
Enum.map(value, fn inner_value -> |
||||||
|
do_value_html(type, inner_value, depth + 1) |
||||||
|
end) |
||||||
|
|
||||||
|
spacing = String.duplicate(" ", depth * 2) |
||||||
|
delimited = Enum.intersperse(values, ",\n") |
||||||
|
|
||||||
|
~E|<%= spacing %>[<%= "\n" %><%= delimited %><%= "\n" %><%= spacing %>]| |
||||||
|
end |
||||||
|
|
||||||
|
defp do_value_html(type, value, depth) do |
||||||
|
spacing = String.duplicate(" ", depth * 2) |
||||||
|
~E|<%= spacing %><%=base_value_html(type, value)%>| |
||||||
|
[spacing, base_value_html(type, value)] |
||||||
|
end |
||||||
|
|
||||||
|
def base_value_html(_, {:dynamic, value}) do |
||||||
|
hex(value) |
||||||
|
end |
||||||
|
|
||||||
|
def base_value_html(:address, value) do |
||||||
|
address = hex(value) |
||||||
|
|
||||||
|
~E|<a href="<%= address_path(BlockScoutWeb.Endpoint, :show, address) %>" target="_blank"><%= address %></a>| |
||||||
|
end |
||||||
|
|
||||||
|
def base_value_html(:bytes, value) do |
||||||
|
hex(value) |
||||||
|
end |
||||||
|
|
||||||
|
def base_value_html(_, value), do: Phoenix.HTML.html_escape(value) |
||||||
|
|
||||||
|
defp hex(value), do: "0x" <> Base.encode16(value, case: :lower) |
||||||
|
end |
@ -1,4 +1,10 @@ |
|||||||
defmodule BlockScoutWeb.TransactionLogView do |
defmodule BlockScoutWeb.TransactionLogView do |
||||||
use BlockScoutWeb, :view |
use BlockScoutWeb, :view |
||||||
@dialyzer :no_match |
@dialyzer :no_match |
||||||
|
|
||||||
|
alias Explorer.Chain.Log |
||||||
|
|
||||||
|
def decode(log, transaction) do |
||||||
|
Log.decode(log, transaction) |
||||||
|
end |
||||||
end |
end |
||||||
|
@ -0,0 +1,91 @@ |
|||||||
|
defmodule BlockScoutWeb.ABIEncodedValueViewTest do |
||||||
|
use BlockScoutWeb.ConnCase, async: true |
||||||
|
|
||||||
|
alias BlockScoutWeb.ABIEncodedValueView |
||||||
|
|
||||||
|
defp value_html(type, value) do |
||||||
|
type |
||||||
|
|> ABIEncodedValueView.value_html(value) |
||||||
|
|> Phoenix.HTML.Safe.to_iodata() |
||||||
|
|> IO.iodata_to_binary() |
||||||
|
end |
||||||
|
|
||||||
|
defp copy_text(type, value) do |
||||||
|
type |
||||||
|
|> ABIEncodedValueView.copy_text(value) |
||||||
|
|> Phoenix.HTML.Safe.to_iodata() |
||||||
|
|> IO.iodata_to_binary() |
||||||
|
end |
||||||
|
|
||||||
|
describe "value_html/2" do |
||||||
|
test "it formats addresses as links" do |
||||||
|
address = "0x0000000000000000000000000000000000000000" |
||||||
|
address_bytes = address |> String.trim_leading("0x") |> Base.decode16!() |
||||||
|
|
||||||
|
expected = ~s(<a href=\"/address/#{address}\" target=\"_blank\">#{address}</a>) |
||||||
|
|
||||||
|
assert value_html("address", address_bytes) == expected |
||||||
|
end |
||||||
|
|
||||||
|
test "it formats lists with newlines and spaces" do |
||||||
|
expected = String.trim(""" |
||||||
|
[ |
||||||
|
1, |
||||||
|
2, |
||||||
|
3, |
||||||
|
4 |
||||||
|
] |
||||||
|
""") |
||||||
|
|
||||||
|
assert value_html("uint[]", [1, 2, 3, 4]) == expected |
||||||
|
end |
||||||
|
|
||||||
|
test "it formats nested lists with nested depth" do |
||||||
|
expected = String.trim(""" |
||||||
|
[ |
||||||
|
[ |
||||||
|
1, |
||||||
|
2 |
||||||
|
], |
||||||
|
[ |
||||||
|
3, |
||||||
|
4 |
||||||
|
] |
||||||
|
] |
||||||
|
""") |
||||||
|
|
||||||
|
assert value_html("uint[][]", [[1, 2], [3, 4]]) == expected |
||||||
|
end |
||||||
|
|
||||||
|
test "it formats lists of addresses as a list of links" do |
||||||
|
address = "0x0000000000000000000000000000000000000000" |
||||||
|
address_link = ~s(<a href=\"/address/#{address}\" target=\"_blank\">#{address}</a>) |
||||||
|
|
||||||
|
expected = String.trim(""" |
||||||
|
[ |
||||||
|
#{address_link}, |
||||||
|
#{address_link}, |
||||||
|
#{address_link}, |
||||||
|
#{address_link} |
||||||
|
] |
||||||
|
""") |
||||||
|
|
||||||
|
address_bytes = "0x0000000000000000000000000000000000000000" |> String.trim_leading("0x") |> Base.decode16!() |
||||||
|
|
||||||
|
assert value_html("address[]", [address_bytes, address_bytes, address_bytes, address_bytes]) == expected |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
describe "copy_text/2" do |
||||||
|
test "it skips link formatting of addresses" do |
||||||
|
address = "0x0000000000000000000000000000000000000000" |
||||||
|
address_bytes = address |> String.trim_leading("0x") |> Base.decode16!() |
||||||
|
|
||||||
|
assert copy_text("address", address_bytes) == address |
||||||
|
end |
||||||
|
|
||||||
|
test "it skips the formatting when copying lists" do |
||||||
|
assert copy_text("uint[]", [1, 2, 3, 4]) == "[1, 2, 3, 4]" |
||||||
|
end |
||||||
|
end |
||||||
|
end |
Loading…
Reference in new issue