ERC-1155 transfers support in UI

(cherry picked from commit d8b6f073c1)
pull/4761/head
Viktor Baranov 4 years ago
parent 5e5dc6eb65
commit c685cd00fd
  1. 9
      apps/block_scout_web/lib/block_scout_web/templates/address_token_balance/_token_balances.html.eex
  2. 5
      apps/block_scout_web/lib/block_scout_web/templates/address_token_balance/_tokens.html.eex
  3. 12
      apps/block_scout_web/lib/block_scout_web/templates/common_components/_token_transfer_type_display_name.html.eex
  4. 2
      apps/block_scout_web/lib/block_scout_web/templates/tokens/holder/_token_balances.html.eex
  5. 9
      apps/block_scout_web/lib/block_scout_web/templates/tokens/transfer/_token_transfer.html.eex
  6. 7
      apps/block_scout_web/lib/block_scout_web/templates/transaction/_total_transfers.html.eex
  7. 9
      apps/block_scout_web/lib/block_scout_web/templates/transaction_token_transfer/_token_transfer.html.eex
  8. 6
      apps/block_scout_web/lib/block_scout_web/views/api/rpc/address_view.ex
  9. 8
      apps/block_scout_web/lib/block_scout_web/views/tokens/helpers.ex
  10. 12
      apps/block_scout_web/lib/block_scout_web/views/tokens/holder_view.ex
  11. 1
      apps/block_scout_web/lib/block_scout_web/views/tokens/overview_view.ex
  12. 1
      apps/block_scout_web/lib/block_scout_web/views/tokens/transfer_view.ex
  13. 2
      apps/block_scout_web/lib/block_scout_web/views/transaction_token_transfer_view.ex
  14. 37
      apps/block_scout_web/lib/block_scout_web/views/transaction_view.ex
  15. 6
      apps/block_scout_web/test/block_scout_web/views/tokens/holder_view_test.exs
  16. 4
      apps/ethereum_jsonrpc/lib/ethereum_jsonrpc.ex
  17. 32
      apps/explorer/lib/explorer/chain.ex
  18. 8
      apps/explorer/lib/explorer/chain/address/current_token_balance.ex
  19. 1
      apps/explorer/lib/explorer/chain/token.ex
  20. 1
      apps/explorer/lib/explorer/chain/token_transfer.ex
  21. 39
      apps/explorer/lib/explorer/token/balance_reader.ex
  22. 12
      apps/explorer/priv/repo/migrations/20210422115740_add_token_id_to_current_token_balances.exs
  23. 9
      apps/explorer/test/explorer/chain/import_test.exs
  24. 16
      apps/indexer/test/indexer/fetcher/token_balance_test.exs

