Global token transfers token_id -> token_ids

pull/6391/head
Qwerty5Uiop 2 years ago committed by Viktor Baranov
parent ee56422bb8
commit f83d3e4ada
  1. 4
      apps/block_scout_web/test/block_scout_web/controllers/api/rpc/address_controller_test.exs
  2. 10
      apps/explorer/lib/explorer/account/notifier/summary.ex
  3. 34
      apps/explorer/lib/explorer/chain.ex
  4. 6
      apps/explorer/lib/explorer/chain/import/runner/token_transfers.ex
  5. 35
      apps/explorer/lib/explorer/chain/token_transfer.ex
  6. 5
      apps/explorer/test/explorer/account/notify/summary_test.exs
  7. 41
      apps/explorer/test/explorer/chain_test.exs
  8. 12
      apps/indexer/test/indexer/transform/token_transfers_test.exs

@ -2221,7 +2221,7 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do
token_transfer = token_transfer =
insert(:token_transfer, %{ insert(:token_transfer, %{
token_contract_address: token_address, token_contract_address: token_address,
token_id: 666, token_ids: [666],
transaction: transaction, transaction: transaction,
block: transaction.block, block: transaction.block,
block_number: transaction.block_number block_number: transaction.block_number
@ -2241,7 +2241,7 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do
|> get("/api", params) |> get("/api", params)
|> json_response(200) |> json_response(200)
assert result["tokenID"] == to_string(token_transfer.token_id) assert result["tokenID"] == to_string(List.first(token_transfer.token_ids))
assert response["status"] == "1" assert response["status"] == "1"
assert response["message"] == "OK" assert response["message"] == "OK"
assert :ok = ExJsonSchema.Validator.validate(tokentx_schema(), response) assert :ok = ExJsonSchema.Validator.validate(tokentx_schema(), response)

@ -132,7 +132,7 @@ defmodule Explorer.Account.Notifier.Summary do
from_address_hash: transfer.from_address_hash, from_address_hash: transfer.from_address_hash,
to_address_hash: transfer.to_address_hash, to_address_hash: transfer.to_address_hash,
block_number: transfer.block_number, block_number: transfer.block_number,
subject: to_string(transfer.token_id), subject: to_string(List.first(transfer.token_ids)),
tx_fee: fee(transaction), tx_fee: fee(transaction),
name: transfer.token.name, name: transfer.token.name,
type: transfer.token.type type: transfer.token.type
@ -193,14 +193,8 @@ defmodule Explorer.Account.Notifier.Summary do
) )
end end
def token_ids(%Chain.TokenTransfer{token_id: token_id, token_ids: token_ids}) do def token_ids(%Chain.TokenTransfer{token_ids: token_ids}) do
case token_id do
nil ->
Enum.map_join(token_ids, ", ", fn id -> to_string(id) end) Enum.map_join(token_ids, ", ", fn id -> to_string(id) end)
_ ->
to_string(token_id)
end
end end
def token_decimals(%Chain.TokenTransfer{} = transfer) do def token_decimals(%Chain.TokenTransfer{} = transfer) do

@ -4714,29 +4714,35 @@ defmodule Explorer.Chain do
select: token.contract_address_hash select: token.contract_address_hash
) )
query = token_ids_query =
from( from(
token_transfer in TokenTransfer, token_transfer in TokenTransfer,
select: %{
token_contract_address_hash: token_transfer.token_contract_address_hash,
token_id: fragment("unnest(?)", token_transfer.token_ids)
}
)
query =
from(
transfer in subquery(token_ids_query),
inner_join: token in subquery(nft_tokens), inner_join: token in subquery(nft_tokens),
on: token.contract_address_hash == token_transfer.token_contract_address_hash, on: token.contract_address_hash == transfer.token_contract_address_hash,
left_join: instance in Instance, left_join: instance in Instance,
on: on:
token_transfer.token_contract_address_hash == instance.token_contract_address_hash and transfer.token_contract_address_hash == instance.token_contract_address_hash and
(token_transfer.token_id == instance.token_id or transfer.token_id == instance.token_id,
fragment("? @> ARRAY[?::decimal]", token_transfer.token_ids, instance.token_id)), where: is_nil(instance.token_id),
where:
is_nil(instance.token_id) and (not is_nil(token_transfer.token_id) or not is_nil(token_transfer.token_ids)),
select: %{ select: %{
contract_address_hash: token_transfer.token_contract_address_hash, contract_address_hash: transfer.token_contract_address_hash,
token_id: token_transfer.token_id, token_id: transfer.token_id
token_ids: token_transfer.token_ids
} }
) )
distinct_query = distinct_query =
from( from(
q in subquery(query), q in subquery(query),
distinct: [q.contract_address_hash, q.token_id, q.token_ids] distinct: [q.contract_address_hash, q.token_id]
) )
Repo.stream_reduce(distinct_query, initial, reducer) Repo.stream_reduce(distinct_query, initial, reducer)
@ -5749,10 +5755,8 @@ defmodule Explorer.Chain do
@spec erc721_or_erc1155_token_instance_exist?(binary() | non_neg_integer(), Hash.Address.t()) :: boolean() @spec erc721_or_erc1155_token_instance_exist?(binary() | non_neg_integer(), Hash.Address.t()) :: boolean()
def erc721_or_erc1155_token_instance_exist?(token_id, hash) do def erc721_or_erc1155_token_instance_exist?(token_id, hash) do
query = query =
from(tt in TokenTransfer, from(i in Instance,
where: where: i.token_contract_address_hash == ^hash and i.token_id == ^Decimal.new(token_id)
tt.token_contract_address_hash == ^hash and
(tt.token_id == ^token_id or fragment("? @> ARRAY[?::decimal]", tt.token_ids, ^Decimal.new(token_id)))
) )
Repo.exists?(query) Repo.exists?(query)

