diff --git a/CHANGELOG.md b/CHANGELOG.md index 40aa891851..3b2770d538 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,11 @@ ## Current ### Features - +- [#1907](https://github.com/poanetwork/blockscout/pull/1907) - dropdown color bug fix (lukso theme) and tooltip color bug fix +- [#1903](https://github.com/poanetwork/blockscout/pull/1903) - added rsk theme and rsk logo +- [#1895](https://github.com/poanetwork/blockscout/pull/1895) - add changes to poa theme and poa logo +- [#1812](https://github.com/poanetwork/blockscout/pull/1812) - add pagination to addresses page +- [#1874](https://github.com/poanetwork/blockscout/pull/1874) - add changes to ethereum theme and ethereum logo - [#1815](https://github.com/poanetwork/blockscout/pull/1815) - able to search without prefix "0x" - [#1813](https://github.com/poanetwork/blockscout/pull/1813) - add total blocks counter to the main page - [#1806](https://github.com/poanetwork/blockscout/pull/1806) - verify contracts with a post request @@ -18,6 +22,9 @@ - [#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 ### Chore diff --git a/README.md b/README.md index a2df24f85c..9755475dbe 100644 --- a/README.md +++ b/README.md @@ -65,6 +65,7 @@ Currently available block explorers (i.e. Etherscan and Etherchain) are closed s * [SpringChain](https://explorer.springrole.com/) * [PIRL](http://pirl.es/) * [Petrichor](https://explorer.petrichor-dev.com/) +* [Ether-1](https://blocks.ether1.wattpool.net/) ### Visual Interface @@ -139,7 +140,7 @@ The [development stack page](https://github.com/poanetwork/blockscout/wiki/Devel ``` * If using Chrome, Enable `chrome://flags/#allow-insecure-localhost`. - 9. Start Phoenix Server. + 9. Run the Phoenix Server from the root directory of your application. `mix phx.server` Now you can visit [`localhost:4000`](http://localhost:4000) from your browser. diff --git a/apps/block_scout_web/assets/css/components/_tooltip.scss b/apps/block_scout_web/assets/css/components/_tooltip.scss index 1cd7797785..4c845a7c1c 100644 --- a/apps/block_scout_web/assets/css/components/_tooltip.scss +++ b/apps/block_scout_web/assets/css/components/_tooltip.scss @@ -14,5 +14,6 @@ $tooltip-color: #fff !default; .arrow::before { border-top-color: $tooltip-background-color; + border-bottom-color: $tooltip-background-color; } } diff --git a/apps/block_scout_web/assets/css/theme/_ethereum_variables.scss b/apps/block_scout_web/assets/css/theme/_ethereum_variables.scss index 98d2da802c..7437274bf6 100644 --- a/apps/block_scout_web/assets/css/theme/_ethereum_variables.scss +++ b/apps/block_scout_web/assets/css/theme/_ethereum_variables.scss @@ -1,3 +1,50 @@ -$primary: #16465b; -$secondary: #5ab3ff; -$tertiary: #77a4c5; +// general +$primary: #153550; +$secondary: #49a2ee; +$tertiary: #4ad7a7; +$additional-font: #89cae6; + +// footer +$footer-background-color: $primary; +$footer-title-color: #fff; +$footer-text-color: #89cae6; +$footer-item-disc-color: $secondary; +.footer-logo { filter: brightness(0) invert(1); } + +// dashboard +$dashboard-line-color-price: $tertiary; // price left border + +$dashboard-banner-chart-legend-value-color: $additional-font; // chart labels + +$dashboard-stats-item-value-color: $additional-font; // stat values + +$dashboard-stats-item-border-color: $secondary; // stat border + +$dashboard-banner-gradient-start: $primary; // gradient begin + +$dashboard-banner-gradient-end: lighten($primary, 5); // gradient end + +$dashboard-banner-network-plain-container-background-color: #1c476c; // stats bg + + +// navigation +.navbar { box-shadow: 0px 0px 30px 0px rgba(21, 53, 80, 0.12); } // header shadow +$header-icon-border-color-hover: $secondary; // top border on hover +$header-icon-color-hover: $secondary; // nav icon on hover +.dropdown-item:hover, .dropdown-item:focus { background-color: $secondary !important; } // dropdown item on hover + +// buttons +$btn-line-bg: #fff; // button bg +$btn-line-color: $secondary; // button border and font color && hover bg color +$btn-copy-color: $secondary; // btn copy +$btn-qr-color: $secondary; // btn qr-code + +//links & tile +.tile a { color: $secondary !important; } // links color for badges +.tile-type-block { + border-left: 4px solid $secondary; +} // tab active bg + +// card +$card-background-1: $secondary; +$card-tab-active: $secondary; diff --git a/apps/block_scout_web/assets/css/theme/_lukso_variables.scss b/apps/block_scout_web/assets/css/theme/_lukso_variables.scss index a09049e7c8..c1d8d630f9 100644 --- a/apps/block_scout_web/assets/css/theme/_lukso_variables.scss +++ b/apps/block_scout_web/assets/css/theme/_lukso_variables.scss @@ -12,7 +12,7 @@ $dashboard-stats-item-value-color: $primary; $dashboard-stats-item-border-color: $primary; $header-links-color-active: #333; - +.dropdown-item:hover, .dropdown-item:focus { background-color: $primary !important; } $tile-type-block-color: $secondary; $navbar-logo-height: 18px; diff --git a/apps/block_scout_web/assets/css/theme/_poa_variables.scss b/apps/block_scout_web/assets/css/theme/_poa_variables.scss index da2fe0de74..198c89ba5a 100644 --- a/apps/block_scout_web/assets/css/theme/_poa_variables.scss +++ b/apps/block_scout_web/assets/css/theme/_poa_variables.scss @@ -1,11 +1,50 @@ -$primary: #5b389f; -$secondary: #7dd79f; -$tertiary: #997fdc; +// general +$primary: #5c34a2; +$secondary: #87e1a9; +$tertiary: #bf9cff; +$additional-font: #fff; -$header-links-color-active: #333; -$button-secondary-color: $primary; - -$footer-background-color: $primary; +// footer +$footer-background-color: #3c226a; $footer-title-color: #fff; -$footer-text-color: #fff; +$footer-text-color: #bda6e7; $footer-item-disc-color: $secondary; +.footer-logo { filter: brightness(0) invert(1); } + +// dashboard +$dashboard-line-color-price: $tertiary; // price left border + +$dashboard-banner-chart-legend-value-color: $additional-font; // chart labels + +$dashboard-stats-item-value-color: $additional-font; // stat values + +$dashboard-stats-item-border-color: $secondary; // stat border + +$dashboard-banner-gradient-start: $primary; // gradient begin + +$dashboard-banner-gradient-end: lighten($primary, 5); // gradient end + +$dashboard-banner-network-plain-container-background-color: #865bd4; // stats bg + + +// navigation +.navbar { box-shadow: 0px 0px 30px 0px rgba(21, 53, 80, 0.12); } // header shadow +$header-icon-border-color-hover: $primary; // top border on hover +$header-icon-color-hover: $primary; // nav icon on hover +.dropdown-item:hover, .dropdown-item:focus { background-color: $primary !important; } // dropdown item on hover + +// buttons +$btn-line-bg: #fff; // button bg +$btn-line-color: $primary; // button border and font color && hover bg color +$btn-copy-color: $primary; // btn copy +$btn-qr-color: $primary; // btn qr-code + +//links & tile +.tile a { color: $primary !important; } // links color for badges +.tile-type-block { + border-left: 4px solid $primary; +} // tab active bg + +// card +$card-background-1: $primary; +$card-tab-active: $primary; diff --git a/apps/block_scout_web/assets/css/theme/_rsk_variables.scss b/apps/block_scout_web/assets/css/theme/_rsk_variables.scss new file mode 100644 index 0000000000..c357f0f571 --- /dev/null +++ b/apps/block_scout_web/assets/css/theme/_rsk_variables.scss @@ -0,0 +1,50 @@ +// general +$primary: #101f25; +$secondary: #27ac8d; +$tertiary: #e39a54; +$additional-font: #a1ded1; + +// footer +$footer-background-color: $primary; +$footer-title-color: #fff; +$footer-text-color: $additional-font; +$footer-item-disc-color: $secondary; +.footer-logo { filter: brightness(0) invert(1); } + +// dashboard +$dashboard-line-color-price: $tertiary; // price left border + +$dashboard-banner-chart-legend-value-color: $additional-font; // chart labels + +$dashboard-stats-item-value-color: $additional-font; // stat values + +$dashboard-stats-item-border-color: $secondary; // stat border + +$dashboard-banner-gradient-start: $primary; // gradient begin + +$dashboard-banner-gradient-end: lighten($primary, 5); // gradient end + +$dashboard-banner-network-plain-container-background-color: #1a323b; // stats bg + + +// navigation +.navbar { box-shadow: 0px 0px 30px 0px rgba(21, 53, 80, 0.12); } // header shadow +$header-icon-border-color-hover: $secondary; // top border on hover +$header-icon-color-hover: $secondary; // nav icon on hover +.dropdown-item:hover, .dropdown-item:focus { background-color: $secondary !important; } // dropdown item on hover + +// buttons +$btn-line-bg: #fff; // button bg +$btn-line-color: $secondary; // button border and font color && hover bg color +$btn-copy-color: $secondary; // btn copy +$btn-qr-color: $secondary; // btn qr-code + +//links & tile +.tile a { color: $secondary !important; } // links color for badges +.tile-type-block { + border-left: 4px solid $secondary; +} // tab active bg + +// card +$card-background-1: $secondary; +$card-tab-active: $secondary; \ No newline at end of file diff --git a/apps/block_scout_web/assets/static/images/ethereum_logo.svg b/apps/block_scout_web/assets/static/images/ethereum_logo.svg index 3f47dc7fe2..b2ebb795f8 100644 --- a/apps/block_scout_web/assets/static/images/ethereum_logo.svg +++ b/apps/block_scout_web/assets/static/images/ethereum_logo.svg @@ -1,40 +1 @@ - - - - -ethereum-logo - - - - - - - - - - - - - + diff --git a/apps/block_scout_web/assets/static/images/poa_logo.svg b/apps/block_scout_web/assets/static/images/poa_logo.svg index 551871a7e1..321033174f 100644 --- a/apps/block_scout_web/assets/static/images/poa_logo.svg +++ b/apps/block_scout_web/assets/static/images/poa_logo.svg @@ -1 +1,3 @@ -poa_logo \ No newline at end of file + + + diff --git a/apps/block_scout_web/assets/static/images/rsk_logo.svg b/apps/block_scout_web/assets/static/images/rsk_logo.svg new file mode 100644 index 0000000000..8502c921e5 --- /dev/null +++ b/apps/block_scout_web/assets/static/images/rsk_logo.svg @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/block_scout_web/lib/block_scout_web/chain.ex b/apps/block_scout_web/lib/block_scout_web/chain.ex index 0aa33b5ffd..cabac4ae02 100644 --- a/apps/block_scout_web/lib/block_scout_web/chain.ex +++ b/apps/block_scout_web/lib/block_scout_web/chain.ex @@ -25,7 +25,8 @@ defmodule BlockScoutWeb.Chain do InternalTransaction, Log, TokenTransfer, - Transaction + Transaction, + Wei } alias Explorer.PagingOptions @@ -87,6 +88,16 @@ defmodule BlockScoutWeb.Chain do Map.merge(params, paging_params(List.last(list))) end + def paging_options(%{"hash" => hash, "fetched_coin_balance" => fetched_coin_balance}) do + with {coin_balance, ""} <- Integer.parse(fetched_coin_balance), + {:ok, address_hash} <- string_to_address_hash(hash) do + [paging_options: %{@default_paging_options | key: {%Wei{value: Decimal.new(coin_balance)}, address_hash}}] + else + _ -> + [paging_options: @default_paging_options] + end + end + def paging_options(%{ "block_number" => block_number_string, "transaction_index" => transaction_index_string, @@ -177,6 +188,10 @@ defmodule BlockScoutWeb.Chain do end end + defp paging_params({%Address{hash: hash, fetched_coin_balance: fetched_coin_balance}, _}) do + %{"hash" => hash, "fetched_coin_balance" => Decimal.to_string(fetched_coin_balance.value)} + end + defp paging_params({%Reward{block: %{number: number}}, _}) do %{"block_number" => number, "index" => 0} end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/address_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/address_controller.ex index a0c443aee6..3d43ed7054 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/address_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_controller.ex @@ -1,16 +1,55 @@ defmodule BlockScoutWeb.AddressController do use BlockScoutWeb, :controller + import BlockScoutWeb.Chain, only: [paging_options: 1, next_page_params: 3, split_list_by_page: 1] + alias Explorer.{Chain, Market} alias Explorer.Chain.Address alias Explorer.ExchangeRates.Token - def index(conn, _params) do + def index(conn, params) do + addresses = + params + |> paging_options() + |> Chain.list_top_addresses() + + {addresses_page, next_page} = split_list_by_page(addresses) + + cur_page_number = + cond do + !params["prev_page_number"] -> 1 + params["next_page"] -> String.to_integer(params["prev_page_number"]) + 1 + params["prev_page"] -> String.to_integer(params["prev_page_number"]) - 1 + end + + next_page_path = + case next_page_params(next_page, addresses_page, params) do + nil -> + nil + + next_page_params -> + next_params = + next_page_params + |> Map.put("prev_page_path", cur_page_path(conn, params)) + |> Map.put("next_page", true) + |> Map.put("prev_page_number", cur_page_number) + + address_path( + conn, + :index, + next_params + ) + end + render(conn, "index.html", - address_tx_count_pairs: Chain.list_top_addresses(), + address_tx_count_pairs: addresses_page, + page_address_count: Enum.count(addresses_page), address_count: Chain.count_addresses_with_balance_from_cache(), exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(), - total_supply: Chain.total_supply() + total_supply: Chain.total_supply(), + next_page_path: next_page_path, + prev_page_path: params["prev_page_path"], + cur_page_number: cur_page_number ) end @@ -25,4 +64,16 @@ defmodule BlockScoutWeb.AddressController do def validation_count(%Address{} = address) do Chain.address_to_validation_count(address) end + + defp cur_page_path(conn, %{"hash" => _hash, "fetched_coin_balance" => _balance} = params) do + new_params = Map.put(params, "next_page", false) + + address_path( + conn, + :index, + new_params + ) + end + + defp cur_page_path(conn, _), do: address_path(conn, :index) end diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address/index.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address/index.html.eex index f9bd1b6dcd..fca5d5b50d 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/address/index.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/address/index.html.eex @@ -1,12 +1,28 @@
+ <%= if @next_page_path do %> + " class="button button-secondary button-small float-right ml-1"> + <%= gettext("Next") %> + + <% end %> + <%= if @prev_page_path do %> + " class="button button-secondary button-small float-right"> + <%= gettext("Back") %> + + <% end %> +

