Merge pull request #6013 from blockscout/fix-erc1155-tokens-fetching

Fix ERC-1155 tokens fetching
pull/6045/head
Victor Baranov 2 years ago committed by GitHub
commit 0b1ca31545
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      CHANGELOG.md
  2. 2
      apps/block_scout_web/lib/block_scout_web/controllers/tokens/instance/transfer_controller.ex
  3. 2
      apps/block_scout_web/lib/block_scout_web/controllers/tokens/instance_controller.ex
  4. 44
      apps/block_scout_web/test/block_scout_web/controllers/tokens/instance/transfer_controller_test.exs
  5. 23
      apps/block_scout_web/test/block_scout_web/controllers/tokens/instance_controller_test.exs
  6. 10
      apps/block_scout_web/test/block_scout_web/views/api_docs_view_test.exs
  7. 108
      apps/explorer/lib/explorer/chain.ex
  8. 6
      apps/explorer/lib/explorer/chain/token_transfer.ex
  9. 13
      apps/explorer/priv/repo/migrations/20220902103213_create_index_token_transfers_token_ids.exs
  10. 8
      apps/explorer/priv/repo/migrations/20220902103527_create_indexes_token_instances_token_contract_address_hash_token_id.exs

@ -28,6 +28,7 @@
- [#5807](https://github.com/blockscout/blockscout/pull/5807) - Update Makefile migrate command due to release build
- [#5786](https://github.com/blockscout/blockscout/pull/5786) - Replace `current_path` with `Controller.current_full_path` in two controllers
- [#5948](https://github.com/blockscout/blockscout/pull/5948) - Fix unexpected messages in `CoinBalanceOnDemand`
- [#6013](https://github.com/blockscout/blockscout/pull/6013) - Fix ERC-1155 tokens fetching
### Chore

@ -59,7 +59,7 @@ defmodule BlockScoutWeb.Tokens.Instance.TransferController do
with {:ok, hash} <- Chain.string_to_address_hash(token_address_hash),
{:ok, token} <- Chain.token_from_address_hash(hash, options),
{:ok, token_transfer} <-
Chain.erc721_token_instance_from_token_id_and_token_address(token_id, hash) do
Chain.erc721_or_erc1155_token_instance_from_token_id_and_token_address(token_id, hash) do
render(
conn,
"index.html",

@ -7,7 +7,7 @@ defmodule BlockScoutWeb.Tokens.InstanceController do
def show(conn, %{"token_id" => token_address_hash, "id" => token_id}) do
with {:ok, hash} <- Chain.string_to_address_hash(token_address_hash),
:ok <- Chain.check_token_exists(hash),
:ok <- Chain.check_erc721_token_instance_exists(token_id, hash) do
:ok <- Chain.check_erc721_or_erc1155_token_instance_exists(token_id, hash) do
token_instance_transfer_path =
conn
|> token_instance_transfer_path(:index, token_address_hash, token_id)

@ -0,0 +1,44 @@
defmodule BlockScoutWeb.Tokens.Instance.TransferControllerTest do
use BlockScoutWeb.ConnCase, async: false
describe "GET token-transfers/2" do
test "works for ERC-721 tokens", %{conn: conn} do
contract_address = insert(:address)
insert(:token, contract_address: contract_address)
token_id = 10
%{log_index: log_index} =
insert(:token_transfer,
from_address: contract_address,
token_contract_address: contract_address,
token_id: token_id
)
conn = get(conn, "/token/#{contract_address.hash}/instance/#{token_id}/token-transfers")
assert %{assigns: %{token_instance: %{log_index: ^log_index}}} = conn
end
test "works for ERC-1155 tokens", %{conn: conn} do
contract_address = insert(:address)
insert(:token, contract_address: contract_address)
token_id = 10
%{log_index: log_index} =
insert(:token_transfer,
from_address: contract_address,
token_contract_address: contract_address,
token_id: nil,
token_ids: [token_id]
)
conn = get(conn, "/token/#{contract_address.hash}/instance/#{token_id}/token-transfers")
assert %{assigns: %{token_instance: %{log_index: ^log_index}}} = conn
end
end
end

@ -19,5 +19,28 @@ defmodule BlockScoutWeb.Tokens.InstanceControllerTest do
assert conn.status == 302
end
test "works for ERC-1155 tokens", %{conn: conn} do
contract_address = insert(:address)
insert(:token, contract_address: contract_address)
token_id = 10
insert(:token_transfer,
from_address: contract_address,
token_contract_address: contract_address,
token_id: nil,
token_ids: [token_id]
)
conn = get(conn, token_instance_path(BlockScoutWeb.Endpoint, :show, to_string(contract_address.hash), token_id))
assert conn.status == 302
assert get_resp_header(conn, "location") == [
"/token/#{contract_address.hash}/instance/#{token_id}/token-transfers"
]
end
end
end

@ -40,11 +40,11 @@ defmodule BlockScoutWeb.ApiDocsViewTest do
describe "blockscout_url/2" do
setup do
on_exit(fn ->
Application.put_env(:block_scout_web, BlockScoutWeb.Endpoint,
url: [scheme: "http", host: "localhost", api_path: nil, path: nil]
)
end)
original = Application.get_env(:block_scout_web, BlockScoutWeb.Endpoint)
on_exit(fn -> Application.put_env(:block_scout_web, BlockScoutWeb.Endpoint, original) end)
:ok
end
test "set_path = true returns url with path" do

@ -4881,6 +4881,28 @@ defmodule Explorer.Chain do
end
end
@spec erc721_or_erc1155_token_instance_from_token_id_and_token_address(binary(), Hash.Address.t()) ::
{:ok, TokenTransfer.t()} | {:error, :not_found}
def erc721_or_erc1155_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 or fragment("? @> ARRAY[?::decimal]", tt.token_ids, instance.token_id)),
where:
tt.token_contract_address_hash == ^token_contract_address and
(tt.token_id == ^token_id or fragment("? @> ARRAY[?::decimal]", tt.token_ids, ^Decimal.new(token_id))),
limit: 1,
select: %{tt | instance: instance}
)
case Repo.one(query) do
nil -> {:error, :not_found}
token_instance -> {:ok, token_instance}
end
end
defp fetch_coin_balances(address_hash, paging_options) do
address = Repo.get_by(Address, hash: address_hash)
@ -5505,7 +5527,7 @@ defmodule Explorer.Chain do
end
@doc """
Checks if a `t:Explorer.Chain.TokenTransfer.t/0` with the given `hash` and `token_id` exists.
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
@ -5533,7 +5555,46 @@ defmodule Explorer.Chain do
end
@doc """
Checks if a `t:Explorer.Chain.TokenTransfer.t/0` with the given `hash` and `token_id` exists.
Checks if a `t:Explorer.Chain.TokenTransfer.t/0` of type ERC-721 or ERC-1155 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_or_erc1155_token_instance_exists(token_id, contract_address.hash)
:ok
iex> contract_address = insert(:address)
iex> token_id = 10
iex> insert(:token_transfer,
...> from_address: contract_address,
...> token_contract_address: contract_address,
...> token_ids: [token_id]
...> )
iex> Explorer.Chain.check_erc721_or_erc1155_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_or_erc1155_token_instance_exists(10, hash)
:not_found
"""
@spec check_erc721_or_erc1155_token_instance_exists(binary() | non_neg_integer(), Hash.Address.t()) ::
:ok | :not_found
def check_erc721_or_erc1155_token_instance_exists(token_id, hash) do
token_id
|> erc721_or_erc1155_token_instance_exist?(hash)
|> 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
@ -5563,6 +5624,49 @@ defmodule Explorer.Chain do
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.
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_or_erc1155_token_instance_exist?(token_id, contract_address.hash)
true
iex> contract_address = insert(:address)
iex> token_id = 10
iex> insert(:token_transfer,
...> from_address: contract_address,
...> token_contract_address: contract_address,
...> token_ids: [token_id]
...> )
iex> Explorer.Chain.erc721_or_erc1155_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_or_erc1155_token_instance_exist?(10, hash)
false
"""
@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
query =
from(tt in TokenTransfer,
where:
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)
end
defp boolean_to_check_result(true), do: :ok
defp boolean_to_check_result(false), do: :not_found

@ -177,7 +177,7 @@ defmodule Explorer.Chain.TokenTransfer do
from(
tt in TokenTransfer,
where: tt.token_contract_address_hash == ^token_address_hash,
where: tt.token_id == ^token_id,
where: tt.token_id == ^token_id or fragment("? @> ARRAY[?::decimal]", tt.token_ids, ^Decimal.new(token_id)),
where: not is_nil(tt.block_number),
preload: [{:transaction, :block}, :token, :from_address, :to_address],
order_by: [desc: tt.block_number]
@ -206,7 +206,9 @@ defmodule Explorer.Chain.TokenTransfer do
query =
from(
tt in TokenTransfer,
where: tt.token_contract_address_hash == ^token_address_hash and tt.token_id == ^token_id,
where:
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))),
select: fragment("COUNT(*)")
)

@ -0,0 +1,13 @@
defmodule Explorer.Repo.Migrations.CreateIndexTokenTransfersTokenIds do
use Ecto.Migration
def up do
execute("""
CREATE INDEX token_transfers_token_ids_index on token_transfers USING GIN ("token_ids")
""")
end
def down do
execute("DROP INDEX token_transfers_token_ids_index")
end
end

@ -0,0 +1,8 @@
defmodule Explorer.Repo.Migrations.CreateIndexesTokenInstancesTokenContractAddressHashTokenId do
use Ecto.Migration
def change do
create_if_not_exists(index(:token_instances, [:token_contract_address_hash, :token_id]))
create_if_not_exists(index(:token_instances, [:token_id]))
end
end
Loading…
Cancel
Save