@ -89,19 +89,19 @@ defmodule Explorer.Chain.Import.Runner.TokenTransfers do
from_address_hash: fragment("EXCLUDED.from_address_hash"), from_address_hash: fragment("EXCLUDED.from_address_hash"),
to_address_hash: fragment("EXCLUDED.to_address_hash"), to_address_hash: fragment("EXCLUDED.to_address_hash"),
token_contract_address_hash: fragment("EXCLUDED.token_contract_address_hash"), token_contract_address_hash: fragment("EXCLUDED.token_contract_address_hash"),
token_id: fragment("EXCLUDED.token_id"), token_ids: fragment("EXCLUDED.token_ids"),
inserted_at: fragment("LEAST(?, EXCLUDED.inserted_at)", token_transfer.inserted_at), inserted_at: fragment("LEAST(?, EXCLUDED.inserted_at)", token_transfer.inserted_at),
updated_at: fragment("GREATEST(?, EXCLUDED.updated_at)", token_transfer.updated_at) updated_at: fragment("GREATEST(?, EXCLUDED.updated_at)", token_transfer.updated_at)
] ]
], ],
where: where:
fragment( fragment(
"(EXCLUDED.amount, EXCLUDED.from_address_hash, EXCLUDED.to_address_hash, EXCLUDED.token_contract_address_hash, EXCLUDED.token_id) IS DISTINCT FROM (?, ? ,? , ?, ?)", "(EXCLUDED.amount, EXCLUDED.from_address_hash, EXCLUDED.to_address_hash, EXCLUDED.token_contract_address_hash, EXCLUDED.token_ids) IS DISTINCT FROM (?, ? ,? , ?, ?)",
token_transfer.amount, token_transfer.amount,
token_transfer.from_address_hash, token_transfer.from_address_hash,
token_transfer.to_address_hash, token_transfer.to_address_hash,
token_transfer.token_contract_address_hash, token_transfer.token_contract_address_hash,
token_transfer.token_id token_transfer.token_ids
) )
) )
end end

