From 13cc4d15e4407dae8102fa50dc36c4863752c5f3 Mon Sep 17 00:00:00 2001 From: Luke Imhoff Date: Thu, 30 Aug 2018 08:39:56 -0500 Subject: [PATCH 01/22] Cover BlockScoutWeb.AddressTokenBalanceController --- .../address_token_balance_controller_test.exs | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 apps/block_scout_web/test/block_scout_web/controllers/address_token_balance_controller_test.exs diff --git a/apps/block_scout_web/test/block_scout_web/controllers/address_token_balance_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/address_token_balance_controller_test.exs new file mode 100644 index 0000000000..6e73dc8d43 --- /dev/null +++ b/apps/block_scout_web/test/block_scout_web/controllers/address_token_balance_controller_test.exs @@ -0,0 +1,46 @@ +defmodule BlockScoutWeb.AddressTokenBalanceControllerTest do + use BlockScoutWeb.ConnCase + + alias Explorer.Chain.Address + alias Explorer.Factory + + describe "GET index/3" do + test "without AJAX", %{conn: conn} do + %Address{hash: hash} = Factory.insert(:address) + + response_conn = get(conn, address_token_balance_path(conn, :index, :en, to_string(hash))) + + assert html_response(response_conn, 404) + end + + test "with AJAX without valid address", %{conn: conn} do + ajax_conn = ajax(conn) + + response_conn = get(ajax_conn, address_token_balance_path(ajax_conn, :index, :en, "invalid_address")) + + assert html_response(response_conn, 404) + end + + test "with AJAX with valid address without address still returns token balances", %{conn: conn} do + ajax_conn = ajax(conn) + + response_conn = get(ajax_conn, address_token_balance_path(ajax_conn, :index, :en, Factory.address_hash())) + + assert html_response(response_conn, 200) + end + + test "with AJAX with valid address with address returns token balances", %{conn: conn} do + %Address{hash: hash} = Factory.insert(:address) + + ajax_conn = ajax(conn) + + response_conn = get(ajax_conn, address_token_balance_path(ajax_conn, :index, :en, hash)) + + assert html_response(response_conn, 200) + end + end + + defp ajax(conn) do + put_req_header(conn, "x-requested-with", "XMLHttpRequest") + end +end From 8aa1e0e129fd3c15e2e0e9eb1c700ad6cc3c979a Mon Sep 17 00:00:00 2001 From: Luke Imhoff Date: Thu, 30 Aug 2018 08:53:16 -0500 Subject: [PATCH 02/22] Cover Explorer.SmartContract.Reader --- .../test/explorer/smart_contract/reader_test.exs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/apps/explorer/test/explorer/smart_contract/reader_test.exs b/apps/explorer/test/explorer/smart_contract/reader_test.exs index 9643f79dac..473281d252 100644 --- a/apps/explorer/test/explorer/smart_contract/reader_test.exs +++ b/apps/explorer/test/explorer/smart_contract/reader_test.exs @@ -177,6 +177,20 @@ defmodule Explorer.SmartContract.ReaderTest do } ] = Reader.query_function(smart_contract.address_hash, %{name: "get", args: []}) end + + test "nil arguments is treated as []" do + smart_contract = insert(:smart_contract) + + blockchain_get_function_mock() + + assert [ + %{ + "name" => "", + "type" => "uint256", + "value" => 0 + } + ] = Reader.query_function(smart_contract.address_hash, %{name: "get", args: nil}) + end end describe "normalize_args/1" do From 60004c5373e59a816ac1b4f808cb8de80fc21382 Mon Sep 17 00:00:00 2001 From: Luke Imhoff Date: Thu, 30 Aug 2018 09:09:42 -0500 Subject: [PATCH 03/22] Remove unused internal_transacion_suicide_factory --- apps/explorer/test/support/factory.ex | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/apps/explorer/test/support/factory.ex b/apps/explorer/test/support/factory.ex index ec5d6f14f8..0c20ad8273 100644 --- a/apps/explorer/test/support/factory.ex +++ b/apps/explorer/test/support/factory.ex @@ -262,17 +262,6 @@ defmodule Explorer.Factory do } end - def internal_transaction_suicide_factory() do - %InternalTransaction{ - from_address: build(:address), - trace_address: [], - # caller MUST supply `transaction` because it can't be built lazily to allow overrides without creating an extra - # transaction - type: :suicide, - value: sequence("internal_transaction_value", &Decimal.new(&1)) - } - end - def log_factory do %Log{ address: build(:address), From f93a4756628bc8204ba5c044af6ea16621a97947 Mon Sep 17 00:00:00 2001 From: Luke Imhoff Date: Thu, 30 Aug 2018 13:57:57 -0500 Subject: [PATCH 04/22] Cover Explorer.Chain --- apps/explorer/test/explorer/chain_test.exs | 539 ++++++++++++++++++++- 1 file changed, 536 insertions(+), 3 deletions(-) diff --git a/apps/explorer/test/explorer/chain_test.exs b/apps/explorer/test/explorer/chain_test.exs index 4829116e2a..6fd9ebfa1c 100644 --- a/apps/explorer/test/explorer/chain_test.exs +++ b/apps/explorer/test/explorer/chain_test.exs @@ -8,6 +8,8 @@ defmodule Explorer.ChainTest do alias Explorer.Chain.{ Address, Block, + Data, + Hash, InternalTransaction, Log, Token, @@ -21,6 +23,12 @@ defmodule Explorer.ChainTest do doctest Explorer.Chain + describe "address_estimated_count/1" do + test "returns integer" do + assert is_integer(Chain.address_estimated_count()) + end + end + describe "address_to_transaction_count/1" do test "without transactions" do address = insert(:address) @@ -269,6 +277,20 @@ defmodule Explorer.ChainTest do end end + describe "average_block_time/0" do + test "without blocks duration is 0" do + assert Chain.average_block_time() == Timex.Duration.parse!("PT0S") + end + + test "with blocks is average duration between blocks" do + first_block = insert(:block) + second_block = insert(:block, timestamp: Timex.shift(first_block.timestamp, seconds: 3)) + insert(:block, timestamp: Timex.shift(second_block.timestamp, seconds: 9)) + + assert Chain.average_block_time() == Timex.Duration.parse!("PT6S") + end + end + describe "balance/2" do test "with Address.t with :wei" do assert Chain.balance(%Address{fetched_coin_balance: %Wei{value: Decimal.new(1)}}, :wei) == Decimal.new(1) @@ -306,7 +328,7 @@ defmodule Explorer.ChainTest do assert [%Transaction{hash: ^transaction_hash}] = Chain.block_to_transactions(block) end - test "with transactions can be paginated" do + test "with transactions can be paginated by {index}" do block = insert(:block) second_page_hashes = @@ -315,14 +337,14 @@ defmodule Explorer.ChainTest do |> with_block(block) |> Enum.map(& &1.hash) - %Transaction{block_number: block_number, index: index} = + %Transaction{index: index} = :transaction |> insert() |> with_block(block) assert second_page_hashes == block - |> Chain.block_to_transactions(paging_options: %PagingOptions{key: {block_number, index}, page_size: 50}) + |> Chain.block_to_transactions(paging_options: %PagingOptions{key: {index}, page_size: 50}) |> Enum.map(& &1.hash) |> Enum.reverse() end @@ -439,6 +461,74 @@ defmodule Explorer.ChainTest do end end + describe "fetch_token_transfers_from_token_hash/2" do + test "without token transfers" do + %Token{contract_address_hash: contract_address_hash} = insert(:token) + + assert Chain.fetch_token_transfers_from_token_hash(contract_address_hash) == [] + end + + test "with token transfers" do + address = insert(:address) + + transaction = + :transaction + |> insert() + |> with_block() + + %TokenTransfer{id: token_transfer_id, token_contract_address_hash: token_contract_address_hash} = + insert(:token_transfer, to_address: address, transaction: transaction) + + assert token_contract_address_hash + |> Chain.fetch_token_transfers_from_token_hash() + |> Enum.map(& &1.id) == [token_transfer_id] + end + end + + describe "count_token_transfers_from_token_hash/1" do + test "without token transfers" do + %Token{contract_address_hash: contract_address_hash} = insert(:token) + + assert Chain.count_token_transfers_from_token_hash(contract_address_hash) == 0 + end + + test "with token transfers" do + address = insert(:address) + + transaction = + :transaction + |> insert() + |> with_block() + + %TokenTransfer{token_contract_address_hash: token_contract_address_hash} = + insert(:token_transfer, to_address: address, transaction: transaction) + + assert Chain.count_token_transfers_from_token_hash(token_contract_address_hash) == 1 + end + end + + describe "count_addresses_in_token_transfers_from_token_hash/1" do + test "without token transfers" do + %Token{contract_address_hash: contract_address_hash} = insert(:token) + + assert Chain.count_addresses_in_token_transfers_from_token_hash(contract_address_hash) == 0 + end + + test "with token transfers" do + address = insert(:address) + + transaction = + :transaction + |> insert() + |> with_block() + + %TokenTransfer{token_contract_address_hash: token_contract_address_hash} = + insert(:token_transfer, to_address: address, transaction: transaction) + + assert Chain.count_addresses_in_token_transfers_from_token_hash(token_contract_address_hash) == 2 + end + end + describe "gas_price/2" do test ":wei unit" do assert Chain.gas_price(%Transaction{gas_price: %Wei{value: Decimal.new(1)}}, :wei) == Decimal.new(1) @@ -590,6 +680,298 @@ defmodule Explorer.ChainTest do end end + # Full tests in `test/explorer/import_test.exs` + describe "import/1" do + @import_data %{ + blocks: %{ + params: [ + %{ + difficulty: 340_282_366_920_938_463_463_374_607_431_768_211_454, + gas_limit: 6_946_336, + gas_used: 50450, + hash: "0xf6b4b8c88df3ebd252ec476328334dc026cf66606a84fb769b3d3cbccc8471bd", + miner_hash: "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca", + nonce: 0, + number: 37, + parent_hash: "0xc37bbad7057945d1bf128c1ff009fb1ad632110bf6a000aac025a80f7766b66e", + size: 719, + timestamp: Timex.parse!("2017-12-15T21:06:30.000000Z", "{ISO:Extended:Z}"), + total_difficulty: 12_590_447_576_074_723_148_144_860_474_975_121_280_509 + } + ] + }, + broadcast: true, + internal_transactions: %{ + params: [ + %{ + call_type: "call", + from_address_hash: "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca", + gas: 4_677_320, + gas_used: 27770, + index: 0, + output: "0x", + to_address_hash: "0x8bf38d4764929064f2d4d3a56520a76ab3df415b", + trace_address: [], + transaction_hash: "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5", + type: "call", + value: 0 + } + ] + }, + logs: %{ + params: [ + %{ + address_hash: "0x8bf38d4764929064f2d4d3a56520a76ab3df415b", + data: "0x0000000000000000000000000000000000000000000000000de0b6b3a7640000", + first_topic: "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + second_topic: "0x000000000000000000000000e8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca", + third_topic: "0x000000000000000000000000515c09c5bba1ed566b02a5b0599ec5d5d0aee73d", + fourth_topic: nil, + index: 0, + transaction_hash: "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5", + type: "mined" + } + ] + }, + transactions: %{ + on_conflict: :replace_all, + params: [ + %{ + block_hash: "0xf6b4b8c88df3ebd252ec476328334dc026cf66606a84fb769b3d3cbccc8471bd", + block_number: 37, + cumulative_gas_used: 50450, + from_address_hash: "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca", + gas: 4_700_000, + gas_price: 100_000_000_000, + gas_used: 50450, + hash: "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5", + index: 0, + input: "0x10855269000000000000000000000000862d67cb0773ee3f8ce7ea89b328ffea861ab3ef", + nonce: 4, + public_key: + "0xe5d196ad4ceada719d9e592f7166d0c75700f6eab2e3c3de34ba751ea786527cb3f6eb96ad9fdfdb9989ff572df50f1c42ef800af9c5207a38b929aff969b5c9", + r: 0xA7F8F45CCE375BB7AF8750416E1B03E0473F93C256DA2285D1134FC97A700E01, + s: 0x1F87A076F13824F4BE8963E3DFFD7300DAE64D5F23C9A062AF0C6EAD347C135F, + standard_v: 1, + status: :ok, + to_address_hash: "0x8bf38d4764929064f2d4d3a56520a76ab3df415b", + v: 0xBE, + value: 0 + } + ] + }, + addresses: %{ + params: [ + %{hash: "0x8bf38d4764929064f2d4d3a56520a76ab3df415b"}, + %{hash: "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca"}, + %{hash: "0x515c09c5bba1ed566b02a5b0599ec5d5d0aee73d"} + ] + }, + tokens: %{ + on_conflict: :nothing, + params: [ + %{ + contract_address_hash: "0x8bf38d4764929064f2d4d3a56520a76ab3df415b", + type: "ERC-20" + } + ] + }, + token_transfers: %{ + params: [ + %{ + amount: Decimal.new(1_000_000_000_000_000_000), + block_number: 37, + log_index: 0, + from_address_hash: "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca", + to_address_hash: "0x515c09c5bba1ed566b02a5b0599ec5d5d0aee73d", + token_contract_address_hash: "0x8bf38d4764929064f2d4d3a56520a76ab3df415b", + transaction_hash: "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5" + } + ] + } + } + + test "with valid data" do + difficulty = Decimal.new(340_282_366_920_938_463_463_374_607_431_768_211_454) + total_difficulty = Decimal.new(12_590_447_576_074_723_148_144_860_474_975_121_280_509) + token_transfer_amount = Decimal.new(1_000_000_000_000_000_000) + + assert {:ok, + %{ + addresses: [ + %Address{ + hash: %Hash{ + byte_count: 20, + bytes: + <<81, 92, 9, 197, 187, 161, 237, 86, 107, 2, 165, 176, 89, 158, 197, 213, 208, 174, 231, 61>> + }, + inserted_at: %{}, + updated_at: %{} + }, + %Address{ + hash: %Hash{ + byte_count: 20, + bytes: + <<139, 243, 141, 71, 100, 146, 144, 100, 242, 212, 211, 165, 101, 32, 167, 106, 179, 223, 65, + 91>> + }, + inserted_at: %{}, + updated_at: %{} + }, + %Address{ + hash: %Hash{ + byte_count: 20, + bytes: + <<232, 221, 197, 199, 162, 210, 240, 215, 169, 121, 132, 89, 192, 16, 79, 223, 94, 152, 122, + 202>> + }, + inserted_at: %{}, + updated_at: %{} + } + ], + blocks: [ + %Block{ + difficulty: ^difficulty, + gas_limit: 6_946_336, + gas_used: 50450, + hash: %Hash{ + byte_count: 32, + bytes: + <<246, 180, 184, 200, 141, 243, 235, 210, 82, 236, 71, 99, 40, 51, 77, 192, 38, 207, 102, 96, + 106, 132, 251, 118, 155, 61, 60, 188, 204, 132, 113, 189>> + }, + miner_hash: %Hash{ + byte_count: 20, + bytes: + <<232, 221, 197, 199, 162, 210, 240, 215, 169, 121, 132, 89, 192, 16, 79, 223, 94, 152, 122, + 202>> + }, + nonce: %Explorer.Chain.Hash{ + byte_count: 8, + bytes: <<0, 0, 0, 0, 0, 0, 0, 0>> + }, + number: 37, + parent_hash: %Hash{ + byte_count: 32, + bytes: + <<195, 123, 186, 215, 5, 121, 69, 209, 191, 18, 140, 31, 240, 9, 251, 26, 214, 50, 17, 11, 246, + 160, 0, 170, 192, 37, 168, 15, 119, 102, 182, 110>> + }, + size: 719, + timestamp: %DateTime{ + year: 2017, + month: 12, + day: 15, + hour: 21, + minute: 6, + second: 30, + microsecond: {0, 6}, + std_offset: 0, + utc_offset: 0, + time_zone: "Etc/UTC", + zone_abbr: "UTC" + }, + total_difficulty: ^total_difficulty, + inserted_at: %{}, + updated_at: %{} + } + ], + internal_transactions: [ + %{ + index: 0, + transaction_hash: %Hash{ + byte_count: 32, + bytes: + <<83, 189, 136, 72, 114, 222, 62, 72, 134, 146, 136, 27, 174, 236, 38, 46, 123, 149, 35, 77, 57, + 101, 36, 140, 57, 254, 153, 47, 255, 212, 51, 229>> + } + } + ], + logs: [ + %Log{ + address_hash: %Hash{ + byte_count: 20, + bytes: + <<139, 243, 141, 71, 100, 146, 144, 100, 242, 212, 211, 165, 101, 32, 167, 106, 179, 223, 65, + 91>> + }, + data: %Data{ + bytes: + <<0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 13, 224, 182, 179, + 167, 100, 0, 0>> + }, + index: 0, + first_topic: "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + second_topic: "0x000000000000000000000000e8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca", + third_topic: "0x000000000000000000000000515c09c5bba1ed566b02a5b0599ec5d5d0aee73d", + fourth_topic: nil, + transaction_hash: %Hash{ + byte_count: 32, + bytes: + <<83, 189, 136, 72, 114, 222, 62, 72, 134, 146, 136, 27, 174, 236, 38, 46, 123, 149, 35, 77, 57, + 101, 36, 140, 57, 254, 153, 47, 255, 212, 51, 229>> + }, + type: "mined", + inserted_at: %{}, + updated_at: %{} + } + ], + transactions: [ + %Hash{ + byte_count: 32, + bytes: + <<83, 189, 136, 72, 114, 222, 62, 72, 134, 146, 136, 27, 174, 236, 38, 46, 123, 149, 35, 77, 57, + 101, 36, 140, 57, 254, 153, 47, 255, 212, 51, 229>> + } + ], + tokens: [ + %Token{ + contract_address_hash: %Hash{ + byte_count: 20, + bytes: + <<139, 243, 141, 71, 100, 146, 144, 100, 242, 212, 211, 165, 101, 32, 167, 106, 179, 223, 65, + 91>> + }, + type: "ERC-20", + inserted_at: %{}, + updated_at: %{} + } + ], + token_transfers: [ + %TokenTransfer{ + amount: ^token_transfer_amount, + log_index: 0, + from_address_hash: %Hash{ + byte_count: 20, + bytes: + <<232, 221, 197, 199, 162, 210, 240, 215, 169, 121, 132, 89, 192, 16, 79, 223, 94, 152, 122, + 202>> + }, + to_address_hash: %Hash{ + byte_count: 20, + bytes: + <<81, 92, 9, 197, 187, 161, 237, 86, 107, 2, 165, 176, 89, 158, 197, 213, 208, 174, 231, 61>> + }, + token_contract_address_hash: %Hash{ + byte_count: 20, + bytes: + <<139, 243, 141, 71, 100, 146, 144, 100, 242, 212, 211, 165, 101, 32, 167, 106, 179, 223, 65, + 91>> + }, + transaction_hash: %Hash{ + byte_count: 32, + bytes: + <<83, 189, 136, 72, 114, 222, 62, 72, 134, 146, 136, 27, 174, 236, 38, 46, 123, 149, 35, 77, 57, + 101, 36, 140, 57, 254, 153, 47, 255, 212, 51, 229>> + }, + inserted_at: %{}, + updated_at: %{} + } + ] + }} = Chain.import(@import_data) + end + end + describe "list_blocks/2" do test "without blocks" do assert [] = Chain.list_blocks() @@ -774,6 +1156,126 @@ defmodule Explorer.ChainTest do assert [second_pending, first_pending, sixth, fifth, fourth, third, second, first] == result end + test "pages by {block_number, transaction_index, index}" do + address = insert(:address) + + pending_transaction = insert(:transaction) + + insert( + :internal_transaction, + transaction: pending_transaction, + to_address: address, + index: 0 + ) + + insert( + :internal_transaction, + transaction: pending_transaction, + to_address: address, + index: 1 + ) + + a_block = insert(:block, number: 2000) + + first_a_transaction = + :transaction + |> insert() + |> with_block(a_block) + + %InternalTransaction{id: first} = + insert( + :internal_transaction, + transaction: first_a_transaction, + to_address: address, + index: 0 + ) + + %InternalTransaction{id: second} = + insert( + :internal_transaction, + transaction: first_a_transaction, + to_address: address, + index: 1 + ) + + second_a_transaction = + :transaction + |> insert() + |> with_block(a_block) + + %InternalTransaction{id: third} = + insert( + :internal_transaction, + transaction: second_a_transaction, + to_address: address, + index: 0 + ) + + %InternalTransaction{id: fourth} = + insert( + :internal_transaction, + transaction: second_a_transaction, + to_address: address, + index: 1 + ) + + b_block = insert(:block, number: 6000) + + first_b_transaction = + :transaction + |> insert() + |> with_block(b_block) + + %InternalTransaction{id: fifth} = + insert( + :internal_transaction, + transaction: first_b_transaction, + to_address: address, + index: 0 + ) + + %InternalTransaction{id: sixth} = + insert( + :internal_transaction, + transaction: first_b_transaction, + to_address: address, + index: 1 + ) + + # When paged, internal transactions need an associated block number, so `second_pending` and `first_pending` are + # excluded. + assert [sixth, fifth, fourth, third, second, first] == + address + |> Chain.address_to_internal_transactions( + paging_options: %PagingOptions{key: {6001, 3, 2}, page_size: 8} + ) + |> Enum.map(& &1.id) + + # block number ==, transaction index ==, internal transaction index < + assert [fifth, fourth, third, second, first] == + address + |> Chain.address_to_internal_transactions( + paging_options: %PagingOptions{key: {6000, 0, 1}, page_size: 8} + ) + |> Enum.map(& &1.id) + + # block number ==, transaction index < + assert [fourth, third, second, first] == + address + |> Chain.address_to_internal_transactions( + paging_options: %PagingOptions{key: {6000, -1, -1}, page_size: 8} + ) + |> Enum.map(& &1.id) + + # block number < + assert [] == + address + |> Chain.address_to_internal_transactions( + paging_options: %PagingOptions{key: {2000, -1, -1}, page_size: 8} + ) + |> Enum.map(& &1.id) + end + test "excludes internal transactions of type `call` when they are alone in the parent transaction" do address = insert(:address) @@ -836,6 +1338,12 @@ defmodule Explorer.ChainTest do end end + describe "transaction_estimated_count/1" do + test "returns integer" do + assert is_integer(Chain.transaction_estimated_count()) + end + end + describe "transaction_to_internal_transactions/1" do test "with transaction without internal transactions" do transaction = insert(:transaction) @@ -955,6 +1463,31 @@ defmodule Explorer.ChainTest do assert [first_id, second_id] == result end + + test "pages by index" do + transaction = + :transaction + |> insert() + |> with_block() + + %InternalTransaction{id: first_id} = insert(:internal_transaction, transaction: transaction, index: 0) + %InternalTransaction{id: second_id} = insert(:internal_transaction, transaction: transaction, index: 1) + + assert [^first_id, ^second_id] = + transaction + |> Chain.transaction_to_internal_transactions(paging_options: %PagingOptions{key: {-1}, page_size: 2}) + |> Enum.map(& &1.id) + + assert [^first_id] = + transaction + |> Chain.transaction_to_internal_transactions(paging_options: %PagingOptions{key: {-1}, page_size: 1}) + |> Enum.map(& &1.id) + + assert [^second_id] = + transaction + |> Chain.transaction_to_internal_transactions(paging_options: %PagingOptions{key: {0}, page_size: 2}) + |> Enum.map(& &1.id) + end end describe "transaction_to_logs/2" do From c1ecb966a7621b21652ac8a1748952819cdacc3a Mon Sep 17 00:00:00 2001 From: Luke Imhoff Date: Thu, 30 Aug 2018 15:53:45 -0500 Subject: [PATCH 05/22] Cover Explorer.Chain.Import --- .../test/explorer/{ => chain}/import_test.exs | 173 ++++++++++++++++-- 1 file changed, 155 insertions(+), 18 deletions(-) rename apps/explorer/test/explorer/{ => chain}/import_test.exs (67%) diff --git a/apps/explorer/test/explorer/import_test.exs b/apps/explorer/test/explorer/chain/import_test.exs similarity index 67% rename from apps/explorer/test/explorer/import_test.exs rename to apps/explorer/test/explorer/chain/import_test.exs index d1416b7396..4440845207 100644 --- a/apps/explorer/test/explorer/import_test.exs +++ b/apps/explorer/test/explorer/chain/import_test.exs @@ -19,6 +19,7 @@ defmodule Explorer.Chain.ImportTest do doctest Import describe "all/1" do + # set :timeout options to cover lines that use the timeout override when available @import_data %{ blocks: %{ params: [ @@ -35,7 +36,8 @@ defmodule Explorer.Chain.ImportTest do timestamp: Timex.parse!("2017-12-15T21:06:30.000000Z", "{ISO:Extended:Z}"), total_difficulty: 12_590_447_576_074_723_148_144_860_474_975_121_280_509 } - ] + ], + timeout: 5 }, broadcast: true, internal_transactions: %{ @@ -52,8 +54,22 @@ defmodule Explorer.Chain.ImportTest do transaction_hash: "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5", type: "call", value: 0 + }, + %{ + call_type: "call", + from_address_hash: "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca", + gas: 4_677_320, + gas_used: 27770, + index: 1, + output: "0x", + to_address_hash: "0x8bf38d4764929064f2d4d3a56520a76ab3df415b", + trace_address: [], + transaction_hash: "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5", + type: "call", + value: 0 } - ] + ], + timeout: 5 }, logs: %{ params: [ @@ -68,7 +84,8 @@ defmodule Explorer.Chain.ImportTest do transaction_hash: "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5", type: "mined" } - ] + ], + timeout: 5 }, transactions: %{ on_conflict: :replace_all, @@ -95,14 +112,16 @@ defmodule Explorer.Chain.ImportTest do v: 0xBE, value: 0 } - ] + ], + timeout: 5 }, addresses: %{ params: [ %{hash: "0x8bf38d4764929064f2d4d3a56520a76ab3df415b"}, %{hash: "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca"}, %{hash: "0x515c09c5bba1ed566b02a5b0599ec5d5d0aee73d"} - ] + ], + timeout: 5 }, tokens: %{ on_conflict: :nothing, @@ -111,7 +130,8 @@ defmodule Explorer.Chain.ImportTest do contract_address_hash: "0x8bf38d4764929064f2d4d3a56520a76ab3df415b", type: "ERC-20" } - ] + ], + timeout: 5 }, token_transfers: %{ params: [ @@ -124,7 +144,8 @@ defmodule Explorer.Chain.ImportTest do token_contract_address_hash: "0x8bf38d4764929064f2d4d3a56520a76ab3df415b", transaction_hash: "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5" } - ] + ], + timeout: 5 } } @@ -222,6 +243,15 @@ defmodule Explorer.Chain.ImportTest do <<83, 189, 136, 72, 114, 222, 62, 72, 134, 146, 136, 27, 174, 236, 38, 46, 123, 149, 35, 77, 57, 101, 36, 140, 57, 254, 153, 47, 255, 212, 51, 229>> } + }, + %{ + index: 1, + transaction_hash: %Hash{ + byte_count: 32, + bytes: + <<83, 189, 136, 72, 114, 222, 62, 72, 134, 146, 136, 27, 174, 236, 38, 46, 123, 149, 35, 77, 57, + 101, 36, 140, 57, 254, 153, 47, 255, 212, 51, 229>> + } } ], logs: [ @@ -315,7 +345,8 @@ defmodule Explorer.Chain.ImportTest do %{hash: "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca"}, %{hash: "0x515c09c5bba1ed566b02a5b0599ec5d5d0aee73d"}, %{hash: "0x8bf38d4764929064f2d4d3a56520a76ab3df415b"} - ] + ], + timeout: 5 }, tokens: %{ on_conflict: :nothing, @@ -324,7 +355,8 @@ defmodule Explorer.Chain.ImportTest do contract_address_hash: "0x8bf38d4764929064f2d4d3a56520a76ab3df415b", type: "ERC-20" } - ] + ], + timeout: 5 }, token_balances: %{ params: [ @@ -343,7 +375,8 @@ defmodule Explorer.Chain.ImportTest do token_contract_address_hash: "0x8bf38d4764929064f2d4d3a56520a76ab3df415b", block_number: "37" } - ] + ], + timeout: 5 } } @@ -365,14 +398,8 @@ defmodule Explorer.Chain.ImportTest do end test "with invalid data" do - invalid_transaction = - @import_data - |> Map.get(:internal_transactions) - |> Map.get(:params) - |> Enum.at(0) - |> Map.delete(:call_type) - - invalid_import_data = put_in(@import_data, [:internal_transactions, :params], [invalid_transaction]) + invalid_import_data = + update_in(@import_data, [:internal_transactions, :params, Access.at(0)], &Map.delete(&1, :call_type)) assert {:error, [changeset]} = Import.all(invalid_import_data) assert changeset_errors(changeset)[:call_type] == ["can't be blank"] @@ -651,5 +678,115 @@ defmodule Explorer.Chain.ImportTest do assert transaction.created_contract_address_hash == nil end + + test "import balances" do + assert {:ok, _} = + Import.all(%{ + addresses: %{ + params: [%{hash: "0x8bf38d4764929064f2d4d3a56520a76ab3df415b"}] + }, + balances: %{ + params: [%{address_hash: "0x8bf38d4764929064f2d4d3a56520a76ab3df415b", block_number: 1}], + timeout: 5 + } + }) + end + + test "transactions with multiple create internal transactions return error" do + assert {:error, :internal_transactions_indexed_at_transactions, + %{exception: %Postgrex.Error{postgres: %{code: :cardinality_violation}}, transaction_hashes: [_]}, + _} = + Import.all(%{ + blocks: %{ + params: [ + %{ + difficulty: 340_282_366_920_938_463_463_374_607_431_768_211_454, + gas_limit: 6_946_336, + gas_used: 50450, + hash: "0xf6b4b8c88df3ebd252ec476328334dc026cf66606a84fb769b3d3cbccc8471bd", + miner_hash: "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca", + nonce: 0, + number: 37, + parent_hash: "0xc37bbad7057945d1bf128c1ff009fb1ad632110bf6a000aac025a80f7766b66e", + size: 719, + timestamp: Timex.parse!("2017-12-15T21:06:30.000000Z", "{ISO:Extended:Z}"), + total_difficulty: 12_590_447_576_074_723_148_144_860_474_975_121_280_509 + } + ], + timeout: 5 + }, + transactions: %{ + on_conflict: :replace_all, + params: [ + %{ + block_hash: "0xf6b4b8c88df3ebd252ec476328334dc026cf66606a84fb769b3d3cbccc8471bd", + block_number: 37, + cumulative_gas_used: 50450, + from_address_hash: "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca", + gas: 4_700_000, + gas_price: 100_000_000_000, + gas_used: 50450, + hash: "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5", + index: 0, + input: "0x10855269000000000000000000000000862d67cb0773ee3f8ce7ea89b328ffea861ab3ef", + nonce: 4, + public_key: + "0xe5d196ad4ceada719d9e592f7166d0c75700f6eab2e3c3de34ba751ea786527cb3f6eb96ad9fdfdb9989ff572df50f1c42ef800af9c5207a38b929aff969b5c9", + r: 0xA7F8F45CCE375BB7AF8750416E1B03E0473F93C256DA2285D1134FC97A700E01, + s: 0x1F87A076F13824F4BE8963E3DFFD7300DAE64D5F23C9A062AF0C6EAD347C135F, + standard_v: 1, + status: :ok, + v: 0xBE, + value: 0 + } + ], + timeout: 5 + }, + internal_transactions: %{ + params: [ + %{ + created_contract_address_hash: "0xffc87239eb0267bc3ca2cd51d12fbf278e02ccb4", + created_contract_code: + "0x606060405260043610610062576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680630900f01014610067578063445df0ac146100a05780638da5cb5b146100c9578063fdacd5761461011e575b600080fd5b341561007257600080fd5b61009e600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610141565b005b34156100ab57600080fd5b6100b3610224565b6040518082815260200191505060405180910390f35b34156100d457600080fd5b6100dc61022a565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561012957600080fd5b61013f600480803590602001909190505061024f565b005b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415610220578190508073ffffffffffffffffffffffffffffffffffffffff1663fdacd5766001546040518263ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040180828152602001915050600060405180830381600087803b151561020b57600080fd5b6102c65a03f1151561021c57600080fd5b5050505b5050565b60015481565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614156102ac57806001819055505b505600a165627a7a72305820a9c628775efbfbc17477a472413c01ee9b33881f550c59d21bee9928835c854b0029", + from_address_hash: "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca", + gas: 4_677_320, + gas_used: 27770, + index: 0, + init: + "0x6060604052341561000f57600080fd5b336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506102db8061005e6000396000f300606060405260043610610062576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680630900f01014610067578063445df0ac146100a05780638da5cb5b146100c9578063fdacd5761461011e575b600080fd5b341561007257600080fd5b61009e600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610141565b005b34156100ab57600080fd5b6100b3610224565b6040518082815260200191505060405180910390f35b34156100d457600080fd5b6100dc61022a565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561012957600080fd5b61013f600480803590602001909190505061024f565b005b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415610220578190508073ffffffffffffffffffffffffffffffffffffffff1663fdacd5766001546040518263ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040180828152602001915050600060405180830381600087803b151561020b57600080fd5b6102c65a03f1151561021c57600080fd5b5050505b5050565b60015481565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614156102ac57806001819055505b505600a165627a7a72305820a9c628775efbfbc17477a472413c01ee9b33881f550c59d21bee9928835c854b0029", + trace_address: [], + transaction_hash: "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5", + type: "create", + value: 0 + }, + %{ + created_contract_address_hash: "0xffc87239eb0267bc3ca2cd51d12fbf278e02ccb5", + created_contract_code: + "0x606060405260043610610062576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680630900f01014610067578063445df0ac146100a05780638da5cb5b146100c9578063fdacd5761461011e575b600080fd5b341561007257600080fd5b61009e600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610141565b005b34156100ab57600080fd5b6100b3610224565b6040518082815260200191505060405180910390f35b34156100d457600080fd5b6100dc61022a565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561012957600080fd5b61013f600480803590602001909190505061024f565b005b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415610220578190508073ffffffffffffffffffffffffffffffffffffffff1663fdacd5766001546040518263ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040180828152602001915050600060405180830381600087803b151561020b57600080fd5b6102c65a03f1151561021c57600080fd5b5050505b5050565b60015481565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614156102ac57806001819055505b505600a165627a7a72305820a9c628775efbfbc17477a472413c01ee9b33881f550c59d21bee9928835c854b0029", + from_address_hash: "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca", + gas: 4_677_320, + gas_used: 27770, + index: 1, + init: + "0x6060604052341561000f57600080fd5b336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506102db8061005e6000396000f300606060405260043610610062576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680630900f01014610067578063445df0ac146100a05780638da5cb5b146100c9578063fdacd5761461011e575b600080fd5b341561007257600080fd5b61009e600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610141565b005b34156100ab57600080fd5b6100b3610224565b6040518082815260200191505060405180910390f35b34156100d457600080fd5b6100dc61022a565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561012957600080fd5b61013f600480803590602001909190505061024f565b005b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415610220578190508073ffffffffffffffffffffffffffffffffffffffff1663fdacd5766001546040518263ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040180828152602001915050600060405180830381600087803b151561020b57600080fd5b6102c65a03f1151561021c57600080fd5b5050505b5050565b60015481565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614156102ac57806001819055505b505600a165627a7a72305820a9c628775efbfbc17477a472413c01ee9b33881f550c59d21bee9928835c854b0029", + trace_address: [], + transaction_hash: "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5", + type: "create", + value: 0 + } + ], + timeout: 5 + }, + addresses: %{ + params: [ + %{hash: "0x8bf38d4764929064f2d4d3a56520a76ab3df415b"}, + %{hash: "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca"}, + %{hash: "0xffc87239eb0267bc3ca2cd51d12fbf278e02ccb4"}, + %{hash: "0xffc87239eb0267bc3ca2cd51d12fbf278e02ccb5"} + ], + timeout: 5 + } + }) + end end end From fe10cae5bd43c19ea933cafaf76edd3d5faac474 Mon Sep 17 00:00:00 2001 From: Tim Mecklem Date: Fri, 31 Aug 2018 15:45:28 -0400 Subject: [PATCH 06/22] Use `txs` for list, `tx` or `address` for individual pages, respectively --- .../assets/js/pages/address.js | 2 +- .../assets/js/pages/transaction.js | 4 +- .../lib/block_scout_web/router.ex | 6 ++- .../templates/address_contract/index.html.eex | 4 +- .../new.html.eex | 2 +- .../controllers/address_controller_test.exs | 6 +-- .../transaction_controller_test.exs | 14 ++--- .../address_contract_verification_test.exs | 41 +++++++-------- .../features/pages/address_contract_page.ex | 23 +++++++++ .../features/pages/address_page.ex | 2 +- .../features/pages/contract_verify_page.ex | 51 +++++++++++++++++++ .../features/pages/transaction_list_page.ex | 2 +- .../features/pages/transaction_page.ex | 4 +- .../features/viewing_addresses_test.exs | 19 +++---- 14 files changed, 127 insertions(+), 53 deletions(-) create mode 100644 apps/block_scout_web/test/block_scout_web/features/pages/address_contract_page.ex create mode 100644 apps/block_scout_web/test/block_scout_web/features/pages/contract_verify_page.ex diff --git a/apps/block_scout_web/assets/js/pages/address.js b/apps/block_scout_web/assets/js/pages/address.js index 5ea3f34acb..dd8116be1e 100644 --- a/apps/block_scout_web/assets/js/pages/address.js +++ b/apps/block_scout_web/assets/js/pages/address.js @@ -72,7 +72,7 @@ export function reducer (state = initialState, action) { } } -router.when('/addresses/:addressHash').then((params) => initRedux(reducer, { +router.when('/address/:addressHash').then((params) => initRedux(reducer, { main (store) { const { addressHash, blockNumber } = params const channel = socket.channel(`addresses:${addressHash}`, {}) diff --git a/apps/block_scout_web/assets/js/pages/transaction.js b/apps/block_scout_web/assets/js/pages/transaction.js index 759994a554..84183423e9 100644 --- a/apps/block_scout_web/assets/js/pages/transaction.js +++ b/apps/block_scout_web/assets/js/pages/transaction.js @@ -65,7 +65,7 @@ export function reducer (state = initialState, action) { } } -router.when('/transactions/:transactionHash').then(() => initRedux(reducer, { +router.when('/tx/:transactionHash').then(() => initRedux(reducer, { main (store) { const blocksChannel = socket.channel(`blocks:new_block`, {}) const $transactionBlockNumber = $('[data-selector="block-number"]') @@ -85,7 +85,7 @@ router.when('/transactions/:transactionHash').then(() => initRedux(reducer, { } })) -router.when('/transactions', { exactPathMatch: true }).then((params) => initRedux(reducer, { +router.when('/txs', { exactPathMatch: true }).then((params) => initRedux(reducer, { main (store) { const { index } = params const transactionsChannel = socket.channel(`transactions:new_transaction`) diff --git a/apps/block_scout_web/lib/block_scout_web/router.ex b/apps/block_scout_web/lib/block_scout_web/router.ex index 244498dc80..11159eee25 100644 --- a/apps/block_scout_web/lib/block_scout_web/router.ex +++ b/apps/block_scout_web/lib/block_scout_web/router.ex @@ -54,7 +54,9 @@ defmodule BlockScoutWeb.Router do resources("/pending_transactions", PendingTransactionController, only: [:index]) - resources "/transactions", TransactionController, only: [:index, :show] do + get("/txs", TransactionController, :index) + + resources "/tx", TransactionController, only: [:show] do resources( "/internal_transactions", TransactionInternalTransactionController, @@ -67,7 +69,7 @@ defmodule BlockScoutWeb.Router do resources("/token_transfers", TransactionTokenTransferController, only: [:index], as: :token_transfer) end - resources "/addresses", AddressController, only: [:show] do + resources "/address", AddressController, only: [:show] do resources("/transactions", AddressTransactionController, only: [:index], as: :transaction) resources( diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address_contract/index.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address_contract/index.html.eex index 1767000106..3a4beae326 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/address_contract/index.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/address_contract/index.html.eex @@ -46,7 +46,9 @@ <%= if !smart_contract_verified?(@address) do %> <%= link( gettext("Verify and Publish"), - to: address_verify_contract_path(@conn, :new, @conn.assigns.locale, @conn.params["address_id"]), class: "button button--primary button--sm float-right ml-3" + to: address_verify_contract_path(@conn, :new, @conn.assigns.locale, @conn.params["address_id"]), + class: "button button--primary button--sm float-right ml-3", + "data-test": "verify_and_publish" ) %> <% end %> diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification/new.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification/new.html.eex index b2dad37cde..404ce842cd 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification/new.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification/new.html.eex @@ -16,7 +16,7 @@
<%= label f, :name, "Contract Name" %> - <%= text_input f, :name, class: "form-control", "aria-describedby": "contract-name-help-block" %> + <%= text_input f, :name, class: "form-control", "aria-describedby": "contract-name-help-block", "data-test": "contract_name" %> <%= error_tag f, :name, id: "contract-name-help-block", class: "text-danger" %>
diff --git a/apps/block_scout_web/test/block_scout_web/controllers/address_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/address_controller_test.exs index e284612310..c658ac5a27 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/address_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/address_controller_test.exs @@ -2,12 +2,12 @@ defmodule BlockScoutWeb.AddressControllerTest do use BlockScoutWeb.ConnCase describe "GET show/3" do - test "redirects to addresses/:address_id/transactions", %{conn: conn} do + test "redirects to address/:address_id/transactions", %{conn: conn} do insert(:address, hash: "0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed") - conn = get(conn, "/en/addresses/0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed") + conn = get(conn, "/en/address/0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed") - assert redirected_to(conn) =~ "/en/addresses/0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed/transactions" + assert redirected_to(conn) =~ "/en/address/0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed/transactions" end end end diff --git a/apps/block_scout_web/test/block_scout_web/controllers/transaction_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/transaction_controller_test.exs index 90106238ce..c96987a839 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/transaction_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/transaction_controller_test.exs @@ -12,7 +12,7 @@ defmodule BlockScoutWeb.TransactionControllerTest do |> insert() |> with_block() - conn = get(conn, "/en/transactions") + conn = get(conn, "/en/txs") assert List.first(conn.assigns.transactions).hash == transaction.hash end @@ -22,7 +22,7 @@ defmodule BlockScoutWeb.TransactionControllerTest do |> insert() |> with_block() - conn = get(conn, "/en/transactions") + conn = get(conn, "/en/txs") assert is_integer(conn.assigns.transaction_estimated_count) end @@ -35,7 +35,7 @@ defmodule BlockScoutWeb.TransactionControllerTest do insert(:transaction) - conn = get(conn, "/en/transactions") + conn = get(conn, "/en/txs") assert [%Transaction{hash: ^hash}] = conn.assigns.transactions end @@ -53,7 +53,7 @@ defmodule BlockScoutWeb.TransactionControllerTest do |> with_block() conn = - get(conn, "/en/transactions", %{ + get(conn, "/en/txs", %{ "block_number" => Integer.to_string(block_number), "index" => Integer.to_string(index) }) @@ -74,7 +74,7 @@ defmodule BlockScoutWeb.TransactionControllerTest do |> insert_list(:transaction, from_address: address) |> with_block(block) - conn = get(conn, "/en/transactions") + conn = get(conn, "/en/txs") assert %{"block_number" => ^number, "index" => 10} = conn.assigns.next_page_params end @@ -86,13 +86,13 @@ defmodule BlockScoutWeb.TransactionControllerTest do |> insert(from_address: address) |> with_block() - conn = get(conn, "/en/transactions") + conn = get(conn, "/en/txs") refute conn.assigns.next_page_params end test "works when there are no transactions", %{conn: conn} do - conn = get(conn, "/en/transactions") + conn = get(conn, "/en/txs") assert conn.assigns.transactions == [] end diff --git a/apps/block_scout_web/test/block_scout_web/features/address_contract_verification_test.exs b/apps/block_scout_web/test/block_scout_web/features/address_contract_verification_test.exs index 1f93e12fc5..b1b60ebd96 100644 --- a/apps/block_scout_web/test/block_scout_web/features/address_contract_verification_test.exs +++ b/apps/block_scout_web/test/block_scout_web/features/address_contract_verification_test.exs @@ -1,11 +1,9 @@ defmodule BlockScoutWeb.AddressContractVerificationTest do use BlockScoutWeb.FeatureCase, async: true - import Wallaby.Query - alias Plug.Conn - alias Explorer.Chain.Address alias Explorer.Factory + alias BlockScoutWeb.{AddressContractPage, ContractVerifyPage} setup do bypass = Bypass.open() @@ -21,7 +19,7 @@ defmodule BlockScoutWeb.AddressContractVerificationTest do %{name: name, source_code: source_code, bytecode: bytecode, version: version} = Factory.contract_code_info() transaction = :transaction |> insert() |> with_block() - address = %Address{hash: address_hash} = insert(:address, contract_code: bytecode) + address = insert(:address, contract_code: bytecode) insert( :internal_transaction_create, @@ -32,33 +30,30 @@ defmodule BlockScoutWeb.AddressContractVerificationTest do ) session - |> visit("/en/addresses/#{address_hash}/contract_verifications/new") - |> fill_in(text_field("Contract Name"), with: name) - |> click(option(version)) - |> click(radio_button("No")) - |> fill_in(text_field("Enter the Solidity Contract Code below"), with: source_code) - |> click(button("Verify and publish")) - - assert current_path(session) =~ ~r/\/en\/addresses\/#{address_hash}\/contracts/ + |> AddressContractPage.visit_page(address) + |> AddressContractPage.click_verify_and_publish() + |> ContractVerifyPage.fill_form(%{ + contract_name: name, + version: version, + optimization: false, + source_code: source_code + }) + |> ContractVerifyPage.verify_and_publish() + + assert AddressContractPage.on_page?(session, address) end test "with invalid data shows error messages", %{session: session, bypass: bypass} do Bypass.expect(bypass, fn conn -> Conn.resp(conn, 200, solc_bin_versions()) end) session - |> visit("/en/addresses/0x1e0eaa06d02f965be2dfe0bc9ff52b2d82133461/contract_verifications/new") - |> fill_in(text_field("Contract Name"), with: "") - |> fill_in(text_field("Enter the Solidity Contract Code below"), with: "") - |> click(button("Verify and publish")) - |> assert_has( - css( - "[data-test='contract-source-code-error']", - text: "there was an error validating your contract, please try again." - ) - ) + |> ContractVerifyPage.visit_page("0x1e0eaa06d02f965be2dfe0bc9ff52b2d82133461") + |> ContractVerifyPage.fill_form(%{contract_name: "", version: nil, optimization: nil, source_code: ""}) + |> ContractVerifyPage.verify_and_publish() + |> assert_has(ContractVerifyPage.validation_error()) end - def solc_bin_versions() do + defp solc_bin_versions do File.read!("./test/support/fixture/smart_contract/solc_bin.json") end end diff --git a/apps/block_scout_web/test/block_scout_web/features/pages/address_contract_page.ex b/apps/block_scout_web/test/block_scout_web/features/pages/address_contract_page.ex new file mode 100644 index 0000000000..e8110aa86d --- /dev/null +++ b/apps/block_scout_web/test/block_scout_web/features/pages/address_contract_page.ex @@ -0,0 +1,23 @@ +defmodule BlockScoutWeb.AddressContractPage do + @moduledoc false + + use Wallaby.DSL + + import Wallaby.Query, only: [css: 1] + + def on_page?(session, address) do + current_path(session) =~ address_contract_path(address) + end + + def click_verify_and_publish(session) do + click(session, css("[data-test='verify_and_publish']")) + end + + def visit_page(session, address) do + visit(session, address_contract_path(address)) + end + + defp address_contract_path(address) do + "/en/address/#{address.hash}/contracts" + end +end diff --git a/apps/block_scout_web/test/block_scout_web/features/pages/address_page.ex b/apps/block_scout_web/test/block_scout_web/features/pages/address_page.ex index dd3a854d4c..e700c2ca8b 100644 --- a/apps/block_scout_web/test/block_scout_web/features/pages/address_page.ex +++ b/apps/block_scout_web/test/block_scout_web/features/pages/address_page.ex @@ -74,7 +74,7 @@ defmodule BlockScoutWeb.AddressPage do def visit_page(session, %Address{hash: address_hash}), do: visit_page(session, address_hash) def visit_page(session, address_hash) do - visit(session, "/en/addresses/#{address_hash}") + visit(session, "/en/address/#{address_hash}") end def token_transfer(%Transaction{hash: transaction_hash}, %Address{hash: address_hash}, count: count) do diff --git a/apps/block_scout_web/test/block_scout_web/features/pages/contract_verify_page.ex b/apps/block_scout_web/test/block_scout_web/features/pages/contract_verify_page.ex new file mode 100644 index 0000000000..0fcfd066a1 --- /dev/null +++ b/apps/block_scout_web/test/block_scout_web/features/pages/contract_verify_page.ex @@ -0,0 +1,51 @@ +defmodule BlockScoutWeb.ContractVerifyPage do + @moduledoc false + + use Wallaby.DSL + + import Wallaby.Query + + def visit_page(session, address_hash) do + visit(session, "/en/address/#{address_hash}/contract_verifications/new") + end + + def fill_form(session, %{ + contract_name: contract_name, + version: version, + optimization: optimization, + source_code: source_code + }) do + session + |> fill_in(css("[data-test='contract_name']"), with: contract_name) + |> fill_in(text_field("Enter the Solidity Contract Code below"), with: source_code) + + case version do + nil -> nil + _ -> click(session, option(version)) + end + + case optimization do + true -> + click(session, radio_button("Yes")) + + false -> + click(session, radio_button("No")) + + _ -> + nil + end + + session + end + + def validation_error do + css( + "[data-test='contract-source-code-error']", + text: "there was an error validating your contract, please try again." + ) + end + + def verify_and_publish(session) do + click(session, button("Verify and publish")) + end +end diff --git a/apps/block_scout_web/test/block_scout_web/features/pages/transaction_list_page.ex b/apps/block_scout_web/test/block_scout_web/features/pages/transaction_list_page.ex index ca6dede9ae..e801d892b8 100644 --- a/apps/block_scout_web/test/block_scout_web/features/pages/transaction_list_page.ex +++ b/apps/block_scout_web/test/block_scout_web/features/pages/transaction_list_page.ex @@ -32,6 +32,6 @@ defmodule BlockScoutWeb.TransactionListPage do end def visit_page(session) do - visit(session, "/en/transactions") + visit(session, "/en/txs") end end diff --git a/apps/block_scout_web/test/block_scout_web/features/pages/transaction_page.ex b/apps/block_scout_web/test/block_scout_web/features/pages/transaction_page.ex index 0e662a914e..d8e3ad7eba 100644 --- a/apps/block_scout_web/test/block_scout_web/features/pages/transaction_page.ex +++ b/apps/block_scout_web/test/block_scout_web/features/pages/transaction_page.ex @@ -16,10 +16,10 @@ defmodule BlockScoutWeb.TransactionPage do end def visit_page(session, %Transaction{hash: transaction_hash}) do - visit(session, "/en/transactions/#{transaction_hash}") + visit(session, "/en/tx/#{transaction_hash}") end def visit_page(session, transaction_hash = %Hash{}) do - visit(session, "/en/transactions/#{transaction_hash}") + visit(session, "/en/tx/#{transaction_hash}") end end diff --git a/apps/block_scout_web/test/block_scout_web/features/viewing_addresses_test.exs b/apps/block_scout_web/test/block_scout_web/features/viewing_addresses_test.exs index 3af69b1ff7..a2221c1003 100644 --- a/apps/block_scout_web/test/block_scout_web/features/viewing_addresses_test.exs +++ b/apps/block_scout_web/test/block_scout_web/features/viewing_addresses_test.exs @@ -2,7 +2,8 @@ defmodule BlockScoutWeb.ViewingAddressesTest do use BlockScoutWeb.FeatureCase, async: true alias Explorer.Chain.Wei - alias BlockScoutWeb.AddressPage + alias Explorer.Factory + alias BlockScoutWeb.{AddressPage, AddressView} setup do block = insert(:block) @@ -40,7 +41,7 @@ defmodule BlockScoutWeb.ViewingAddressesTest do describe "viewing contract creator" do test "see the contract creator and transaction links", %{session: session} do address = insert(:address) - contract = insert(:address, contract_code: Explorer.Factory.data("contract_code")) + contract = insert(:address, contract_code: Factory.data("contract_code")) transaction = insert(:transaction, from_address: address, created_contract_address: contract) internal_transaction = @@ -52,8 +53,8 @@ defmodule BlockScoutWeb.ViewingAddressesTest do created_contract_address: contract ) - address_hash = BlockScoutWeb.AddressView.trimmed_hash(address.hash) - transaction_hash = BlockScoutWeb.AddressView.trimmed_hash(transaction.hash) + address_hash = AddressView.trimmed_hash(address.hash) + transaction_hash = AddressView.trimmed_hash(transaction.hash) session |> AddressPage.visit_page(internal_transaction.created_contract_address) @@ -62,9 +63,9 @@ defmodule BlockScoutWeb.ViewingAddressesTest do test "see the contract creator and transaction links even when the creator is another contract", %{session: session} do lincoln = insert(:address) - contract = insert(:address, contract_code: Explorer.Factory.data("contract_code")) + contract = insert(:address, contract_code: Factory.data("contract_code")) transaction = insert(:transaction) - another_contract = insert(:address, contract_code: Explorer.Factory.data("contract_code")) + another_contract = insert(:address, contract_code: Factory.data("contract_code")) insert( :internal_transaction, @@ -85,8 +86,8 @@ defmodule BlockScoutWeb.ViewingAddressesTest do created_contract_address: another_contract ) - contract_hash = BlockScoutWeb.AddressView.trimmed_hash(contract.hash) - transaction_hash = BlockScoutWeb.AddressView.trimmed_hash(transaction.hash) + contract_hash = AddressView.trimmed_hash(contract.hash) + transaction_hash = AddressView.trimmed_hash(transaction.hash) session |> AddressPage.visit_page(internal_transaction.created_contract_address) @@ -337,7 +338,7 @@ defmodule BlockScoutWeb.ViewingAddressesTest do contract_token_address = insert( :address, - contract_code: Explorer.Factory.data("contract_code") + contract_code: Factory.data("contract_code") ) insert(:token, contract_address: contract_token_address) From fc7097893b789c0a21290c13a5e076148c21060e Mon Sep 17 00:00:00 2001 From: Tim Mecklem Date: Fri, 31 Aug 2018 16:11:38 -0400 Subject: [PATCH 07/22] Gettext --- apps/block_scout_web/priv/gettext/default.pot | 2 +- apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/block_scout_web/priv/gettext/default.pot b/apps/block_scout_web/priv/gettext/default.pot index be1c0e9a33..f608253a62 100644 --- a/apps/block_scout_web/priv/gettext/default.pot +++ b/apps/block_scout_web/priv/gettext/default.pot @@ -586,7 +586,7 @@ msgstr "" #, elixir-format #: lib/block_scout_web/templates/address/overview.html.eex:8 -#: lib/block_scout_web/templates/address_contract/index.html.eex:73 +#: lib/block_scout_web/templates/address_contract/index.html.eex:75 msgid "Copy Address" 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 c157273a27..28e9393ac0 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 @@ -598,7 +598,7 @@ msgstr "" #, elixir-format #: lib/block_scout_web/templates/address/overview.html.eex:8 -#: lib/block_scout_web/templates/address_contract/index.html.eex:73 +#: lib/block_scout_web/templates/address_contract/index.html.eex:75 msgid "Copy Address" msgstr "" From 9e6198da1b576a5ce0086db8d64590853e2ed0c8 Mon Sep 17 00:00:00 2001 From: Luke Imhoff Date: Fri, 31 Aug 2018 13:18:31 -0500 Subject: [PATCH 08/22] Cover EthereumJSONRPC --- apps/ethereum_jsonrpc/lib/ethereum_jsonrpc.ex | 94 ++--- .../lib/ethereum_jsonrpc/receipts.ex | 39 ++- .../test/ethereum_jsonrpc_test.exs | 324 ++++++++++++++++++ 3 files changed, 402 insertions(+), 55 deletions(-) diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc.ex index ef21cc3fcf..a86bd604b4 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc.ex @@ -188,10 +188,15 @@ defmodule EthereumJSONRPC do Transaction data is included for each block. """ def fetch_blocks_by_hash(block_hashes, json_rpc_named_arguments) do - block_hashes + id_to_params = + block_hashes + |> Enum.map(fn block_hash -> %{hash: block_hash} end) + |> id_to_params() + + id_to_params |> get_block_by_hash_requests() |> json_rpc(json_rpc_named_arguments) - |> handle_get_blocks() + |> handle_get_blocks(id_to_params) |> case do {:ok, _next, results} -> {:ok, results} {:error, reason} -> {:error, reason} @@ -202,10 +207,15 @@ defmodule EthereumJSONRPC do Fetches blocks by block number range. """ def fetch_blocks_by_range(_first.._last = range, json_rpc_named_arguments) do - range + id_to_params = + range + |> Enum.map(fn number -> %{number: number} end) + |> id_to_params() + + id_to_params |> get_block_by_number_requests() |> json_rpc(json_rpc_named_arguments) - |> handle_get_blocks() + |> handle_get_blocks(id_to_params) end @doc """ @@ -284,14 +294,6 @@ defmodule EthereumJSONRPC do transport.json_rpc(request, transport_options) end - @doc """ - Converts `t:nonce/0` to `t:non_neg_integer/0` - """ - @spec nonce_to_integer(nonce) :: non_neg_integer() - def nonce_to_integer(nonce) do - quantity_to_integer(nonce) - end - @doc """ Converts `t:quantity/0` to `t:non_neg_integer/0`. """ @@ -427,10 +429,10 @@ defmodule EthereumJSONRPC do {:error, annotated_error} end - defp get_block_by_hash_requests(block_hashes) do - for block_hash <- block_hashes do - get_block_by_hash_request(%{id: block_hash, hash: block_hash, transactions: :full}) - end + defp get_block_by_hash_requests(id_to_params) do + Enum.map(id_to_params, fn {id, %{hash: hash}} -> + get_block_by_hash_request(%{id: id, hash: hash, transactions: :full}) + end) end defp get_block_by_hash_request(%{id: id} = options) do @@ -441,10 +443,10 @@ defmodule EthereumJSONRPC do [hash, get_block_transactions(options)] end - defp get_block_by_number_requests(range) do - for current <- range do - get_block_by_number_request(%{id: current, quantity: current, transactions: :full}) - end + defp get_block_by_number_requests(id_to_params) do + Enum.map(id_to_params, fn {id, %{number: number}} -> + get_block_by_number_request(%{id: id, quantity: number, transactions: :full}) + end) end defp get_block_by_number_request(%{id: id} = options) do @@ -467,12 +469,6 @@ defmodule EthereumJSONRPC do {:error, {:ok, tag}} -> tag - - {{:ok, _}, {:ok, _}} -> - raise ArgumentError, "Only one of :quantity or :tag can be passed to get_block_by_number_request" - - {:error, :error} -> - raise ArgumentError, "One of :quantity or :tag MUST be passed to get_block_by_number_request" end end @@ -483,26 +479,40 @@ defmodule EthereumJSONRPC do end end - defp handle_get_blocks({:ok, results}) do - {blocks, next} = - Enum.reduce(results, {[], :more}, fn - %{result: nil}, {blocks, _} -> {blocks, :end_of_chain} - %{result: %{} = block}, {blocks, next} -> {[block | blocks], next} - end) + defp handle_get_blocks({:ok, results}, id_to_params) when is_list(results) do + with {:ok, next, blocks} <- reduce_results(results, id_to_params) do + elixir_blocks = Blocks.to_elixir(blocks) + elixir_transactions = Blocks.elixir_to_transactions(elixir_blocks) + blocks_params = Blocks.elixir_to_params(elixir_blocks) + transactions_params = Transactions.elixir_to_params(elixir_transactions) - elixir_blocks = Blocks.to_elixir(blocks) - elixir_transactions = Blocks.elixir_to_transactions(elixir_blocks) - blocks_params = Blocks.elixir_to_params(elixir_blocks) - transactions_params = Transactions.elixir_to_params(elixir_transactions) + {:ok, next, + %{ + blocks: blocks_params, + transactions: transactions_params + }} + end + end - {:ok, next, - %{ - blocks: blocks_params, - transactions: transactions_params - }} + defp handle_get_blocks({:error, _} = error, _id_to_params), do: error + + defp reduce_results(results, id_to_params) do + Enum.reduce(results, {:ok, :more, []}, &reduce_result(&1, &2, id_to_params)) end - defp handle_get_blocks({:error, _} = error), do: error + defp reduce_result(%{result: nil}, {:ok, _, blocks}, _id_to_params), do: {:ok, :end_of_chain, blocks} + defp reduce_result(%{result: %{} = block}, {:ok, next, blocks}, _id_to_params), do: {:ok, next, [block | blocks]} + defp reduce_result(%{result: _}, {:error, _} = error, _id_to_params), do: error + + defp reduce_result(%{error: reason, id: id}, acc, id_to_params) do + data = Map.fetch!(id_to_params, id) + annotated_reason = Map.put(reason, :data, data) + + case acc do + {:ok, _, _} -> {:error, [annotated_reason]} + {:error, reasons} -> {:error, [annotated_reason | reasons]} + end + end defp handle_get_block_by_tag({:ok, %{"number" => quantity}}) do {:ok, quantity_to_integer(quantity)} diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/receipts.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/receipts.ex index 0f5dd2edd2..6056e07ba4 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/receipts.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/receipts.ex @@ -132,11 +132,9 @@ defmodule EthereumJSONRPC.Receipts do {requests, id_to_transaction_params} end) - with {:ok, responses} <- json_rpc(requests, json_rpc_named_arguments) do - elixir_receipts = - responses - |> responses_to_receipts(id_to_transaction_params) - |> to_elixir() + with {:ok, responses} <- json_rpc(requests, json_rpc_named_arguments), + {:ok, receipts} <- reduce_responses(responses, id_to_transaction_params) do + elixir_receipts = to_elixir(receipts) elixir_logs = elixir_to_logs(elixir_receipts) receipts = elixir_to_params(elixir_receipts) @@ -220,20 +218,35 @@ defmodule EthereumJSONRPC.Receipts do }) end - defp response_to_receipt(%{result: nil}, _), do: %{} + defp response_to_receipt(%{id: id, result: nil}, id_to_transaction_params) do + data = Map.fetch!(id_to_transaction_params, id) + {:error, %{code: -32602, data: data, message: "Not Found"}} + end defp response_to_receipt(%{id: id, result: receipt}, id_to_transaction_params) do - gas = - id_to_transaction_params - |> Map.fetch!(id) - |> Map.fetch!(:gas) + %{gas: gas} = Map.fetch!(id_to_transaction_params, id) # gas from the transaction is needed for pre-Byzantium derived status - Map.put(receipt, "gas", gas) + {:ok, Map.put(receipt, "gas", gas)} + end + + defp response_to_receipt(%{id: id, error: reason}, id_to_transaction_params) do + data = Map.fetch!(id_to_transaction_params, id) + annotated_reason = Map.put(reason, :data, data) + {:error, annotated_reason} end - defp responses_to_receipts(responses, id_to_transaction_params) + defp reduce_responses(responses, id_to_transaction_params) when is_list(responses) and is_map(id_to_transaction_params) do - Enum.map(responses, &response_to_receipt(&1, id_to_transaction_params)) + responses + |> Stream.map(&response_to_receipt(&1, id_to_transaction_params)) + |> Enum.reduce({:ok, []}, &reduce_receipt(&1, &2)) end + + defp reduce_receipt({:ok, receipt}, {:ok, receipts}) when is_list(receipts), + do: {:ok, [receipt | receipts]} + + defp reduce_receipt({:ok, _}, {:error, _} = error), do: error + defp reduce_receipt({:error, reason}, {:ok, _}), do: {:error, [reason]} + defp reduce_receipt({:error, reason}, {:error, reasons}) when is_list(reasons), do: {:error, [reason | reasons]} end diff --git a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc_test.exs b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc_test.exs index e7fba66202..d8d43b995a 100644 --- a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc_test.exs +++ b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc_test.exs @@ -164,6 +164,217 @@ defmodule EthereumJSONRPCTest do end end + describe "fetch_block_by_hash/2" do + test "can fetch blocks", %{json_rpc_named_arguments: json_rpc_named_arguments} do + %{block_hash: block_hash, transaction_hash: transaction_hash} = + case Keyword.fetch!(json_rpc_named_arguments, :variant) do + EthereumJSONRPC.Parity -> + %{ + block_hash: "0x29c850324e357f3c0c836d79860c5af55f7b651e5d7ee253c1af1b14908af49c", + transaction_hash: "0xa2e81bb56b55ba3dab2daf76501b50dfaad240cccb905dbf89d65c7a84a4a48e" + } + + EthereumJSONRPC.Geth -> + %{ + block_hash: "0xe065eed62c152c8c3dd14d6e5948e652c3e36a9cdb10b79853802ef9fa1d536c", + transaction_hash: "0x615506d9872bb07faa2ce17c02b902148eae88ccba0298902be6a0dbba1124de" + } + end + + if json_rpc_named_arguments[:transport] == EthereumJSONRPC.Mox do + expect(EthereumJSONRPC.Mox, :json_rpc, fn _json, _options -> + block_number = "0x0" + + {:ok, + [ + %{ + result: %{ + "difficulty" => "0x0", + "gasLimit" => "0x0", + "gasUsed" => "0x0", + "hash" => block_hash, + "miner" => "0x0", + "number" => block_number, + "parentHash" => "0x0", + "size" => "0x0", + "timestamp" => "0x0", + "totalDifficulty" => "0x0", + "transactions" => [ + %{ + "blockHash" => block_hash, + "blockNumber" => block_number, + "from" => "0x0", + "gas" => "0x0", + "gasPrice" => "0x0", + "hash" => transaction_hash, + "input" => "0x", + "nonce" => "0x0", + "r" => "0x0", + "s" => "0x0", + "to" => "0x0", + "transactionIndex" => "0x0", + "v" => "0x0", + "value" => "0x0" + } + ] + } + } + ]} + end) + end + + assert {:ok, %{blocks: [_ | _], transactions: [_ | _]}} = + EthereumJSONRPC.fetch_blocks_by_hash([block_hash], json_rpc_named_arguments) + end + + test "returns errors with block hash in data", %{json_rpc_named_arguments: json_rpc_named_arguments} do + if json_rpc_named_arguments[:transport] == EthereumJSONRPC.Mox do + expect(EthereumJSONRPC.Mox, :json_rpc, fn _json, _options -> + {:ok, + [ + %{ + error: %{ + code: -32602, + message: "Hash too short" + }, + id: 0, + jsonrpc: "2.0" + } + ]} + end) + end + + assert {:error, [%{data: %{hash: "0x0"}}]} = + EthereumJSONRPC.fetch_blocks_by_hash(["0x0"], json_rpc_named_arguments) + end + + test "full batch errors are returned", %{json_rpc_named_arguments: json_rpc_named_arguments} do + # I'm not sure how to reliably trigger this on the real chains, so only do mox + moxed_json_rpc_named_arguments = Keyword.put(json_rpc_named_arguments, :transport, EthereumJSONRPC.Mox) + + error = {:error, %{"message" => "methodNotSupported"}} + + expect(EthereumJSONRPC.Mox, :json_rpc, fn _json, _options -> + error + end) + + assert EthereumJSONRPC.fetch_blocks_by_hash(["0x0"], moxed_json_rpc_named_arguments) == error + end + end + + describe "fetch_block_by_range/2" do + test "returns errors with block number in data", %{json_rpc_named_arguments: json_rpc_named_arguments} do + if json_rpc_named_arguments[:transport] == EthereumJSONRPC.Mox do + expect(EthereumJSONRPC.Mox, :json_rpc, fn _json, _options -> + {:ok, + [ + %{ + error: %{ + code: -32602, + message: "Invalid params: Invalid block number: number too large to fit in target type." + }, + id: 0, + jsonrpc: "2.0" + }, + %{ + error: %{ + code: -32602, + message: "Invalid params: Invalid block number: number too large to fit in target type." + }, + id: 1, + jsonrpc: "2.0" + } + ]} + end) + end + + assert {:error, + [%{data: %{number: 1_000_000_000_000_000_000_001}}, %{data: %{number: 1_000_000_000_000_000_000_000}}]} = + EthereumJSONRPC.fetch_blocks_by_range( + 1_000_000_000_000_000_000_000..1_000_000_000_000_000_000_001, + json_rpc_named_arguments + ) + end + + test "returns only errors if a mix of results and errors", %{json_rpc_named_arguments: json_rpc_named_arguments} do + # Can't be faked reliably on real chain + moxed_json_rpc_named_arguments = Keyword.put(json_rpc_named_arguments, :transport, EthereumJSONRPC.Mox) + + expect(EthereumJSONRPC.Mox, :json_rpc, fn _json, _options -> + {:ok, + [ + %{ + error: %{ + code: -32602, + message: "Invalid params: Invalid block number: number too large to fit in target type." + }, + id: 0, + jsonrpc: "2.0" + }, + %{ + id: 1, + result: %{ + "difficulty" => "0x0", + "gasLimit" => "0x0", + "gasUsed" => "0x0", + "hash" => "0x0", + "miner" => "0x0", + "number" => "0x0", + "parentHash" => "0x0", + "size" => "0x0", + "timestamp" => "0x0", + "totalDifficulty" => "0x0", + "transactions" => [] + }, + jsonrpc: "2.0" + } + ]} + end) + + assert {:error, [%{data: %{number: 1_000_000_000_000_000_000_000}}]} = + EthereumJSONRPC.fetch_blocks_by_range( + 1_000_000_000_000_000_000_000..1_000_000_000_000_000_000_001, + moxed_json_rpc_named_arguments + ) + end + + test "nil result indicated end-of-chain", %{json_rpc_named_arguments: json_rpc_named_arguments} do + # Can't be faked reliably on real chain + moxed_json_rpc_named_arguments = Keyword.put(json_rpc_named_arguments, :transport, EthereumJSONRPC.Mox) + + expect(EthereumJSONRPC.Mox, :json_rpc, fn _json, _options -> + {:ok, + [ + %{ + id: 0, + result: %{ + "difficulty" => "0x0", + "gasLimit" => "0x0", + "gasUsed" => "0x0", + "hash" => "0x0", + "miner" => "0x0", + "number" => "0x0", + "parentHash" => "0x0", + "size" => "0x0", + "timestamp" => "0x0", + "totalDifficulty" => "0x0", + "transactions" => [] + }, + jsonrpc: "2.0" + }, + %{ + result: nil, + id: 1, + jsonrpc: "2.0" + } + ]} + end) + + assert {:ok, :end_of_chain, %{blocks: [_], transactions: []}} = + EthereumJSONRPC.fetch_blocks_by_range(0..1, moxed_json_rpc_named_arguments) + end + end + describe "fetch_block_number_by_tag" do @tag capture_log: false test "with earliest", %{json_rpc_named_arguments: json_rpc_named_arguments} do @@ -220,6 +431,113 @@ defmodule EthereumJSONRPCTest do end ) end + + test "unknown errors are returned", %{json_rpc_named_arguments: json_rpc_named_arguments} do + # Can't be faked reliably on real chain + moxed_json_rpc_named_arguments = Keyword.put(json_rpc_named_arguments, :transport, EthereumJSONRPC.Mox) + + unknown_error = {:error, %{"code" => 500, "message" => "Unknown error"}} + + expect(EthereumJSONRPC.Mox, :json_rpc, fn _json, _options -> + unknown_error + end) + + assert {:error, unknown_error} = + EthereumJSONRPC.fetch_block_number_by_tag("latest", moxed_json_rpc_named_arguments) + end + end + + describe "fetch_pending_transactions/2" do + @tag :no_geth + test "pending transactions are returned", %{json_rpc_named_arguments: json_rpc_named_arguments} do + if json_rpc_named_arguments[:transport] == EthereumJSONRPC.Mox do + expect(EthereumJSONRPC.Mox, :json_rpc, fn _json, _options -> + {:ok, + [ + %{ + "blockHash" => nil, + "blockNumber" => nil, + "from" => "0x0", + "gas" => "0x0", + "gasPrice" => "0x0", + "hash" => "0x73c5599001f77bd570e32c4a5e63157200747910a502fae009821767c36b2ac9", + "input" => "0x", + "nonce" => "0x0", + "r" => "0x0", + "s" => "0x0", + "to" => "0x0", + "transactionIndex" => nil, + "v" => "0x0", + "value" => "0x0" + } + ]} + end) + end + + assert {:ok, pending_transactions} = EthereumJSONRPC.fetch_pending_transactions(json_rpc_named_arguments) + # can't say more because there could be no pending transactions on test chains + assert is_list(pending_transactions) + end + end + + describe "fetch_transaction_receipts/2" do + test "with invalid transaction hash", %{json_rpc_named_arguments: json_rpc_named_arguments} do + hash = "0x0000000000000000000000000000000000000000000000000000000000000000" + + if json_rpc_named_arguments[:transport] == EthereumJSONRPC.Mox do + expect(EthereumJSONRPC.Mox, :json_rpc, fn _json, _options -> + {:ok, [%{id: 0, jsonrpc: "2.0", result: nil}]} + end) + end + + assert {:error, [%{data: %{hash: ^hash}, message: "Not Found"}]} = + EthereumJSONRPC.fetch_transaction_receipts( + [%{hash: hash, gas: "0x0"}], + json_rpc_named_arguments + ) + end + + test "with valid transaction hash", %{json_rpc_named_arguments: json_rpc_named_arguments} do + hash = + case Keyword.fetch!(json_rpc_named_arguments, :variant) do + EthereumJSONRPC.Parity -> + "0xa2e81bb56b55ba3dab2daf76501b50dfaad240cccb905dbf89d65c7a84a4a48e" + + EthereumJSONRPC.Geth -> + "0x615506d9872bb07faa2ce17c02b902148eae88ccba0298902be6a0dbba1124de" + end + + if json_rpc_named_arguments[:transport] == EthereumJSONRPC.Mox do + expect(EthereumJSONRPC.Mox, :json_rpc, fn _json, _options -> + {:ok, + [ + %{ + id: 0, + jsonrpc: "2.0", + result: %{ + "blockHash" => "0x29c850324e357f3c0c836d79860c5af55f7b651e5d7ee253c1af1b14908af49c", + "blockNumber" => "0x414911", + "contractAddress" => nil, + "cumulativeGasUsed" => "0x5208", + "gasUsed" => "0x5208", + "logs" => [], + "logsBloom" => + "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "root" => nil, + "status" => "0x1", + "transactionHash" => hash, + "transactionIndex" => "0x0" + } + } + ]} + end) + end + + assert {:ok, %{logs: logs, receipts: [_]}} = + EthereumJSONRPC.fetch_transaction_receipts([%{hash: hash, gas: "0x0"}], json_rpc_named_arguments) + + assert is_list(logs) + end end describe "subscribe/2" do @@ -379,6 +697,12 @@ defmodule EthereumJSONRPCTest do end end + describe "unique_request_id" do + test "returns integer" do + assert is_integer(EthereumJSONRPC.unique_request_id()) + end + end + describe "execute_contract_functions/3" do test "executes the functions with the block_number" do json_rpc_named_arguments = Application.get_env(:explorer, :json_rpc_named_arguments) From c4f8b64a96b8465d90531f2bcf2aa4ad776b4cb1 Mon Sep 17 00:00:00 2001 From: Luke Imhoff Date: Fri, 31 Aug 2018 16:03:47 -0500 Subject: [PATCH 09/22] Cover EthereumJSONRPC.Receipts --- .../test/ethereum_jsonrpc/receipts_test.exs | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/receipts_test.exs b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/receipts_test.exs index 8d6d0e4acb..613d370d3c 100644 --- a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/receipts_test.exs +++ b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/receipts_test.exs @@ -130,5 +130,43 @@ defmodule EthereumJSONRPC.ReceiptsTest do json_rpc_named_arguments ) end + + test "with errors return all errors", %{json_rpc_named_arguments: json_rpc_named_arguments} do + # Can't be faked reliably on real chain + moxed_json_rpc_named_arguments = Keyword.put(json_rpc_named_arguments, :transport, EthereumJSONRPC.Mox) + + expect(EthereumJSONRPC.Mox, :json_rpc, fn json, _options -> + assert length(json) == 5 + + {:ok, + [ + %{id: 0, result: %{}}, + # :ok, :ok + %{id: 1, result: %{}}, + # :error, :ok + %{id: 2, error: %{code: 2}}, + # :ok, :error + %{id: 3, result: %{}}, + # :error, :error + %{id: 4, error: %{code: 4}} + ]} + end) + + assert {:error, + [ + %{code: 4, data: %{gas: 4, hash: "0x4"}}, + %{code: 2, data: %{gas: 2, hash: "0x2"}} + ]} = + Receipts.fetch( + [ + %{gas: 0, hash: "0x0"}, + %{gas: 1, hash: "0x1"}, + %{gas: 2, hash: "0x2"}, + %{gas: 3, hash: "0x3"}, + %{gas: 4, hash: "0x4"} + ], + moxed_json_rpc_named_arguments + ) + end end end From 1ad6f068f5e89923adc18cdd4bd36fd41121aa01 Mon Sep 17 00:00:00 2001 From: Luke Imhoff Date: Fri, 31 Aug 2018 16:31:08 -0500 Subject: [PATCH 10/22] Failing regression test for #638 --- apps/ethereum_jsonrpc/test/ethereum_jsonrpc/receipt_test.exs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/receipt_test.exs b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/receipt_test.exs index 60e80282d5..b54b2ae984 100644 --- a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/receipt_test.exs +++ b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/receipt_test.exs @@ -43,5 +43,10 @@ defmodule EthereumJSONRPC.ReceiptTest do }) end end + + # Regression test for https://github.com/poanetwork/blockscout/issues/638 + test ~s|"status" => nil is treated the same as no status| do + assert Receipt.to_elixir(%{"status" => nil, "transactionHash" => "0x0"}) == %{"transactionHash" => "0x0"} + end end end From d42fbdb06e3a00aca3ddd89fa5ef665d24d86f68 Mon Sep 17 00:00:00 2001 From: Luke Imhoff Date: Fri, 31 Aug 2018 16:31:41 -0500 Subject: [PATCH 11/22] Treat "status" => nil the same as no status While Geth does not return a status for pre-Byzantium blocks, Parity returns `"status" => nil`, so treat `"status" => nil` as if `"status"` wasn't there. --- apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/receipt.ex | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/receipt.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/receipt.ex index 69975bf08f..0649d5ad0c 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/receipt.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/receipt.ex @@ -266,6 +266,7 @@ defmodule EthereumJSONRPC.Receipt do defp elixir_reducer({:ok, {_, _}}, {:error, _reasons} = acc_error), do: acc_error defp elixir_reducer({:error, reason}, {:ok, _}), do: {:error, [reason]} defp elixir_reducer({:error, reason}, {:error, reasons}), do: {:error, [reason | reasons]} + defp elixir_reducer(:ignore, acc), do: acc defp ok!({:ok, elixir}, _receipt), do: elixir @@ -327,6 +328,8 @@ defmodule EthereumJSONRPC.Receipt do case status do "0x0" -> {:ok, {key, :error}} "0x1" -> {:ok, {key, :ok}} + # pre-Byzantium / Ethereum Classic on Parity + nil -> :ignore other -> {:error, {:unknown_value, %{key: key, value: other}}} end end From 926ceab0ee84d8aa680d01379bf1bb9c91f698fb Mon Sep 17 00:00:00 2001 From: Sebastian Abondano Date: Thu, 30 Aug 2018 18:01:44 -0400 Subject: [PATCH 12/22] Add stop propagation to links in API docs page Why: * For API users not to show/hide an API action's details without meaning to when clicking on a link within an action's description. * Issue link: https://github.com/poanetwork/blockscout/issues/647 This change addresses the need by: * Adding `stop_progation.js` to stop clicks from bubbling up on elements with `data-selector=stop-propagation`. * Adding a span element with `data-selector=stop-propagation` that wraps around API action descriptions. --- apps/block_scout_web/assets/js/app.js | 1 + apps/block_scout_web/assets/js/lib/stop_propagation.js | 3 +++ .../block_scout_web/templates/api_docs/_action_tile.html.eex | 2 +- 3 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 apps/block_scout_web/assets/js/lib/stop_propagation.js diff --git a/apps/block_scout_web/assets/js/app.js b/apps/block_scout_web/assets/js/app.js index a8bd35b021..b41db73a23 100644 --- a/apps/block_scout_web/assets/js/app.js +++ b/apps/block_scout_web/assets/js/app.js @@ -31,6 +31,7 @@ import './lib/pretty_json' import './lib/try_api' import './lib/token_balance_dropdown' import './lib/token_transfers_toggle' +import './lib/stop_propagation' import './pages/address' import './pages/block' diff --git a/apps/block_scout_web/assets/js/lib/stop_propagation.js b/apps/block_scout_web/assets/js/lib/stop_propagation.js new file mode 100644 index 0000000000..c32fb6b049 --- /dev/null +++ b/apps/block_scout_web/assets/js/lib/stop_propagation.js @@ -0,0 +1,3 @@ +import $ from 'jquery' + +$('[data-selector="stop-propagation"]').click((event) => event.stopPropagation()) diff --git a/apps/block_scout_web/lib/block_scout_web/templates/api_docs/_action_tile.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/api_docs/_action_tile.html.eex index 507d3eb073..7c2eab9fe9 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/api_docs/_action_tile.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/api_docs/_action_tile.html.eex @@ -7,7 +7,7 @@ <%= gettext "GET" %> <%= @action.name %> -

<%= raw @action.description %>

+

<%= raw @action.description %>

<%= raw query_params(@module_name, @action) %> From e86c5c79f33660f116d40f8b68c8021921922871 Mon Sep 17 00:00:00 2001 From: Felipe Renan Date: Mon, 3 Sep 2018 17:14:05 -0300 Subject: [PATCH 13/22] Handle timeout when fetching token balances from blockchain --- apps/indexer/lib/indexer/token_balances.ex | 25 +++++++++++-- .../test/indexer/token_balances_test.exs | 36 +++++++++++++++++++ 2 files changed, 58 insertions(+), 3 deletions(-) diff --git a/apps/indexer/lib/indexer/token_balances.ex b/apps/indexer/lib/indexer/token_balances.ex index 3a1e6b180f..9fb12318cc 100644 --- a/apps/indexer/lib/indexer/token_balances.ex +++ b/apps/indexer/lib/indexer/token_balances.ex @@ -5,11 +5,26 @@ defmodule Indexer.TokenBalances do alias Explorer.Token.BalanceReader + @doc """ + Fetches TokenBalances from specific Addresses and Blocks in the Blockchain + + Every `TokenBalance` is fetched asynchronously, but in case an exception is raised (such as a + timeout) during the RPC call the particular TokenBalance request is ignored. + + ## token_balances + + It is a list of a Map so that each map must have: + + * `token_contract_address_hash` - The contract address that represents the Token in the blockchain. + * `address_hash` - The address_hash that we want to know the balance. + * `block_number` - The block number that the address_hash has the balance. + """ def fetch_token_balances_from_blockchain(token_balances) do result = token_balances - |> Task.async_stream(&fetch_token_balance/1) - |> Enum.map(&format_result/1) + |> Task.async_stream(&fetch_token_balance/1, on_timeout: :kill_task) + |> Stream.map(&format_task_results/1) + |> Enum.filter(&ignore_request_with_timeouts/1) {:ok, result} end @@ -34,5 +49,9 @@ defmodule Indexer.TokenBalances do Map.merge(token_balance, %{value: nil, value_fetched_at: nil}) end - def format_result({_, token_balance}), do: token_balance + def format_task_results({:exit, :timeout}), do: {:error, :timeout} + def format_task_results({:ok, token_balance}), do: token_balance + + def ignore_request_with_timeouts({:error, :timeout}), do: false + def ignore_request_with_timeouts(_token_balance), do: true end diff --git a/apps/indexer/test/indexer/token_balances_test.exs b/apps/indexer/test/indexer/token_balances_test.exs index 3ec235b834..b5cc0f3e1f 100644 --- a/apps/indexer/test/indexer/token_balances_test.exs +++ b/apps/indexer/test/indexer/token_balances_test.exs @@ -60,6 +60,32 @@ defmodule Indexer.TokenBalancesTest do value_fetched_at: nil } = List.first(result) end + + test "ignores results that raised :timeout" do + address = insert(:address) + token = insert(:token, contract_address: build(:contract_address)) + address_hash_string = Hash.to_string(address.hash) + + token_balance_params = [ + %{ + token_contract_address_hash: Hash.to_string(token.contract_address_hash), + address_hash: address_hash_string, + block_number: 1_000 + }, + %{ + token_contract_address_hash: Hash.to_string(token.contract_address_hash), + address_hash: address_hash_string, + block_number: 1_001 + } + ] + + get_balance_from_blockchain() + get_balance_from_blockchain_with_timeout() + + {:ok, result} = TokenBalances.fetch_token_balances_from_blockchain(token_balance_params) + + assert length(result) == 1 + end end defp get_balance_from_blockchain() do @@ -79,6 +105,16 @@ defmodule Indexer.TokenBalancesTest do ) end + defp get_balance_from_blockchain_with_timeout() do + expect( + EthereumJSONRPC.Mox, + :json_rpc, + fn [%{id: _, method: _, params: [%{data: _, to: _}, _]}], _options -> + :timer.sleep(5001) + end + ) + end + defp get_balance_from_blockchain_with_error() do expect( EthereumJSONRPC.Mox, From ff6060be7b9dcd5753b1015b22333820d843ecdd Mon Sep 17 00:00:00 2001 From: katibest Date: Thu, 30 Aug 2018 15:34:57 -0400 Subject: [PATCH 14/22] Remove obsolete file --- .../address/_token_holdings.html.eex | 20 ------------------- 1 file changed, 20 deletions(-) delete mode 100644 apps/block_scout_web/lib/block_scout_web/templates/address/_token_holdings.html.eex diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address/_token_holdings.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address/_token_holdings.html.eex deleted file mode 100644 index 4e80ebcb81..0000000000 --- a/apps/block_scout_web/lib/block_scout_web/templates/address/_token_holdings.html.eex +++ /dev/null @@ -1,20 +0,0 @@ -
-
-

<%= gettext "Token Holdings" %>

- - -
- class="icon-links ml-3 mb-3" - > -

- - <%= gettext("Fetching tokens...") %> -

- - -
-
-
From cf8c97a338cf5fe275a22868fc78006bc71ed826 Mon Sep 17 00:00:00 2001 From: jimmay5469 Date: Tue, 28 Aug 2018 14:55:56 -0400 Subject: [PATCH 15/22] Move all USD conversions to JS --- .gitignore | 5 +- .../assets/__tests__/lib/currency.js | 4 ++ .../assets/__tests__/pages/chain.js | 2 - apps/block_scout_web/assets/js/app.js | 1 + .../block_scout_web/assets/js/lib/currency.js | 36 ++++++++++++++ .../assets/js/lib/market_history_chart.js | 23 ++++----- apps/block_scout_web/assets/js/pages/chain.js | 10 +--- apps/block_scout_web/assets/package-lock.json | 5 ++ apps/block_scout_web/assets/package.json | 1 + .../lib/block_scout_web/exchange_rates/usd.ex | 42 ---------------- .../templates/address/_balance_card.html.eex | 7 ++- .../templates/chain/show.html.eex | 6 +-- .../templates/layout/app.html.eex | 6 +++ .../templates/transaction/overview.html.eex | 6 ++- .../lib/block_scout_web/views/address_view.ex | 19 +------- .../lib/block_scout_web/views/chain_view.ex | 15 ------ .../block_scout_web/views/currency_helpers.ex | 38 --------------- .../block_scout_web/views/transaction_view.ex | 33 +++---------- .../exchange_rates/usd_test.exs | 48 ------------------- .../features/pages/chain_page.ex | 5 -- .../views/address_view_test.exs | 21 -------- .../block_scout_web/views/chain_view_test.exs | 19 -------- .../views/currency_helpers_test.exs | 9 ---- .../views/transaction_view_test.exs | 8 +--- 24 files changed, 83 insertions(+), 286 deletions(-) delete mode 100644 apps/block_scout_web/lib/block_scout_web/exchange_rates/usd.ex delete mode 100644 apps/block_scout_web/test/block_scout_web/exchange_rates/usd_test.exs diff --git a/.gitignore b/.gitignore index e8ce5e7d53..20aa9132d7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ # App artifacts /_build /apps/*/cover +/apps/*/logs /cover /db /deps @@ -15,8 +16,7 @@ erl_crash.dump npm-debug.log # Static artifacts -/apps/block_scout_web/assets/node_modules -/apps/explorer/node_modules +/apps/**/node_modules # Since we are building assets from assets/, # we ignore priv/static. You may want to comment @@ -30,7 +30,6 @@ npm-debug.log # secrets files as long as you replace their contents by environment # variables. /apps/*/config/*.secret.exs -/apps/*/cover/ # Wallaby screenshots screenshots/ diff --git a/apps/block_scout_web/assets/__tests__/lib/currency.js b/apps/block_scout_web/assets/__tests__/lib/currency.js index cf5ec22e50..3571bcb666 100644 --- a/apps/block_scout_web/assets/__tests__/lib/currency.js +++ b/apps/block_scout_web/assets/__tests__/lib/currency.js @@ -1,6 +1,10 @@ import { formatUsdValue } from '../../js/lib/currency' test('formatUsdValue', () => { + window.localized = { + 'less than': 'less than' + } + expect(formatUsdValue(0)).toEqual('$0.000000 USD') expect(formatUsdValue(0.0000001)).toEqual('< $0.000001 USD') expect(formatUsdValue(0.123456789)).toEqual('$0.123457 USD') expect(formatUsdValue(0.1234)).toEqual('$0.123400 USD') diff --git a/apps/block_scout_web/assets/__tests__/pages/chain.js b/apps/block_scout_web/assets/__tests__/pages/chain.js index 36866ddaf0..dca639315b 100644 --- a/apps/block_scout_web/assets/__tests__/pages/chain.js +++ b/apps/block_scout_web/assets/__tests__/pages/chain.js @@ -40,7 +40,6 @@ test('RECEIVED_NEW_EXCHANGE_RATE', () => { msg: { exchangeRate: { availableSupply: 1000000, - usdValue: 1.23, marketCapUsd: 1230000 }, marketHistoryData: { data: 'some stuff' } @@ -50,7 +49,6 @@ test('RECEIVED_NEW_EXCHANGE_RATE', () => { expect(output.availableSupply).toEqual(1000000) expect(output.marketHistoryData).toEqual({ data: 'some stuff' }) - expect(output.usdExchangeRate).toEqual(1.23) expect(output.usdMarketCap).toEqual(1230000) }) diff --git a/apps/block_scout_web/assets/js/app.js b/apps/block_scout_web/assets/js/app.js index b41db73a23..a01a3610b7 100644 --- a/apps/block_scout_web/assets/js/app.js +++ b/apps/block_scout_web/assets/js/app.js @@ -21,6 +21,7 @@ import 'bootstrap' import './locale' import './lib/clipboard_buttons' +import './lib/currency' import './lib/from_now' import './lib/loading_element' import './lib/market_history_chart' diff --git a/apps/block_scout_web/assets/js/lib/currency.js b/apps/block_scout_web/assets/js/lib/currency.js index a2c76d9fb4..0c4842be2f 100644 --- a/apps/block_scout_web/assets/js/lib/currency.js +++ b/apps/block_scout_web/assets/js/lib/currency.js @@ -1,8 +1,44 @@ +import $ from 'jquery' +import humps from 'humps' import numeral from 'numeral' +import { BigNumber } from 'bignumber.js' +import socket from '../socket' export function formatUsdValue (value) { + if (value === 0) return '$0.000000 USD' if (value < 0.000001) return '< $0.000001 USD' if (value < 1) return `$${numeral(value).format('0.000000')} USD` if (value < 100000) return `$${numeral(value).format('0,0.00')} USD` return `$${numeral(value).format('0,0')} USD` } + +function weiToEther (wei) { + return new BigNumber(wei).dividedBy('1000000000000000000').toNumber() +} + +function etherToUSD (ether, usdExchangeRate) { + return new BigNumber(ether).multipliedBy(usdExchangeRate).toNumber() +} + +function formatAllUsdValues () { + $('[data-usd-value]').each((i, el) => { + el.innerHTML = formatUsdValue(el.dataset.usdValue) + }) +} +formatAllUsdValues() + +function tryUpdateCalculatedUsdValues (el, usdExchangeRate = el.dataset.usdExchangeRate) { + if (!el.dataset.hasOwnProperty('weiValue')) return + const ether = weiToEther(el.dataset.weiValue) + const usd = etherToUSD(ether, usdExchangeRate) + const formattedUsd = formatUsdValue(usd) + if (formattedUsd !== el.innerHTML) el.innerHTML = formattedUsd +} +function updateAllCalculatedUsdValues (usdExchangeRate) { + $('[data-usd-exchange-rate]').each((i, el) => tryUpdateCalculatedUsdValues(el, usdExchangeRate)) +} +updateAllCalculatedUsdValues() + +export const exchangeRateChannel = socket.channel(`exchange_rate:new_rate`) +exchangeRateChannel.join() +exchangeRateChannel.on('new_rate', (msg) => updateAllCalculatedUsdValues(humps.camelizeKeys(msg).exchangeRate.usdValue)) diff --git a/apps/block_scout_web/assets/js/lib/market_history_chart.js b/apps/block_scout_web/assets/js/lib/market_history_chart.js index d130fc2c36..f7676775bf 100644 --- a/apps/block_scout_web/assets/js/lib/market_history_chart.js +++ b/apps/block_scout_web/assets/js/lib/market_history_chart.js @@ -1,6 +1,7 @@ import Chart from 'chart.js' import humps from 'humps' import numeral from 'numeral' +import { formatUsdValue } from '../lib/currency' import sassVariables from '../../css/app.scss' const config = { @@ -33,7 +34,7 @@ const config = { }, ticks: { beginAtZero: true, - callback: (value, index, values) => formatPrice(value), + callback: (value, index, values) => `$${numeral(value).format('0,0.00')} USD`, maxTicksLimit: 4 } }, { @@ -56,10 +57,10 @@ const config = { callbacks: { label: ({datasetIndex, yLabel}, {datasets}) => { const label = datasets[datasetIndex].label - if (datasets[datasetIndex].label === 'Price') { - return `${label}: ${formatPrice(yLabel)}` - } else if (datasets[datasetIndex].label === 'Market Cap') { - return `${label}: ${formatMarketCap(yLabel)}` + if (datasets[datasetIndex].yAxisID === 'price') { + return `${label}: ${formatUsdValue(yLabel)}` + } else if (datasets[datasetIndex].yAxisID === 'marketCap') { + return `${label}: ${formatUsdValue(yLabel)}` } else { return yLabel } @@ -69,14 +70,6 @@ const config = { } } -function formatPrice (price) { - return `$${numeral(price).format('0,0.00[0000000000000000]')}` -} - -function formatMarketCap (marketCap) { - return numeral(marketCap).format('($0,0a)') -} - function getPriceData (marketHistoryData) { return marketHistoryData.map(({ date, closingPrice }) => ({x: date, y: closingPrice})) } @@ -88,7 +81,7 @@ function getMarketCapData (marketHistoryData, availableSupply) { class MarketHistoryChart { constructor (el, availableSupply, marketHistoryData) { this.price = { - label: 'Price', + label: window.localized['Price'], yAxisID: 'price', data: getPriceData(marketHistoryData), fill: false, @@ -98,7 +91,7 @@ class MarketHistoryChart { lineTension: 0 } this.marketCap = { - label: 'Market Cap', + label: window.localized['Market Cap'], yAxisID: 'marketCap', data: getMarketCapData(marketHistoryData, availableSupply), fill: false, diff --git a/apps/block_scout_web/assets/js/pages/chain.js b/apps/block_scout_web/assets/js/pages/chain.js index eed9a1eb0d..43b2a2b464 100644 --- a/apps/block_scout_web/assets/js/pages/chain.js +++ b/apps/block_scout_web/assets/js/pages/chain.js @@ -4,7 +4,7 @@ import numeral from 'numeral' import router from '../router' import socket from '../socket' import { updateAllAges } from '../lib/from_now' -import { formatUsdValue } from '../lib/currency' +import { exchangeRateChannel, formatUsdValue } from '../lib/currency' import { batchChannel, initRedux } from '../utils' import { createMarketHistoryChart } from '../lib/market_history_chart' @@ -19,7 +19,6 @@ export const initialState = { newBlock: null, newTransactions: [], transactionCount: null, - usdExchangeRate: null, usdMarketCap: null } @@ -45,7 +44,6 @@ export function reducer (state = initialState, action) { return Object.assign({}, state, { availableSupply: action.msg.exchangeRate.availableSupply, marketHistoryData: action.msg.marketHistoryData, - usdExchangeRate: action.msg.exchangeRate.usdValue, usdMarketCap: action.msg.exchangeRate.marketCapUsd }) } @@ -85,8 +83,6 @@ router.when('', { exactPathMatch: true }).then(() => initRedux(reducer, { blocksChannel.join() blocksChannel.on('new_block', msg => store.dispatch({ type: 'RECEIVED_NEW_BLOCK', msg: humps.camelizeKeys(msg) })) - const exchangeRateChannel = socket.channel(`exchange_rate:new_rate`) - exchangeRateChannel.join() exchangeRateChannel.on('new_rate', (msg) => store.dispatch({ type: 'RECEIVED_NEW_EXCHANGE_RATE', msg: humps.camelizeKeys(msg) })) const transactionsChannel = socket.channel(`transactions:new_transaction`) @@ -103,7 +99,6 @@ router.when('', { exactPathMatch: true }).then(() => initRedux(reducer, { const $blockList = $('[data-selector="chain-block-list"]') const $channelBatching = $('[data-selector="channel-batching-message"]') const $channelBatchingCount = $('[data-selector="channel-batching-count"]') - const $exchangeRate = $('[data-selector="exchange-rate"]') const $marketCap = $('[data-selector="market-cap"]') const $transactionsList = $('[data-selector="transactions-list"]') const $transactionCount = $('[data-selector="transaction-count"]') @@ -114,9 +109,6 @@ router.when('', { exactPathMatch: true }).then(() => initRedux(reducer, { if (oldState.averageBlockTime !== state.averageBlockTime) { $averageBlockTime.empty().append(state.averageBlockTime) } - if (oldState.usdExchangeRate !== state.usdExchangeRate) { - $exchangeRate.empty().append(formatUsdValue(state.usdExchangeRate)) - } if (oldState.usdMarketCap !== state.usdMarketCap) { $marketCap.empty().append(formatUsdValue(state.usdMarketCap)) } diff --git a/apps/block_scout_web/assets/package-lock.json b/apps/block_scout_web/assets/package-lock.json index a44439f92e..caf95acc73 100644 --- a/apps/block_scout_web/assets/package-lock.json +++ b/apps/block_scout_web/assets/package-lock.json @@ -1448,6 +1448,11 @@ "integrity": "sha512-+hN/Zh2D08Mx65pZ/4g5bsmNiZUuChDiQfTUQ7qJr4/kuopCr88xZsAXv6mBoZEsUI4OuGHlX59qE94K2mMW8Q==", "dev": true }, + "bignumber.js": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-7.2.1.tgz", + "integrity": "sha512-S4XzBk5sMB+Rcb/LNcpzXr57VRTxgAvaAEDAl1AwRx27j00hT84O6OkteE7u8UB3NuaaygCRrEpqox4uDOrbdQ==" + }, "binary-extensions": { "version": "1.11.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.11.0.tgz", diff --git a/apps/block_scout_web/assets/package.json b/apps/block_scout_web/assets/package.json index 49f60bba6d..f363ff1246 100644 --- a/apps/block_scout_web/assets/package.json +++ b/apps/block_scout_web/assets/package.json @@ -20,6 +20,7 @@ }, "dependencies": { "@fortawesome/fontawesome-free": "^5.1.0-4", + "bignumber.js": "^7.2.1", "bootstrap": "^4.1.0", "chart.js": "^2.7.2", "clipboard": "^2.0.1", diff --git a/apps/block_scout_web/lib/block_scout_web/exchange_rates/usd.ex b/apps/block_scout_web/lib/block_scout_web/exchange_rates/usd.ex deleted file mode 100644 index 34b2a1f258..0000000000 --- a/apps/block_scout_web/lib/block_scout_web/exchange_rates/usd.ex +++ /dev/null @@ -1,42 +0,0 @@ -defmodule BlockScoutWeb.ExchangeRates.USD do - @moduledoc """ - Struct and associated conversion functions for USD currency - """ - - @typedoc """ - Represents USD currency - - * `:value` - value in USD - """ - @type t :: %__MODULE__{ - value: Decimal.t() | nil - } - - defstruct ~w(value)a - - alias Explorer.Chain.Wei - alias Explorer.ExchangeRates.Token - - def from(nil), do: null() - - def from(%Decimal{} = usd_decimal) do - %__MODULE__{value: usd_decimal} - end - - def from(nil, _), do: null() - - def from(_, nil), do: null() - - def from(%Wei{value: nil}, _), do: null() - - def from(_, %Token{usd_value: nil}), do: null() - - def from(%Wei{} = wei, %Token{usd_value: exchange_rate}) do - ether = Wei.to(wei, :ether) - %__MODULE__{value: Decimal.mult(ether, exchange_rate)} - end - - def null do - %__MODULE__{value: nil} - end -end diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address/_balance_card.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address/_balance_card.html.eex index 1c4c9bba4a..4ce0b0934b 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/address/_balance_card.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/address/_balance_card.html.eex @@ -4,9 +4,12 @@

<%= balance(@address) %>

- <%= formatted_usd(@address, @exchange_rate) %> + + -
> +
<%= gettext "Market Cap" %> - - <%= format_market_cap(@exchange_rate) %> +
diff --git a/apps/block_scout_web/lib/block_scout_web/templates/layout/app.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/layout/app.html.eex index 93d6b2ebd3..7cfc24a13e 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/layout/app.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/layout/app.html.eex @@ -18,6 +18,12 @@ <%= render BlockScoutWeb.LayoutView, "_footer.html", assigns %>
+ diff --git a/apps/block_scout_web/lib/block_scout_web/templates/transaction/overview.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/transaction/overview.html.eex index 1dab6247a5..cdc86b5a6b 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/transaction/overview.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/transaction/overview.html.eex @@ -55,7 +55,9 @@
<%= gettext "TX Fee" %>
-
<%= formatted_fee(@transaction, denomination: :ether) %> (<%= formatted_fee(@transaction, exchange_rate: @exchange_rate) %>)
+
+ <%= formatted_fee(@transaction, denomination: :ether) %> ( data-usd-exchange-rate=<%= @exchange_rate.usd_value %>>) +
@@ -82,7 +84,7 @@

<%= gettext "Ether" %> <%= gettext "Value" %>

<%= value(@transaction) %>

- <%= formatted_usd_value(@transaction, @exchange_rate) %> + data-usd-exchange-rate=<%= @exchange_rate.usd_value %>>
diff --git a/apps/block_scout_web/lib/block_scout_web/views/address_view.ex b/apps/block_scout_web/lib/block_scout_web/views/address_view.ex index fe5bf39828..c5365efc11 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/address_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/address_view.ex @@ -1,10 +1,7 @@ defmodule BlockScoutWeb.AddressView do use BlockScoutWeb, :view - alias Explorer.Chain.{Address, Hash, SmartContract, Wei} - - alias Explorer.ExchangeRates.Token - alias BlockScoutWeb.ExchangeRates.USD + alias Explorer.Chain.{Address, Hash, SmartContract} @dialyzer :no_match @@ -37,20 +34,6 @@ defmodule BlockScoutWeb.AddressView do def contract?(nil), do: true - def formatted_usd(%Address{fetched_coin_balance: nil}, _), do: nil - - def formatted_usd(%Address{fetched_coin_balance: balance}, %Token{} = exchange_rate) do - case Wei.cast(balance) do - {:ok, wei} -> - wei - |> USD.from(exchange_rate) - |> format_usd_value() - - _ -> - nil - end - end - def hash(%Address{hash: hash}) do to_string(hash) end diff --git a/apps/block_scout_web/lib/block_scout_web/views/chain_view.ex b/apps/block_scout_web/lib/block_scout_web/views/chain_view.ex index be4f383c3f..bb5814e87d 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/chain_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/chain_view.ex @@ -1,9 +1,6 @@ defmodule BlockScoutWeb.ChainView do use BlockScoutWeb, :view - alias Explorer.ExchangeRates.Token - alias BlockScoutWeb.ExchangeRates.USD - def encode_market_history_data(market_history_data) do market_history_data |> Enum.map(fn day -> Map.take(day, [:closing_price, :date]) end) @@ -13,16 +10,4 @@ defmodule BlockScoutWeb.ChainView do _ -> [] end end - - def format_exchange_rate(%Token{usd_value: usd_value}) do - usd_value - |> USD.from() - |> format_usd_value() - end - - def format_market_cap(%Token{market_cap_usd: market_cap}) do - market_cap - |> USD.from() - |> format_usd_value() - end end diff --git a/apps/block_scout_web/lib/block_scout_web/views/currency_helpers.ex b/apps/block_scout_web/lib/block_scout_web/views/currency_helpers.ex index 9005997a30..9d50e68ce4 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/currency_helpers.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/currency_helpers.ex @@ -3,46 +3,8 @@ defmodule BlockScoutWeb.CurrencyHelpers do Helper functions for interacting with `t:BlockScoutWeb.ExchangeRates.USD.t/0` values. """ - alias BlockScoutWeb.ExchangeRates.USD alias BlockScoutWeb.Cldr.Number - @doc """ - Formats a `BlockScoutWeb.ExchangeRates.USD` value into USD and applies a unit label. - - ## Examples - - iex> format_usd_value(%USD{value: Decimal.new(0.0000001)}) - "< $0.000001 USD" - - iex> format_usd_value(%USD{value: Decimal.new(0.123456789)}) - "$0.123457 USD" - - iex> format_usd_value(%USD{value: Decimal.new(0.1234)}) - "$0.123400 USD" - - iex> format_usd_value(%USD{value: Decimal.new(1.23456789)}) - "$1.23 USD" - - iex> format_usd_value(%USD{value: Decimal.new(1.2)}) - "$1.20 USD" - - iex> format_usd_value(%USD{value: Decimal.new(123456.789)}) - "$123,457 USD" - """ - @spec format_usd_value(USD.t() | nil) :: binary() | nil - def format_usd_value(nil), do: nil - - def format_usd_value(%USD{value: nil}), do: nil - - def format_usd_value(%USD{value: value}) do - cond do - Decimal.cmp(value, "0.000001") == :lt -> "< $0.000001 USD" - Decimal.cmp(value, 1) == :lt -> "$#{Number.to_string!(value, format: "0.000000")} USD" - Decimal.cmp(value, 100_000) == :lt -> "$#{Number.to_string!(value, format: "#,###.00")} USD" - true -> "$#{Number.to_string!(value, format: "#,###")} USD" - end - end - @doc """ Formats the given integer value to a currency format. diff --git a/apps/block_scout_web/lib/block_scout_web/views/transaction_view.ex b/apps/block_scout_web/lib/block_scout_web/views/transaction_view.ex index 5b851a372f..973529b626 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/transaction_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/transaction_view.ex @@ -4,9 +4,7 @@ defmodule BlockScoutWeb.TransactionView do alias Cldr.Number alias Explorer.Chain alias Explorer.Chain.{Address, InternalTransaction, Transaction, Wei} - alias Explorer.ExchangeRates.Token alias BlockScoutWeb.{AddressView, BlockView} - alias BlockScoutWeb.ExchangeRates.USD import BlockScoutWeb.Gettext @@ -30,12 +28,16 @@ defmodule BlockScoutWeb.TransactionView do def to_address_hash(%Transaction{to_address: %Address{hash: address_hash}}), do: address_hash + def fee(%Transaction{} = transaction) do + {_, value} = Chain.fee(transaction, :wei) + value + end + def formatted_fee(%Transaction{} = transaction, opts) do transaction |> Chain.fee(:wei) - |> fee_to_currency(opts) + |> fee_to_denomination(opts) |> case do - {_, nil} -> nil {:actual, value} -> value {:maximum, value} -> "<= " <> value end @@ -80,12 +82,6 @@ defmodule BlockScoutWeb.TransactionView do end end - def formatted_usd_value(%Transaction{value: nil}, _token), do: nil - - def formatted_usd_value(%Transaction{value: value}, token) do - format_usd_value(USD.from(value, token)) - end - defdelegate formatted_timestamp(block), to: BlockView def gas(%type{gas: gas}) when is_transaction_type(type) do @@ -137,23 +133,6 @@ defmodule BlockScoutWeb.TransactionView do format_wei_value(value, :ether, include_unit_label: include_label?) end - defp fee_to_currency(fee, options) do - case Keyword.fetch(options, :exchange_rate) do - {:ok, exchange_rate} -> fee_to_usd(fee, exchange_rate) - :error -> fee_to_denomination(fee, options) - end - end - - defp fee_to_usd({fee_type, fee}, %Token{} = exchange_rate) do - formatted = - fee - |> Wei.from(:wei) - |> USD.from(exchange_rate) - |> format_usd_value() - - {fee_type, formatted} - end - defp fee_to_denomination({fee_type, fee}, opts) do denomination = Keyword.get(opts, :denomination) include_label? = Keyword.get(opts, :include_label, true) diff --git a/apps/block_scout_web/test/block_scout_web/exchange_rates/usd_test.exs b/apps/block_scout_web/test/block_scout_web/exchange_rates/usd_test.exs deleted file mode 100644 index d63fd31e18..0000000000 --- a/apps/block_scout_web/test/block_scout_web/exchange_rates/usd_test.exs +++ /dev/null @@ -1,48 +0,0 @@ -defmodule BlockScoutWeb.ExchangeRates.USDTest do - use ExUnit.Case, async: true - - alias BlockScoutWeb.ExchangeRates.USD - alias Explorer.ExchangeRates.Token - alias Explorer.Chain.Wei - - describe "from/2" do - test "with nil wei returns null object" do - token = %Token{usd_value: Decimal.new(0.5)} - - assert USD.null() == USD.from(nil, token) - end - - test "with nil token returns nil" do - wei = %Wei{value: Decimal.new(10_000_000_000_000)} - - assert USD.null() == USD.from(wei, nil) - end - - test "without a wei value returns nil" do - wei = %Wei{value: nil} - token = %Token{usd_value: Decimal.new(0.5)} - - assert USD.null() == USD.from(wei, token) - end - - test "without an exchange rate returns nil" do - wei = %Wei{value: Decimal.new(10_000_000_000_000)} - token = %Token{usd_value: nil} - - assert USD.null() == USD.from(wei, token) - end - - test "returns formatted usd value" do - wei = %Wei{value: Decimal.new(10_000_000_000_000)} - token = %Token{usd_value: Decimal.new(0.5)} - - assert %USD{value: Decimal.new(0.000005)} == USD.from(wei, token) - end - - test "returns USD struct from decimal usd value" do - value = Decimal.new(0.000005) - - assert %USD{value: ^value} = USD.from(value) - end - end -end diff --git a/apps/block_scout_web/test/block_scout_web/features/pages/chain_page.ex b/apps/block_scout_web/test/block_scout_web/features/pages/chain_page.ex index 6f92efcd37..70d192881d 100644 --- a/apps/block_scout_web/test/block_scout_web/features/pages/chain_page.ex +++ b/apps/block_scout_web/test/block_scout_web/features/pages/chain_page.ex @@ -6,7 +6,6 @@ defmodule BlockScoutWeb.ChainPage do import Wallaby.Query, only: [css: 1, css: 2] alias Explorer.Chain.Transaction - alias BlockScoutWeb.ChainView def blocks(count: count) do css("[data-selector='chain-block']", count: count) @@ -16,10 +15,6 @@ defmodule BlockScoutWeb.ChainPage do css("[data-test='contract-creation'] [data-address-hash='#{hash}']") end - def exchange_rate(token) do - css("[data-selector='exchange-rate']", text: ChainView.format_exchange_rate(token)) - end - def non_loaded_transaction_count(count) do css("[data-selector='channel-batching-count']", text: count) end diff --git a/apps/block_scout_web/test/block_scout_web/views/address_view_test.exs b/apps/block_scout_web/test/block_scout_web/views/address_view_test.exs index 0f8c7a12c7..7ba5c71171 100644 --- a/apps/block_scout_web/test/block_scout_web/views/address_view_test.exs +++ b/apps/block_scout_web/test/block_scout_web/views/address_view_test.exs @@ -3,7 +3,6 @@ defmodule BlockScoutWeb.AddressViewTest do alias Explorer.Chain.Data alias BlockScoutWeb.AddressView - alias Explorer.ExchangeRates.Token describe "contract?/1" do test "with a smart contract" do @@ -18,26 +17,6 @@ defmodule BlockScoutWeb.AddressViewTest do end end - describe "formatted_usd/2" do - test "without a fetched_coin_balance returns nil" do - address = build(:address, fetched_coin_balance: nil) - token = %Token{usd_value: Decimal.new(0.5)} - assert nil == AddressView.formatted_usd(address, token) - end - - test "without a usd_value returns nil" do - address = build(:address) - token = %Token{usd_value: nil} - assert nil == AddressView.formatted_usd(address, token) - end - - test "returns formatted usd value" do - address = build(:address, fetched_coin_balance: 10_000_000_000_000) - token = %Token{usd_value: Decimal.new(0.5)} - assert "$0.000005 USD" == AddressView.formatted_usd(address, token) - end - end - describe "qr_code/1" do test "it returns an encoded value" do address = build(:address) diff --git a/apps/block_scout_web/test/block_scout_web/views/chain_view_test.exs b/apps/block_scout_web/test/block_scout_web/views/chain_view_test.exs index 89657ae12b..87508724c7 100644 --- a/apps/block_scout_web/test/block_scout_web/views/chain_view_test.exs +++ b/apps/block_scout_web/test/block_scout_web/views/chain_view_test.exs @@ -1,7 +1,6 @@ defmodule BlockScoutWeb.ChainViewTest do use BlockScoutWeb.ConnCase, async: true - alias Explorer.ExchangeRates.Token alias BlockScoutWeb.ChainView describe "encode_market_history_data/1" do @@ -17,22 +16,4 @@ defmodule BlockScoutWeb.ChainViewTest do ChainView.encode_market_history_data(market_history_data) end end - - describe "format_exchange_rate/1" do - test "returns a formatted usd value from a `Token`'s usd_value" do - token = %Token{usd_value: Decimal.new(5.45)} - - assert "$5.45 USD" == ChainView.format_exchange_rate(token) - assert nil == ChainView.format_exchange_rate(%Token{usd_value: nil}) - end - end - - describe "format_market_cap/1" do - test "returns a formatted usd value from a `Token`'s market_cap_usd" do - token = %Token{market_cap_usd: Decimal.new(5.4)} - - assert "$5.40 USD" == ChainView.format_market_cap(token) - assert nil == ChainView.format_market_cap(%Token{market_cap_usd: nil}) - end - end end diff --git a/apps/block_scout_web/test/block_scout_web/views/currency_helpers_test.exs b/apps/block_scout_web/test/block_scout_web/views/currency_helpers_test.exs index b47e5deeea..fc6e8496de 100644 --- a/apps/block_scout_web/test/block_scout_web/views/currency_helpers_test.exs +++ b/apps/block_scout_web/test/block_scout_web/views/currency_helpers_test.exs @@ -2,18 +2,9 @@ defmodule BlockScoutWeb.CurrencyHelpersTest do use ExUnit.Case alias BlockScoutWeb.CurrencyHelpers - alias BlockScoutWeb.ExchangeRates.USD doctest BlockScoutWeb.CurrencyHelpers, import: true - test "with nil it returns nil" do - assert nil == CurrencyHelpers.format_usd_value(nil) - end - - test "with USD.null() it returns nil" do - assert nil == CurrencyHelpers.format_usd_value(USD.null()) - end - describe "format_according_to_decimals/1" do test "formats the amount as value considering the given decimals" do amount = Decimal.new(205_000_000_000_000) diff --git a/apps/block_scout_web/test/block_scout_web/views/transaction_view_test.exs b/apps/block_scout_web/test/block_scout_web/views/transaction_view_test.exs index f05f163744..b242187d9c 100644 --- a/apps/block_scout_web/test/block_scout_web/views/transaction_view_test.exs +++ b/apps/block_scout_web/test/block_scout_web/views/transaction_view_test.exs @@ -2,7 +2,6 @@ defmodule BlockScoutWeb.TransactionViewTest do use BlockScoutWeb.ConnCase, async: true alias Explorer.Chain.Wei - alias Explorer.ExchangeRates.Token alias Explorer.Repo alias BlockScoutWeb.TransactionView @@ -18,21 +17,16 @@ defmodule BlockScoutWeb.TransactionViewTest do gas_used: nil ) - token = %Token{usd_value: Decimal.new(0.50)} - expected_value = "<= 0.009 POA" assert expected_value == TransactionView.formatted_fee(transaction, denomination: :ether) - assert "<= $0.004500 USD" == TransactionView.formatted_fee(transaction, exchange_rate: token) end - test "with fee and exchange_rate" do + test "with fee" do {:ok, gas_price} = Wei.cast(3_000_000_000) transaction = build(:transaction, gas_price: gas_price, gas_used: Decimal.new(1_034_234.0)) - token = %Token{usd_value: Decimal.new(0.50)} expected_value = "0.003102702 POA" assert expected_value == TransactionView.formatted_fee(transaction, denomination: :ether) - assert "$0.001551 USD" == TransactionView.formatted_fee(transaction, exchange_rate: token) end test "with fee but no available exchange_rate" do From 9dcbc50839ac2247b131e1f0d7f99918cccc3d0b Mon Sep 17 00:00:00 2001 From: jimmay5469 Date: Thu, 30 Aug 2018 09:59:35 -0400 Subject: [PATCH 16/22] Copy improvements --- apps/block_scout_web/assets/__tests__/lib/currency.js | 4 ++-- apps/block_scout_web/assets/js/lib/currency.js | 2 +- apps/block_scout_web/assets/js/lib/market_history_chart.js | 2 +- .../lib/block_scout_web/templates/address/overview.html.eex | 2 +- .../lib/block_scout_web/templates/layout/app.html.eex | 1 + .../lib/block_scout_web/views/transaction_view.ex | 2 +- .../test/block_scout_web/views/transaction_view_test.exs | 2 +- 7 files changed, 8 insertions(+), 7 deletions(-) diff --git a/apps/block_scout_web/assets/__tests__/lib/currency.js b/apps/block_scout_web/assets/__tests__/lib/currency.js index 3571bcb666..3c79433416 100644 --- a/apps/block_scout_web/assets/__tests__/lib/currency.js +++ b/apps/block_scout_web/assets/__tests__/lib/currency.js @@ -2,10 +2,10 @@ import { formatUsdValue } from '../../js/lib/currency' test('formatUsdValue', () => { window.localized = { - 'less than': 'less than' + 'Less than': 'Less than' } expect(formatUsdValue(0)).toEqual('$0.000000 USD') - expect(formatUsdValue(0.0000001)).toEqual('< $0.000001 USD') + expect(formatUsdValue(0.0000001)).toEqual('Less than $0.000001 USD') expect(formatUsdValue(0.123456789)).toEqual('$0.123457 USD') expect(formatUsdValue(0.1234)).toEqual('$0.123400 USD') expect(formatUsdValue(1.23456789)).toEqual('$1.23 USD') diff --git a/apps/block_scout_web/assets/js/lib/currency.js b/apps/block_scout_web/assets/js/lib/currency.js index 0c4842be2f..e8a4a4b065 100644 --- a/apps/block_scout_web/assets/js/lib/currency.js +++ b/apps/block_scout_web/assets/js/lib/currency.js @@ -6,7 +6,7 @@ import socket from '../socket' export function formatUsdValue (value) { if (value === 0) return '$0.000000 USD' - if (value < 0.000001) return '< $0.000001 USD' + if (value < 0.000001) return `${window.localized['Less than']} $0.000001 USD` if (value < 1) return `$${numeral(value).format('0.000000')} USD` if (value < 100000) return `$${numeral(value).format('0,0.00')} USD` return `$${numeral(value).format('0,0')} USD` diff --git a/apps/block_scout_web/assets/js/lib/market_history_chart.js b/apps/block_scout_web/assets/js/lib/market_history_chart.js index f7676775bf..7cc6527fcf 100644 --- a/apps/block_scout_web/assets/js/lib/market_history_chart.js +++ b/apps/block_scout_web/assets/js/lib/market_history_chart.js @@ -34,7 +34,7 @@ const config = { }, ticks: { beginAtZero: true, - callback: (value, index, values) => `$${numeral(value).format('0,0.00')} USD`, + callback: (value, index, values) => `$${numeral(value).format('0,0.00')}`, maxTicksLimit: 4 } }, { diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address/overview.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address/overview.html.eex index d2e28ece6e..3ff2a32532 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/address/overview.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/address/overview.html.eex @@ -51,7 +51,7 @@ -
+
<%= render BlockScoutWeb.AddressView, "_balance_card.html", conn: @conn, address: @address, exchange_rate: @exchange_rate %>
diff --git a/apps/block_scout_web/lib/block_scout_web/templates/layout/app.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/layout/app.html.eex index 7cfc24a13e..08c7f9db9c 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/layout/app.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/layout/app.html.eex @@ -20,6 +20,7 @@