From 1c8e59071937f04d1796a0dd7f12f569867dbddf Mon Sep 17 00:00:00 2001 From: Qwerty5Uiop Date: Tue, 30 Aug 2022 20:32:48 +0400 Subject: [PATCH 1/6] Fix ERC-1155 tokens fetching --- .../tokens/instance/transfer_controller.ex | 2 +- .../controllers/tokens/instance_controller.ex | 2 +- .../instance/transfer_controller_test.exs | 44 ++++++++ .../tokens/instance_controller_test.exs | 23 ++++ apps/explorer/lib/explorer/chain.ex | 106 +++++++++++++++++- .../lib/explorer/chain/token_transfer.ex | 6 +- 6 files changed, 177 insertions(+), 6 deletions(-) create mode 100644 apps/block_scout_web/test/block_scout_web/controllers/tokens/instance/transfer_controller_test.exs diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/tokens/instance/transfer_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/tokens/instance/transfer_controller.ex index 7350c31881..3aa9c61878 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/tokens/instance/transfer_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/tokens/instance/transfer_controller.ex @@ -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", diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/tokens/instance_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/tokens/instance_controller.ex index 1696ac1339..d7d0a4d8c4 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/tokens/instance_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/tokens/instance_controller.ex @@ -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) diff --git a/apps/block_scout_web/test/block_scout_web/controllers/tokens/instance/transfer_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/tokens/instance/transfer_controller_test.exs new file mode 100644 index 0000000000..ee04094cc9 --- /dev/null +++ b/apps/block_scout_web/test/block_scout_web/controllers/tokens/instance/transfer_controller_test.exs @@ -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 diff --git a/apps/block_scout_web/test/block_scout_web/controllers/tokens/instance_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/tokens/instance_controller_test.exs index 10d8831331..7ced36daf4 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/tokens/instance_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/tokens/instance_controller_test.exs @@ -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 diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index ac9ff7acb6..59df00fd21 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -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 instance.token_id in tt.token_ids), + where: + tt.token_contract_address_hash == ^token_contract_address and + (tt.token_id == ^token_id or ^token_id in tt.token_ids), + 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,47 @@ 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 ^token_id in tt.token_ids) + ) + + Repo.exists?(query) + end + defp boolean_to_check_result(true), do: :ok defp boolean_to_check_result(false), do: :not_found diff --git a/apps/explorer/lib/explorer/chain/token_transfer.ex b/apps/explorer/lib/explorer/chain/token_transfer.ex index fe17971fb5..eb8c7265e2 100644 --- a/apps/explorer/lib/explorer/chain/token_transfer.ex +++ b/apps/explorer/lib/explorer/chain/token_transfer.ex @@ -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 ^token_id in tt.token_ids, 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 ^token_id in tt.token_ids), select: fragment("COUNT(*)") ) From df4477aa901c1a0acbc03c91d57b20a10ed312a4 Mon Sep 17 00:00:00 2001 From: Qwerty5Uiop Date: Wed, 31 Aug 2022 20:52:50 +0400 Subject: [PATCH 2/6] Fix test envs --- .../test/block_scout_web/views/api_docs_view_test.exs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/block_scout_web/test/block_scout_web/views/api_docs_view_test.exs b/apps/block_scout_web/test/block_scout_web/views/api_docs_view_test.exs index 764c982063..cc1c9f1297 100644 --- a/apps/block_scout_web/test/block_scout_web/views/api_docs_view_test.exs +++ b/apps/block_scout_web/test/block_scout_web/views/api_docs_view_test.exs @@ -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 From 384aa88178decf7773070d24140c2694061225d8 Mon Sep 17 00:00:00 2001 From: Qwerty5Uiop Date: Thu, 1 Sep 2022 23:17:59 +0400 Subject: [PATCH 3/6] Check token_ids via @> --- apps/explorer/lib/explorer/chain.ex | 8 +++++--- apps/explorer/lib/explorer/chain/token_transfer.ex | 4 ++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index 59df00fd21..54b7623646 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -4889,10 +4889,10 @@ defmodule Explorer.Chain do left_join: instance in Instance, on: tt.token_contract_address_hash == instance.token_contract_address_hash and - (tt.token_id == instance.token_id or instance.token_id in tt.token_ids), + (tt.token_id == instance.token_id or fragment("? @> ARRAY[?::numeric]", tt.token_ids, instance.token_id)), where: tt.token_contract_address_hash == ^token_contract_address and - (tt.token_id == ^token_id or ^token_id in tt.token_ids), + (tt.token_id == ^token_id or fragment("? @> ARRAY[?::numeric]", tt.token_ids, ^token_id)), limit: 1, select: %{tt | instance: instance} ) @@ -5659,7 +5659,9 @@ defmodule Explorer.Chain do 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 ^token_id in tt.token_ids) + where: + tt.token_contract_address_hash == ^hash and + (tt.token_id == ^token_id or fragment("? @> ARRAY[?::numeric]", tt.token_ids, ^token_id)) ) Repo.exists?(query) diff --git a/apps/explorer/lib/explorer/chain/token_transfer.ex b/apps/explorer/lib/explorer/chain/token_transfer.ex index eb8c7265e2..0449a6df60 100644 --- a/apps/explorer/lib/explorer/chain/token_transfer.ex +++ b/apps/explorer/lib/explorer/chain/token_transfer.ex @@ -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 or ^token_id in tt.token_ids, + where: tt.token_id == ^token_id or fragment("? @> ARRAY[?::numeric]", tt.token_ids, ^token_id), where: not is_nil(tt.block_number), preload: [{:transaction, :block}, :token, :from_address, :to_address], order_by: [desc: tt.block_number] @@ -208,7 +208,7 @@ defmodule Explorer.Chain.TokenTransfer do tt in TokenTransfer, where: tt.token_contract_address_hash == ^token_address_hash and - (tt.token_id == ^token_id or ^token_id in tt.token_ids), + (tt.token_id == ^token_id or fragment("? @> ARRAY[?::numeric]", tt.token_ids, ^token_id)), select: fragment("COUNT(*)") ) From 01e7d696dcf07e55a382ee8dd7d79fd633136af8 Mon Sep 17 00:00:00 2001 From: Qwerty5Uiop Date: Fri, 2 Sep 2022 00:36:07 +0400 Subject: [PATCH 4/6] Fix token_ids array type cast --- apps/explorer/lib/explorer/chain.ex | 6 +++--- apps/explorer/lib/explorer/chain/token_transfer.ex | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index 54b7623646..8c121738bd 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -4889,10 +4889,10 @@ defmodule Explorer.Chain do 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[?::numeric]", tt.token_ids, instance.token_id)), + (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[?::numeric]", tt.token_ids, ^token_id)), + (tt.token_id == ^token_id or fragment("? @> ARRAY[?::decimal]", tt.token_ids, ^Decimal.new(token_id))), limit: 1, select: %{tt | instance: instance} ) @@ -5661,7 +5661,7 @@ defmodule Explorer.Chain do from(tt in TokenTransfer, where: tt.token_contract_address_hash == ^hash and - (tt.token_id == ^token_id or fragment("? @> ARRAY[?::numeric]", tt.token_ids, ^token_id)) + (tt.token_id == ^token_id or fragment("? @> ARRAY[?::decimal]", tt.token_ids, ^Decimal.new(token_id))) ) Repo.exists?(query) diff --git a/apps/explorer/lib/explorer/chain/token_transfer.ex b/apps/explorer/lib/explorer/chain/token_transfer.ex index 0449a6df60..4c23267682 100644 --- a/apps/explorer/lib/explorer/chain/token_transfer.ex +++ b/apps/explorer/lib/explorer/chain/token_transfer.ex @@ -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 or fragment("? @> ARRAY[?::numeric]", tt.token_ids, ^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] @@ -208,7 +208,7 @@ defmodule Explorer.Chain.TokenTransfer do tt in TokenTransfer, where: tt.token_contract_address_hash == ^token_address_hash and - (tt.token_id == ^token_id or fragment("? @> ARRAY[?::numeric]", tt.token_ids, ^token_id)), + (tt.token_id == ^token_id or fragment("? @> ARRAY[?::decimal]", tt.token_ids, ^Decimal.new(token_id))), select: fragment("COUNT(*)") ) From d53bd8f1ba4435430779885b56e6778af36b5116 Mon Sep 17 00:00:00 2001 From: Qwerty5Uiop Date: Fri, 2 Sep 2022 14:44:10 +0400 Subject: [PATCH 5/6] Added token_transfers and token_instances migrations --- ...03213_create_index_token_transfers_token_ids.exs | 13 +++++++++++++ ...stances_token_contract_address_hash_token_id.exs | 8 ++++++++ 2 files changed, 21 insertions(+) create mode 100644 apps/explorer/priv/repo/migrations/20220902103213_create_index_token_transfers_token_ids.exs create mode 100644 apps/explorer/priv/repo/migrations/20220902103527_create_indexes_token_instances_token_contract_address_hash_token_id.exs diff --git a/apps/explorer/priv/repo/migrations/20220902103213_create_index_token_transfers_token_ids.exs b/apps/explorer/priv/repo/migrations/20220902103213_create_index_token_transfers_token_ids.exs new file mode 100644 index 0000000000..e7f30e9cc0 --- /dev/null +++ b/apps/explorer/priv/repo/migrations/20220902103213_create_index_token_transfers_token_ids.exs @@ -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 diff --git a/apps/explorer/priv/repo/migrations/20220902103527_create_indexes_token_instances_token_contract_address_hash_token_id.exs b/apps/explorer/priv/repo/migrations/20220902103527_create_indexes_token_instances_token_contract_address_hash_token_id.exs new file mode 100644 index 0000000000..a91fc7d0fe --- /dev/null +++ b/apps/explorer/priv/repo/migrations/20220902103527_create_indexes_token_instances_token_contract_address_hash_token_id.exs @@ -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 From c4e06cb4e9892c33c3dbab5043c8532645c7fafe Mon Sep 17 00:00:00 2001 From: Qwerty5Uiop Date: Fri, 2 Sep 2022 17:14:19 +0400 Subject: [PATCH 6/6] Update Changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 844ac563d5..755733dc3c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,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 - [#5836](https://github.com/blockscout/blockscout/pull/5836) - Bump comeonin from 4.1.2 to 5.3.3