@ -110,8 +110,8 @@ defmodule Explorer.Chain.TokenTransfer do
type: Hash.Full type: Hash.Full
) )
has_one( has_many(
:instance, :instances,
Instance, Instance,
foreign_key: :token_contract_address_hash, foreign_key: :token_contract_address_hash,
references: :token_contract_address_hash references: :token_contract_address_hash
@ -177,7 +177,7 @@ defmodule Explorer.Chain.TokenTransfer do
from( from(
tt in TokenTransfer, tt in TokenTransfer,
where: tt.token_contract_address_hash == ^token_address_hash, where: tt.token_contract_address_hash == ^token_address_hash,
where: tt.token_id == ^token_id or fragment("? @> ARRAY[?::decimal]", tt.token_ids, ^Decimal.new(token_id)), where: fragment("? @> ARRAY[?::decimal]", tt.token_ids, ^Decimal.new(token_id)),
where: not is_nil(tt.block_number), where: not is_nil(tt.block_number),
preload: [{:transaction, :block}, :token, :from_address, :to_address], preload: [{:transaction, :block}, :token, :from_address, :to_address],
order_by: [desc: tt.block_number] order_by: [desc: tt.block_number]
@ -208,7 +208,7 @@ defmodule Explorer.Chain.TokenTransfer do
tt in TokenTransfer, tt in TokenTransfer,
where: where:
tt.token_contract_address_hash == ^token_address_hash and tt.token_contract_address_hash == ^token_address_hash and
(tt.token_id == ^token_id or fragment("? @> ARRAY[?::decimal]", tt.token_ids, ^Decimal.new(token_id))), fragment("? @> ARRAY[?::decimal]", tt.token_ids, ^Decimal.new(token_id)),
select: fragment("COUNT(*)") select: fragment("COUNT(*)")
) )
@ -218,11 +218,11 @@ 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}, asc_order: true}) do def page_token_transfer(query, %PagingOptions{key: {token_id}, asc_order: true}) do
where(query, [tt], tt.token_id > ^token_id) where(query, [tt], fragment("?[1] > ?", tt.token_ids, ^token_id))
end end
def page_token_transfer(query, %PagingOptions{key: {token_id}}) do def page_token_transfer(query, %PagingOptions{key: {token_id}}) do
where(query, [tt], tt.token_id < ^token_id) where(query, [tt], fragment("?[1] < ?", tt.token_ids, ^token_id))
end end
def page_token_transfer(query, %PagingOptions{key: {block_number, log_index}, asc_order: true}) do def page_token_transfer(query, %PagingOptions{key: {block_number, log_index}, asc_order: true}) do
@ -304,27 +304,4 @@ defmodule Explorer.Chain.TokenTransfer do
tt.block_number < ^block_number tt.block_number < ^block_number
) )
end end
@doc """
Innventory 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.
"""
@spec address_to_unique_tokens(Hash.Address.t()) :: Ecto.Query.t()
def address_to_unique_tokens(contract_address_hash) do
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 == ^contract_address_hash,
where: tt.to_address_hash != ^"0x0000000000000000000000000000000000000000",
order_by: [desc: tt.block_number],
distinct: [desc: tt.token_id],
preload: [:to_address],
select: %{tt | instance: instance}
)
end
end end

