diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index 4c7914a7ff..4dc297fef1 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -938,7 +938,7 @@ defmodule Explorer.Chain do def stream_unfetched_token_balances(initial, reducer) when is_function(reducer, 2) do Repo.transaction( fn -> - query = from(tb in TokenBalance, where: is_nil(tb.value_fetched_at)) + query = TokenBalance.unfetched_token_balances() query |> Repo.stream(timeout: :infinity) diff --git a/apps/explorer/lib/explorer/chain/address/token_balance.ex b/apps/explorer/lib/explorer/chain/address/token_balance.ex index c8dec08d45..c7a9eefed5 100644 --- a/apps/explorer/lib/explorer/chain/address/token_balance.ex +++ b/apps/explorer/lib/explorer/chain/address/token_balance.ex @@ -129,4 +129,24 @@ defmodule Explorer.Chain.Address.TokenBalance do tb.value < ^value or (tb.value == ^value and tb.address_hash < ^address_hash) ) end + + {:ok, burn_address_hash} = Chain.string_to_address_hash("0x0000000000000000000000000000000000000000") + @burn_address_hash burn_address_hash + + @doc """ + Builds an `Ecto.Query` to fetch the unfetched token balances. + + Unfetched token balances are the ones that have the column `value_fetched_at` nil. This query also + ignores the burn_address for tokens ERC-721 since the most tokens ERC-721 don't allow get the + balance for burn_address. + """ + def unfetched_token_balances do + from( + tb in TokenBalance, + join: t in Token, + on: tb.token_contract_address_hash == t.contract_address_hash, + where: is_nil(tb.value_fetched_at), + where: (tb.address_hash != ^@burn_address_hash and t.type != "ERC-721") or t.type == "ERC-20" + ) + end end diff --git a/apps/explorer/test/explorer/chain/address/token_balance_test.ex b/apps/explorer/test/explorer/chain/address/token_balance_test.ex new file mode 100644 index 0000000000..4c59c675bf --- /dev/null +++ b/apps/explorer/test/explorer/chain/address/token_balance_test.ex @@ -0,0 +1,79 @@ +defmodule Explorer.Chain.Address.TokenBalanceTest do + use Explorer.DataCase + + alias Explorer.Repo + alias Explorer.Chain.Address.TokenBalance + + describe "unfetched_token_balances/0" do + test "returns only the token balances that have value_fetched_at nil" do + address = insert(:address, hash: "0xc45e4830dff873cf8b70de2b194d0ddd06ef651e") + token_balance = insert(:token_balance, value_fetched_at: nil, address: address) + insert(:token_balance) + + result = + TokenBalance.unfetched_token_balances() + |> Repo.all() + |> List.first() + + assert result.block_number == token_balance.block_number + end + + test "does not ignore token balance when the address isn't the burn address with Token ERC-20" do + address = insert(:address, hash: "0xc45e4830dff873cf8b70de2b194d0ddd06ef651e") + token = insert(:token, type: "ERC-20") + + token_balance = + insert( + :token_balance, + value_fetched_at: nil, + address: address, + token_contract_address_hash: token.contract_address_hash + ) + + result = + TokenBalance.unfetched_token_balances() + |> Repo.all() + |> List.first() + + assert result.block_number == token_balance.block_number + end + + test "ignores the burn_address when the token type is ERC-721" do + burn_address = insert(:address, hash: "0x0000000000000000000000000000000000000000") + token = insert(:token, type: "ERC-721") + + insert( + :token_balance, + address: burn_address, + token_contract_address_hash: token.contract_address_hash, + value_fetched_at: nil + ) + + result = + TokenBalance.unfetched_token_balances() + |> Repo.all() + + assert result == [] + end + + test "does not ignore the burn_address when the token type is ERC-20" do + burn_address = insert(:address, hash: "0x0000000000000000000000000000000000000000") + token = insert(:token, type: "ERC-20") + + token_balance = + insert( + :token_balance, + address: burn_address, + token_contract_address_hash: token.contract_address_hash, + value_fetched_at: nil + ) + + result = + TokenBalance.unfetched_token_balances() + |> Repo.all() + |> List.first() + + assert result.block_number == token_balance.block_number + end + end +end diff --git a/apps/explorer/test/explorer/chain_test.exs b/apps/explorer/test/explorer/chain_test.exs index 69d99c71fa..285f2a8523 100644 --- a/apps/explorer/test/explorer/chain_test.exs +++ b/apps/explorer/test/explorer/chain_test.exs @@ -2326,8 +2326,9 @@ defmodule Explorer.ChainTest do end describe "stream_unfetched_token_balances/2" do - test "returns only the token balances that have value_fetched_at nil" do - token_balance = insert(:token_balance, value_fetched_at: nil) + test "executes the given reducer with the query result" do + address = insert(:address, hash: "0xc45e4830dff873cf8b70de2b194d0ddd06ef651e") + token_balance = insert(:token_balance, value_fetched_at: nil, address: address) insert(:token_balance) assert Chain.stream_unfetched_token_balances([], &[&1.block_number | &2]) == {:ok, [token_balance.block_number]} diff --git a/apps/indexer/lib/indexer/address/token_balances.ex b/apps/indexer/lib/indexer/address/token_balances.ex index 830db6fc9e..4d47c0213a 100644 --- a/apps/indexer/lib/indexer/address/token_balances.ex +++ b/apps/indexer/lib/indexer/address/token_balances.ex @@ -8,16 +8,17 @@ defmodule Indexer.Address.TokenBalances do end defp reducer({:token_transfers_params, token_transfers_params}, initial) when is_list(token_transfers_params) do - Enum.reduce(token_transfers_params, initial, fn %{ - block_number: block_number, - from_address_hash: from_address_hash, - to_address_hash: to_address_hash, - token_contract_address_hash: token_contract_address_hash - }, - acc - when is_integer(block_number) and is_binary(from_address_hash) and - is_binary(to_address_hash) and - is_binary(token_contract_address_hash) -> + token_transfers_params + |> ignore_burn_address_transfers_for_token_erc_721 + |> Enum.reduce(initial, fn %{ + block_number: block_number, + from_address_hash: from_address_hash, + to_address_hash: to_address_hash, + token_contract_address_hash: token_contract_address_hash + }, + acc + when is_integer(block_number) and is_binary(from_address_hash) and + is_binary(to_address_hash) and is_binary(token_contract_address_hash) -> acc |> MapSet.put(%{ address_hash: from_address_hash, @@ -36,4 +37,20 @@ defmodule Indexer.Address.TokenBalances do }) end) end + + defp ignore_burn_address_transfers_for_token_erc_721(token_transfers_params) do + Enum.filter(token_transfers_params, &do_filter_burn_address/1) + end + + def do_filter_burn_address(%{from_address_hash: "0x0000000000000000000000000000000000000000", token_type: "ERC-721"}) do + false + end + + def do_filter_burn_address(%{to_address_hash: "0x0000000000000000000000000000000000000000", token_type: "ERC-721"}) do + false + end + + def do_filter_burn_address(_token_balance_param) do + true + end end diff --git a/apps/indexer/lib/indexer/token_transfers.ex b/apps/indexer/lib/indexer/token_transfers.ex index c7483a0d5b..87c30b1766 100644 --- a/apps/indexer/lib/indexer/token_transfers.ex +++ b/apps/indexer/lib/indexer/token_transfers.ex @@ -44,7 +44,8 @@ defmodule Indexer.TokenTransfers do from_address_hash: truncate_address_hash(log.second_topic), to_address_hash: truncate_address_hash(log.third_topic), token_contract_address_hash: log.address_hash, - transaction_hash: log.transaction_hash + transaction_hash: log.transaction_hash, + token_type: "ERC-20" } token = %{ @@ -67,7 +68,8 @@ defmodule Indexer.TokenTransfers do to_address_hash: truncate_address_hash(log.third_topic), token_contract_address_hash: log.address_hash, token_id: token_id || 0, - transaction_hash: log.transaction_hash + transaction_hash: log.transaction_hash, + token_type: "ERC-721" } token = %{ @@ -90,7 +92,8 @@ defmodule Indexer.TokenTransfers do to_address_hash: encode_address_hash(to_address_hash), token_contract_address_hash: log.address_hash, token_id: token_id, - transaction_hash: log.transaction_hash + transaction_hash: log.transaction_hash, + token_type: "ERC-721" } token = %{ diff --git a/apps/indexer/test/indexer/address/token_balances_test.exs b/apps/indexer/test/indexer/address/token_balances_test.exs index 18c2faa658..885d42dcb9 100644 --- a/apps/indexer/test/indexer/address/token_balances_test.exs +++ b/apps/indexer/test/indexer/address/token_balances_test.exs @@ -34,5 +34,43 @@ defmodule Indexer.Address.TokenBalancesTest do assert %{address_hash: to_address_hash, block_number: block_number} assert %{address_hash: token_contract_address_hash, block_number: block_number} end + + test "does not set params when the from_address_hash is the burn address for the Token ERC-721" do + block_number = 1 + from_address_hash = "0x0000000000000000000000000000000000000000" + to_address_hash = "0x5b8410f67eb8040bb1cd1e8a4ff9d5f6ce678a15" + token_contract_address_hash = "0xe18035bf8712672935fdb4e5e431b1a0183d2dfc" + + token_transfer_params = %{ + block_number: block_number, + from_address_hash: from_address_hash, + to_address_hash: to_address_hash, + token_contract_address_hash: token_contract_address_hash, + token_type: "ERC-721" + } + + params_set = TokenBalances.params_set(%{token_transfers_params: [token_transfer_params]}) + + assert MapSet.size(params_set) == 0 + end + + test "does not set params when the to_address_hash is the burn address for the Token ERC-721" do + block_number = 1 + from_address_hash = "0x5b8410f67eb8040bb1cd1e8a4ff9d5f6ce678a15" + to_address_hash = "0x0000000000000000000000000000000000000000" + token_contract_address_hash = "0xe18035bf8712672935fdb4e5e431b1a0183d2dfc" + + token_transfer_params = %{ + block_number: block_number, + from_address_hash: from_address_hash, + to_address_hash: to_address_hash, + token_contract_address_hash: token_contract_address_hash, + token_type: "ERC-721" + } + + params_set = TokenBalances.params_set(%{token_transfers_params: [token_transfer_params]}) + + assert MapSet.size(params_set) == 0 + end end end diff --git a/apps/indexer/test/indexer/token_transfers_test.exs b/apps/indexer/test/indexer/token_transfers_test.exs index 47d0dafc35..5560647f33 100644 --- a/apps/indexer/test/indexer/token_transfers_test.exs +++ b/apps/indexer/test/indexer/token_transfers_test.exs @@ -66,7 +66,8 @@ defmodule Indexer.TokenTransfersTest do to_address_hash: truncated_hash(log_3.third_topic), token_contract_address_hash: log_3.address_hash, token_id: 183, - transaction_hash: log_3.transaction_hash + transaction_hash: log_3.transaction_hash, + token_type: "ERC-721" }, %{ amount: Decimal.new(17_000_000_000_000_000_000), @@ -75,7 +76,8 @@ defmodule Indexer.TokenTransfersTest do from_address_hash: truncated_hash(log_1.second_topic), to_address_hash: truncated_hash(log_1.third_topic), token_contract_address_hash: log_1.address_hash, - transaction_hash: log_1.transaction_hash + transaction_hash: log_1.transaction_hash, + token_type: "ERC-20" } ] } @@ -113,7 +115,8 @@ defmodule Indexer.TokenTransfersTest do to_address_hash: "0xbe8cdfc13ffda20c844ac3da2b53a23ac5787f1e", token_contract_address_hash: log.address_hash, token_id: 14_939, - transaction_hash: log.transaction_hash + transaction_hash: log.transaction_hash, + token_type: "ERC-721" } ] }