From 92a241f4ac908c5f2fdaf687f8edad1f5a545b0d Mon Sep 17 00:00:00 2001 From: Amanda Sposito Date: Thu, 20 Sep 2018 16:31:26 -0300 Subject: [PATCH 1/4] Add 'Token Inventory' query --- apps/explorer/lib/explorer/chain.ex | 11 +++ .../lib/explorer/chain/token_transfer.ex | 33 ++++++++- .../explorer/chain/token_transfer_test.exs | 71 ++++++++++++++++++- apps/explorer/test/explorer/chain_test.exs | 43 +++++++++++ 4 files changed, 156 insertions(+), 2 deletions(-) diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index 88b4db01ed..71ed5ea09e 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -1868,4 +1868,15 @@ defmodule Explorer.Chain do |> TokenBalance.token_holders_from_token_hash() |> Repo.aggregate(:count, :address_hash) end + + @spec address_to_unique_tokens(Hash.Address.t(), [paging_options]) :: [TokenTransfer.t()] + def address_to_unique_tokens(contract_address_hash, options \\ []) do + paging_options = Keyword.get(options, :paging_options, @default_paging_options) + + contract_address_hash + |> TokenTransfer.address_to_unique_tokens() + |> TokenTransfer.page_token_transfer(paging_options) + |> limit(^paging_options.page_size) + |> Repo.all() + end end diff --git a/apps/explorer/lib/explorer/chain/token_transfer.ex b/apps/explorer/lib/explorer/chain/token_transfer.ex index 6ceed7d6a7..50d5b10a1b 100644 --- a/apps/explorer/lib/explorer/chain/token_transfer.ex +++ b/apps/explorer/lib/explorer/chain/token_transfer.ex @@ -26,7 +26,7 @@ defmodule Explorer.Chain.TokenTransfer do import Ecto.{Changeset, Query} - alias Explorer.Chain.{Address, Block, Hash, Transaction, TokenTransfer} + alias Explorer.Chain.{Address, Block, Hash, Transaction, Token, TokenTransfer} alias Explorer.{PagingOptions, Repo} @default_paging_options %PagingOptions{page_size: 50} @@ -141,6 +141,14 @@ defmodule Explorer.Chain.TokenTransfer do def page_token_transfer(query, %PagingOptions{key: nil}), do: query + def page_token_transfer(query, %PagingOptions{key: {token_id}}) do + where( + query, + [token_transfer], + token_transfer.token_id > ^token_id + ) + end + def page_token_transfer(query, %PagingOptions{key: inserted_at}) do where( query, @@ -166,4 +174,27 @@ defmodule Explorer.Chain.TokenTransfer do |> join(:left, [transaction], tt in assoc(transaction, :token_transfers)) |> where([_transaction, tt], tt.to_address_hash == ^address_hash or tt.from_address_hash == ^address_hash) end + + @doc """ + A token ERC-721 is considered unique because it corresponds to the possession + of a specific asset. + + To find out its current owner, it is necessary to look at the token last + transfer. + """ + @spec address_to_unique_tokens(Hash.Address.t()) :: %Ecto.Query{} + def address_to_unique_tokens(contract_address_hash) do + from( + tt in TokenTransfer, + join: t in Token, + on: tt.token_contract_address_hash == t.contract_address_hash, + join: ts in Transaction, + on: tt.transaction_hash == ts.hash, + where: t.contract_address_hash == ^contract_address_hash and t.type == "ERC-721", + order_by: [desc: ts.block_number], + distinct: tt.token_id, + preload: [:to_address], + select: tt + ) + end end diff --git a/apps/explorer/test/explorer/chain/token_transfer_test.exs b/apps/explorer/test/explorer/chain/token_transfer_test.exs index a0ec6cf378..8473dbeb51 100644 --- a/apps/explorer/test/explorer/chain/token_transfer_test.exs +++ b/apps/explorer/test/explorer/chain/token_transfer_test.exs @@ -3,7 +3,7 @@ defmodule Explorer.Chain.TokenTransferTest do import Explorer.Factory - alias Explorer.PagingOptions + alias Explorer.{PagingOptions, Repo} alias Explorer.Chain.TokenTransfer doctest Explorer.Chain.TokenTransfer @@ -142,4 +142,73 @@ defmodule Explorer.Chain.TokenTransferTest do assert TokenTransfer.count_token_transfers_from_token_hash(token_contract_address.hash) == 2 end end + + describe "address_to_unique_tokens/2" do + test "returns list of unique tokens for a token contract" do + token_contract_address = insert(:contract_address) + token = insert(:token, contract_address: token_contract_address, type: "ERC-721") + + transaction = + :transaction + |> insert() + |> with_block(insert(:block, number: 1)) + + insert( + :token_transfer, + to_address: build(:address), + transaction: transaction, + token_contract_address: token_contract_address, + token: token, + token_id: 42 + ) + + another_transaction = + :transaction + |> insert() + |> with_block(insert(:block, number: 2)) + + last_owner = + insert( + :token_transfer, + to_address: build(:address), + transaction: another_transaction, + token_contract_address: token_contract_address, + token: token, + token_id: 42 + ) + + results = + token_contract_address.hash + |> TokenTransfer.address_to_unique_tokens() + |> Repo.all() + + assert Enum.map(results, & &1.token_id) == [last_owner.token_id] + assert Enum.map(results, & &1.to_address_hash) == [last_owner.to_address_hash] + end + + test "won't return tokens that aren't uniques" do + token_contract_address = insert(:contract_address) + token = insert(:token, contract_address: token_contract_address, type: "ERC-20") + + transaction = + :transaction + |> insert() + |> with_block(insert(:block, number: 1)) + + insert( + :token_transfer, + to_address: build(:address), + transaction: transaction, + token_contract_address: token_contract_address, + token: token + ) + + results = + token_contract_address.hash + |> TokenTransfer.address_to_unique_tokens() + |> Repo.all() + + assert results == [] + end + end end diff --git a/apps/explorer/test/explorer/chain_test.exs b/apps/explorer/test/explorer/chain_test.exs index cc196e8af2..5e834724ef 100644 --- a/apps/explorer/test/explorer/chain_test.exs +++ b/apps/explorer/test/explorer/chain_test.exs @@ -2819,4 +2819,47 @@ defmodule Explorer.ChainTest do assert result == [transaction.hash] end end + + describe "address_to_unique_tokens/2" do + test "unique tokens can be paginated through token_id" do + token_contract_address = insert(:contract_address) + token = insert(:token, contract_address: token_contract_address, type: "ERC-721") + + transaction = + :transaction + |> insert() + |> with_block(insert(:block, number: 1)) + + first_page = + insert( + :token_transfer, + to_address: build(:address), + transaction: transaction, + token_contract_address: token_contract_address, + token: token, + token_id: 11 + ) + + second_page = + insert( + :token_transfer, + to_address: build(:address), + transaction: transaction, + token_contract_address: token_contract_address, + token: token, + token_id: 29 + ) + + paging_options = %PagingOptions{key: {first_page.token_id}, page_size: 1} + + unique_tokens_ids_paginated = + Chain.address_to_unique_tokens( + token_contract_address.hash, + paging_options: paging_options + ) + |> Enum.map(& &1.token_id) + + assert unique_tokens_ids_paginated == [second_page.token_id] + end + end end From d3c47e2f86940b652e3a9d9dc9c6073b5d59bcd1 Mon Sep 17 00:00:00 2001 From: Amanda Sposito Date: Thu, 20 Sep 2018 16:33:15 -0300 Subject: [PATCH 2/4] Add 'Token Inventory' page and tab control * The pagination at the 'Tokens.InventoryController' doesn't follow the 'BlockScoutWeb.Chain', because we have two different 'TokenTransfers' listings that are ordered in different ways. The 'Transfers' page is ordered from the most recent to the older and the 'Inventory' page is ordered according to the 'token_id'. --- .../assets/css/components/_tile.scss | 27 ++++++++++ .../lib/block_scout_web/chain.ex | 4 ++ .../tokens/inventory_controller.ex | 52 +++++++++++++++++++ .../lib/block_scout_web/router.ex | 7 +++ .../tokens/inventory/_token.html.eex | 28 ++++++++++ .../templates/tokens/inventory/index.html.eex | 42 +++++++++++++++ .../templates/tokens/overview/_tabs.html.eex | 18 +++++++ .../views/tokens/inventory_view.ex | 5 ++ .../views/tokens/overview_view.ex | 6 ++- .../views/tokens/overview_view_test.exs | 14 +++++ 10 files changed, 202 insertions(+), 1 deletion(-) create mode 100644 apps/block_scout_web/lib/block_scout_web/controllers/tokens/inventory_controller.ex create mode 100644 apps/block_scout_web/lib/block_scout_web/templates/tokens/inventory/_token.html.eex create mode 100644 apps/block_scout_web/lib/block_scout_web/templates/tokens/inventory/index.html.eex create mode 100644 apps/block_scout_web/lib/block_scout_web/views/tokens/inventory_view.ex diff --git a/apps/block_scout_web/assets/css/components/_tile.scss b/apps/block_scout_web/assets/css/components/_tile.scss index 9f07317c08..d7a83abdf0 100644 --- a/apps/block_scout_web/assets/css/components/_tile.scss +++ b/apps/block_scout_web/assets/css/components/_tile.scss @@ -63,6 +63,28 @@ } } + &-unique-token { + border-left: 4px solid $orange; + padding: 35px 0; + + .tile-label { + color: $orange; + } + } + + &-unique-token-image{ + border-left: 4px solid $orange; + padding: 0; + + .tile-label { + color: $orange; + } + + & .tile-content { + padding: 45px 0; + } + } + &-internal-transaction { border-left: 4px solid $teal; @@ -150,3 +172,8 @@ margin: 0; } } + +.tile-image { + max-width: 140px; + max-height: 140px; +} diff --git a/apps/block_scout_web/lib/block_scout_web/chain.ex b/apps/block_scout_web/lib/block_scout_web/chain.ex index c41229acec..d65d11de7d 100644 --- a/apps/block_scout_web/lib/block_scout_web/chain.ex +++ b/apps/block_scout_web/lib/block_scout_web/chain.ex @@ -29,6 +29,10 @@ defmodule BlockScoutWeb.Chain do @page_size 50 @default_paging_options %PagingOptions{page_size: @page_size + 1} + def default_paging_options do + @default_paging_options + end + def current_filter(%{paging_options: paging_options} = params) do params |> Map.get("filter") diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/tokens/inventory_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/tokens/inventory_controller.ex new file mode 100644 index 0000000000..a6a19a8523 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/controllers/tokens/inventory_controller.ex @@ -0,0 +1,52 @@ +defmodule BlockScoutWeb.Tokens.InventoryController do + use BlockScoutWeb, :controller + + alias Explorer.Chain + alias Explorer.Chain.TokenTransfer + + import BlockScoutWeb.Chain, only: [split_list_by_page: 1, default_paging_options: 0] + + def index(conn, %{"token_id" => address_hash_string} = params) do + with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string), + {:ok, token} <- Chain.token_from_address_hash(address_hash) do + unique_tokens = + Chain.address_to_unique_tokens( + token.contract_address_hash, + unique_tokens_paging_options(params) + ) + + {unique_tokens_paginated, next_page} = split_list_by_page(unique_tokens) + + render( + conn, + "index.html", + token: token, + unique_tokens: unique_tokens_paginated, + total_token_transfers: Chain.count_token_transfers_from_token_hash(address_hash), + total_token_holders: Chain.count_token_holders_from_token_hash(address_hash), + next_page_params: unique_tokens_next_page(next_page, unique_tokens_paginated, params) + ) + else + :error -> + not_found(conn) + + {:error, :not_found} -> + not_found(conn) + end + end + + defp unique_tokens_paging_options(%{"unique_token" => token_id}), + do: [paging_options: %{default_paging_options() | key: {token_id}}] + + defp unique_tokens_paging_options(_params), do: [paging_options: default_paging_options()] + + defp unique_tokens_next_page([], _list, _params), do: nil + + defp unique_tokens_next_page(_, list, params) do + Map.merge(params, paging_params(List.last(list))) + end + + defp paging_params(%TokenTransfer{token_id: token_id}) do + %{"unique_token" => Decimal.to_integer(token_id)} + end +end 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 93e6532cd3..cc18314f4b 100644 --- a/apps/block_scout_web/lib/block_scout_web/router.ex +++ b/apps/block_scout_web/lib/block_scout_web/router.ex @@ -135,6 +135,13 @@ defmodule BlockScoutWeb.Router do only: [:index], as: :holder ) + + resources( + "/inventory", + Tokens.InventoryController, + only: [:index], + as: :inventory + ) end resources( diff --git a/apps/block_scout_web/lib/block_scout_web/templates/tokens/inventory/_token.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/tokens/inventory/_token.html.eex new file mode 100644 index 0000000000..88c36e5158 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/tokens/inventory/_token.html.eex @@ -0,0 +1,28 @@ + +
+
+
+ + <%= gettext "Unique Token" %> +
+ +
+ + <%= gettext "Token ID" %>: + + <%= @token_transfer.token_id %> + + + + + <%= gettext "Owner Address" %>: + + <%= render BlockScoutWeb.AddressView, + "_link.html", + address: @token_transfer.to_address, + contract: false %> + + +
+
+
diff --git a/apps/block_scout_web/lib/block_scout_web/templates/tokens/inventory/index.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/tokens/inventory/index.html.eex new file mode 100644 index 0000000000..8b2fdcb5b7 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/tokens/inventory/index.html.eex @@ -0,0 +1,42 @@ +
+ <%= render( + OverviewView, + "_details.html", + token: @token, + total_token_transfers: @total_token_transfers, + total_token_holders: @total_token_holders, + conn: @conn + ) %> + +
+
+
+ <%= render OverviewView, "_tabs.html", assigns %> +
+ +
+

<%= gettext "Inventory" %>

+ + <%= if Enum.any?(@unique_tokens) do %> + <%= for token_transfer <- @unique_tokens do %> + <%= render "_token.html", token_transfer: token_transfer %> + <% end %> + <% else %> +
+ + <%= gettext "There are no tokens." %> + +
+ <% end %> + + <%= if @next_page_params do %> + <%= link( + gettext("Next Page"), + class: "button button-secondary button-small float-right mt-4", + to: token_inventory_path(@conn, :index, @token.contract_address_hash, @next_page_params) + ) %> + <% end %> +
+
+
+
diff --git a/apps/block_scout_web/lib/block_scout_web/templates/tokens/overview/_tabs.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/tokens/overview/_tabs.html.eex index b5dc0dc9b8..b2ca5f3d18 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/tokens/overview/_tabs.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/tokens/overview/_tabs.html.eex @@ -25,6 +25,16 @@ to: token_holder_path(@conn, :index, @token.contract_address_hash) ) %> + + <%= if display_inventory?(@token) do %> + + <% end %> @@ -56,6 +66,14 @@ class: "dropdown-item #{tab_status("token_holders", @conn.request_path)}", to: token_holder_path(@conn, :index, @token.contract_address_hash) ) %> + + <%= if display_inventory?(@token) do %> + <%= link( + gettext("Inventory"), + class: "dropdown-item #{tab_status("inventory", @conn.request_path)}", + to: token_inventory_path(@conn, :index, @token.contract_address_hash) + ) %> + <% end %> diff --git a/apps/block_scout_web/lib/block_scout_web/views/tokens/inventory_view.ex b/apps/block_scout_web/lib/block_scout_web/views/tokens/inventory_view.ex new file mode 100644 index 0000000000..6c3f3a6845 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/views/tokens/inventory_view.ex @@ -0,0 +1,5 @@ +defmodule BlockScoutWeb.Tokens.InventoryView do + use BlockScoutWeb, :view + + alias BlockScoutWeb.Tokens.OverviewView +end diff --git a/apps/block_scout_web/lib/block_scout_web/views/tokens/overview_view.ex b/apps/block_scout_web/lib/block_scout_web/views/tokens/overview_view.ex index 80bb51a04e..f36bca8b80 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/tokens/overview_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/tokens/overview_view.ex @@ -4,7 +4,7 @@ defmodule BlockScoutWeb.Tokens.OverviewView do alias Explorer.Chain.Token alias BlockScoutWeb.Tokens.TransferView - @tabs ["token_transfers", "token_holders", "read_contract"] + @tabs ["token_transfers", "token_holders", "read_contract", "inventory"] def decimals?(%Token{decimals: nil}), do: false def decimals?(%Token{decimals: _}), do: true @@ -34,4 +34,8 @@ defmodule BlockScoutWeb.Tokens.OverviewView do defp tab_name(["token_transfers"]), do: gettext("Token Transfers") defp tab_name(["token_holders"]), do: gettext("Token Holders") defp tab_name(["read_contract"]), do: gettext("Read Contract") + defp tab_name(["inventory"]), do: gettext("Inventory") + + def display_inventory?(%Token{type: "ERC-721"}), do: true + def display_inventory?(_), do: false end diff --git a/apps/block_scout_web/test/block_scout_web/views/tokens/overview_view_test.exs b/apps/block_scout_web/test/block_scout_web/views/tokens/overview_view_test.exs index 5f18eff56a..22f1e81299 100644 --- a/apps/block_scout_web/test/block_scout_web/views/tokens/overview_view_test.exs +++ b/apps/block_scout_web/test/block_scout_web/views/tokens/overview_view_test.exs @@ -64,4 +64,18 @@ defmodule BlockScoutWeb.Tokens.OverviewViewTest do assert OverviewView.current_tab_name(read_contract_path) == "Read Contract" end end + + describe "display_inventory?/1" do + test "returns true when token is unique" do + token = insert(:token, type: "ERC-721") + + assert OverviewView.display_inventory?(token) == true + end + + test "returns false when token is not unique" do + token = insert(:token, type: "ERC-20") + + assert OverviewView.display_inventory?(token) == false + end + end end From 78ba601dd49cd1847fb5a2ca7f07bbd9516ac409 Mon Sep 17 00:00:00 2001 From: Amanda Sposito Date: Tue, 25 Sep 2018 16:25:53 -0300 Subject: [PATCH 3/4] Move tab controlling functions to Tokens/OverviewView --- .../templates/tokens/overview/_tabs.html.eex | 32 +++++----- .../views/tokens/overview_view.ex | 11 +++- .../views/tokens/transfer_view.ex | 9 --- .../views/tokens/overview_view_test.exs | 58 +++++++++++++++++++ .../views/tokens/transfer_view_test.exs | 58 ------------------- 5 files changed, 84 insertions(+), 84 deletions(-) diff --git a/apps/block_scout_web/lib/block_scout_web/templates/tokens/overview/_tabs.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/tokens/overview/_tabs.html.eex index b2ca5f3d18..ac7d9fce2a 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/tokens/overview/_tabs.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/tokens/overview/_tabs.html.eex @@ -8,15 +8,6 @@ ) %> - <%= if TransferView.smart_contract_with_read_only_functions?(@token) do %> - - <% end %> - <% end %> + + <%= if smart_contract_with_read_only_functions?(@token) do %> + + <% end %> @@ -55,12 +55,7 @@ class: "dropdown-item #{tab_status("token_transfers", @conn.request_path)}", to: token_path(@conn, :show, @token.contract_address_hash) ) %> - <%= if TransferView.smart_contract_with_read_only_functions?(@token) do %> - <%= link( - gettext("Read Contract"), - to: "#", - class: "dropdown-item #{tab_status("read_contract", @conn.request_path)}")%> - <% end %> + <%= link( gettext("Token Holders"), class: "dropdown-item #{tab_status("token_holders", @conn.request_path)}", @@ -74,6 +69,13 @@ to: token_inventory_path(@conn, :index, @token.contract_address_hash) ) %> <% end %> + + <%= if smart_contract_with_read_only_functions?(@token) do %> + <%= link( + gettext("Read Contract"), + to: "#", + class: "dropdown-item #{tab_status("read_contract", @conn.request_path)}")%> + <% end %> diff --git a/apps/block_scout_web/lib/block_scout_web/views/tokens/overview_view.ex b/apps/block_scout_web/lib/block_scout_web/views/tokens/overview_view.ex index f36bca8b80..f8fdfb867b 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/tokens/overview_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/tokens/overview_view.ex @@ -1,8 +1,7 @@ defmodule BlockScoutWeb.Tokens.OverviewView do use BlockScoutWeb, :view - alias Explorer.Chain.Token - alias BlockScoutWeb.Tokens.TransferView + alias Explorer.Chain.{Address, SmartContract, Token} @tabs ["token_transfers", "token_holders", "read_contract", "inventory"] @@ -38,4 +37,12 @@ defmodule BlockScoutWeb.Tokens.OverviewView do def display_inventory?(%Token{type: "ERC-721"}), do: true def display_inventory?(_), do: false + + def smart_contract_with_read_only_functions?( + %Token{contract_address: %Address{smart_contract: %SmartContract{}}} = token + ) do + Enum.any?(token.contract_address.smart_contract.abi, & &1["constant"]) + end + + def smart_contract_with_read_only_functions?(%Token{contract_address: %Address{smart_contract: nil}}), do: false end diff --git a/apps/block_scout_web/lib/block_scout_web/views/tokens/transfer_view.ex b/apps/block_scout_web/lib/block_scout_web/views/tokens/transfer_view.ex index 09944bcd03..cf002200e4 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/tokens/transfer_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/tokens/transfer_view.ex @@ -1,14 +1,5 @@ defmodule BlockScoutWeb.Tokens.TransferView do use BlockScoutWeb, :view - alias Explorer.Chain.{Address, SmartContract, Token} alias BlockScoutWeb.Tokens.OverviewView - - def smart_contract_with_read_only_functions?( - %Token{contract_address: %Address{smart_contract: %SmartContract{}}} = token - ) do - Enum.any?(token.contract_address.smart_contract.abi, & &1["constant"]) - end - - def smart_contract_with_read_only_functions?(%Token{contract_address: %Address{smart_contract: nil}}), do: false end diff --git a/apps/block_scout_web/test/block_scout_web/views/tokens/overview_view_test.exs b/apps/block_scout_web/test/block_scout_web/views/tokens/overview_view_test.exs index 22f1e81299..4ac295a710 100644 --- a/apps/block_scout_web/test/block_scout_web/views/tokens/overview_view_test.exs +++ b/apps/block_scout_web/test/block_scout_web/views/tokens/overview_view_test.exs @@ -78,4 +78,62 @@ defmodule BlockScoutWeb.Tokens.OverviewViewTest do assert OverviewView.display_inventory?(token) == false end end + + describe "smart_contract_with_read_only_functions?/1" do + test "returns true when abi has read only functions" do + smart_contract = + insert( + :smart_contract, + abi: [ + %{ + "constant" => true, + "inputs" => [], + "name" => "get", + "outputs" => [%{"name" => "", "type" => "uint256"}], + "payable" => false, + "stateMutability" => "view", + "type" => "function" + } + ] + ) + + address = insert(:address, smart_contract: smart_contract) + + token = insert(:token, contract_address: address) + + assert OverviewView.smart_contract_with_read_only_functions?(token) + end + + test "returns false when there is no read only functions" do + smart_contract = + insert( + :smart_contract, + abi: [ + %{ + "constant" => false, + "inputs" => [%{"name" => "x", "type" => "uint256"}], + "name" => "set", + "outputs" => [], + "payable" => false, + "stateMutability" => "nonpayable", + "type" => "function" + } + ] + ) + + address = insert(:address, smart_contract: smart_contract) + + token = insert(:token, contract_address: address) + + refute OverviewView.smart_contract_with_read_only_functions?(token) + end + + test "returns false when smart contract is not verified" do + address = insert(:address, smart_contract: nil) + + token = insert(:token, contract_address: address) + + refute OverviewView.smart_contract_with_read_only_functions?(token) + end + end end diff --git a/apps/block_scout_web/test/block_scout_web/views/tokens/transfer_view_test.exs b/apps/block_scout_web/test/block_scout_web/views/tokens/transfer_view_test.exs index a8a1c726ce..147c34c861 100644 --- a/apps/block_scout_web/test/block_scout_web/views/tokens/transfer_view_test.exs +++ b/apps/block_scout_web/test/block_scout_web/views/tokens/transfer_view_test.exs @@ -2,62 +2,4 @@ defmodule BlockScoutWeb.Tokens.TransferViewTest do use BlockScoutWeb.ConnCase, async: true alias BlockScoutWeb.Tokens.TransferView - - describe "smart_contract_with_read_only_functions?/1" do - test "returns true when abi has read only functions" do - smart_contract = - insert( - :smart_contract, - abi: [ - %{ - "constant" => true, - "inputs" => [], - "name" => "get", - "outputs" => [%{"name" => "", "type" => "uint256"}], - "payable" => false, - "stateMutability" => "view", - "type" => "function" - } - ] - ) - - address = insert(:address, smart_contract: smart_contract) - - token = insert(:token, contract_address: address) - - assert TransferView.smart_contract_with_read_only_functions?(token) - end - - test "returns false when there is no read only functions" do - smart_contract = - insert( - :smart_contract, - abi: [ - %{ - "constant" => false, - "inputs" => [%{"name" => "x", "type" => "uint256"}], - "name" => "set", - "outputs" => [], - "payable" => false, - "stateMutability" => "nonpayable", - "type" => "function" - } - ] - ) - - address = insert(:address, smart_contract: smart_contract) - - token = insert(:token, contract_address: address) - - refute TransferView.smart_contract_with_read_only_functions?(token) - end - - test "returns false when smart contract is not verified" do - address = insert(:address, smart_contract: nil) - - token = insert(:token, contract_address: address) - - refute TransferView.smart_contract_with_read_only_functions?(token) - end - end end From 7055d77d0aee08d353e5486f787f75583f1ab8b1 Mon Sep 17 00:00:00 2001 From: Amanda Sposito Date: Wed, 26 Sep 2018 19:23:35 -0300 Subject: [PATCH 4/4] Add gettext strings --- apps/block_scout_web/priv/gettext/default.pot | 45 +++++++++++++++---- .../priv/gettext/en/LC_MESSAGES/default.po | 45 +++++++++++++++---- 2 files changed, 74 insertions(+), 16 deletions(-) diff --git a/apps/block_scout_web/priv/gettext/default.pot b/apps/block_scout_web/priv/gettext/default.pot index ddac06d944..ae5d77c836 100644 --- a/apps/block_scout_web/priv/gettext/default.pot +++ b/apps/block_scout_web/priv/gettext/default.pot @@ -270,6 +270,7 @@ msgid "TPM" msgstr "" #: lib/block_scout_web/templates/tokens/holder/index.html.eex:35 +#: lib/block_scout_web/templates/tokens/inventory/index.html.eex:34 msgid "Next Page" msgstr "" @@ -712,9 +713,9 @@ msgstr "" #: lib/block_scout_web/templates/address_token_transfer/index.html.eex:100 #: lib/block_scout_web/templates/address_transaction/index.html.eex:50 #: lib/block_scout_web/templates/address_transaction/index.html.eex:91 -#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:14 -#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:50 -#: lib/block_scout_web/views/tokens/overview_view.ex:36 +#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:33 +#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:75 +#: lib/block_scout_web/views/tokens/overview_view.ex:35 msgid "Read Contract" msgstr "" @@ -816,12 +817,12 @@ msgstr "" #, elixir-format #: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:5 -#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:44 +#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:54 #: lib/block_scout_web/templates/tokens/transfer/index.html.eex:18 #: lib/block_scout_web/templates/transaction/_tabs.html.eex:6 #: lib/block_scout_web/templates/transaction/_tabs.html.eex:36 #: lib/block_scout_web/templates/transaction_token_transfer/index.html.eex:10 -#: lib/block_scout_web/views/tokens/overview_view.ex:34 +#: lib/block_scout_web/views/tokens/overview_view.ex:33 #: lib/block_scout_web/views/transaction_view.ex:169 msgid "Token Transfers" msgstr "" @@ -1090,9 +1091,9 @@ msgstr "" #, elixir-format #: lib/block_scout_web/templates/tokens/holder/index.html.eex:19 -#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:22 -#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:55 -#: lib/block_scout_web/views/tokens/overview_view.ex:35 +#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:13 +#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:60 +#: lib/block_scout_web/views/tokens/overview_view.ex:34 msgid "Token Holders" msgstr "" @@ -1279,3 +1280,31 @@ msgstr "" #: lib/block_scout_web/templates/address_token_balance/_token_balances.html.eex:26 msgid "Search tokens" msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/tokens/inventory/index.html.eex:18 +#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:23 +#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:67 +#: lib/block_scout_web/views/tokens/overview_view.ex:36 +msgid "Inventory" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/tokens/inventory/_token.html.eex:18 +msgid "Owner Address" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/tokens/inventory/index.html.eex:27 +msgid "There are no tokens." +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/tokens/inventory/_token.html.eex:11 +msgid "Token ID" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/tokens/inventory/_token.html.eex:6 +msgid "Unique Token" +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 74e593ef62..015f397338 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 @@ -282,6 +282,7 @@ msgid "TPM" msgstr "" #: lib/block_scout_web/templates/tokens/holder/index.html.eex:35 +#: lib/block_scout_web/templates/tokens/inventory/index.html.eex:34 msgid "Next Page" msgstr "" @@ -724,9 +725,9 @@ msgstr "" #: lib/block_scout_web/templates/address_token_transfer/index.html.eex:100 #: lib/block_scout_web/templates/address_transaction/index.html.eex:50 #: lib/block_scout_web/templates/address_transaction/index.html.eex:91 -#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:14 -#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:50 -#: lib/block_scout_web/views/tokens/overview_view.ex:36 +#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:33 +#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:75 +#: lib/block_scout_web/views/tokens/overview_view.ex:35 msgid "Read Contract" msgstr "" @@ -828,12 +829,12 @@ msgstr "" #, elixir-format #: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:5 -#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:44 +#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:54 #: lib/block_scout_web/templates/tokens/transfer/index.html.eex:18 #: lib/block_scout_web/templates/transaction/_tabs.html.eex:6 #: lib/block_scout_web/templates/transaction/_tabs.html.eex:36 #: lib/block_scout_web/templates/transaction_token_transfer/index.html.eex:10 -#: lib/block_scout_web/views/tokens/overview_view.ex:34 +#: lib/block_scout_web/views/tokens/overview_view.ex:33 #: lib/block_scout_web/views/transaction_view.ex:169 msgid "Token Transfers" msgstr "" @@ -1102,9 +1103,9 @@ msgstr "" #, elixir-format #: lib/block_scout_web/templates/tokens/holder/index.html.eex:19 -#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:22 -#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:55 -#: lib/block_scout_web/views/tokens/overview_view.ex:35 +#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:13 +#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:60 +#: lib/block_scout_web/views/tokens/overview_view.ex:34 msgid "Token Holders" msgstr "" @@ -1291,3 +1292,31 @@ msgstr "" #: lib/block_scout_web/templates/address_token_balance/_token_balances.html.eex:26 msgid "Search tokens" msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/tokens/inventory/index.html.eex:18 +#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:23 +#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:67 +#: lib/block_scout_web/views/tokens/overview_view.ex:36 +msgid "Inventory" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/tokens/inventory/_token.html.eex:18 +msgid "Owner Address" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/tokens/inventory/index.html.eex:27 +msgid "There are no tokens." +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/tokens/inventory/_token.html.eex:11 +msgid "Token ID" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/tokens/inventory/_token.html.eex:6 +msgid "Unique Token" +msgstr ""