diff --git a/apps/block_scout_web/lib/block_scout_web/templates/transaction/overview.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/transaction/overview.html.eex index 98acd3b3e6..a3e4490910 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/transaction/overview.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/transaction/overview.html.eex @@ -138,9 +138,10 @@ - <% token_transfer = assigns[:token_transfers] && erc20_token_transfer(@transaction, @token_transfers) %> - <%= if token_transfer do %> -
+ <%= cond do %> + <% erc20_token_transfer = assigns[:token_transfers] && erc20_token_transfer(@transaction, @token_transfers) -> %> + +
@@ -148,15 +149,32 @@

- <%= token_transfer_amount(token_transfer) %> - <%= link(token_symbol(token_transfer.token), to: token_path(BlockScoutWeb.Endpoint, :show, token_transfer.token.contract_address_hash)) %> + <%= token_transfer_amount(erc20_token_transfer) %> + <%= link(token_symbol(erc20_token_transfer.token), to: token_path(BlockScoutWeb.Endpoint, :show, erc20_token_transfer.token.contract_address_hash)) %>

-
- <% else %> -
+
+ <% erc721_token_transfer = assigns[:token_transfers] && erc721_token_transfer(@transaction, @token_transfers) -> %> + +
+ +
+
+

<%= gettext "ERC-721" %> <%= gettext "Token Transfer" %>

+
+

+ + <%= token_transfer_amount(erc721_token_transfer) %> + <%= link(token_symbol(erc721_token_transfer.token), to: token_path(BlockScoutWeb.Endpoint, :show, erc721_token_transfer.token.contract_address_hash)) %> + +

+
+
+
+ <% true -> %> +
diff --git a/apps/block_scout_web/lib/block_scout_web/views/transaction_view.ex b/apps/block_scout_web/lib/block_scout_web/views/transaction_view.ex index a6f58ac33e..402dfde53f 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/transaction_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/transaction_view.ex @@ -48,30 +48,67 @@ defmodule BlockScoutWeb.TransactionView do {unquote(TokenTransfer.transfer_function_signature()) <> params, ^zero_wei} -> types = [:address, {:uint, 256}] - try do - [address, value] = - params - |> Base.decode16!(case: :mixed) - |> TypeDecoder.decode_raw(types) + [address, value] = decode_params(params, types) - decimal_value = Decimal.new(value) + decimal_value = Decimal.new(value) - Enum.find(token_transfers, fn token_transfer -> - token_transfer.to_address_hash.bytes == address && token_transfer.amount == decimal_value - end) - rescue - _ -> nil - end + Enum.find(token_transfers, fn token_transfer -> + token_transfer.to_address_hash.bytes == address && token_transfer.amount == decimal_value + end) _ -> nil end + rescue + _ -> nil end def erc20_token_transfer(_, _) do nil end + def erc721_token_transfer( + %Transaction{ + status: :ok, + created_contract_address_hash: nil, + input: input, + value: value + }, + token_transfers + ) do + zero_wei = %Wei{value: Decimal.new(0)} + + # https://github.com/OpenZeppelin/openzeppelin-solidity/blob/master/contracts/token/ERC721/ERC721.sol#L35 + {from_address, to_address} = + case {to_string(input), value} do + {"0x23b872dd" <> params, ^zero_wei} -> + types = [:address, :address, {:uint, 256}] + [from_address, to_address, _value] = decode_params(params, types) + {from_address, to_address} + + {"0x42842e0e" <> params, ^zero_wei} -> + types = [:address, :address, {:uint, 256}] + [from_address, to_address, _value] = decode_params(params, types) + {from_address, to_address} + + {"0xb88d4fde" <> params, ^zero_wei} -> + types = [:address, :address, {:uint, 256}, :bytes] + [from_address, to_address, _value, _data] = decode_params(params, types) + {from_address, to_address} + + _ -> + nil + end + + Enum.find(token_transfers, fn token_transfer -> + token_transfer.from_address_hash.bytes == from_address && token_transfer.to_address_hash.bytes == to_address + end) + rescue + _ -> nil + end + + def erc721_token_transfer(_, _), do: nil + def processing_time_duration(%Transaction{block: nil}) do :pending end @@ -298,4 +335,10 @@ defmodule BlockScoutWeb.TransactionView do defp tab_name(["token_transfers"]), do: gettext("Token Transfers") defp tab_name(["internal_transactions"]), do: gettext("Internal Transactions") defp tab_name(["logs"]), do: gettext("Logs") + + defp decode_params(params, types) do + params + |> Base.decode16!(case: :mixed) + |> TypeDecoder.decode_raw(types) + end end diff --git a/apps/block_scout_web/test/block_scout_web/views/transaction_view_test.exs b/apps/block_scout_web/test/block_scout_web/views/transaction_view_test.exs index 2a3e5492ed..e8efeff6a0 100644 --- a/apps/block_scout_web/test/block_scout_web/views/transaction_view_test.exs +++ b/apps/block_scout_web/test/block_scout_web/views/transaction_view_test.exs @@ -47,6 +47,30 @@ defmodule BlockScoutWeb.TransactionViewTest do end end + describe "erc721_token_transfer/2" do + test "finds token transfer" do + from_address_hash = "0x7a30272c902563b712245696f0a81c5a0e45ddc8" + to_address_hash = "0xb544cead8b660aae9f2e37450f7be2ffbc501793" + from_address = insert(:address, hash: from_address_hash) + to_address = insert(:address, hash: to_address_hash) + block = insert(:block) + + transaction = + insert(:transaction, + input: + "0x23b872dd0000000000000000000000007a30272c902563b712245696f0a81c5a0e45ddc8000000000000000000000000b544cead8b660aae9f2e37450f7be2ffbc5017930000000000000000000000000000000000000000000000000000000000000002", + value: Decimal.new(0), + created_contract_address_hash: nil + ) + |> with_block(block, status: :ok) + + token_transfer = + insert(:token_transfer, from_address: from_address, to_address: to_address, transaction: transaction) + + assert TransactionView.erc721_token_transfer(transaction, [token_transfer]) == token_transfer + end + end + describe "processing_time_duration/2" do test "returns :pending if the transaction has no block" do transaction = build(:transaction, block: nil)