<%= gettext "Addresses" %>

- <%= gettext "Showing 250 addresses of" %> + <%= gettext "Showing " %> + <%= Cldr.Number.to_string!(@page_address_count, format: "#,###") %> + <%= gettext " addresses of" %> <%= Cldr.Number.to_string!(@address_count, format: "#,###") %> <%= gettext "total addresses with a balance" %> + <%= gettext " (page" %> + <%= Cldr.Number.to_string!(@cur_page_number, format: "#,###)") %>

+ <%= for {{address, tx_count}, index} <- Enum.with_index(@address_tx_count_pairs, 1) do %> <%= render "_tile.html", @@ -14,6 +30,17 @@ total_supply: @total_supply, tx_count: tx_count, validation_count: validation_count(address) %> <% end %> +
+ <%= if @next_page_path do %> + " class="button button-secondary button-small float-right mt-0 mb-0 ml-1"> + <%= gettext("Next") %> + + <% end %> + <%= if @prev_page_path do %> + " class="button button-secondary button-small float-right mt-0 mb-0"> + <%= gettext("Back") %> + + <% end %>
diff --git a/apps/block_scout_web/lib/block_scout_web/templates/layout/_topnav.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/layout/_topnav.html.eex index 032ab22930..f7392a6b39 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/layout/_topnav.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/layout/_topnav.html.eex @@ -112,9 +112,8 @@ "data-test": "search_input" ], [ url: "#{chain_path(@conn, :token_autocomplete)}?q=", - prepop: true, - minChars: 3, - maxItems: 8, + limit: 0, + minChars: 2, value: "contract_address_hash", label: "contract_address_hash", descrSearch: true, 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/priv/gettext/default.pot b/apps/block_scout_web/priv/gettext/default.pot index 6f198f8793..d38ebaced2 100644 --- a/apps/block_scout_web/priv/gettext/default.pot +++ b/apps/block_scout_web/priv/gettext/default.pot @@ -104,7 +104,7 @@ msgid "Address" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/address/index.html.eex:4 +#: lib/block_scout_web/templates/address/index.html.eex:15 #: lib/block_scout_web/templates/tokens/overview/_details.html.eex:59 msgid "Addresses" msgstr "" @@ -578,6 +578,8 @@ msgid "Newer" msgstr "" #, elixir-format +#: lib/block_scout_web/templates/address/index.html.eex:6 +#: lib/block_scout_web/templates/address/index.html.eex:36 #: lib/block_scout_web/templates/address_token/index.html.eex:22 msgid "Next" msgstr "" @@ -712,7 +714,7 @@ msgstr "" #, elixir-format #: lib/block_scout_web/templates/layout/_topnav.html.eex:111 -#: lib/block_scout_web/templates/layout/_topnav.html.eex:129 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:128 msgid "Search" msgstr "" @@ -739,11 +741,6 @@ msgstr "" msgid "Showing" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address/index.html.eex:6 -msgid "Showing 250 addresses of" -msgstr "" - #, elixir-format #: lib/block_scout_web/templates/transaction/_emission_reward_tile.html.eex:8 #: lib/block_scout_web/views/transaction_view.ex:210 @@ -1078,7 +1075,7 @@ msgid "string" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/address/index.html.eex:8 +#: lib/block_scout_web/templates/address/index.html.eex:21 msgid "total addresses with a balance" msgstr "" @@ -1708,11 +1705,6 @@ msgstr "" msgid "ERC-721" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/chain/show.html.eex:64 -msgid "Total blocks" -msgstr "" - #, elixir-format #: lib/block_scout_web/templates/api_docs/index.html.eex:4 msgid "API Documentation" @@ -1739,3 +1731,29 @@ msgstr "" #: lib/block_scout_web/views/transaction_view.ex:341 msgid "Raw Trace" msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/address/index.html.eex:22 +msgid " (page" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/address/index.html.eex:19 +msgid " addresses of" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/address/index.html.eex:11 +#: lib/block_scout_web/templates/address/index.html.eex:41 +msgid "Back" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/address/index.html.eex:17 +msgid "Showing " +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/chain/show.html.eex:64 +msgid "Total blocks" +msgstr "" diff --git a/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po b/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po index 64201bee65..067af0cd66 100644 --- a/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po +++ b/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po @@ -104,7 +104,7 @@ msgid "Address" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/address/index.html.eex:4 +#: lib/block_scout_web/templates/address/index.html.eex:15 #: lib/block_scout_web/templates/tokens/overview/_details.html.eex:59 msgid "Addresses" msgstr "" @@ -578,6 +578,8 @@ msgid "Newer" msgstr "" #, elixir-format +#: lib/block_scout_web/templates/address/index.html.eex:6 +#: lib/block_scout_web/templates/address/index.html.eex:36 #: lib/block_scout_web/templates/address_token/index.html.eex:22 msgid "Next" msgstr "" @@ -712,7 +714,7 @@ msgstr "" #, elixir-format #: lib/block_scout_web/templates/layout/_topnav.html.eex:111 -#: lib/block_scout_web/templates/layout/_topnav.html.eex:129 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:128 msgid "Search" msgstr "" @@ -739,11 +741,6 @@ msgstr "" msgid "Showing" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address/index.html.eex:6 -msgid "Showing 250 addresses of" -msgstr "" - #, elixir-format #: lib/block_scout_web/templates/transaction/_emission_reward_tile.html.eex:8 #: lib/block_scout_web/views/transaction_view.ex:210 @@ -1078,7 +1075,7 @@ msgid "string" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/address/index.html.eex:8 +#: lib/block_scout_web/templates/address/index.html.eex:21 msgid "total addresses with a balance" msgstr "" @@ -1708,11 +1705,6 @@ msgstr "" msgid "ERC-721" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/chain/show.html.eex:64 -msgid "Total blocks" -msgstr "" - #, elixir-format #: lib/block_scout_web/templates/api_docs/index.html.eex:4 msgid "API Documentation" @@ -1739,3 +1731,29 @@ msgstr "" #: lib/block_scout_web/views/transaction_view.ex:341 msgid "Raw Trace" msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/address/index.html.eex:22 +msgid " (page" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/address/index.html.eex:19 +msgid " addresses of" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/address/index.html.eex:11 +#: lib/block_scout_web/templates/address/index.html.eex:41 +msgid "Back" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/address/index.html.eex:17 +msgid "Showing " +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/chain/show.html.eex:64 +msgid "Total blocks" +msgstr "" 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 diff --git a/apps/explorer/.gitignore b/apps/explorer/.gitignore index f4cd5728cc..bf75b19355 100644 --- a/apps/explorer/.gitignore +++ b/apps/explorer/.gitignore @@ -1 +1,2 @@ -priv/.recovery \ No newline at end of file +priv/.recovery +priv/solc_compilers/ diff --git a/apps/explorer/lib/explorer/application.ex b/apps/explorer/lib/explorer/application.ex index 47b30cfa2b..b4f8589a57 100644 --- a/apps/explorer/lib/explorer/application.ex +++ b/apps/explorer/lib/explorer/application.ex @@ -26,6 +26,7 @@ defmodule Explorer.Application do Supervisor.Spec.worker(SpandexDatadog.ApiServer, [datadog_opts()]), Supervisor.child_spec({Task.Supervisor, name: Explorer.MarketTaskSupervisor}, id: Explorer.MarketTaskSupervisor), Supervisor.child_spec({Task.Supervisor, name: Explorer.TaskSupervisor}, id: Explorer.TaskSupervisor), + Explorer.SmartContract.SolcDownloader, {Registry, keys: :duplicate, name: Registry.ChainEvents, id: Registry.ChainEvents}, {Admin.Recovery, [[], [name: Admin.Recovery]]}, {TransactionCountCache, [[], []]} diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index beee541ebf..e6fb9c011e 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -1143,21 +1143,25 @@ defmodule Explorer.Chain do end @doc """ - Lists the top 250 `t:Explorer.Chain.Address.t/0`'s' in descending order based on coin balance. + Lists the top `t:Explorer.Chain.Address.t/0`'s' in descending order based on coin balance and address hash. """ @spec list_top_addresses :: [{Address.t(), non_neg_integer()}] - def list_top_addresses do - query = + def list_top_addresses(options \\ []) do + paging_options = Keyword.get(options, :paging_options, @default_paging_options) + + base_query = from(a in Address, where: a.fetched_coin_balance > ^0, order_by: [desc: a.fetched_coin_balance, asc: a.hash], preload: [:names], - select: {a, fragment("coalesce(1 + ?, 0)", a.nonce)}, - limit: 250 + select: {a, fragment("coalesce(1 + ?, 0)", a.nonce)} ) - Repo.all(query) + base_query + |> page_addresses(paging_options) + |> limit(^paging_options.page_size) + |> Repo.all() end @doc """ @@ -2293,6 +2297,12 @@ defmodule Explorer.Chain do end) end + defp page_addresses(query, %PagingOptions{key: nil}), do: query + + defp page_addresses(query, %PagingOptions{key: {coin_balance, hash}}) do + where(query, [address], address.fetched_coin_balance <= ^coin_balance and address.hash > ^hash) + end + defp page_blocks(query, %PagingOptions{key: nil}), do: query defp page_blocks(query, %PagingOptions{key: {block_number}}) do diff --git a/apps/explorer/lib/explorer/smart_contract/solc_downloader.ex b/apps/explorer/lib/explorer/smart_contract/solc_downloader.ex new file mode 100644 index 0000000000..1aa81d2488 --- /dev/null +++ b/apps/explorer/lib/explorer/smart_contract/solc_downloader.ex @@ -0,0 +1,92 @@ +defmodule Explorer.SmartContract.SolcDownloader do + @moduledoc """ + Checks to see if the requested solc compiler version exists, and if not it + downloads and stores the file. + """ + use GenServer + + alias Explorer.SmartContract.Solidity.CompilerVersion + + @latest_compiler_refetch_time :timer.minutes(30) + + def ensure_exists(version) do + path = file_path(version) + + if File.exists?(path) do + path + else + {:ok, compiler_versions} = CompilerVersion.fetch_versions() + + if version in compiler_versions do + GenServer.call(__MODULE__, {:ensure_exists, version}, 60_000) + else + false + end + end + end + + def start_link(_) do + GenServer.start_link(__MODULE__, [], name: __MODULE__) + end + + # sobelow_skip ["Traversal"] + @impl true + def init([]) do + File.mkdir(compiler_dir()) + + {:ok, []} + end + + # sobelow_skip ["Traversal"] + @impl true + def handle_call({:ensure_exists, version}, _from, state) do + path = file_path(version) + + if fetch?(version, path) do + temp_path = file_path("#{version}-tmp") + + contents = download(version) + + file = File.open!(temp_path, [:write, :exclusive]) + + IO.binwrite(file, contents) + + File.rename(temp_path, path) + end + + {:reply, path, state} + end + + defp fetch?("latest", path) do + case File.stat(path) do + {:error, :enoent} -> + true + + {:ok, %{mtime: mtime}} -> + last_modified = NaiveDateTime.from_erl!(mtime) + diff = Timex.diff(NaiveDateTime.utc_now(), last_modified, :milliseconds) + + diff > @latest_compiler_refetch_time + end + end + + defp fetch?(_, path) do + not File.exists?(path) + end + + defp file_path(version) do + Path.join(compiler_dir(), "#{version}.js") + end + + defp compiler_dir do + Application.app_dir(:explorer, "priv/solc_compilers/") + end + + defp download(version) do + download_path = "https://ethereum.github.io/solc-bin/bin/soljson-#{version}.js" + + download_path + |> HTTPoison.get!([], timeout: 60_000) + |> Map.get(:body) + end +end diff --git a/apps/explorer/lib/explorer/smart_contract/solidity/code_compiler.ex b/apps/explorer/lib/explorer/smart_contract/solidity/code_compiler.ex index e7f6090707..72c5bae186 100644 --- a/apps/explorer/lib/explorer/smart_contract/solidity/code_compiler.ex +++ b/apps/explorer/lib/explorer/smart_contract/solidity/code_compiler.ex @@ -3,6 +3,8 @@ defmodule Explorer.SmartContract.Solidity.CodeCompiler do Module responsible to compile the Solidity code of a given Smart Contract. """ + alias Explorer.SmartContract.SolcDownloader + @new_contract_name "New.sol" @allowed_evm_versions ["homestead", "tangerineWhistle", "spuriousDragon", "byzantium", "constantinople", "petersburg"] @@ -79,31 +81,36 @@ defmodule Explorer.SmartContract.Solidity.CodeCompiler do "byzantium" end - {response, _status} = - System.cmd( - "node", - [ - Application.app_dir(:explorer, "priv/compile_solc.js"), - code, - compiler_version, - optimize_value(optimize), - optimization_runs, - @new_contract_name, - external_libs_string, - checked_evm_version - ] - ) - - with {:ok, contracts} <- Jason.decode(response), - %{"abi" => abi, "evm" => %{"deployedBytecode" => %{"object" => bytecode}}} <- - get_contract_info(contracts, name) do - {:ok, %{"abi" => abi, "bytecode" => bytecode, "name" => name}} - else - {:error, %Jason.DecodeError{}} -> - {:error, :compilation} - - error -> - parse_error(error) + path = SolcDownloader.ensure_exists(compiler_version) + + if path do + {response, _status} = + System.cmd( + "node", + [ + Application.app_dir(:explorer, "priv/compile_solc.js"), + code, + compiler_version, + optimize_value(optimize), + optimization_runs, + @new_contract_name, + external_libs_string, + checked_evm_version, + path + ] + ) + + with {:ok, contracts} <- Jason.decode(response), + %{"abi" => abi, "evm" => %{"deployedBytecode" => %{"object" => bytecode}}} <- + get_contract_info(contracts, name) do + {:ok, %{"abi" => abi, "bytecode" => bytecode, "name" => name}} + else + {:error, %Jason.DecodeError{}} -> + {:error, :compilation} + + error -> + parse_error(error) + end end end diff --git a/apps/explorer/priv/compile_solc.js b/apps/explorer/priv/compile_solc.js index 7179ebda56..5aaf3f54d7 100755 --- a/apps/explorer/priv/compile_solc.js +++ b/apps/explorer/priv/compile_solc.js @@ -1,7 +1,5 @@ #!/usr/bin/env node -const solc = require('solc'); - var sourceCode = process.argv[2]; var version = process.argv[3]; var optimize = process.argv[4]; @@ -9,38 +7,38 @@ var optimizationRuns = parseInt(process.argv[5], 10); var newContractName = process.argv[6]; var externalLibraries = JSON.parse(process.argv[7]) var evmVersion = process.argv[8]; +var compilerVersionPath = process.argv[9]; + +var solc = require('solc') +var compilerSnapshot = require(compilerVersionPath); +var solc = solc.setupMethods(compilerSnapshot); -var compiled_code = solc.loadRemoteVersion(version, function (err, solcSnapshot) { - if (err) { - console.log(JSON.stringify(err.message)); - } else { - const input = { - language: 'Solidity', - sources: { - [newContractName]: { - content: sourceCode - } - }, - settings: { - evmVersion: evmVersion, - optimizer: { - enabled: optimize == '1', - runs: optimizationRuns - }, - libraries: { - [newContractName]: externalLibraries - }, - outputSelection: { - '*': { - '*': ['*'] - } - } +const input = { + language: 'Solidity', + sources: { + [newContractName]: { + content: sourceCode + } + }, + settings: { + evmVersion: evmVersion, + optimizer: { + enabled: optimize == '1', + runs: optimizationRuns + }, + libraries: { + [newContractName]: externalLibraries + }, + outputSelection: { + '*': { + '*': ['*'] } } - - const output = JSON.parse(solcSnapshot.compile(JSON.stringify(input))) - /** Older solc-bin versions don't use filename as contract key */ - const response = output.contracts[newContractName] || output.contracts[''] - console.log(JSON.stringify(response)); } -}); +} + + +const output = JSON.parse(solc.compile(JSON.stringify(input))) +/** Older solc-bin versions don't use filename as contract key */ +const response = output.contracts[newContractName] || output.contracts[''] +console.log(JSON.stringify(response)); diff --git a/apps/explorer/test/explorer/chain_test.exs b/apps/explorer/test/explorer/chain_test.exs index 36edace635..a9b479c3bd 100644 --- a/apps/explorer/test/explorer/chain_test.exs +++ b/apps/explorer/test/explorer/chain_test.exs @@ -1415,6 +1415,33 @@ defmodule Explorer.ChainTest do |> Enum.map(fn {address, _transaction_count} -> address end) |> Enum.map(& &1.hash) end + + test "paginates addresses" do + test_hashes = + 4..0 + |> Enum.map(&Explorer.Chain.Hash.cast(Explorer.Chain.Hash.Address, &1)) + |> Enum.map(&elem(&1, 1)) + + result = + 4..1 + |> Enum.map(&insert(:address, fetched_coin_balance: &1, hash: Enum.fetch!(test_hashes, &1 - 1))) + |> Enum.map(& &1.hash) + + options = [paging_options: %PagingOptions{page_size: 1}] + + [{top_address, _}] = Chain.list_top_addresses(options) + assert top_address.hash == List.first(result) + + tail_options = [ + paging_options: %PagingOptions{key: {top_address.fetched_coin_balance.value, top_address.hash}, page_size: 3} + ] + + tail_result = tail_options |> Chain.list_top_addresses() |> Enum.map(fn {address, _} -> address.hash end) + + [_ | expected_tail] = result + + assert tail_result == expected_tail + end end describe "stream_blocks_without_rewards/2" do