diff --git a/CHANGELOG.md b/CHANGELOG.md index a546f01a95..5d47496305 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ - [#1869](https://github.com/poanetwork/blockscout/pull/1869) - Fix output and gas extraction in JS tracer for Geth - [#1868](https://github.com/poanetwork/blockscout/pull/1868) - fix: logs list endpoint performance - [#1822](https://github.com/poanetwork/blockscout/pull/1822) - Fix style breaks in decompiled contract code view +- [#1885](https://github.com/poanetwork/blockscout/pull/1885) - highlight reserved words in decompiled code - [#1896](https://github.com/poanetwork/blockscout/pull/1896) - re-query tokens in top nav automplete - [#1881](https://github.com/poanetwork/blockscout/pull/1881) - fix: store solc versions locally for performance - [#1898](https://github.com/poanetwork/blockscout/pull/1898) - check if the constructor has arguments before verifying constructor arguments diff --git a/apps/block_scout_web/lib/block_scout_web/views/address_decompiled_contract_view.ex b/apps/block_scout_web/lib/block_scout_web/views/address_decompiled_contract_view.ex index 1418255594..f80a0ded99 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/address_decompiled_contract_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/address_decompiled_contract_view.ex @@ -2,51 +2,231 @@ defmodule BlockScoutWeb.AddressDecompiledContractView do use BlockScoutWeb, :view @colors %{ - "\e[95m" => "136, 0, 0", + "\e[95m" => "", # red - "\e[91m" => "236, 89, 58", + "\e[91m" => "", # gray - "\e[38;5;8m" => "111, 110, 111", + "\e[38;5;8m" => "", # green - "\e[32m" => "57, 115, 0", + "\e[32m" => "", # yellowgreen - "\e[93m" => "57, 115, 0", + "\e[93m" => "", # yellow - "\e[92m" => "119, 232, 81", + "\e[92m" => "", # red - "\e[94m" => "136, 0, 0" + "\e[94m" => "" } + @comment_start "#" + + @reserved_words_types [ + "var", + "bool", + "string", + "int", + "uint", + "int8", + "uint8", + "int16", + "uint16", + "int24", + "uint24", + "int32", + "uint32", + "int40", + "uint40", + "int48", + "uint48", + "int56", + "uint56", + "int64", + "uint64", + "int72", + "uint72", + "int80", + "uint80", + "int88", + "uint88", + "int96", + "uint96", + "int104", + "uint104", + "int112", + "uint112", + "int120", + "uint120", + "int128", + "uint128", + "int136", + "uint136", + "int144", + "uint144", + "int152", + "uint152", + "int160", + "uint160", + "int168", + "uint168", + "int176", + "uint176", + "int184", + "uint184", + "int192", + "uint192", + "int200", + "uint200", + "int208", + "uint208", + "int216", + "uint216", + "int224", + "uint224", + "int232", + "uint232", + "int240", + "uint240", + "int248", + "uint248", + "int256", + "uint256", + "byte", + "bytes", + "bytes1", + "bytes2", + "bytes3", + "bytes4", + "bytes5", + "bytes6", + "bytes7", + "bytes8", + "bytes9", + "bytes10", + "bytes11", + "bytes12", + "bytes13", + "bytes14", + "bytes15", + "bytes16", + "bytes17", + "bytes18", + "bytes19", + "bytes20", + "bytes21", + "bytes22", + "bytes23", + "bytes24", + "bytes25", + "bytes26", + "bytes27", + "bytes28", + "bytes29", + "bytes30", + "bytes31", + "bytes32", + "true", + "false", + "enum", + "struct", + "mapping", + "address" + ] + + @reserved_words_keywords [ + "def", + "require", + "revert", + "return", + "assembly", + "memory", + "mem" + ] + + @modifiers [ + "payable", + "public", + "view", + "pure", + "returns", + "internal" + ] + + @reserved_words @reserved_words_keywords ++ @reserved_words_types + + @reserved_words_regexp ([@comment_start | @reserved_words] ++ @modifiers) + |> Enum.reduce("", fn el, acc -> acc <> "|" <> el end) + |> Regex.compile!() + def highlight_decompiled_code(code) do {_, result} = @colors |> Enum.reduce(code, fn {symbol, rgb}, acc -> - String.replace(acc, symbol, "") + String.replace(acc, symbol, rgb) end) |> String.replace("\e[1m", "") |> String.replace("ยป", "»") |> String.replace("\e[0m", "") |> String.split(~r/\|\<\/span\>/, include_captures: true, trim: true) - |> Enum.reduce({"", []}, fn part, {style, acc} -> - new_style = - cond do - String.contains?(part, " part - part == "" -> "" - true -> style - end - - new_part = new_part(part, new_style) - - {new_style, [new_part | acc]} - end) + |> add_styles_to_every_line() result |> Enum.reduce("", fn part, acc -> part <> acc end) + |> add_styles_to_reserved_words() |> add_line_numbers() end + defp add_styles_to_every_line(lines) do + lines + |> Enum.reduce({"", []}, fn part, {style, acc} -> + new_style = + cond do + String.contains?(part, " part + part == "" -> "" + true -> style + end + + new_part = new_part(part, new_style) + + {new_style, [new_part | acc]} + end) + end + + defp add_styles_to_reserved_words(code) do + code + |> String.split("\n") + |> Enum.map(fn line -> + add_styles_to_line(line) + end) + |> Enum.reduce("", fn el, acc -> + acc <> el <> "\n" + end) + end + + defp add_styles_to_line(line) do + parts = + line + |> String.split(@reserved_words_regexp, + include_captures: true + ) + + comment_position = Enum.find_index(parts, fn part -> part == "#" end) + + parts + |> Enum.with_index() + |> Enum.map(fn {el, index} -> + cond do + !(is_nil(comment_position) || comment_position > index) -> el + el in @reserved_words -> "" <> el <> "" + el in @modifiers -> "" <> el <> "" + true -> el + end + end) + |> Enum.reduce("", fn el, acc -> + acc <> el + end) + end + def sort_contracts_by_version(decompiled_contracts) do decompiled_contracts |> Enum.sort_by(& &1.decompiler_version) @@ -76,18 +256,12 @@ defmodule BlockScoutWeb.AddressDecompiledContractView do part true -> - result = - part - |> String.split("\n") - |> Enum.reduce("", fn p, a -> - a <> new_style <> p <> "\n" - end) - - if String.ends_with?(part, "\n") do - result - else - String.slice(result, 0..-2) - end + part + |> String.split("\n") + |> Enum.reduce("", fn p, a -> + a <> new_style <> p <> "\n" + end) + |> String.slice(0..-2) end end end diff --git a/apps/block_scout_web/test/block_scout_web/views/address_decompiled_contract_view_test.exs b/apps/block_scout_web/test/block_scout_web/views/address_decompiled_contract_view_test.exs index c3ff123584..8f1636ba06 100644 --- a/apps/block_scout_web/test/block_scout_web/views/address_decompiled_contract_view_test.exs +++ b/apps/block_scout_web/test/block_scout_web/views/address_decompiled_contract_view_test.exs @@ -56,7 +56,7 @@ defmodule BlockScoutWeb.AddressDecompiledContractViewTest do result = AddressDecompiledContractView.highlight_decompiled_code(code) assert result == - " #\n # eveem.org 6 Feb 2019\n # Decompiled source of 0x00Bd9e214FAb74d6fC21bf1aF34261765f57e875\n #\n # Let's make the world open source\n # \n #\n # I failed with these:\n # - unknowne77c646d(?)\n # - transferFromWithData(address _from, address _to, uint256 _value, bytes _data)\n # All the rest is below.\n #\n\n\n # Storage definitions and getters\n\n def storage:\n allowance is uint256 => uint256 # mask(256, 0) at storage #2\n stor4 is uint256 => uint8 # mask(8, 0) at storage #4\n\n def allowance(address _owner, address _spender) payable: 64\n return allowance[sha3(((320 - 1) and (320 - 1) and _owner), 1), ((320 - 1) and _spender and (320 - 1))]\n\n\n #\n # Regular functions - see Tutorial for understanding quirks of the code\n #\n\n\n # folder failed in this function - may be terribly long, sorry\n def unknownc47d033b(?) payable: not cd[4]:\n revert\n else:\n mem[0]cd[4]\n mem[32] = 4\n mem[96] = bool(stor4[((320 - 1) and (320 - 1) and cd[4])])\n return bool(stor4[((320 - 1) and (320 - 1) and cd[4])])\n\n def _fallback() payable: # default function\n revert\n\n" + " #\n # eveem.org 6 Feb 2019\n # Decompiled source of 0x00Bd9e214FAb74d6fC21bf1aF34261765f57e875\n #\n # Let's make the world open source\n # \n #\n # I failed with these:\n # - unknowne77c646d(?)\n # - transferFromWithData(address _from, address _to, uint256 _value, bytes _data)\n # All the rest is below.\n #\n\n\n # Storage definitions and getters\n\n def storage:\n allowance is uint256 => uint256 # mask(256, 0) at storage #2\n stor4 is uint256 => uint8 # mask(8, 0) at storage #4\n\n def allowance(address _owner, address _spender) payable: 64\n return allowance[_owner_spender(320 - 1))]\n\n\n #\n # Regular functions - see Tutorial for understanding quirks of the code\n #\n\n\n # folder failed in this function - may be terribly long, sorry\n def unknownc47d033b(?) payable: not cd[4]:\n revert\n else:\n mem[0]cd[4]\n mem[32] = 4\n mem[96] = bool(stor4[cd[4])])\n return bool(stor4[cd[4])])\n\n def _fallback() payable: # default function\n revert\n\n\n" end test "adds style span to every line" do @@ -70,7 +70,7 @@ defmodule BlockScoutWeb.AddressDecompiledContractViewTest do """ assert AddressDecompiledContractView.highlight_decompiled_code(code) == - " #\n # eveem.org 6 Feb 2019\n # Decompiled source of 0x00Bd9e214FAb74d6fC21bf1aF34261765f57e875\n #\n # Let's make the world open source\n # \n\n" + " #\n # eveem.org 6 Feb 2019\n # Decompiled source of 0x00Bd9e214FAb74d6fC21bf1aF34261765f57e875\n #\n # Let's make the world open source\n # \n\n\n" end end