From 3d9b044ab91708d628375842b81105b892a3407e Mon Sep 17 00:00:00 2001 From: Qwerty5Uiop Date: Mon, 31 Oct 2022 21:51:47 +0400 Subject: [PATCH] InventoryController token_id -> token_ids --- .../tokens/inventory_controller.ex | 16 ++-- .../tokens/inventory/_token.html.eex | 14 ++-- .../tokens/inventory_controller_test.exs | 4 +- apps/explorer/lib/explorer/chain.ex | 83 +------------------ .../lib/explorer/chain/token/instance.ex | 38 ++++++++- .../explorer/chain/token_transfer_test.exs | 50 ----------- apps/explorer/test/explorer/chain_test.exs | 8 +- 7 files changed, 61 insertions(+), 152 deletions(-) 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 index 5129856a18..d9cb630ca4 100644 --- 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 @@ -4,7 +4,7 @@ defmodule BlockScoutWeb.Tokens.InventoryController do alias BlockScoutWeb.AccessHelpers alias BlockScoutWeb.Tokens.{HolderController, InventoryView} alias Explorer.Chain - alias Explorer.Chain.TokenTransfer + alias Explorer.Chain.Token.Instance alias Phoenix.View import BlockScoutWeb.Chain, only: [split_list_by_page: 1, default_paging_options: 0] @@ -13,16 +13,16 @@ defmodule BlockScoutWeb.Tokens.InventoryController do with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string), {:ok, token} <- Chain.token_from_address_hash(address_hash), {:ok, false} <- AccessHelpers.restricted_access?(address_hash_string, params) do - unique_tokens = + unique_token_instances = 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) + {unique_token_instances_paginated, next_page} = split_list_by_page(unique_token_instances) next_page_path = - case unique_tokens_next_page(next_page, unique_tokens_paginated, params) do + case unique_tokens_next_page(next_page, unique_token_instances_paginated, params) do nil -> nil @@ -36,12 +36,12 @@ defmodule BlockScoutWeb.Tokens.InventoryController do end items = - unique_tokens_paginated - |> Enum.map(fn token_transfer -> + unique_token_instances_paginated + |> Enum.map(fn instance -> View.render_to_string( InventoryView, "_token.html", - token_transfer: token_transfer, + instance: instance, token: token, conn: conn ) @@ -81,7 +81,7 @@ defmodule BlockScoutWeb.Tokens.InventoryController do Map.merge(params, paging_params(List.last(list))) end - defp paging_params(%TokenTransfer{token_id: token_id}) do + defp paging_params(%Instance{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/templates/tokens/inventory/_token.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/tokens/inventory/_token.html.eex index 9d2ce63e1f..e92fa90b93 100644 --- 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 @@ -1,5 +1,5 @@ <% is_1155 = @token.type == "ERC-1155"%> -<% is_unique = Chain.token_id_1155_is_unique?(@token.contract_address_hash, @token_transfer.token_id) or not is_1155%> +<% is_unique = Chain.token_id_1155_is_unique?(@token.contract_address_hash, @instance.token_id) or not is_1155%>
@@ -17,7 +17,7 @@ <%= gettext "Token ID" %>: - <%= link(@token_transfer.token_id, to: token_instance_path(@conn, :show, "#{@token.contract_address_hash}", "#{@token_transfer.token_id}")) %> + <%= link(@instance.token_id, to: token_instance_path(@conn, :show, "#{@token.contract_address_hash}", "#{@instance.token_id}")) %> @@ -25,7 +25,7 @@ <%= render BlockScoutWeb.AddressView, "_link.html", - address: @token_transfer.to_address, + address: @instance.owner, contract: false, use_custom_tooltip: false %> @@ -36,7 +36,7 @@ <%= gettext "Token ID" %>: - <%= link(@token_transfer.token_id, to: token_instance_path(@conn, :show, "#{@token.contract_address_hash}", "#{@token_transfer.token_id}")) %> + <%= link(@instance.token_id, to: token_instance_path(@conn, :show, "#{@token.contract_address_hash}", "#{@instance.token_id}")) %>
@@ -45,12 +45,12 @@
- <%= if media_type(media_src(@token_transfer.instance)) == "video" do %> + <%= if media_type(media_src(@instance)) == "video" do %> <% else %> - /> + /> <% end %>
diff --git a/apps/block_scout_web/test/block_scout_web/controllers/tokens/inventory_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/tokens/inventory_controller_test.exs index e1591a4db9..799b54da79 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/tokens/inventory_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/tokens/inventory_controller_test.exs @@ -55,7 +55,7 @@ defmodule BlockScoutWeb.Tokens.InventoryControllerTest do transaction: transaction, token_contract_address: token.contract_address, token: token, - token_id: i + 1000 + token_ids: [i + 1000] ) insert( @@ -94,7 +94,7 @@ defmodule BlockScoutWeb.Tokens.InventoryControllerTest do transaction: transaction, token_contract_address: token.contract_address, token: token, - token_id: i + 1000 + token_ids: [i + 1000] ) insert( diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index b008c36822..09123d9513 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -5035,24 +5035,6 @@ defmodule Explorer.Chain do |> Repo.all() end - @spec erc721_token_instance_from_token_id_and_token_address(binary(), Hash.Address.t()) :: - {:ok, TokenTransfer.t()} | {:error, :not_found} - def erc721_token_instance_from_token_id_and_token_address(token_id, token_contract_address) do - query = - from(tt in TokenTransfer, - left_join: instance in Instance, - on: tt.token_contract_address_hash == instance.token_contract_address_hash and tt.token_id == instance.token_id, - where: tt.token_contract_address_hash == ^token_contract_address and tt.token_id == ^token_id, - limit: 1, - select: %{tt | instance: instance} - ) - - case Repo.one(query) do - nil -> {:error, :not_found} - token_instance -> {:ok, token_instance} - end - end - @spec erc721_or_erc1155_token_instance_from_token_id_and_token_address(binary(), Hash.Address.t()) :: {:ok, Instance.t()} | {:error, :not_found} def erc721_or_erc1155_token_instance_from_token_id_and_token_address(token_id, token_contract_address) do @@ -5265,13 +5247,13 @@ defmodule Explorer.Chain do Repo.one!(query, timeout: :infinity) end - @spec address_to_unique_tokens(Hash.Address.t(), [paging_options]) :: [TokenTransfer.t()] + @spec address_to_unique_tokens(Hash.Address.t(), [paging_options]) :: [Instance.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) + |> Instance.address_to_unique_token_instances() + |> Instance.page_token_instance(paging_options) |> limit(^paging_options.page_size) |> Repo.all() end @@ -5694,34 +5676,6 @@ defmodule Explorer.Chain do Repo.exists?(query) end - @doc """ - Checks if a `t:Explorer.Chain.TokenTransfer.t/0` of type ERC-721 with the given `hash` and `token_id` exists. - - Returns `:ok` if found - - iex> contract_address = insert(:address) - iex> token_id = 10 - iex> insert(:token_transfer, - ...> from_address: contract_address, - ...> token_contract_address: contract_address, - ...> token_id: token_id - ...> ) - iex> Explorer.Chain.check_erc721_token_instance_exists(token_id, contract_address.hash) - :ok - - Returns `:not_found` if not found - - iex> {:ok, hash} = Explorer.Chain.string_to_address_hash("0x5aaeb6053f3e94c9b9a09f33669435e7ef1beaed") - iex> Explorer.Chain.check_erc721_token_instance_exists(10, hash) - :not_found - """ - @spec check_erc721_token_instance_exists(binary() | non_neg_integer(), Hash.Address.t()) :: :ok | :not_found - def check_erc721_token_instance_exists(token_id, hash) do - token_id - |> erc721_token_instance_exist?(hash) - |> boolean_to_check_result() - end - @doc """ Checks if a `t:Explorer.Chain.TokenTransfer.t/0` of type ERC-721 or ERC-1155 with the given `hash` and `token_id` exists. @@ -5761,37 +5715,6 @@ defmodule Explorer.Chain do |> boolean_to_check_result() end - @doc """ - Checks if a `t:Explorer.Chain.TokenTransfer.t/0` of type ERC-721 with the given `hash` and `token_id` exists. - - Returns `true` if found - - iex> contract_address = insert(:address) - iex> token_id = 10 - iex> insert(:token_transfer, - ...> from_address: contract_address, - ...> token_contract_address: contract_address, - ...> token_id: token_id - ...> ) - iex> Explorer.Chain.erc721_token_instance_exist?(token_id, contract_address.hash) - true - - Returns `false` if not found - - iex> {:ok, hash} = Explorer.Chain.string_to_address_hash("0x5aaeb6053f3e94c9b9a09f33669435e7ef1beaed") - iex> Explorer.Chain.erc721_token_instance_exist?(10, hash) - false - """ - @spec erc721_token_instance_exist?(binary() | non_neg_integer(), Hash.Address.t()) :: boolean() - def erc721_token_instance_exist?(token_id, hash) do - query = - from(tt in TokenTransfer, - where: tt.token_contract_address_hash == ^hash and tt.token_id == ^token_id - ) - - Repo.exists?(query) - end - @doc """ Checks if a `t:Explorer.Chain.TokenTransfer.t/0` of type ERC-721 or ERC-1155 with the given `hash` and `token_id` exists. diff --git a/apps/explorer/lib/explorer/chain/token/instance.ex b/apps/explorer/lib/explorer/chain/token/instance.ex index 005ee3a2c0..c0bcf2c5fc 100644 --- a/apps/explorer/lib/explorer/chain/token/instance.ex +++ b/apps/explorer/lib/explorer/chain/token/instance.ex @@ -5,8 +5,9 @@ defmodule Explorer.Chain.Token.Instance do use Explorer.Schema - alias Explorer.Chain.{Hash, Token} + alias Explorer.Chain.{Hash, Token, TokenTransfer} alias Explorer.Chain.Token.Instance + alias Explorer.PagingOptions @typedoc """ * `token_id` - ID of the token @@ -27,6 +28,7 @@ defmodule Explorer.Chain.Token.Instance do field(:token_id, :decimal, primary_key: true) field(:metadata, :map) field(:error, :string) + field(:owner, Hash.Address, virtual: true) belongs_to( :token, @@ -46,4 +48,38 @@ defmodule Explorer.Chain.Token.Instance do |> validate_required([:token_id, :token_contract_address_hash]) |> foreign_key_constraint(:token_contract_address_hash) end + + @doc """ + Inventory tab query. + 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. + """ + + def address_to_unique_token_instances(contract_address_hash) do + from( + i in Instance, + left_join: tt in TokenTransfer, + on: + tt.token_contract_address_hash == i.token_contract_address_hash and + fragment("? @> ARRAY[?::decimal]", tt.token_ids, i.token_id), + join: to_address in assoc(tt, :to_address), + where: i.token_contract_address_hash == ^contract_address_hash, + order_by: [desc: tt.block_number], + distinct: [desc: i.token_id], + select: %{i | owner: to_address} + ) + end + + def page_token_instance(query, %PagingOptions{key: {token_id}, asc_order: true}) do + where(query, [i], i.token_id > ^token_id) + end + + def page_token_instance(query, %PagingOptions{key: {token_id}}) do + where(query, [i], i.token_id < ^token_id) + end + + def page_token_instance(query, _), do: query end diff --git a/apps/explorer/test/explorer/chain/token_transfer_test.exs b/apps/explorer/test/explorer/chain/token_transfer_test.exs index f82ab71323..da4720281a 100644 --- a/apps/explorer/test/explorer/chain/token_transfer_test.exs +++ b/apps/explorer/test/explorer/chain/token_transfer_test.exs @@ -146,56 +146,6 @@ defmodule Explorer.Chain.TokenTransferTest do 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_instance, - token_id: 42, - token_contract_address_hash: token_contract_address.hash - ) - - 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: 3)) - - 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 - end - describe "where_any_address_fields_match/3" do test "when to_address_hash match returns transactions hashes list" do john = insert(:address) diff --git a/apps/explorer/test/explorer/chain_test.exs b/apps/explorer/test/explorer/chain_test.exs index 06ffb936d0..5e55b9ac1f 100644 --- a/apps/explorer/test/explorer/chain_test.exs +++ b/apps/explorer/test/explorer/chain_test.exs @@ -5272,7 +5272,7 @@ defmodule Explorer.ChainTest do transaction: transaction, token_contract_address: token_contract_address, token: token, - token_id: 29 + token_ids: [29] ) second_page = @@ -5283,17 +5283,17 @@ defmodule Explorer.ChainTest do transaction: transaction, token_contract_address: token_contract_address, token: token, - token_id: 11 + token_ids: [11] ) - paging_options = %PagingOptions{key: {first_page.token_id}, page_size: 1} + paging_options = %PagingOptions{key: {List.first(first_page.token_ids)}, page_size: 1} unique_tokens_ids_paginated = token_contract_address.hash |> Chain.address_to_unique_tokens(paging_options: paging_options) |> Enum.map(& &1.token_id) - assert unique_tokens_ids_paginated == [second_page.token_id] + assert unique_tokens_ids_paginated == [List.first(second_page.token_ids)] end end