diff --git a/CHANGELOG.md b/CHANGELOG.md index cb4d67d77d..2225df726e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ - [#4931](https://github.com/blockscout/blockscout/pull/4931) - Web3 modal with Wallet Connect for Write contract page and Staking Dapp ### Fixes +- [#5032](https://github.com/blockscout/blockscout/pull/5032) - Fix token transfer csv export - [#5020](https://github.com/blockscout/blockscout/pull/5020) - Token instance image display imrovement - [#5019](https://github.com/blockscout/blockscout/pull/5019) - Fix fetch_last_token_balance function termination - [#5011](https://github.com/blockscout/blockscout/pull/5011) - Fix `0x0` implementation address diff --git a/apps/block_scout_web/test/block_scout_web/controllers/address_transaction_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/address_transaction_controller_test.exs index c9be5bbcad..e28e6ac401 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/address_transaction_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/address_transaction_controller_test.exs @@ -167,8 +167,8 @@ defmodule BlockScoutWeb.AddressTransactionControllerTest do |> insert(from_address: address) |> with_block() - insert(:token_transfer, transaction: transaction, from_address: address) - insert(:token_transfer, transaction: transaction, to_address: address) + insert(:token_transfer, transaction: transaction, from_address: address, block_number: transaction.block_number) + insert(:token_transfer, transaction: transaction, to_address: address, block_number: transaction.block_number) from_period = Timex.format!(Timex.shift(Timex.now(), minutes: -1), "%Y-%m-%d", :strftime) to_period = Timex.format!(Timex.now(), "%Y-%m-%d", :strftime) diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index 2515f1b8e2..c165eb66a1 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -559,22 +559,67 @@ defmodule Explorer.Chain do @spec address_hash_to_token_transfers_including_contract(Hash.Address.t(), Keyword.t()) :: [TokenTransfer.t()] def address_hash_to_token_transfers_including_contract(address_hash, options \\ []) do paging_options = Keyword.get(options, :paging_options, @default_paging_options) + from_block = Keyword.get(options, :from_block) + to_block = Keyword.get(options, :to_block) query = - from( - token_transfer in TokenTransfer, - where: token_transfer.to_address_hash == ^address_hash, - or_where: token_transfer.from_address_hash == ^address_hash, - or_where: token_transfer.token_contract_address_hash == ^address_hash - ) + from_block + |> query_address_hash_to_token_transfers_including_contract(to_block, address_hash) + |> order_by([token_transfer], asc: token_transfer.block_number, asc: token_transfer.log_index) query - |> handle_paging_options(paging_options) + |> handle_token_transfer_paging_options(paging_options) |> preload(transaction: :block) |> preload(:token) |> Repo.all() end + defp query_address_hash_to_token_transfers_including_contract(nil, to_block, address_hash) + when not is_nil(to_block) do + from( + token_transfer in TokenTransfer, + where: + (token_transfer.to_address_hash == ^address_hash or + token_transfer.from_address_hash == ^address_hash or + token_transfer.token_contract_address_hash == ^address_hash) and + token_transfer.block_number <= ^to_block + ) + end + + defp query_address_hash_to_token_transfers_including_contract(from_block, nil, address_hash) + when not is_nil(from_block) do + from( + token_transfer in TokenTransfer, + where: + (token_transfer.to_address_hash == ^address_hash or + token_transfer.from_address_hash == ^address_hash or + token_transfer.token_contract_address_hash == ^address_hash) and + token_transfer.block_number >= ^from_block + ) + end + + defp query_address_hash_to_token_transfers_including_contract(from_block, to_block, address_hash) + when not is_nil(from_block) and not is_nil(to_block) do + from( + token_transfer in TokenTransfer, + where: + (token_transfer.to_address_hash == ^address_hash or + token_transfer.from_address_hash == ^address_hash or + token_transfer.token_contract_address_hash == ^address_hash) and + (token_transfer.block_number >= ^from_block and token_transfer.block_number <= ^to_block) + ) + end + + defp query_address_hash_to_token_transfers_including_contract(_, _, address_hash) do + from( + token_transfer in TokenTransfer, + where: + token_transfer.to_address_hash == ^address_hash or + token_transfer.from_address_hash == ^address_hash or + token_transfer.token_contract_address_hash == ^address_hash + ) + end + @spec address_to_logs(Hash.Address.t(), Keyword.t()) :: [Log.t()] def address_to_logs(address_hash, options \\ []) when is_list(options) do paging_options = Keyword.get(options, :paging_options) || %PagingOptions{page_size: 50} @@ -4258,6 +4303,14 @@ defmodule Explorer.Chain do |> limit(^paging_options.page_size) end + defp handle_token_transfer_paging_options(query, nil), do: query + + defp handle_token_transfer_paging_options(query, paging_options) do + query + |> TokenTransfer.page_token_transfer(paging_options) + |> limit(^paging_options.page_size) + end + defp join_association(query, [{association, nested_preload}], necessity) when is_atom(association) and is_atom(nested_preload) do case necessity do diff --git a/apps/explorer/lib/explorer/chain/address_token_transfer_csv_exporter.ex b/apps/explorer/lib/explorer/chain/address_token_transfer_csv_exporter.ex index 4b73db11c6..fa8139a412 100644 --- a/apps/explorer/lib/explorer/chain/address_token_transfer_csv_exporter.ex +++ b/apps/explorer/lib/explorer/chain/address_token_transfer_csv_exporter.ex @@ -8,7 +8,7 @@ defmodule Explorer.Chain.AddressTokenTransferCsvExporter do alias NimbleCSV.RFC4180 @page_size 150 - @paging_options %PagingOptions{page_size: @page_size + 1} + @paging_options %PagingOptions{page_size: @page_size + 1, asc_order: true} @spec export(Address.t(), String.t(), String.t()) :: Enumerable.t() def export(address, from_period, to_period) do @@ -30,7 +30,7 @@ defmodule Explorer.Chain.AddressTokenTransferCsvExporter do token_transfers = Chain.address_hash_to_token_transfers_including_contract(address_hash, options) - new_acc = token_transfers ++ acc + new_acc = acc ++ token_transfers case Enum.split(token_transfers, @page_size) do {_token_transfers, [%TokenTransfer{block_number: block_number, log_index: log_index}]} -> diff --git a/apps/explorer/lib/explorer/chain/token_transfer.ex b/apps/explorer/lib/explorer/chain/token_transfer.ex index 34ab5076c0..6a8022db35 100644 --- a/apps/explorer/lib/explorer/chain/token_transfer.ex +++ b/apps/explorer/lib/explorer/chain/token_transfer.ex @@ -219,6 +219,14 @@ defmodule Explorer.Chain.TokenTransfer do where(query, [tt], tt.token_id < ^token_id) end + def page_token_transfer(query, %PagingOptions{key: {block_number, log_index}, asc_order: true}) do + where( + query, + [tt], + tt.block_number > ^block_number or (tt.block_number == ^block_number and tt.log_index > ^log_index) + ) + end + def page_token_transfer(query, %PagingOptions{key: {block_number, log_index}}) do where( query, diff --git a/apps/explorer/lib/explorer/paging_options.ex b/apps/explorer/lib/explorer/paging_options.ex index cd7cbb2882..9af5b6bd6b 100644 --- a/apps/explorer/lib/explorer/paging_options.ex +++ b/apps/explorer/lib/explorer/paging_options.ex @@ -9,7 +9,8 @@ defmodule Explorer.PagingOptions do page_size: page_size, page_number: page_number, is_pending_tx: is_pending_tx, - is_index_in_asc_order: is_index_in_asc_order + is_index_in_asc_order: is_index_in_asc_order, + asc_order: asc_order } @typep key :: any() @@ -17,6 +18,7 @@ defmodule Explorer.PagingOptions do @typep page_number :: pos_integer() @typep is_pending_tx :: atom() @typep is_index_in_asc_order :: atom() + @typep asc_order :: atom() - defstruct [:key, :page_size, page_number: 1, is_pending_tx: false, is_index_in_asc_order: false] + defstruct [:key, :page_size, page_number: 1, is_pending_tx: false, is_index_in_asc_order: false, asc_order: false] end diff --git a/apps/explorer/test/explorer/chain/address_token_transfer_csv_exporter_test.exs b/apps/explorer/test/explorer/chain/address_token_transfer_csv_exporter_test.exs index 62d97cd3fd..99655c832f 100644 --- a/apps/explorer/test/explorer/chain/address_token_transfer_csv_exporter_test.exs +++ b/apps/explorer/test/explorer/chain/address_token_transfer_csv_exporter_test.exs @@ -12,7 +12,8 @@ defmodule Explorer.Chain.AddressTokenTransferCsvExporterTest do |> insert(from_address: address) |> with_block() - token_transfer = insert(:token_transfer, transaction: transaction, from_address: address) + token_transfer = + insert(:token_transfer, transaction: transaction, from_address: address, block_number: transaction.block_number) from_period = Timex.format!(Timex.shift(Timex.now(), minutes: -1), "%Y-%m-%d", :strftime) to_period = Timex.format!(Timex.now(), "%Y-%m-%d", :strftime)