Add 'Token Inventory' query

pull/774/head
Amanda Sposito 6 years ago committed by Amanda Sposito
parent f77d72757f
commit 92a241f4ac
  1. 11
      apps/explorer/lib/explorer/chain.ex
  2. 33
      apps/explorer/lib/explorer/chain/token_transfer.ex
  3. 71
      apps/explorer/test/explorer/chain/token_transfer_test.exs
  4. 43
      apps/explorer/test/explorer/chain_test.exs

@ -1868,4 +1868,15 @@ defmodule Explorer.Chain do
|> TokenBalance.token_holders_from_token_hash() |> TokenBalance.token_holders_from_token_hash()
|> Repo.aggregate(:count, :address_hash) |> Repo.aggregate(:count, :address_hash)
end 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 end

@ -26,7 +26,7 @@ defmodule Explorer.Chain.TokenTransfer do
import Ecto.{Changeset, Query} 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} alias Explorer.{PagingOptions, Repo}
@default_paging_options %PagingOptions{page_size: 50} @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: 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 def page_token_transfer(query, %PagingOptions{key: inserted_at}) do
where( where(
query, query,
@ -166,4 +174,27 @@ defmodule Explorer.Chain.TokenTransfer do
|> join(:left, [transaction], tt in assoc(transaction, :token_transfers)) |> 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) |> where([_transaction, tt], tt.to_address_hash == ^address_hash or tt.from_address_hash == ^address_hash)
end 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 end

@ -3,7 +3,7 @@ defmodule Explorer.Chain.TokenTransferTest do
import Explorer.Factory import Explorer.Factory
alias Explorer.PagingOptions alias Explorer.{PagingOptions, Repo}
alias Explorer.Chain.TokenTransfer alias Explorer.Chain.TokenTransfer
doctest 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 assert TokenTransfer.count_token_transfers_from_token_hash(token_contract_address.hash) == 2
end end
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 end

@ -2819,4 +2819,47 @@ defmodule Explorer.ChainTest do
assert result == [transaction.hash] assert result == [transaction.hash]
end end
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 end

Loading…
Cancel
Save