@ -152,7 +152,7 @@ defmodule Explorer.Account.Notify.SummaryTest do
:token_transfer :token_transfer
|> insert( |> insert(
transaction: tx, transaction: tx,
token_id: 42, token_ids: [42],
token_contract_address: token.contract_address token_contract_address: token.contract_address
) )
|> Repo.preload([ |> Repo.preload([
@ -198,7 +198,7 @@ defmodule Explorer.Account.Notify.SummaryTest do
:token_transfer :token_transfer
|> insert( |> insert(
transaction: tx, transaction: tx,
token_id: 42, token_ids: [42],
token_contract_address: token.contract_address token_contract_address: token.contract_address
) )
|> Repo.preload([ |> Repo.preload([
@ -244,7 +244,6 @@ defmodule Explorer.Account.Notify.SummaryTest do
:token_transfer :token_transfer
|> insert( |> insert(
transaction: tx, transaction: tx,
token_id: nil,
token_ids: [23, 42], token_ids: [23, 42],
token_contract_address: token.contract_address token_contract_address: token.contract_address
) )

@ -137,7 +137,7 @@ defmodule Explorer.ChainTest do
end end
end end
describe "ERC721_token_instance_from_token_id_and_token_address/2" do describe "ERC721_or_ERC1155_token_instance_from_token_id_and_token_address/2" do
test "return ERC721 token instance" do test "return ERC721 token instance" do
contract_address = insert(:address) contract_address = insert(:address)
@ -146,11 +146,11 @@ defmodule Explorer.ChainTest do
insert(:token_transfer, insert(:token_transfer,
from_address: contract_address, from_address: contract_address,
token_contract_address: contract_address, token_contract_address: contract_address,
token_id: token_id token_ids: [token_id]
) )
assert {:ok, result} = assert {:ok, result} =
Chain.erc721_token_instance_from_token_id_and_token_address(token_id, contract_address.hash) Chain.erc721_or_erc1155_token_instance_from_token_id_and_token_address(token_id, contract_address.hash)
assert result.token_id == Decimal.new(token_id) assert result.token_id == Decimal.new(token_id)
end end
@ -4837,41 +4837,15 @@ defmodule Explorer.ChainTest do
transaction: transaction, transaction: transaction,
token_contract_address: token_contract_address, token_contract_address: token_contract_address,
token: token, token: token,
token_id: 11
)
assert {:ok, [result]} = Chain.stream_unfetched_token_instances([], &[&1 | &2])
assert result.token_id == token_transfer.token_id
assert result.contract_address_hash == token_transfer.token_contract_address_hash
end
test "reduces with given reducer and accumulator for ERC-1155 token" do
token_contract_address = insert(:contract_address)
token = insert(:token, contract_address: token_contract_address, type: "ERC-1155")
transaction =
:transaction
|> insert()
|> with_block(insert(:block, number: 1))
token_transfer =
insert(
:token_transfer,
block_number: 1000,
to_address: build(:address),
transaction: transaction,
token_contract_address: token_contract_address,
token: token,
token_id: nil,
token_ids: [11] token_ids: [11]
) )
assert {:ok, [result]} = Chain.stream_unfetched_token_instances([], &[&1 | &2]) assert {:ok, [result]} = Chain.stream_unfetched_token_instances([], &[&1 | &2])
assert result.token_ids == token_transfer.token_ids assert result.token_id == List.first(token_transfer.token_ids)
assert result.contract_address_hash == token_transfer.token_contract_address_hash assert result.contract_address_hash == token_transfer.token_contract_address_hash
end end
test "does not fetch token transfers without token id or token_ids" do test "does not fetch token transfers without token_ids" do
token_contract_address = insert(:contract_address) token_contract_address = insert(:contract_address)
token = insert(:token, contract_address: token_contract_address, type: "ERC-721") token = insert(:token, contract_address: token_contract_address, type: "ERC-721")
@ -4887,7 +4861,6 @@ defmodule Explorer.ChainTest do
transaction: transaction, transaction: transaction,
token_contract_address: token_contract_address, token_contract_address: token_contract_address,
token: token, token: token,
token_id: nil,
token_ids: nil token_ids: nil
) )
@ -4911,11 +4884,11 @@ defmodule Explorer.ChainTest do
transaction: transaction, transaction: transaction,
token_contract_address: token_contract_address, token_contract_address: token_contract_address,
token: token, token: token,
token_id: 11 token_ids: [11]
) )
insert(:token_instance, insert(:token_instance,
token_id: token_transfer.token_id, token_id: List.first(token_transfer.token_ids),
token_contract_address_hash: token_transfer.token_contract_address_hash token_contract_address_hash: token_transfer.token_contract_address_hash
) )

@ -68,13 +68,13 @@ defmodule Indexer.Transform.TokenTransfersTest do
from_address_hash: truncated_hash(log_3.second_topic), from_address_hash: truncated_hash(log_3.second_topic),
to_address_hash: truncated_hash(log_3.third_topic), to_address_hash: truncated_hash(log_3.third_topic),
token_contract_address_hash: log_3.address_hash, token_contract_address_hash: log_3.address_hash,
token_id: 183, token_ids: [183],
transaction_hash: log_3.transaction_hash, transaction_hash: log_3.transaction_hash,
token_type: "ERC-721", token_type: "ERC-721",
block_hash: log_3.block_hash block_hash: log_3.block_hash
}, },
%{ %{
token_id: nil, token_ids: nil,
amount: Decimal.new(17_000_000_000_000_000_000), amount: Decimal.new(17_000_000_000_000_000_000),
block_number: log_1.block_number, block_number: log_1.block_number,
log_index: log_1.index, log_index: log_1.index,
@ -122,7 +122,7 @@ defmodule Indexer.Transform.TokenTransfersTest do
to_address_hash: "0xbe8cdfc13ffda20c844ac3da2b53a23ac5787f1e", to_address_hash: "0xbe8cdfc13ffda20c844ac3da2b53a23ac5787f1e",
block_hash: "0x79594150677f083756a37eee7b97ed99ab071f502104332cb3835bac345711ca", block_hash: "0x79594150677f083756a37eee7b97ed99ab071f502104332cb3835bac345711ca",
token_contract_address_hash: log.address_hash, token_contract_address_hash: log.address_hash,
token_id: 14_939, token_ids: [14_939],
transaction_hash: log.transaction_hash, transaction_hash: log.transaction_hash,
token_type: "ERC-721" token_type: "ERC-721"
} }
@ -158,8 +158,9 @@ defmodule Indexer.Transform.TokenTransfersTest do
log_index: 2, log_index: 2,
to_address_hash: "0x9c978f4cfa1fe13406bcc05baf26a35716f881dd", to_address_hash: "0x9c978f4cfa1fe13406bcc05baf26a35716f881dd",
token_contract_address_hash: "0x58Ab73CB79c8275628E0213742a85B163fE0A9Fb", token_contract_address_hash: "0x58Ab73CB79c8275628E0213742a85B163fE0A9Fb",
token_id: token_ids: [
7_237_005_577_332_282_011_952_059_972_634_123_378_909_214_838_582_411_639_295_170_840_059_424_276_480, 7_237_005_577_332_282_011_952_059_972_634_123_378_909_214_838_582_411_639_295_170_840_059_424_276_480
],
token_type: "ERC-1155", token_type: "ERC-1155",
transaction_hash: "0x6d2dd62c178e55a13b65601f227c4ffdd8aa4e3bcb1f24731363b4f7619e92c8" transaction_hash: "0x6d2dd62c178e55a13b65601f227c4ffdd8aa4e3bcb1f24731363b4f7619e92c8"
} }
@ -198,7 +199,6 @@ defmodule Indexer.Transform.TokenTransfersTest do
log_index: 2, log_index: 2,
to_address_hash: "0x6c943470780461b00783ad530a53913bd2c104d3", to_address_hash: "0x6c943470780461b00783ad530a53913bd2c104d3",
token_contract_address_hash: "0x58Ab73CB79c8275628E0213742a85B163fE0A9Fb", token_contract_address_hash: "0x58Ab73CB79c8275628E0213742a85B163fE0A9Fb",
token_id: nil,
token_ids: [680_564_733_841_876_926_926_749_214_863_536_422_912], token_ids: [680_564_733_841_876_926_926_749_214_863_536_422_912],
token_type: "ERC-1155", token_type: "ERC-1155",
transaction_hash: "0x6d2dd62c178e55a13b65601f227c4ffdd8aa4e3bcb1f24731363b4f7619e92c8", transaction_hash: "0x6d2dd62c178e55a13b65601f227c4ffdd8aa4e3bcb1f24731363b4f7619e92c8",

Loading…
Cancel
Save