diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/address_transaction_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/address_transaction_controller.ex index d33d54ccb8..f719d6073d 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/address_transaction_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_transaction_controller.ex @@ -19,7 +19,10 @@ defmodule BlockScoutWeb.AddressTransactionController do [created_contract_address: :names] => :optional, [from_address: :names] => :optional, [to_address: :names] => :optional, - :token_transfers => :optional + [token_transfers: :token] => :optional, + [token_transfers: :to_address] => :optional, + [token_transfers: :from_address] => :optional, + [token_transfers: :token_contract_address] => :optional } ] @@ -81,7 +84,7 @@ defmodule BlockScoutWeb.AddressTransactionController do {:ok, address} <- Chain.hash_to_address(address_hash) do pending_options = @transaction_necessity_by_association - |> Keyword.merge(paging_options(%{})) + |> Keyword.merge(paging_options(params)) |> Keyword.merge(current_filter(params)) full_options = put_in(pending_options, [:necessity_by_association, :block], :required) diff --git a/apps/block_scout_web/lib/block_scout_web/templates/transaction/_tile.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/transaction/_tile.html.eex index 2ae13afdb6..9d2d2c5a50 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/transaction/_tile.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/transaction/_tile.html.eex @@ -52,14 +52,17 @@ <%= if involves_token_transfers?(@transaction) do %>
- <% [first_token_transfer | remaining_token_transfers]= @transaction.token_transfers %> + <% [first_token_transfer | remaining_token_transfers] = @transaction.token_transfers %> + <%= render "_token_transfer.html", address: assigns[:current_address], token_transfer: first_token_transfer %> +
<%= for token_transfer <- remaining_token_transfers do %> <%= render "_token_transfer.html", address: assigns[:current_address], token_transfer: token_transfer %> <% end %>
+ <%= if Enum.any?(remaining_token_transfers) do %>
diff --git a/apps/block_scout_web/priv/gettext/default.pot b/apps/block_scout_web/priv/gettext/default.pot index eba53626ff..e3b939482d 100644 --- a/apps/block_scout_web/priv/gettext/default.pot +++ b/apps/block_scout_web/priv/gettext/default.pot @@ -1080,12 +1080,12 @@ msgid "View Contract" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/transaction/_tile.html.eex:67 +#: lib/block_scout_web/templates/transaction/_tile.html.eex:70 msgid "View Less Transfers" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/transaction/_tile.html.eex:66 +#: lib/block_scout_web/templates/transaction/_tile.html.eex:69 msgid "View More Transfers" msgstr "" diff --git a/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po b/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po index 8aac131ef9..d96c8a2539 100644 --- a/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po +++ b/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po @@ -1080,12 +1080,12 @@ msgid "View Contract" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/transaction/_tile.html.eex:67 +#: lib/block_scout_web/templates/transaction/_tile.html.eex:70 msgid "View Less Transfers" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/transaction/_tile.html.eex:66 +#: lib/block_scout_web/templates/transaction/_tile.html.eex:69 msgid "View More Transfers" msgstr "" diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index 8cfb97bfcf..c3c08ef8be 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -219,38 +219,22 @@ defmodule Explorer.Chain do necessity_by_association = Keyword.get(options, :necessity_by_association, %{}) paging_options = Keyword.get(options, :paging_options, @default_paging_options) - transaction_matches = - direction - |> case do - :from -> [:from_address_hash] - :to -> [:to_address_hash, :created_contract_address_hash] - _ -> [:from_address_hash, :to_address_hash, :created_contract_address_hash] - end - |> Enum.map(fn address_field -> - paging_options - |> fetch_transactions() - |> Transaction.where_address_fields_match(address_hash, address_field) - |> join_associations(necessity_by_association) - |> Transaction.preload_token_transfers(address_hash) - |> Repo.all() - |> MapSet.new() - end) - - token_transfer_matches = + {:ok, address_bytes} = Explorer.Chain.Hash.Address.dump(address_hash) + + token_transfers_dynamic = TokenTransfer.dynamic_any_address_fields_match(direction, address_bytes) + + transaction_dynamic = + Transaction.dynamic_where_address_hash_matches(address_hash, direction, token_transfers_dynamic) + + base_query = paging_options |> fetch_transactions() - |> TokenTransfer.where_address_fields_match(address_hash, direction) |> join_associations(necessity_by_association) |> Transaction.preload_token_transfers(address_hash) - |> Repo.all() - |> MapSet.new() - transaction_matches - |> Enum.reduce(token_transfer_matches, &MapSet.union/2) - |> MapSet.to_list() - |> Enum.sort_by(& &1.index, &>=/2) - |> Enum.sort_by(& &1.block_number, &>=/2) - |> Enum.slice(0..paging_options.page_size) + base_query + |> from(where: ^transaction_dynamic) + |> Repo.all() end @doc """ diff --git a/apps/explorer/lib/explorer/chain/token_transfer.ex b/apps/explorer/lib/explorer/chain/token_transfer.ex index 43dba819f6..ccebaa2ee6 100644 --- a/apps/explorer/lib/explorer/chain/token_transfer.ex +++ b/apps/explorer/lib/explorer/chain/token_transfer.ex @@ -151,22 +151,57 @@ defmodule Explorer.Chain.TokenTransfer do ) end - def where_address_fields_match(query, address_hash, :from) do - query - |> join(:left, [transaction], tt in assoc(transaction, :token_transfers)) - |> where([_transaction, tt], tt.from_address_hash == ^address_hash) + @doc """ + Builds a dynamic query expression to identify if there is a token transfer + related to the hash. + """ + def dynamic_any_address_fields_match(:to, address_bytes) do + dynamic( + [t], + t.hash == + fragment( + ~s""" + (SELECT tt.transaction_hash + FROM "token_transfers" AS tt + WHERE (tt."to_address_hash" = ?) + LIMIT 1) + """, + ^address_bytes + ) + ) end - def where_address_fields_match(query, address_hash, :to) do - query - |> join(:left, [transaction], tt in assoc(transaction, :token_transfers)) - |> where([_transaction, tt], tt.to_address_hash == ^address_hash) + def dynamic_any_address_fields_match(:from, address_bytes) do + dynamic( + [t], + t.hash == + fragment( + ~s""" + (SELECT tt.transaction_hash + FROM "token_transfers" AS tt + WHERE (tt."from_address_hash" = ?) + LIMIT 1) + """, + ^address_bytes + ) + ) end - def where_address_fields_match(query, address_hash, _) do - query - |> 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) + def dynamic_any_address_fields_match(_, address_bytes) do + dynamic( + [t], + t.hash == + fragment( + ~s""" + (SELECT tt.transaction_hash + FROM "token_transfers" AS tt + WHERE ((tt."to_address_hash" = ?) OR (tt."from_address_hash" = ?)) + LIMIT 1) + """, + ^address_bytes, + ^address_bytes + ) + ) end @doc """ diff --git a/apps/explorer/lib/explorer/chain/transaction.ex b/apps/explorer/lib/explorer/chain/transaction.ex index fa69b98fe2..fecf72722c 100644 --- a/apps/explorer/lib/explorer/chain/transaction.ex +++ b/apps/explorer/lib/explorer/chain/transaction.ex @@ -3,7 +3,7 @@ defmodule Explorer.Chain.Transaction do use Explorer.Schema - import Ecto.Query, only: [from: 2, preload: 3, where: 3, subquery: 1] + import Ecto.Query, only: [dynamic: 2, from: 2, preload: 3, subquery: 1, where: 3] alias Ecto.Changeset @@ -427,6 +427,32 @@ defmodule Explorer.Chain.Transaction do where(query, [t], field(t, ^address_field) == ^address_hash) end + @doc """ + Builds a dynamic query expression to identify if there is a transaction + related to the hash. + """ + def dynamic_where_address_hash_matches(address_hash, :to, dynamic) do + dynamic( + [t], + t.to_address_hash == ^address_hash or t.created_contract_address_hash == ^address_hash or ^dynamic + ) + end + + def dynamic_where_address_hash_matches(address_hash, :from, dynamic) do + dynamic( + [t], + t.from_address_hash == ^address_hash or ^dynamic + ) + end + + def dynamic_where_address_hash_matches(address_hash, _, dynamic) do + dynamic( + [t], + t.to_address_hash == ^address_hash or t.from_address_hash == ^address_hash or + t.created_contract_address_hash == ^address_hash or ^dynamic + ) + end + @collated_fields ~w(block_number cumulative_gas_used gas_used index)a @collated_message "can't be blank when the transaction is collated into a block" diff --git a/apps/explorer/test/explorer/chain_test.exs b/apps/explorer/test/explorer/chain_test.exs index 0ed17acb4e..47d950f143 100644 --- a/apps/explorer/test/explorer/chain_test.exs +++ b/apps/explorer/test/explorer/chain_test.exs @@ -208,15 +208,14 @@ defmodule Explorer.ChainTest do transaction = :transaction - |> insert() + |> insert(to_address: address, to_address_hash: address.hash) |> with_block() - insert(:token_transfer, to_address: address, transaction: transaction) - - transaction = - Transaction - |> Repo.get!(transaction.hash) - |> Repo.preload([:block, :to_address, :from_address, token_transfers: :token]) + insert( + :token_transfer, + to_address: address, + transaction: transaction + ) assert [transaction.hash] == Chain.address_to_transactions(address) @@ -228,33 +227,75 @@ defmodule Explorer.ChainTest do transaction = :transaction - |> insert() + |> insert(to_address: address) |> with_block() - token_transfer = insert(:token_transfer, to_address: address, transaction: transaction) - insert(:token_transfer, to_address: build(:address), transaction: transaction) + token_transfer = + insert( + :token_transfer, + to_address: address, + transaction: transaction + ) + + insert( + :token_transfer, + to_address: build(:address), + transaction: transaction + ) + + transaction = + address + |> Chain.address_to_transactions() + |> List.first() - transaction = Chain.address_to_transactions(address) |> List.first() + token_transfers_related = + Enum.map( + transaction.token_transfers, + &{&1.transaction_hash, &1.log_index} + ) - assert transaction.token_transfers |> Enum.map(&{&1.transaction_hash, &1.log_index}) == [ + assert token_transfers_related == [ {token_transfer.transaction_hash, token_transfer.log_index} ] end test "returns just the token transfers related to the given contract address" do - contract_address = insert(:address, contract_code: Factory.data("contract_code")) + contract_address = + insert( + :address, + contract_code: Factory.data("contract_code") + ) transaction = :transaction - |> insert() + |> insert(to_address: contract_address) |> with_block() - token_transfer = insert(:token_transfer, to_address: contract_address, transaction: transaction) - insert(:token_transfer, to_address: build(:address), transaction: transaction) + token_transfer = + insert( + :token_transfer, + to_address: contract_address, + transaction: transaction + ) - transaction = Chain.address_to_transactions(contract_address) |> List.first() + insert( + :token_transfer, + to_address: build(:address), + transaction: transaction + ) + + transaction = + contract_address + |> Chain.address_to_transactions() + |> List.first() + + token_transfers_contract_address = + Enum.map( + transaction.token_transfers, + &{&1.transaction_hash, &1.log_index} + ) - assert Enum.map(transaction.token_transfers, &{&1.transaction_hash, &1.log_index}) == [ + assert token_transfers_contract_address == [ {token_transfer.transaction_hash, token_transfer.log_index} ] end @@ -289,7 +330,7 @@ defmodule Explorer.ChainTest do address = insert(:address) second_page_hashes = - 50 + 2 |> insert_list(:transaction, from_address: address) |> with_block() |> Enum.map(& &1.hash) @@ -302,7 +343,10 @@ defmodule Explorer.ChainTest do assert second_page_hashes == address |> Chain.address_to_transactions( - paging_options: %PagingOptions{key: {block_number, index}, page_size: 50} + paging_options: %PagingOptions{ + key: {block_number, index}, + page_size: 2 + } ) |> Enum.map(& &1.hash) |> Enum.reverse()