@ -59,6 +59,15 @@
) %>
<% end %>
<%= if Enum.any?(@token_balances, fn {token_balance, _, _} -> token_balance.token.type == "ERC-1155" end) do %>
<%= render(
"_tokens.html",
conn: @conn,
token_balances: filter_by_type(@token_balances, "ERC-1155"),
type: "ERC-721"
) %>
<% end %>
<%= if Enum.any?(@token_balances, fn {token_balance, _, _} -> token_balance.token.type == "ERC-20" end) do %>
<%= render(
"_tokens.html",

@ -36,7 +36,10 @@
<div class="row">
<% col_md = if token_balance.token.usd_value, do: "col-md-6", else: "col-md-12" %>
<p class="mb-0 <%= col_md%> ">
<%= format_according_to_decimals(token_balance.value, token.decimals) %> <%= token_symbol(token) %>
<%= format_according_to_decimals(token_balance.value, token_balance.token.decimals) %> <%= token_balance.token.symbol %>
<%= if token_balance.token_type == "ERC-1155" do %>
<%= " TokenID " <> to_string(token_balance.token_id) %>
<% end %>
</p>
<%= if token_balance.token.usd_value do %>
<p class="mb-0 col-md-6 text-right text-muted">

@ -0,0 +1,12 @@
<%= case @type do %>
<% :token_burning -> %>
<%= gettext("Token Burning") %>
<% :token_minting -> %>
<%= gettext("Token Minting") %>
<% :token_spawning -> %>
<%= gettext("Token Creation") %>
<% :token_transfer -> %>
<%= gettext("Token Transfer") %>
<% _ -> %>
<%= gettext("Token Transfer") %>
<% end %>

@ -7,7 +7,7 @@
<span>
<span class="text-dark">
<%= format_token_balance_value(@token_balance.value, @token) %> <%= @token.symbol %>
<%= format_token_balance_value(@token_balance.value, @token_balance.token_id, @token) %> <%= @token.symbol %>
</span>
<%= if show_total_supply_percentage?(@token.total_supply) do %>

@ -3,14 +3,7 @@
<!-- Color Block -->
<div class="tile-transaction-type-block col-md-2 d-flex flex-row flex-md-column">
<span class="tile-label">
<%= cond do %>
<% @token_transfer.to_address.hash == @burn_address_hash -> %>
<%= gettext("Token Burning") %>
<% @token_transfer.from_address.hash == @burn_address_hash -> %>
<%= gettext("Token Minting") %>
<% true -> %>
<%= gettext("Token Transfer") %>
<% end %>
<%= render(BlockScoutWeb.CommonComponentsView, "_token_transfer_type_display_name.html", type: Chain.get_token_transfer_type(@token_transfer)) %>
</span>
</div>
<!-- Content -->

@ -1,6 +1,13 @@
<%= case token_transfer_amount(@transfer) do %>
<% {:ok, :erc721_instance} -> %>
<%= "TokenID ["%><%= link(short_token_id(@transfer.token_id, 30), to: token_instance_path(BlockScoutWeb.Endpoint, :show, @transfer.token.contract_address_hash, to_string(@transfer.token_id)), "data-test": "token_link") %><%= "]" %>
<% {:ok, :erc1155_instance, value} -> %>
<% transfer_type = Chain.get_token_transfer_type(@transfer) %>
<%= if transfer_type == :token_spawning do %>
<%= "TokenID ["%><%= link(short_token_id(@transfer.token_id, 30), to: token_instance_path(BlockScoutWeb.Endpoint, :show, @transfer.token.contract_address_hash, to_string(@transfer.token_id)), "data-test": "token_link") %><%= "]" %>
<% else %>
<%= "#{value} TokenID ["%><%= link(short_token_id(@transfer.token_id, 30), to: token_instance_path(BlockScoutWeb.Endpoint, :show, @transfer.token.contract_address_hash, to_string(@transfer.token_id)), "data-test": "token_link") %><%= "]" %>
<% end %>
<% {:ok, value} -> %>
<%= value %>
<% end %>

@ -1,14 +1,7 @@
<div class="tile tile-type-token-transfer fade-in">
<div class="row justify-content-end">
<div class="col-12 col-md-4 col-lg-2 d-flex align-items-center justify-content-start justify-content-lg-center tile-label">
<%= cond do %>
<% @token_transfer.to_address.hash == @burn_address_hash -> %>
<%= gettext("Token Burning") %>
<% @token_transfer.from_address.hash == @burn_address_hash -> %>
<%= gettext("Token Minting") %>
<% true -> %>
<%= gettext("Token Transfer") %>
<% end %>
<%= render(BlockScoutWeb.CommonComponentsView, "_token_transfer_type_display_name.html", type: Chain.get_token_transfer_type(@token_transfer)) %>
</div>
<div class="col-12 col-md-8 col-lg-10 d-flex flex-column text-nowrap">

@ -172,6 +172,12 @@ defmodule BlockScoutWeb.API.RPC.AddressView do
|> Map.put_new(:tokenID, token_transfer.token_id)
end
defp prepare_token_transfer(%{token_type: "ERC-1155"} = token_transfer) do
token_transfer
|> prepare_common_token_transfer()
|> Map.put_new(:tokenID, token_transfer.token_id)
end
defp prepare_token_transfer(%{token_type: "ERC-20"} = token_transfer) do
token_transfer
|> prepare_common_token_transfer()

@ -32,14 +32,14 @@ defmodule BlockScoutWeb.Tokens.Helpers do
{:ok, CurrencyHelpers.format_according_to_decimals(amount, decimals)}
end
defp do_token_transfer_amount(%Token{type: "ERC-1155", decimals: decimals}, amount, _token_id) do
{:ok, CurrencyHelpers.format_according_to_decimals(amount, decimals)}
end
defp do_token_transfer_amount(%Token{type: "ERC-721"}, _amount, _token_id) do
{:ok, :erc721_instance}
end
defp do_token_transfer_amount(%Token{type: "ERC-1155", decimals: decimals}, amount, _token_id) do
{:ok, :erc1155_instance, CurrencyHelpers.format_according_to_decimals(amount, decimals)}
end
defp do_token_transfer_amount(_token, _amount, _token_id) do
nil
end

@ -54,19 +54,23 @@ defmodule BlockScoutWeb.Tokens.HolderView do
## Examples
iex> token = build(:token, type: "ERC-20", decimals: Decimal.new(2))
iex> BlockScoutWeb.Tokens.HolderView.format_token_balance_value(100000, token)
iex> BlockScoutWeb.Tokens.HolderView.format_token_balance_value(100000, nil, token)
"1,000"
iex> token = build(:token, type: "ERC-721")
iex> BlockScoutWeb.Tokens.HolderView.format_token_balance_value(1, token)
iex> BlockScoutWeb.Tokens.HolderView.format_token_balance_value(1, nil, token)
1
"""
def format_token_balance_value(value, %Token{type: "ERC-20", decimals: decimals}) do
def format_token_balance_value(value, _id, %Token{type: "ERC-20", decimals: decimals}) do
format_according_to_decimals(value, decimals)
end
def format_token_balance_value(value, _token) do
def format_token_balance_value(value, id, %Token{type: "ERC-1155", decimals: decimals}) do
to_string(format_according_to_decimals(value, decimals)) <> " TokenID " <> to_string(id)
end
def format_token_balance_value(value, _id, _token) do
value
end
end

@ -44,6 +44,7 @@ defmodule BlockScoutWeb.Tokens.OverviewView do
defp tab_name(["inventory"]), do: gettext("Inventory")
def display_inventory?(%Token{type: "ERC-721"}), do: true
def display_inventory?(%Token{type: "ERC-1155"}), do: true
def display_inventory?(_), do: false
def smart_contract_with_read_only_functions?(

@ -2,5 +2,6 @@ defmodule BlockScoutWeb.Tokens.TransferView do
use BlockScoutWeb, :view
alias BlockScoutWeb.Tokens.OverviewView
alias Explorer.Chain
alias Explorer.Chain.Address
end

@ -1,3 +1,5 @@
defmodule BlockScoutWeb.TransactionTokenTransferView do
use BlockScoutWeb, :view
alias Explorer.Chain
end

@ -123,32 +123,21 @@ defmodule BlockScoutWeb.TransactionView do
end
)
transfers = ft_transfers ++ nft_transfers
final_ft_transfers = Map.values(ft_transfers)
transfers = final_ft_transfers ++ nft_transfers
mintings = ft_mintings ++ nft_mintings
final_ft_mintings = Map.values(ft_mintings)
mintings = final_ft_mintings ++ nft_mintings
burnings = ft_burnings ++ nft_burnings
final_ft_burnings = Map.values(ft_burnings)
burnings = final_ft_burnings ++ nft_burnings
creations = ft_creations ++ nft_creations
final_ft_creations = Map.values(ft_creations)
creations = final_ft_creations ++ nft_creations
%{transfers: transfers, mintings: mintings, burnings: burnings, creations: creations}
end
defp aggregate_reducer(%{amount: amount, amounts: amounts} = token_transfer, {acc1, acc2})
when is_nil(amount) and is_nil(amounts) do
new_entry = %{
token: token_transfer.token,
amount: nil,
amounts: [],
token_id: token_transfer.token_id,
token_ids: [],
to_address_hash: token_transfer.to_address_hash,
from_address_hash: token_transfer.from_address_hash
}
{acc1, [new_entry | acc2]}
end
defp aggregate_reducer(%{amount: amount, amounts: amounts} = token_transfer, {acc1, acc2})
when is_nil(amount) and not is_nil(amounts) do
new_entry = %{
@ -220,6 +209,7 @@ defmodule BlockScoutWeb.TransactionView do
case type do
:erc20 -> gettext("ERC-20 ")
:erc721 -> gettext("ERC-721 ")
:erc1155 -> gettext("ERC-1155 ")
_ -> ""
end
end
@ -531,6 +521,7 @@ defmodule BlockScoutWeb.TransactionView do
creations_count =
Enum.count(token_transfers_types, fn token_transfers_type -> token_transfers_type == @token_creation_type end)
<<<<<<< HEAD
cond do
Enum.count(token_transfers_types) == burnings_count -> @token_burning_type
@ -554,6 +545,14 @@ defmodule BlockScoutWeb.TransactionView do
case Integer.parse(string_value) do
{integer, ""} -> integer
_ -> 0
=======
cond do
Enum.count(token_transfers_types) == burnings_count -> @token_burning_type
Enum.count(token_transfers_types) == mintings_count -> @token_minting_type
Enum.count(token_transfers_types) == creations_count -> @token_creation_type
true -> @token_transfer_type
>>>>>>> d8b6f073c (ERC-1155 transfers support in UI)
end
end

@ -56,19 +56,19 @@ defmodule BlockScoutWeb.Tokens.HolderViewTest do
end
end
describe "format_token_balance_value/1" do
describe "format_token_balance_value/3" do
test "formats according to token decimals when it's a ERC-20" do
token = build(:token, type: "ERC-20", decimals: Decimal.new(2))
token_balance = build(:token_balance, value: 2_000_000)
assert HolderView.format_token_balance_value(token_balance.value, token) == "20,000"
assert HolderView.format_token_balance_value(token_balance.value, nil, token) == "20,000"
end
test "returns the value when it's ERC-721" do
token = build(:token, type: "ERC-721")
token_balance = build(:token_balance, value: 1)
assert HolderView.format_token_balance_value(token_balance.value, token) == 1
assert HolderView.format_token_balance_value(token_balance.value, nil, token) == 1
end
end
end

@ -168,7 +168,11 @@ defmodule EthereumJSONRPC do
"""
@spec execute_contract_functions([Contract.call()], [map()], json_rpc_named_arguments) :: [Contract.call_result()]
def execute_contract_functions(functions, abi, json_rpc_named_arguments) do
if Enum.count(functions) > 0 do
Contract.execute_contract_functions(functions, abi, json_rpc_named_arguments)
else
[]
end
end
@doc """

@ -5885,7 +5885,7 @@ defmodule Explorer.Chain do
end
@spec transaction_token_transfer_type(Transaction.t()) ::
:erc20 | :erc721 | :token_transfer | nil
:erc20 | :erc721 | :erc1155 | :token_transfer | nil
def transaction_token_transfer_type(
%Transaction{
status: :ok,
@ -5932,10 +5932,24 @@ defmodule Explorer.Chain do
find_erc721_token_transfer(transaction.token_transfers, {from_address, to_address})
# safeTransferFrom(address,address,uint256,uint256,bytes)
{"0xf242432a" <> params, ^zero_wei} ->
types = [:address, :address, {:uint, 256}, {:uint, 256}, :bytes]
[from_address, to_address, _id, _value, _data] = decode_params(params, types)
find_erc1155_token_transfer(transaction.token_transfers, {from_address, to_address})
# safeBatchTransferFrom(address,address,uint256[],uint256[],bytes)
{"0x2eb2c2d6" <> params, ^zero_wei} ->
types = [:address, :address, [{:uint, 256}], [{:uint, 256}], :bytes]
[from_address, to_address, _ids, _values, _data] = decode_params(params, types)
find_erc1155_token_transfer(transaction.token_transfers, {from_address, to_address})
{"0xf907fc5b" <> _params, ^zero_wei} ->
:erc20
# check for ERC 20 or for old ERC 721 token versions
# check for ERC-20 or for old ERC-721, ERC-1155 token versions
{unquote(TokenTransfer.transfer_function_signature()) <> params, ^zero_wei} ->
types = [:address, {:uint, 256}]
@ -5943,7 +5957,7 @@ defmodule Explorer.Chain do
decimal_value = Decimal.new(value)
find_erc721_or_erc20_token_transfer(transaction.token_transfers, {address, decimal_value})
find_erc721_or_erc20_or_erc1155_token_transfer(transaction.token_transfers, {address, decimal_value})
_ ->
nil
@ -5959,7 +5973,16 @@ defmodule Explorer.Chain do
if token_transfer, do: :erc721
end
defp find_erc721_or_erc20_token_transfer(token_transfers, {address, decimal_value}) do
defp find_erc1155_token_transfer(token_transfers, {from_address, to_address}) do
token_transfer =
Enum.find(token_transfers, fn token_transfer ->
token_transfer.from_address_hash.bytes == from_address && token_transfer.to_address_hash.bytes == to_address
end)
if token_transfer, do: :erc1155
end
defp find_erc721_or_erc20_or_erc1155_token_transfer(token_transfers, {address, decimal_value}) do
token_transfer =
Enum.find(token_transfers, fn token_transfer ->
token_transfer.to_address_hash.bytes == address && token_transfer.amount == decimal_value
@ -5969,6 +5992,7 @@ defmodule Explorer.Chain do
case token_transfer.token do
%Token{type: "ERC-20"} -> :erc20
%Token{type: "ERC-721"} -> :erc721
%Token{type: "ERC-1155"} -> :erc1155
_ -> nil
end
else

@ -23,6 +23,8 @@ defmodule Explorer.Chain.Address.CurrentTokenBalance do
* `token_contract_address_hash` - The contract address hash foreign key.
* `block_number` - The block's number that the transfer took place.
* `value` - The value that's represents the balance.
* `token_id` - The token_id of the transferred token (applicable for ERC-1155 and ERC-721 tokens)
* `token_type` - The type of the token
"""
@type t :: %__MODULE__{
address: %Ecto.Association.NotLoaded{} | Address.t(),
@ -39,6 +41,8 @@ defmodule Explorer.Chain.Address.CurrentTokenBalance do
field(:value, :decimal)
field(:block_number, :integer)
field(:value_fetched_at, :utc_datetime_usec)
field(:token_id, :decimal)
field(:token_type, :string)
# A transient field for deriving token holder count deltas during address_current_token_balances upserts
field(:old_value, :decimal)
@ -56,8 +60,8 @@ defmodule Explorer.Chain.Address.CurrentTokenBalance do
timestamps()
end
@optional_fields ~w(value value_fetched_at)a
@required_fields ~w(address_hash block_number token_contract_address_hash)a
@optional_fields ~w(value value_fetched_at token_id)a
@required_fields ~w(address_hash block_number token_contract_address_hash token_type)a
@allowed_fields @optional_fields ++ @required_fields
@doc false

@ -8,6 +8,7 @@ defmodule Explorer.Chain.Token do
* ERC-20
* ERC-721
* ERC-1155
## Token Specifications

@ -300,6 +300,7 @@ defmodule Explorer.Chain.TokenTransfer do
left_join: instance in Instance,
on: tt.token_contract_address_hash == instance.token_contract_address_hash and tt.token_id == instance.token_id,
where: tt.token_contract_address_hash == ^contract_address_hash,
where: tt.to_address_hash != ^"0x0000000000000000000000000000000000000000",
order_by: [desc: tt.block_number],
distinct: [desc: tt.token_id],
preload: [:to_address],

@ -45,6 +45,13 @@ defmodule Explorer.Token.BalanceReader do
def get_balances_of(token_balance_requests) do
regular_balances =
token_balance_requests
|> Enum.filter(fn request ->
if Map.has_key?(request, :token_type) do
request.token_type !== "ERC-1155"
else
true
end
end)
|> Enum.map(&format_balance_request/1)
|> Reader.query_contracts(@balance_function_abi)
|> Enum.map(&format_balance_result/1)
@ -52,21 +59,13 @@ defmodule Explorer.Token.BalanceReader do
erc1155_balances =
token_balance_requests
|> Enum.filter(fn request ->
if Map.has_key?(request, :token_type) do
request.token_type == "ERC-1155"
else
false
end
end)
|> Enum.map(fn %{
address_hash: address_hash,
block_number: block_number,
token_contract_address_hash: token_contract_address_hash,
token_id: token_id
} ->
%{
contract_address: token_contract_address_hash,
function_name: "balanceOf",
args: [address_hash, token_id],
block_number: block_number
}
end)
|> Enum.map(&format_erc_1155_balance_request/1)
|> Reader.query_contracts(@erc1155_balance_function_abi)
|> Enum.map(&format_balance_result/1)
@ -86,6 +85,20 @@ defmodule Explorer.Token.BalanceReader do
}
end
defp format_erc_1155_balance_request(%{
address_hash: address_hash,
block_number: block_number,
token_contract_address_hash: token_contract_address_hash,
token_id: token_id
}) do
%{
contract_address: token_contract_address_hash,
method_id: "00fdd58e",
args: [address_hash, token_id],
block_number: block_number
}
end
defp format_balance_result({:ok, [balance]}) do
{:ok, balance}
end

@ -0,0 +1,12 @@
defmodule Explorer.Repo.Migrations.AddTokenIdToCurrentTokenBalances do
use Ecto.Migration
def change do
alter table(:address_current_token_balances) do
add(:token_id, :numeric, precision: 78, scale: 0, null: true)
add(:token_type, :string, null: true)
end
create(index(:address_current_token_balances, [:token_id]))
end
end

@ -428,13 +428,15 @@ defmodule Explorer.Chain.ImportTest do
address_hash: "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca",
token_contract_address_hash: "0x8bf38d4764929064f2d4d3a56520a76ab3df415b",
block_number: "37",
value: 200
value: 200,
token_type: "ERC-20"
},
%{
address_hash: "0x515c09c5bba1ed566b02a5b0599ec5d5d0aee73d",
token_contract_address_hash: "0x8bf38d4764929064f2d4d3a56520a76ab3df415b",
block_number: "37",
value: 100
value: 100,
token_type: "ERC-20"
}
],
timeout: 5
@ -2264,7 +2266,8 @@ defmodule Explorer.Chain.ImportTest do
address_hash: address_hash,
token_contract_address_hash: token_contract_address_hash,
block_number: block_number,
value: value_after
value: value_after,
token_type: "ERC-20"
}
]
},

@ -76,22 +76,6 @@ defmodule Indexer.Fetcher.TokenBalanceTest do
token_balance_a = insert(:token_balance, value_fetched_at: nil, value: nil)
token_balance_b = insert(:token_balance, value_fetched_at: nil, value: nil)
expect(
EthereumJSONRPC.Mox,
:json_rpc,
1,
fn [%{id: id, method: "eth_call", params: [%{data: _, to: _}, _]}], _options ->
{:ok,
[
%{
error: %{code: -32015, data: "Reverted 0x", message: "VM execution error."},
id: id,
jsonrpc: "2.0"
}
]}
end
)
token_balances = [
{
token_balance_a.address_hash.bytes,

Loading…
Cancel
Save