InventoryController token_id -> token_ids

pull/6391/head
Qwerty5Uiop 2 years ago committed by Viktor Baranov
parent c4bbbfdd7a
commit 3d9b044ab9
  1. 16
      apps/block_scout_web/lib/block_scout_web/controllers/tokens/inventory_controller.ex
  2. 14
      apps/block_scout_web/lib/block_scout_web/templates/tokens/inventory/_token.html.eex
  3. 4
      apps/block_scout_web/test/block_scout_web/controllers/tokens/inventory_controller_test.exs
  4. 83
      apps/explorer/lib/explorer/chain.ex
  5. 38
      apps/explorer/lib/explorer/chain/token/instance.ex
  6. 50
      apps/explorer/test/explorer/chain/token_transfer_test.exs
  7. 8
      apps/explorer/test/explorer/chain_test.exs

@ -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

@ -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%>
<!-- use "tile-type-unique-token-image" to token with images -->
<div class="tile tile-type-unique-token fade-in">
<div class="row">
@ -17,7 +17,7 @@
<span class="d-flex flex-md-row flex-column mt-3 mt-md-0">
<span class="mr-1"><%= gettext "Token ID" %>:</span>
<span class="tile-title">
<%= 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}")) %>
</span>
</span>
<span class="d-flex flex-md-row flex-column mt-3 mt-md-0">
@ -25,7 +25,7 @@
<span class="tile-title">
<%= render BlockScoutWeb.AddressView,
"_link.html",
address: @token_transfer.to_address,
address: @instance.owner,
contract: false,
use_custom_tooltip: false %>
</span>
@ -36,7 +36,7 @@
<span class="d-flex flex-md-row flex-column mt-3 mt-md-0">
<span class="mr-1"><%= gettext "Token ID" %>:</span>
<span class="tile-title">
<%= 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}")) %>
</span>
</span>
</div>
@ -45,12 +45,12 @@
<div class="pl-5 col-md-2 d-flex flex-column align-items-left justify-content-start justify-content-lg-center">
<!-- substitute the span with <img class="tile-image" /> to token with images -->
<div class="inventory-erc721-media" >
<%= if media_type(media_src(@token_transfer.instance)) == "video" do %>
<%= if media_type(media_src(@instance)) == "video" do %>
<video style="overflow: auto;">
<source src=<%= media_src(@token_transfer.instance) %> type="video/mp4">
<source src=<%= media_src(@instance) %> type="video/mp4">
</video>
<% else %>
<img src=<%=media_src(@token_transfer.instance)%> />
<img src=<%=media_src(@instance)%> />
<% end %>
</div>
</div>

@ -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(

@ -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.

@ -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

@ -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)

@ -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

Loading…
Cancel
Save