Merge branch 'master' into patch-1

pull/701/head
Jimmy Lauzau 6 years ago committed by GitHub
commit 52af3b38b1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 21
      apps/block_scout_web/lib/block_scout_web/chain.ex
  2. 36
      apps/block_scout_web/lib/block_scout_web/controllers/tokens/holder_controller.ex
  3. 2
      apps/block_scout_web/lib/block_scout_web/controllers/tokens/read_contract_controller.ex
  4. 2
      apps/block_scout_web/lib/block_scout_web/controllers/tokens/token_controller.ex
  5. 7
      apps/block_scout_web/lib/block_scout_web/router.ex
  6. 6
      apps/block_scout_web/lib/block_scout_web/templates/address/_link.html.eex
  7. 19
      apps/block_scout_web/lib/block_scout_web/templates/tokens/holder/_token_balances.html.eex
  8. 94
      apps/block_scout_web/lib/block_scout_web/templates/tokens/holder/index.html.eex
  9. 2
      apps/block_scout_web/lib/block_scout_web/templates/tokens/overview/_details.html.eex
  10. 16
      apps/block_scout_web/lib/block_scout_web/templates/tokens/read_contract/index.html.eex
  11. 20
      apps/block_scout_web/lib/block_scout_web/templates/tokens/token/show.html.eex
  12. 8
      apps/block_scout_web/lib/block_scout_web/templates/transaction/_tile.html.eex
  13. 4
      apps/block_scout_web/lib/block_scout_web/templates/transaction/_token_transfer.html.eex
  14. 10
      apps/block_scout_web/lib/block_scout_web/templates/transaction/overview.html.eex
  15. 94
      apps/block_scout_web/lib/block_scout_web/views/address_view.ex
  16. 50
      apps/block_scout_web/lib/block_scout_web/views/tokens/holder_view.ex
  17. 93
      apps/block_scout_web/lib/block_scout_web/views/transaction_view.ex
  18. 105
      apps/block_scout_web/priv/gettext/default.pot
  19. 105
      apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po
  20. 92
      apps/block_scout_web/test/block_scout_web/controllers/tokens/holder_controller_test.exs
  21. 2
      apps/block_scout_web/test/block_scout_web/controllers/tokens/read_contract_controller_test.exs
  22. 23
      apps/block_scout_web/test/block_scout_web/features/pages/token_page.ex
  23. 45
      apps/block_scout_web/test/block_scout_web/features/viewing_addresses_test.exs
  24. 22
      apps/block_scout_web/test/block_scout_web/features/viewing_tokens_test.exs
  25. 124
      apps/block_scout_web/test/block_scout_web/views/address_view_test.exs
  26. 40
      apps/block_scout_web/test/block_scout_web/views/tokens/holder_view_test.exs
  27. 51
      apps/block_scout_web/test/block_scout_web/views/transaction_view_test.exs
  28. 19
      apps/explorer/lib/explorer/chain.ex
  29. 66
      apps/explorer/lib/explorer/chain/address/token_balance.ex
  30. 27
      apps/explorer/lib/explorer/chain/token_transfer.ex
  31. 49
      apps/explorer/test/explorer/chain/token_transfer_test.exs
  32. 291
      apps/explorer/test/explorer/chain_test.exs

@ -12,7 +12,18 @@ defmodule BlockScoutWeb.Chain do
string_to_transaction_hash: 1
]
alias Explorer.Chain.{Address, Block, InternalTransaction, Log, Token, TokenTransfer, Transaction}
alias Explorer.Chain.{
Address,
Address.TokenBalance,
Block,
Hash,
InternalTransaction,
Log,
Token,
TokenTransfer,
Transaction
}
alias Explorer.PagingOptions
@page_size 50
@ -121,6 +132,10 @@ defmodule BlockScoutWeb.Chain do
def paging_options(%{"token_name" => name, "token_type" => type, "token_inserted_at" => inserted_at}),
do: [paging_options: %{@default_paging_options | key: {name, type, inserted_at}}]
def paging_options(%{"value" => value, "address_hash" => address_hash}) do
[paging_options: %{@default_paging_options | key: {value, address_hash}}]
end
def paging_options(_params), do: [paging_options: @default_paging_options]
def param_to_block_number(formatted_number) when is_binary(formatted_number) do
@ -179,6 +194,10 @@ defmodule BlockScoutWeb.Chain do
%{"token_name" => name, "token_type" => type, "token_inserted_at" => inserted_at_datetime}
end
defp paging_params(%TokenBalance{address_hash: address_hash, value: value}) do
%{"address_hash" => Hash.to_string(address_hash), "value" => Decimal.to_integer(value)}
end
defp transaction_from_param(param) do
with {:ok, hash} <- string_to_transaction_hash(param) do
hash_to_transaction(hash)

@ -0,0 +1,36 @@
defmodule BlockScoutWeb.Tokens.HolderController do
use BlockScoutWeb, :controller
alias Explorer.Chain
import BlockScoutWeb.Chain,
only: [
split_list_by_page: 1,
paging_options: 1,
next_page_params: 3
]
def index(conn, %{"token_id" => address_hash_string} = params) do
with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string),
{:ok, token} <- Chain.token_from_address_hash(address_hash),
token_balances <- Chain.fetch_token_holders_from_token_hash(address_hash, paging_options(params)) do
{token_balances_paginated, next_page} = split_list_by_page(token_balances)
render(
conn,
"index.html",
token: token,
token_balances: token_balances_paginated,
total_token_holders: Chain.count_token_holders_from_token_hash(address_hash),
total_token_transfers: Chain.count_token_transfers_from_token_hash(address_hash),
next_page_params: next_page_params(next_page, token_balances_paginated, params)
)
else
:error ->
not_found(conn)
{:error, :not_found} ->
not_found(conn)
end
end
end

@ -11,7 +11,7 @@ defmodule BlockScoutWeb.Tokens.ReadContractController do
"index.html",
token: token,
total_token_transfers: Chain.count_token_transfers_from_token_hash(address_hash),
total_address_in_token_transfers: Chain.count_addresses_in_token_transfers_from_token_hash(address_hash)
total_token_holders: Chain.count_token_holders_from_token_hash(address_hash)
)
else
:error ->

@ -17,7 +17,7 @@ defmodule BlockScoutWeb.Tokens.TokenController do
transfers: token_transfers_paginated,
token: token,
total_token_transfers: Chain.count_token_transfers_from_token_hash(address_hash),
total_address_in_token_transfers: Chain.count_addresses_in_token_transfers_from_token_hash(address_hash),
total_token_holders: Chain.count_token_holders_from_token_hash(address_hash),
next_page_params: next_page_params(next_page, token_transfers_paginated, params)
)
else

@ -113,6 +113,13 @@ defmodule BlockScoutWeb.Router do
only: [:index],
as: :read_contract
)
resources(
"/token_holders",
Tokens.HolderController,
only: [:index],
as: :holder
)
end
resources(

@ -1,5 +1,3 @@
<%= if @address_hash do %>
<%= link to: address_path(BlockScoutWeb.Endpoint, :show, @address_hash), "data-test": "address_hash_link" do %>
<%= render BlockScoutWeb.AddressView, "_responsive_hash.html", address_hash: @address_hash, contract: @contract, truncate: assigns[:truncate] %>
<% end %>
<%= link to: address_path(BlockScoutWeb.Endpoint, :show, @address_hash), "data-test": "address_hash_link" do %>
<%= render BlockScoutWeb.AddressView, "_responsive_hash.html", address_hash: @address_hash, contract: @contract, truncate: assigns[:truncate] %>
<% end %>

@ -0,0 +1,19 @@
<div class="tile tile-type-token fade-in mb-10" data-test="token_holders">
<div class="row">
<div class="col-md-7 col-lg-8 d-flex flex-column">
<span>
<%= render BlockScoutWeb.AddressView, "_link.html", address_hash: @token_balance.address_hash, contract: BlockScoutWeb.AddressView.contract?(@token_balance.address) %>
</span>
<span>
<span class="text-dark">
<%= format_token_balance_value(@token_balance.value, @token) %> <%= @token.symbol %>
</span>
<%= if @token.total_supply > 0 do %>
(<%= total_supply_percentage(@token_balance.value, @token.total_supply) %>)
<% end %>
</span>
</div>
</div>
</div>

@ -0,0 +1,94 @@
<section class="container">
<%= render(
OverviewView,
"_details.html",
token: @token,
total_token_transfers: @total_token_transfers,
total_token_holders: @total_token_holders
) %>
<section>
<div class="card">
<div class="card-header">
<!-- DESKTOP TAB NAV -->
<ul class="nav nav-tabs card-header-tabs d-none d-md-inline-flex">
<li class="nav-item">
<%= link(
gettext("Token Transfers"),
class: "nav-link",
to: token_path(@conn, :show, @token.contract_address_hash)
) %>
</li>
<%= if TokenView.smart_contract_with_read_only_functions?(@token) do %>
<li class="nav-item">
<%= link(
gettext("Read Contract"),
to: token_read_contract_path(@conn, :index, @conn.params["id"]),
class: "nav-link")%>
</li>
<% end %>
<li class="nav-item">
<%= link(
gettext("Token Holders"),
class: "nav-link active",
"data-test": "token_holders_tab",
to: token_holder_path(@conn, :index, @token.contract_address_hash)
) %>
</li>
</ul>
<!-- MOBILE DROPDOWN NAV -->
<ul class="nav nav-tabs card-header-tabs d-md-none">
<li class="nav-item dropdown flex-fill text-center">
<a class="nav-link active dropdown-toggle" data-toggle="dropdown" href="#" role="button" aria-haspopup="true" aria-expanded="false"><%= gettext("Token Holders") %></a>
<div class="dropdown-menu">
<%= link(
gettext("Token Transfers"),
class: "dropdown-item",
to: token_path(@conn, :show, @token.contract_address_hash)
) %>
<%= if TokenView.smart_contract_with_read_only_functions?(@token) do %>
<%= link(
gettext("Read Contract"),
to: "#",
class: "dropdown-item")%>
<% end %>
<%= link(
gettext("Token Holders"),
class: "dropdown-item",
to: token_holder_path(@conn, :index, @token.contract_address_hash)
) %>
</div>
</li>
</ul>
</div>
<!-- Token Holders -->
<div class="card-body">
<h2 class="card-title"><%= gettext "Token Holders" %></h2>
<%= if Enum.any?(@token_balances) do %>
<%= for token_balance <- @token_balances do %>
<%= render "_token_balances.html", token: @token, token_balance: token_balance %>
<% end %>
<% else %>
<div class="tile tile-muted text-center">
<span data-selector="empty-transactions-list">
<%= gettext "There are no holders for this Token." %>
</span>
</div>
<% end %>
<%= if @next_page_params do %>
<%= link(
gettext("Next Page"),
class: "button button-secondary button-small float-right mt-4",
to: token_holder_path(@conn, :index, @token.contract_address_hash, @next_page_params)
) %>
<% end %>
</div>
</div>
</section>
</section>

@ -27,7 +27,7 @@
<div class="d-flex flex-row justify-content-start text-muted">
<span class="mr-4"> <%= @token.type %> </span>
<span class="mr-4"><%= @total_address_in_token_transfers %> <%= gettext "addresses" %></span>
<span class="mr-4"><%= @total_token_holders %> <%= gettext "addresses" %></span>
<span class="mr-4"><%= @total_token_transfers %> <%= gettext "Transfers" %></span>
<%= if decimals?(@token) do %>
<span class="mr-4"><%= @token.decimals %> <%= gettext "decimals" %></span>

@ -4,7 +4,7 @@
"_details.html",
token: @token,
total_token_transfers: @total_token_transfers,
total_address_in_token_transfers: @total_address_in_token_transfers
total_token_holders: @total_token_holders
) %>
<section>
@ -26,6 +26,15 @@
to: token_read_contract_path(@conn, :index, @conn.params["token_id"]),
class: "nav-link active")%>
</li>
<li class="nav-item">
<%= link(
gettext("Token Holders"),
class: "nav-link active",
"data-test": "token_holders_tab",
to: token_holder_path(@conn, :index, @token.contract_address_hash)
) %>
</li>
</ul>
<!-- MOBILE DROPDOWN NAV -->
@ -42,6 +51,11 @@
gettext("Read Contract"),
to: "#",
class: "nav-link")%>
<%= link(
gettext("Token Holders"),
class: "dropdown-item",
to: token_holder_path(@conn, :index, @token.contract_address_hash)
) %>
</div>
</li>
</ul>

@ -4,7 +4,7 @@
"_details.html",
token: @token,
total_token_transfers: @total_token_transfers,
total_address_in_token_transfers: @total_address_in_token_transfers
total_token_holders: @total_token_holders
) %>
<section>
@ -28,6 +28,15 @@
class: "nav-link")%>
</li>
<% end %>
<li class="nav-item"i>
<%= link(
gettext("Token Holders"),
class: "nav-link",
"data-test": "token_holders_tab",
to: token_holder_path(@conn, :index, @token.contract_address_hash)
) %>
</li>
</ul>
<!-- MOBILE DROPDOWN NAV -->
@ -37,15 +46,20 @@
<div class="dropdown-menu">
<%= link(
gettext("Token Transfers"),
class: "nav-link active",
class: "dropdown-item",
to: token_path(@conn, :show, @token.contract_address_hash)
) %>
<%= if smart_contract_with_read_only_functions?(@token) do %>
<%= link(
gettext("Read Contract"),
to: "#",
class: "nav-link")%>
class: "dropdown-item")%>
<% end %>
<%= link(
gettext("Token Holders"),
class: "dropdown-item",
to: token_holder_path(@conn, :index, @token.contract_address_hash)
) %>
</div>
</li>
</ul>

@ -11,13 +11,9 @@
<div class="col-md-7 col-lg-8 d-flex flex-column pr-2 pr-sm-2 pr-md-0">
<%= render "_link.html", transaction_hash: @transaction.hash %>
<span class="text-nowrap">
<%= BlockScoutWeb.AddressView.display_address_hash(assigns[:current_address], @transaction.from_address) %>
<%= @transaction |> BlockScoutWeb.AddressView.address_partial_selector(:from, assigns[:current_address]) |> BlockScoutWeb.AddressView.render_partial() %>
&rarr;
<%= if assigns[:current_address] && assigns[:current_address].hash == to_address_hash(@transaction) do %>
<%= render BlockScoutWeb.AddressView, "_responsive_hash.html", address_hash: to_address_hash(@transaction), contract: BlockScoutWeb.AddressView.contract?(@transaction.to_address) %>
<% else %>
<%= render BlockScoutWeb.AddressView, "_link.html", address_hash: to_address_hash(@transaction), contract: BlockScoutWeb.AddressView.contract?(@transaction.to_address) %>
<% end %>
<%= @transaction |> BlockScoutWeb.AddressView.address_partial_selector(:to, assigns[:current_address]) |> BlockScoutWeb.AddressView.render_partial() %>
</span>
<span class="d-flex flex-md-row flex-column mt-3 mt-md-0">
<span class="tile-title">

@ -11,9 +11,9 @@
</span>
<% end %>
<% end %>
<%= BlockScoutWeb.AddressView.display_address_hash(@address, @token_transfer.from_address, true) %>
<%= @token_transfer |> BlockScoutWeb.AddressView.address_partial_selector(:from, @address, true) |> BlockScoutWeb.AddressView.render_partial() %>
&rarr;
<%= BlockScoutWeb.AddressView.display_address_hash(@address, @token_transfer.to_address, true) %>
<%= @token_transfer |> BlockScoutWeb.AddressView.address_partial_selector(:to, @address, true) |> BlockScoutWeb.AddressView.render_partial() %>
</span>
<span class="col-12 col-md-7 ml-3 ml-sm-0">
<%= token_transfer_amount(@token_transfer) %>

@ -14,14 +14,10 @@
</div>
<h1 class="card-title"><%= gettext "Transaction Details" %> </h1>
<h3 data-test="transaction_detail_hash"><%= @transaction %> </h3>
<span class="d-block mb-2">
<%= render BlockScoutWeb.AddressView, "_link.html", address_hash: @transaction.from_address_hash, contract: BlockScoutWeb.AddressView.contract?(@transaction.from_address) %>
<span class="d-block mb-2 text-muted">
<%= @transaction |> BlockScoutWeb.AddressView.address_partial_selector(:from, nil) |> BlockScoutWeb.AddressView.render_partial() %>
<span class="text-muted"> &rarr; </span>
<%= if @transaction.to_address_hash do %>
<%= render BlockScoutWeb.AddressView, "_link.html", address_hash: @transaction.to_address_hash, contract: BlockScoutWeb.AddressView.contract?(@transaction.to_address) %>
<% else %>
<%= gettext("Contract Address Pending") %>
<% end %>
<%= @transaction |> BlockScoutWeb.AddressView.address_partial_selector(:to, nil) |> BlockScoutWeb.AddressView.render_partial() %>
</span>
<div class="d-flex flex-row justify-content-start text-muted">
<span class="mr-4 text-<%= BlockScoutWeb.TransactionView.type_suffix(@transaction) %>"><%= BlockScoutWeb.TransactionView.transaction_display_type(@transaction) %></span>

@ -1,10 +1,46 @@
defmodule BlockScoutWeb.AddressView do
use BlockScoutWeb, :view
alias Explorer.Chain.{Address, Hash, SmartContract}
alias Explorer.Chain.{Address, Hash, SmartContract, TokenTransfer, Transaction}
@dialyzer :no_match
def address_partial_selector(struct_to_render_from, direction, current_address, truncate \\ false)
def address_partial_selector(%TokenTransfer{to_address: address}, :to, current_address, truncate) do
matching_address_check(current_address, address.hash, contract?(address), truncate)
end
def address_partial_selector(%TokenTransfer{from_address: address}, :from, current_address, truncate) do
matching_address_check(current_address, address.hash, contract?(address), truncate)
end
def address_partial_selector(
%Transaction{to_address_hash: nil, created_contract_address_hash: nil},
:to,
_current_address,
_truncate
) do
gettext("Contract Address Pending")
end
def address_partial_selector(
%Transaction{to_address_hash: nil, created_contract_address_hash: hash},
:to,
current_address,
truncate
) do
matching_address_check(current_address, hash, true, truncate)
end
def address_partial_selector(%Transaction{to_address: address}, :to, current_address, truncate) do
matching_address_check(current_address, address.hash, contract?(address), truncate)
end
def address_partial_selector(%Transaction{from_address: address}, :from, current_address, truncate) do
matching_address_check(current_address, address.hash, contract?(address), truncate)
end
def address_title(%Address{} = address) do
if contract?(address) do
gettext("Contract Address")
@ -45,16 +81,20 @@ defmodule BlockScoutWeb.AddressView do
|> Base.encode64()
end
def smart_contract_verified?(%Address{smart_contract: %SmartContract{}}), do: true
def render_partial(%{partial: partial, address_hash: hash, contract: contract?, truncate: truncate}) do
render(
partial,
address_hash: hash,
contract: contract?,
truncate: truncate
)
end
def smart_contract_verified?(%Address{smart_contract: nil}), do: false
def render_partial(text), do: text
def trimmed_hash(%Hash{} = hash) do
string_hash = to_string(hash)
"#{String.slice(string_hash, 0..5)}#{String.slice(string_hash, -6..-1)}"
end
def smart_contract_verified?(%Address{smart_contract: %SmartContract{}}), do: true
def trimmed_hash(_), do: ""
def smart_contract_verified?(%Address{smart_contract: nil}), do: false
def smart_contract_with_read_only_functions?(%Address{smart_contract: %SmartContract{}} = address) do
Enum.any?(address.smart_contract.abi, & &1["constant"])
@ -62,32 +102,28 @@ defmodule BlockScoutWeb.AddressView do
def smart_contract_with_read_only_functions?(%Address{smart_contract: nil}), do: false
def display_address_hash(current_address, target_address, truncate \\ false)
def display_address_hash(nil, target_address, truncate) do
render(
"_link.html",
address_hash: target_address.hash,
contract: contract?(target_address),
truncate: truncate
)
def trimmed_hash(%Hash{} = hash) do
string_hash = to_string(hash)
"#{String.slice(string_hash, 0..5)}#{String.slice(string_hash, -6..-1)}"
end
def display_address_hash(current_address, target_address, truncate) do
if current_address.hash == target_address.hash do
render(
"_responsive_hash.html",
address_hash: current_address.hash,
contract: contract?(current_address),
def trimmed_hash(_), do: ""
defp matching_address_check(current_address, hash, contract?, truncate) do
if current_address && current_address.hash == hash do
%{
partial: "_responsive_hash.html",
address_hash: hash,
contract: contract?,
truncate: truncate
)
}
else
render(
"_link.html",
address_hash: target_address.hash,
contract: contract?(target_address),
%{
partial: "_link.html",
address_hash: hash,
contract: contract?,
truncate: truncate
)
}
end
end
end

@ -0,0 +1,50 @@
defmodule BlockScoutWeb.Tokens.HolderView do
use BlockScoutWeb, :view
alias BlockScoutWeb.Tokens.{OverviewView, TokenView}
alias Explorer.Chain.{Token}
@doc """
Calculates the percentage of the value from the given total supply.
## Examples
iex> value = Decimal.new(200)
iex> total_supply = Decimal.new(1000)
iex> BlockScoutWeb.Tokens.HolderView.total_supply_percentage(value, total_supply)
"20.0000%"
"""
def total_supply_percentage(value, total_supply) do
result =
value
|> Decimal.div(total_supply)
|> Decimal.mult(100)
|> Decimal.round(4)
|> Decimal.to_string()
result <> "%"
end
@doc """
Formats the token balance value according to the Token's type.
## Examples
iex> token = build(:token, type: "ERC-20", decimals: 2)
iex> BlockScoutWeb.Tokens.HolderView.format_token_balance_value(100000, token)
"1,000"
iex> token = build(:token, type: "ERC-721")
iex> BlockScoutWeb.Tokens.HolderView.format_token_balance_value(1, token)
1
"""
def format_token_balance_value(value, %Token{type: "ERC-20", decimals: decimals}) do
format_according_to_decimals(value, decimals)
end
def format_token_balance_value(value, _token) do
value
end
end

@ -10,6 +10,8 @@ defmodule BlockScoutWeb.TransactionView do
defguardp is_transaction_type(mod) when mod in [InternalTransaction, Transaction]
defdelegate formatted_timestamp(block), to: BlockView
def confirmations(%Transaction{block: block}, named_arguments) when is_list(named_arguments) do
case block do
nil -> 0
@ -17,22 +19,19 @@ defmodule BlockScoutWeb.TransactionView do
end
end
def from_or_to_address?(_token_transfer, nil), do: false
def from_or_to_address?(%{from_address_hash: from_hash, to_address_hash: to_hash}, %Address{hash: hash}) do
from_hash == hash || to_hash == hash
end
# This is the address to be shown in the to field
def to_address_hash(%Transaction{to_address_hash: nil, created_contract_address_hash: address_hash}), do: address_hash
def contract_creation?(%Transaction{to_address: nil}), do: true
def to_address_hash(%Transaction{to_address: %Address{hash: address_hash}}), do: address_hash
def contract_creation?(_), do: false
def fee(%Transaction{} = transaction) do
{_, value} = Chain.fee(transaction, :wei)
value
end
def format_gas_limit(gas) do
Number.to_string!(gas)
end
def formatted_fee(%Transaction{} = transaction, opts) do
transaction
|> Chain.fee(:wei)
@ -43,34 +42,6 @@ defmodule BlockScoutWeb.TransactionView do
end
end
def gas_used(%Transaction{gas_used: nil}), do: gettext("Pending")
def gas_used(%Transaction{gas_used: gas_used}) do
Number.to_string!(gas_used)
end
def involves_contract?(%Transaction{from_address: from_address, to_address: to_address}) do
AddressView.contract?(from_address) || AddressView.contract?(to_address)
end
def involves_token_transfers?(%Transaction{token_transfers: []}), do: false
def involves_token_transfers?(%Transaction{token_transfers: transfers}) when is_list(transfers), do: true
def contract_creation?(%Transaction{to_address: nil}), do: true
def contract_creation?(_), do: false
def qr_code(%Transaction{hash: hash}) do
hash
|> to_string()
|> QRCode.to_png()
|> Base.encode64()
end
def format_gas_limit(gas) do
Number.to_string!(gas)
end
def formatted_status(transaction) do
transaction
|> Chain.transaction_to_status()
@ -82,7 +53,11 @@ defmodule BlockScoutWeb.TransactionView do
end
end
defdelegate formatted_timestamp(block), to: BlockView
def from_or_to_address?(_token_transfer, nil), do: false
def from_or_to_address?(%{from_address_hash: from_hash, to_address_hash: to_hash}, %Address{hash: hash}) do
from_hash == hash || to_hash == hash
end
def gas(%type{gas: gas}) when is_transaction_type(type) do
Cldr.Number.to_string!(gas)
@ -95,22 +70,39 @@ defmodule BlockScoutWeb.TransactionView do
format_wei_value(gas_price, unit)
end
def gas_used(%Transaction{gas_used: nil}), do: gettext("Pending")
def gas_used(%Transaction{gas_used: gas_used}) do
Number.to_string!(gas_used)
end
def hash(%Transaction{hash: hash}) do
to_string(hash)
end
def involves_contract?(%Transaction{from_address: from_address, to_address: to_address}) do
AddressView.contract?(from_address) || AddressView.contract?(to_address)
end
def involves_token_transfers?(%Transaction{token_transfers: []}), do: false
def involves_token_transfers?(%Transaction{token_transfers: transfers}) when is_list(transfers), do: true
def qr_code(%Transaction{hash: hash}) do
hash
|> to_string()
|> QRCode.to_png()
|> Base.encode64()
end
def status(transaction) do
Chain.transaction_to_status(transaction)
end
def type_suffix(%Transaction{} = transaction) do
cond do
involves_token_transfers?(transaction) -> "token-transfer"
contract_creation?(transaction) -> "contract-creation"
involves_contract?(transaction) -> "contract-call"
true -> "transaction"
end
end
# This is the address to be shown in the to field
def to_address_hash(%Transaction{to_address_hash: nil, created_contract_address_hash: address_hash}),
do: address_hash
def to_address_hash(%Transaction{to_address: %Address{hash: address_hash}}), do: address_hash
def transaction_display_type(%Transaction{} = transaction) do
cond do
@ -121,6 +113,15 @@ defmodule BlockScoutWeb.TransactionView do
end
end
def type_suffix(%Transaction{} = transaction) do
cond do
involves_token_transfers?(transaction) -> "token-transfer"
contract_creation?(transaction) -> "contract-creation"
involves_contract?(transaction) -> "contract-call"
true -> "transaction"
end
end
@doc """
Converts a transaction's Wei value to Ether and returns a formatted display value.

@ -52,7 +52,7 @@ msgstr ""
msgid "Transactions"
msgstr ""
#: lib/block_scout_web/templates/transaction/overview.html.eex:91
#: lib/block_scout_web/templates/transaction/overview.html.eex:87
msgid "Value"
msgstr ""
@ -76,7 +76,7 @@ msgid "Miner"
msgstr ""
#: lib/block_scout_web/templates/block/overview.html.eex:59
#: lib/block_scout_web/templates/transaction/overview.html.eex:59
#: lib/block_scout_web/templates/transaction/overview.html.eex:55
msgid "Nonce"
msgstr ""
@ -100,7 +100,7 @@ msgstr ""
msgid "Total Difficulty"
msgstr ""
#: lib/block_scout_web/templates/transaction/overview.html.eex:38
#: lib/block_scout_web/templates/transaction/overview.html.eex:34
msgid "Block Number"
msgstr ""
@ -112,7 +112,7 @@ msgstr ""
msgid "Cumulative Gas Used"
msgstr ""
#: lib/block_scout_web/templates/transaction/overview.html.eex:102
#: lib/block_scout_web/templates/transaction/overview.html.eex:98
msgid "Gas"
msgstr ""
@ -120,7 +120,7 @@ msgstr ""
msgid "Gas Price"
msgstr ""
#: lib/block_scout_web/templates/transaction/overview.html.eex:72
#: lib/block_scout_web/templates/transaction/overview.html.eex:68
msgid "Input"
msgstr ""
@ -132,7 +132,7 @@ msgstr ""
msgid "%{count} transactions in this block"
msgstr ""
#: lib/block_scout_web/views/address_view.ex:12
#: lib/block_scout_web/views/address_view.ex:48
msgid "Address"
msgstr ""
@ -148,7 +148,7 @@ msgstr ""
msgid "Overview"
msgstr ""
#: lib/block_scout_web/views/transaction_view.ex:81
#: lib/block_scout_web/views/transaction_view.ex:52
msgid "Success"
msgstr ""
@ -199,9 +199,9 @@ msgstr ""
#: lib/block_scout_web/templates/pending_transaction/index.html.eex:35
#: lib/block_scout_web/templates/transaction/index.html.eex:16
#: lib/block_scout_web/templates/transaction/index.html.eex:35
#: lib/block_scout_web/templates/transaction/overview.html.eex:47
#: lib/block_scout_web/views/transaction_view.ex:46
#: lib/block_scout_web/views/transaction_view.ex:80
#: lib/block_scout_web/templates/transaction/overview.html.eex:43
#: lib/block_scout_web/views/transaction_view.ex:51
#: lib/block_scout_web/views/transaction_view.ex:73
msgid "Pending"
msgstr ""
@ -265,15 +265,15 @@ msgstr ""
msgid "TPM"
msgstr ""
#: lib/block_scout_web/templates/pending_transaction/index.html.eex:69
#: lib/block_scout_web/templates/tokens/holder/index.html.eex:86
msgid "Next Page"
msgstr ""
#: lib/block_scout_web/views/transaction_view.ex:78
#: lib/block_scout_web/views/transaction_view.ex:49
msgid "Failed"
msgstr ""
#: lib/block_scout_web/views/transaction_view.ex:79
#: lib/block_scout_web/views/transaction_view.ex:50
msgid "Out of Gas"
msgstr ""
@ -292,8 +292,8 @@ msgstr ""
#:
#: lib/block_scout_web/templates/address_internal_transaction/_internal_transaction.html.eex:22
#: lib/block_scout_web/templates/pending_transaction/index.html.eex:68
#: lib/block_scout_web/templates/transaction/_tile.html.eex:24
#: lib/block_scout_web/templates/transaction/overview.html.eex:91
#: lib/block_scout_web/templates/transaction/_tile.html.eex:20
#: lib/block_scout_web/templates/transaction/overview.html.eex:87
#: lib/block_scout_web/templates/transaction_internal_transaction/_internal_transaction.html.eex:16
#: lib/block_scout_web/views/wei_helpers.ex:72
msgid "Ether"
@ -453,7 +453,7 @@ msgstr ""
msgid "Total Gas Used"
msgstr ""
#: lib/block_scout_web/views/transaction_view.ex:120
#: lib/block_scout_web/views/transaction_view.ex:112
msgid "Transaction"
msgstr ""
@ -467,8 +467,8 @@ msgid "View All"
msgstr ""
#: lib/block_scout_web/templates/pending_transaction/index.html.eex:69
#: lib/block_scout_web/templates/transaction/_tile.html.eex:27
#: lib/block_scout_web/templates/transaction/overview.html.eex:64
#: lib/block_scout_web/templates/transaction/_tile.html.eex:23
#: lib/block_scout_web/templates/transaction/overview.html.eex:60
msgid "TX Fee"
msgstr ""
@ -476,7 +476,7 @@ msgstr ""
msgid "Contract"
msgstr ""
#: lib/block_scout_web/views/address_view.ex:10
#: lib/block_scout_web/views/address_view.ex:46
msgid "Contract Address"
msgstr ""
@ -493,7 +493,7 @@ msgstr ""
#: lib/block_scout_web/templates/block/index.html.eex:15
#: lib/block_scout_web/templates/block_transaction/index.html.eex:50
#: lib/block_scout_web/templates/pending_transaction/index.html.eex:78
#: lib/block_scout_web/templates/tokens/token/show.html.eex:71
#: lib/block_scout_web/templates/tokens/token/show.html.eex:85
#: lib/block_scout_web/templates/transaction/index.html.eex:66
#: lib/block_scout_web/templates/transaction_token_transfer/index.html.eex:72
msgid "Older"
@ -540,7 +540,7 @@ msgid "Newer"
msgstr ""
#, elixir-format
#: lib/block_scout_web/views/transaction_view.ex:118
#: lib/block_scout_web/views/transaction_view.ex:110
msgid "Contract Creation"
msgstr ""
@ -634,12 +634,12 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/pending_transaction/index.html.eex:64
#: lib/block_scout_web/templates/transaction/overview.html.eex:23
#: lib/block_scout_web/views/address_view.ex:24
msgid "Contract Address Pending"
msgstr ""
#, elixir-format
#: lib/block_scout_web/views/transaction_view.ex:119
#: lib/block_scout_web/views/transaction_view.ex:111
msgid "Contract Call"
msgstr ""
@ -655,21 +655,21 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/tokens/token/_token_transfer.html.eex:36
#: lib/block_scout_web/templates/transaction/_tile.html.eex:34
#: lib/block_scout_web/templates/transaction/_tile.html.eex:30
msgid "Block #%{number}"
msgstr ""
#, elixir-format
#:
#: lib/block_scout_web/templates/address_internal_transaction/_internal_transaction.html.eex:29
#: lib/block_scout_web/templates/transaction/_tile.html.eex:47
#: lib/block_scout_web/templates/transaction/_tile.html.eex:43
msgid "IN"
msgstr ""
#, elixir-format
#:
#: lib/block_scout_web/templates/address_internal_transaction/_internal_transaction.html.eex:27
#: lib/block_scout_web/templates/transaction/_tile.html.eex:43
#: lib/block_scout_web/templates/transaction/_tile.html.eex:39
msgid "OUT"
msgstr ""
@ -685,10 +685,12 @@ msgstr ""
#: lib/block_scout_web/templates/address_token/index.html.eex:50
#: lib/block_scout_web/templates/address_token/index.html.eex:58
#: lib/block_scout_web/templates/address_transaction/index.html.eex:49
#: lib/block_scout_web/templates/tokens/holder/index.html.eex:26
#: lib/block_scout_web/templates/tokens/holder/index.html.eex:54
#: lib/block_scout_web/templates/tokens/read_contract/index.html.eex:25
#: lib/block_scout_web/templates/tokens/read_contract/index.html.eex:42
#: lib/block_scout_web/templates/tokens/read_contract/index.html.eex:51
#: lib/block_scout_web/templates/tokens/token/show.html.eex:26
#: lib/block_scout_web/templates/tokens/token/show.html.eex:45
#: lib/block_scout_web/templates/tokens/token/show.html.eex:54
msgid "Read Contract"
msgstr ""
@ -714,12 +716,12 @@ msgid "Github"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/transaction/overview.html.eex:52
#: lib/block_scout_web/templates/transaction/overview.html.eex:48
msgid "Block Confirmations"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/transaction/overview.html.eex:114
#: lib/block_scout_web/templates/transaction/overview.html.eex:110
msgid "Limit"
msgstr ""
@ -735,14 +737,14 @@ msgid "There are no logs for this transaction."
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/transaction/overview.html.eex:107
#: lib/block_scout_web/templates/transaction/overview.html.eex:103
msgid "Used"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/tokens/token/_token_transfer.html.eex:4
#: lib/block_scout_web/templates/transaction_token_transfer/_token_transfer.html.eex:4
#: lib/block_scout_web/views/transaction_view.ex:117
#: lib/block_scout_web/views/transaction_view.ex:109
msgid "Token Transfer"
msgstr ""
@ -775,7 +777,7 @@ msgstr ""
msgid "Validated Transactions"
msgstr ""
#: lib/block_scout_web/templates/tokens/token/show.html.eex:64
#: lib/block_scout_web/templates/tokens/token/show.html.eex:78
msgid "There are no transfers for this Token."
msgstr ""
@ -785,13 +787,15 @@ msgid "Token Details"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/tokens/holder/index.html.eex:17
#: lib/block_scout_web/templates/tokens/holder/index.html.eex:48
#: lib/block_scout_web/templates/tokens/read_contract/index.html.eex:17
#: lib/block_scout_web/templates/tokens/read_contract/index.html.eex:34
#: lib/block_scout_web/templates/tokens/read_contract/index.html.eex:37
#: lib/block_scout_web/templates/tokens/read_contract/index.html.eex:43
#: lib/block_scout_web/templates/tokens/read_contract/index.html.eex:46
#: lib/block_scout_web/templates/tokens/token/show.html.eex:17
#: lib/block_scout_web/templates/tokens/token/show.html.eex:36
#: lib/block_scout_web/templates/tokens/token/show.html.eex:39
#: lib/block_scout_web/templates/tokens/token/show.html.eex:55
#: lib/block_scout_web/templates/tokens/token/show.html.eex:45
#: lib/block_scout_web/templates/tokens/token/show.html.eex:48
#: lib/block_scout_web/templates/tokens/token/show.html.eex:69
#: lib/block_scout_web/templates/transaction_internal_transaction/index.html.eex:12
#: lib/block_scout_web/templates/transaction_internal_transaction/index.html.eex:42
#: lib/block_scout_web/templates/transaction_log/index.html.eex:13
@ -830,7 +834,7 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_read_contract/index.html.eex:52
#: lib/block_scout_web/templates/tokens/read_contract/index.html.eex:52
#: lib/block_scout_web/templates/tokens/read_contract/index.html.eex:66
msgid "loading..."
msgstr ""
@ -1001,12 +1005,12 @@ msgid "loading....."
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/transaction/_tile.html.eex:67
#: lib/block_scout_web/templates/transaction/_tile.html.eex:63
msgid "View More Transfers"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/transaction/_tile.html.eex:68
#: lib/block_scout_web/templates/transaction/_tile.html.eex:64
msgid "View Less Transfers"
msgstr ""
@ -1016,7 +1020,7 @@ msgid "Less than"
msgstr ""
#, elixir-format
#: lib/block_scout_web/views/transaction_view.ex:42
#: lib/block_scout_web/views/transaction_view.ex:41
msgid "Max of"
msgstr ""
@ -1056,3 +1060,20 @@ msgstr ""
#: lib/block_scout_web/templates/address_token/index.html.eex:111
msgid "There are no tokens for this address."
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/tokens/holder/index.html.eex:79
msgid "There are no holders for this Token."
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/tokens/holder/index.html.eex:34
#: lib/block_scout_web/templates/tokens/holder/index.html.eex:45
#: lib/block_scout_web/templates/tokens/holder/index.html.eex:59
#: lib/block_scout_web/templates/tokens/holder/index.html.eex:70
#: lib/block_scout_web/templates/tokens/read_contract/index.html.eex:32
#: lib/block_scout_web/templates/tokens/read_contract/index.html.eex:55
#: lib/block_scout_web/templates/tokens/token/show.html.eex:34
#: lib/block_scout_web/templates/tokens/token/show.html.eex:59
msgid "Token Holders"
msgstr ""

@ -64,7 +64,7 @@ msgstr "BlockScout"
msgid "Transactions"
msgstr "Transactions"
#: lib/block_scout_web/templates/transaction/overview.html.eex:91
#: lib/block_scout_web/templates/transaction/overview.html.eex:87
msgid "Value"
msgstr "Value"
@ -88,7 +88,7 @@ msgid "Miner"
msgstr "Validator"
#: lib/block_scout_web/templates/block/overview.html.eex:59
#: lib/block_scout_web/templates/transaction/overview.html.eex:59
#: lib/block_scout_web/templates/transaction/overview.html.eex:55
msgid "Nonce"
msgstr "Nonce"
@ -112,7 +112,7 @@ msgstr "Timestamp"
msgid "Total Difficulty"
msgstr "Total Difficulty"
#: lib/block_scout_web/templates/transaction/overview.html.eex:38
#: lib/block_scout_web/templates/transaction/overview.html.eex:34
msgid "Block Number"
msgstr "Block Height"
@ -124,7 +124,7 @@ msgstr "Transaction Details"
msgid "Cumulative Gas Used"
msgstr "Cumulative Gas Used"
#: lib/block_scout_web/templates/transaction/overview.html.eex:102
#: lib/block_scout_web/templates/transaction/overview.html.eex:98
msgid "Gas"
msgstr "Gas"
@ -132,7 +132,7 @@ msgstr "Gas"
msgid "Gas Price"
msgstr "Gas Price"
#: lib/block_scout_web/templates/transaction/overview.html.eex:72
#: lib/block_scout_web/templates/transaction/overview.html.eex:68
msgid "Input"
msgstr "Input"
@ -144,7 +144,7 @@ msgstr "%{confirmations} block confirmations"
msgid "%{count} transactions in this block"
msgstr "%{count} transactions in this block"
#: lib/block_scout_web/views/address_view.ex:12
#: lib/block_scout_web/views/address_view.ex:48
msgid "Address"
msgstr "Address"
@ -160,7 +160,7 @@ msgstr "From"
msgid "Overview"
msgstr "Overview"
#: lib/block_scout_web/views/transaction_view.ex:81
#: lib/block_scout_web/views/transaction_view.ex:52
msgid "Success"
msgstr "Success"
@ -211,9 +211,9 @@ msgstr "Showing %{count} Transactions"
#: lib/block_scout_web/templates/pending_transaction/index.html.eex:35
#: lib/block_scout_web/templates/transaction/index.html.eex:16
#: lib/block_scout_web/templates/transaction/index.html.eex:35
#: lib/block_scout_web/templates/transaction/overview.html.eex:47
#: lib/block_scout_web/views/transaction_view.ex:46
#: lib/block_scout_web/views/transaction_view.ex:80
#: lib/block_scout_web/templates/transaction/overview.html.eex:43
#: lib/block_scout_web/views/transaction_view.ex:51
#: lib/block_scout_web/views/transaction_view.ex:73
msgid "Pending"
msgstr "Pending"
@ -277,15 +277,15 @@ msgstr ""
msgid "TPM"
msgstr ""
#: lib/block_scout_web/templates/pending_transaction/index.html.eex:69
#: lib/block_scout_web/templates/tokens/holder/index.html.eex:86
msgid "Next Page"
msgstr ""
#: lib/block_scout_web/views/transaction_view.ex:78
#: lib/block_scout_web/views/transaction_view.ex:49
msgid "Failed"
msgstr ""
#: lib/block_scout_web/views/transaction_view.ex:79
#: lib/block_scout_web/views/transaction_view.ex:50
msgid "Out of Gas"
msgstr ""
@ -304,8 +304,8 @@ msgstr ""
#:
#: lib/block_scout_web/templates/address_internal_transaction/_internal_transaction.html.eex:22
#: lib/block_scout_web/templates/pending_transaction/index.html.eex:68
#: lib/block_scout_web/templates/transaction/_tile.html.eex:24
#: lib/block_scout_web/templates/transaction/overview.html.eex:91
#: lib/block_scout_web/templates/transaction/_tile.html.eex:20
#: lib/block_scout_web/templates/transaction/overview.html.eex:87
#: lib/block_scout_web/templates/transaction_internal_transaction/_internal_transaction.html.eex:16
#: lib/block_scout_web/views/wei_helpers.ex:72
msgid "Ether"
@ -465,7 +465,7 @@ msgstr ""
msgid "Total Gas Used"
msgstr ""
#: lib/block_scout_web/views/transaction_view.ex:120
#: lib/block_scout_web/views/transaction_view.ex:112
msgid "Transaction"
msgstr ""
@ -479,8 +479,8 @@ msgid "View All"
msgstr ""
#: lib/block_scout_web/templates/pending_transaction/index.html.eex:69
#: lib/block_scout_web/templates/transaction/_tile.html.eex:27
#: lib/block_scout_web/templates/transaction/overview.html.eex:64
#: lib/block_scout_web/templates/transaction/_tile.html.eex:23
#: lib/block_scout_web/templates/transaction/overview.html.eex:60
msgid "TX Fee"
msgstr ""
@ -488,7 +488,7 @@ msgstr ""
msgid "Contract"
msgstr ""
#: lib/block_scout_web/views/address_view.ex:10
#: lib/block_scout_web/views/address_view.ex:46
msgid "Contract Address"
msgstr ""
@ -505,7 +505,7 @@ msgstr ""
#: lib/block_scout_web/templates/block/index.html.eex:15
#: lib/block_scout_web/templates/block_transaction/index.html.eex:50
#: lib/block_scout_web/templates/pending_transaction/index.html.eex:78
#: lib/block_scout_web/templates/tokens/token/show.html.eex:71
#: lib/block_scout_web/templates/tokens/token/show.html.eex:85
#: lib/block_scout_web/templates/transaction/index.html.eex:66
#: lib/block_scout_web/templates/transaction_token_transfer/index.html.eex:72
msgid "Older"
@ -552,7 +552,7 @@ msgid "Newer"
msgstr ""
#, elixir-format
#: lib/block_scout_web/views/transaction_view.ex:118
#: lib/block_scout_web/views/transaction_view.ex:110
msgid "Contract Creation"
msgstr ""
@ -646,12 +646,12 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/pending_transaction/index.html.eex:64
#: lib/block_scout_web/templates/transaction/overview.html.eex:23
#: lib/block_scout_web/views/address_view.ex:24
msgid "Contract Address Pending"
msgstr ""
#, elixir-format
#: lib/block_scout_web/views/transaction_view.ex:119
#: lib/block_scout_web/views/transaction_view.ex:111
msgid "Contract Call"
msgstr ""
@ -667,21 +667,21 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/tokens/token/_token_transfer.html.eex:36
#: lib/block_scout_web/templates/transaction/_tile.html.eex:34
#: lib/block_scout_web/templates/transaction/_tile.html.eex:30
msgid "Block #%{number}"
msgstr ""
#, elixir-format
#:
#: lib/block_scout_web/templates/address_internal_transaction/_internal_transaction.html.eex:29
#: lib/block_scout_web/templates/transaction/_tile.html.eex:47
#: lib/block_scout_web/templates/transaction/_tile.html.eex:43
msgid "IN"
msgstr ""
#, elixir-format
#:
#: lib/block_scout_web/templates/address_internal_transaction/_internal_transaction.html.eex:27
#: lib/block_scout_web/templates/transaction/_tile.html.eex:43
#: lib/block_scout_web/templates/transaction/_tile.html.eex:39
msgid "OUT"
msgstr ""
@ -697,10 +697,12 @@ msgstr ""
#: lib/block_scout_web/templates/address_token/index.html.eex:50
#: lib/block_scout_web/templates/address_token/index.html.eex:58
#: lib/block_scout_web/templates/address_transaction/index.html.eex:49
#: lib/block_scout_web/templates/tokens/holder/index.html.eex:26
#: lib/block_scout_web/templates/tokens/holder/index.html.eex:54
#: lib/block_scout_web/templates/tokens/read_contract/index.html.eex:25
#: lib/block_scout_web/templates/tokens/read_contract/index.html.eex:42
#: lib/block_scout_web/templates/tokens/read_contract/index.html.eex:51
#: lib/block_scout_web/templates/tokens/token/show.html.eex:26
#: lib/block_scout_web/templates/tokens/token/show.html.eex:45
#: lib/block_scout_web/templates/tokens/token/show.html.eex:54
msgid "Read Contract"
msgstr ""
@ -726,12 +728,12 @@ msgid "Github"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/transaction/overview.html.eex:52
#: lib/block_scout_web/templates/transaction/overview.html.eex:48
msgid "Block Confirmations"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/transaction/overview.html.eex:114
#: lib/block_scout_web/templates/transaction/overview.html.eex:110
msgid "Limit"
msgstr ""
@ -747,14 +749,14 @@ msgid "There are no logs for this transaction."
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/transaction/overview.html.eex:107
#: lib/block_scout_web/templates/transaction/overview.html.eex:103
msgid "Used"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/tokens/token/_token_transfer.html.eex:4
#: lib/block_scout_web/templates/transaction_token_transfer/_token_transfer.html.eex:4
#: lib/block_scout_web/views/transaction_view.ex:117
#: lib/block_scout_web/views/transaction_view.ex:109
msgid "Token Transfer"
msgstr ""
@ -787,7 +789,7 @@ msgstr ""
msgid "Validated Transactions"
msgstr ""
#: lib/block_scout_web/templates/tokens/token/show.html.eex:64
#: lib/block_scout_web/templates/tokens/token/show.html.eex:78
msgid "There are no transfers for this Token."
msgstr ""
@ -797,13 +799,15 @@ msgid "Token Details"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/tokens/holder/index.html.eex:17
#: lib/block_scout_web/templates/tokens/holder/index.html.eex:48
#: lib/block_scout_web/templates/tokens/read_contract/index.html.eex:17
#: lib/block_scout_web/templates/tokens/read_contract/index.html.eex:34
#: lib/block_scout_web/templates/tokens/read_contract/index.html.eex:37
#: lib/block_scout_web/templates/tokens/read_contract/index.html.eex:43
#: lib/block_scout_web/templates/tokens/read_contract/index.html.eex:46
#: lib/block_scout_web/templates/tokens/token/show.html.eex:17
#: lib/block_scout_web/templates/tokens/token/show.html.eex:36
#: lib/block_scout_web/templates/tokens/token/show.html.eex:39
#: lib/block_scout_web/templates/tokens/token/show.html.eex:55
#: lib/block_scout_web/templates/tokens/token/show.html.eex:45
#: lib/block_scout_web/templates/tokens/token/show.html.eex:48
#: lib/block_scout_web/templates/tokens/token/show.html.eex:69
#: lib/block_scout_web/templates/transaction_internal_transaction/index.html.eex:12
#: lib/block_scout_web/templates/transaction_internal_transaction/index.html.eex:42
#: lib/block_scout_web/templates/transaction_log/index.html.eex:13
@ -842,7 +846,7 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_read_contract/index.html.eex:52
#: lib/block_scout_web/templates/tokens/read_contract/index.html.eex:52
#: lib/block_scout_web/templates/tokens/read_contract/index.html.eex:66
msgid "loading..."
msgstr ""
@ -1013,12 +1017,12 @@ msgid "loading....."
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/transaction/_tile.html.eex:67
#: lib/block_scout_web/templates/transaction/_tile.html.eex:63
msgid "View More Transfers"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/transaction/_tile.html.eex:68
#: lib/block_scout_web/templates/transaction/_tile.html.eex:64
msgid "View Less Transfers"
msgstr ""
@ -1028,7 +1032,7 @@ msgid "Less than"
msgstr ""
#, elixir-format
#: lib/block_scout_web/views/transaction_view.ex:42
#: lib/block_scout_web/views/transaction_view.ex:41
msgid "Max of"
msgstr ""
@ -1068,3 +1072,20 @@ msgstr ""
#: lib/block_scout_web/templates/address_token/index.html.eex:111
msgid "There are no tokens for this address."
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/tokens/holder/index.html.eex:79
msgid "There are no holders for this Token."
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/tokens/holder/index.html.eex:34
#: lib/block_scout_web/templates/tokens/holder/index.html.eex:45
#: lib/block_scout_web/templates/tokens/holder/index.html.eex:59
#: lib/block_scout_web/templates/tokens/holder/index.html.eex:70
#: lib/block_scout_web/templates/tokens/read_contract/index.html.eex:32
#: lib/block_scout_web/templates/tokens/read_contract/index.html.eex:55
#: lib/block_scout_web/templates/tokens/token/show.html.eex:34
#: lib/block_scout_web/templates/tokens/token/show.html.eex:59
msgid "Token Holders"
msgstr ""

@ -0,0 +1,92 @@
defmodule BlockScoutWeb.Tokens.HolderControllerTest do
use BlockScoutWeb.ConnCase
alias Explorer.Chain.Hash
describe "GET index/3" do
test "with invalid address hash", %{conn: conn} do
conn = get(conn, token_holder_path(BlockScoutWeb.Endpoint, :index, "invalid_address"))
assert html_response(conn, 404)
end
test "with a token that doesn't exist", %{conn: conn} do
address = build(:address)
conn = get(conn, token_holder_path(BlockScoutWeb.Endpoint, :index, address.hash))
assert html_response(conn, 404)
end
test "successfully renders the page", %{conn: conn} do
token = insert(:token)
insert_list(
2,
:token_balance,
token_contract_address_hash: token.contract_address_hash
)
conn =
get(
conn,
token_holder_path(BlockScoutWeb.Endpoint, :index, token.contract_address_hash)
)
assert html_response(conn, 200)
end
test "returns next page of results based on last seen token balance", %{conn: conn} do
contract_address = build(:contract_address, hash: "0x6937cb25eb54bc013b9c13c47ab38eb63edd1493")
token = insert(:token, contract_address: contract_address)
second_page_token_balances =
1..50
|> Enum.map(
&insert(
:token_balance,
token_contract_address_hash: token.contract_address_hash,
value: &1 + 1000
)
)
|> Enum.map(& &1.value)
token_balance =
insert(
:token_balance,
token_contract_address_hash: token.contract_address_hash,
value: 50000
)
conn =
get(conn, token_holder_path(conn, :index, token.contract_address_hash), %{
"value" => Decimal.to_integer(token_balance.value),
"address_hash" => Hash.to_string(token_balance.address_hash)
})
actual_token_balances =
conn.assigns.token_balances
|> Enum.map(& &1.value)
|> Enum.reverse()
assert second_page_token_balances == actual_token_balances
end
test "next_page_params exists if not on last page", %{conn: conn} do
contract_address = build(:contract_address, hash: "0x6937cb25eb54bc013b9c13c47ab38eb63edd1493")
token = insert(:token, contract_address: contract_address)
Enum.each(
1..51,
&insert(
:token_balance,
token_contract_address_hash: token.contract_address_hash,
value: &1 + 1000
)
)
conn = get(conn, token_holder_path(conn, :index, token.contract_address_hash))
assert conn.assigns.next_page_params
end
end
end

@ -31,7 +31,7 @@ defmodule BlockScoutWeb.Tokens.ReadContractControllerTest do
assert html_response(conn, 200)
assert token.contract_address_hash == conn.assigns.token.contract_address_hash
assert conn.assigns.total_token_transfers
assert conn.assigns.total_address_in_token_transfers
assert conn.assigns.total_token_holders
end
end
end

@ -0,0 +1,23 @@
defmodule BlockScoutWeb.TokenPage do
@moduledoc false
use Wallaby.DSL
import Wallaby.Query, only: [css: 1, css: 2]
alias Explorer.Chain.{Address}
def visit_page(session, %Address{hash: address_hash}) do
visit_page(session, address_hash)
end
def visit_page(session, contract_address_hash) do
visit(session, "tokens/#{contract_address_hash}")
end
def click_tokens_holders(session) do
click(session, css("[data-test='token_holders_tab']"))
end
def token_holders(count: count) do
css("[data-test='token_holders']", count: count)
end
end

@ -2,7 +2,6 @@ defmodule BlockScoutWeb.ViewingAddressesTest do
use BlockScoutWeb.FeatureCase, async: true
alias Explorer.Chain.Wei
alias Explorer.Factory
alias BlockScoutWeb.{AddressPage, AddressView, Notifier}
setup do
@ -41,7 +40,7 @@ defmodule BlockScoutWeb.ViewingAddressesTest do
describe "viewing contract creator" do
test "see the contract creator and transaction links", %{session: session} do
address = insert(:address)
contract = insert(:address, contract_code: Factory.data("contract_code"))
contract = insert(:contract_address)
transaction = insert(:transaction, from_address: address, created_contract_address: contract)
internal_transaction =
@ -63,9 +62,9 @@ defmodule BlockScoutWeb.ViewingAddressesTest do
test "see the contract creator and transaction links even when the creator is another contract", %{session: session} do
lincoln = insert(:address)
contract = insert(:address, contract_code: Factory.data("contract_code"))
contract = insert(:contract_address)
transaction = insert(:transaction)
another_contract = insert(:address, contract_code: Factory.data("contract_code"))
another_contract = insert(:contract_address)
insert(
:internal_transaction,
@ -285,12 +284,12 @@ defmodule BlockScoutWeb.ViewingAddressesTest do
lincoln = addresses.lincoln
taft = addresses.taft
contract_token_address = insert(:contract_address)
insert(:token, contract_address: contract_token_address)
contract_address = insert(:contract_address)
insert(:token, contract_address: contract_address)
transaction =
:transaction
|> insert(from_address: lincoln, to_address: contract_token_address)
|> insert(from_address: lincoln, to_address: contract_address)
|> with_block(block)
insert(
@ -298,7 +297,7 @@ defmodule BlockScoutWeb.ViewingAddressesTest do
from_address: lincoln,
to_address: taft,
transaction: transaction,
token_contract_address: contract_token_address
token_contract_address: contract_address
)
session
@ -318,12 +317,12 @@ defmodule BlockScoutWeb.ViewingAddressesTest do
taft = addresses.taft
morty = build(:address)
contract_token_address = insert(:contract_address)
insert(:token, contract_address: contract_token_address)
contract_address = insert(:contract_address)
insert(:token, contract_address: contract_address)
transaction =
:transaction
|> insert(from_address: lincoln, to_address: contract_token_address)
|> insert(from_address: lincoln, to_address: contract_address)
|> with_block(block)
insert(
@ -331,7 +330,7 @@ defmodule BlockScoutWeb.ViewingAddressesTest do
from_address: lincoln,
to_address: taft,
transaction: transaction,
token_contract_address: contract_token_address
token_contract_address: contract_address
)
insert(
@ -339,7 +338,7 @@ defmodule BlockScoutWeb.ViewingAddressesTest do
from_address: lincoln,
to_address: morty,
transaction: transaction,
token_contract_address: contract_token_address
token_contract_address: contract_address
)
session
@ -358,17 +357,13 @@ defmodule BlockScoutWeb.ViewingAddressesTest do
lincoln = addresses.lincoln
taft = addresses.taft
contract_token_address =
insert(
:address,
contract_code: Factory.data("contract_code")
)
contract_address = insert(:contract_address)
insert(:token, contract_address: contract_token_address)
insert(:token, contract_address: contract_address)
transaction =
:transaction
|> insert(from_address: lincoln, to_address: contract_token_address)
|> insert(from_address: lincoln, to_address: contract_address)
|> with_block(block)
insert_list(
@ -377,7 +372,7 @@ defmodule BlockScoutWeb.ViewingAddressesTest do
from_address: lincoln,
to_address: taft,
transaction: transaction,
token_contract_address: contract_token_address
token_contract_address: contract_address
)
session
@ -393,12 +388,12 @@ defmodule BlockScoutWeb.ViewingAddressesTest do
lincoln = addresses.lincoln
taft = addresses.taft
contract_token_address = insert(:contract_address)
insert(:token, contract_address: contract_token_address)
contract_address = insert(:contract_address)
insert(:token, contract_address: contract_address)
transaction =
:transaction
|> insert(from_address: lincoln, to_address: contract_token_address)
|> insert(from_address: lincoln, to_address: contract_address)
|> with_block(block)
insert_list(
@ -407,7 +402,7 @@ defmodule BlockScoutWeb.ViewingAddressesTest do
from_address: lincoln,
to_address: taft,
transaction: transaction,
token_contract_address: contract_token_address
token_contract_address: contract_address
)
session

@ -0,0 +1,22 @@
defmodule BlockScoutWeb.ViewingTokensTest do
use BlockScoutWeb.FeatureCase, async: true
alias BlockScoutWeb.TokenPage
describe "viewing token holders" do
test "list the token holders", %{session: session} do
token = insert(:token)
insert_list(
2,
:token_balance,
token_contract_address_hash: token.contract_address_hash
)
session
|> TokenPage.visit_page(token.contract_address)
|> TokenPage.click_tokens_holders()
|> assert_has(TokenPage.token_holders(count: 2))
end
end
end

@ -1,9 +1,106 @@
defmodule BlockScoutWeb.AddressViewTest do
use BlockScoutWeb.ConnCase, async: true
alias Explorer.Chain.Data
alias Explorer.Chain.{Address, Data, Transaction}
alias BlockScoutWeb.AddressView
describe "address_partial_selector/4" do
test "for a pending contract creation to address" do
transaction = insert(:transaction, to_address: nil, created_contract_address_hash: nil)
assert AddressView.address_partial_selector(transaction, :to, nil) == "Contract Address Pending"
end
test "will truncate address" do
transaction = %Transaction{to_address_hash: hash} = insert(:transaction)
assert %{
partial: "_link.html",
address_hash: ^hash,
contract: false,
truncate: true
} = AddressView.address_partial_selector(transaction, :to, nil, true)
end
test "for a non-contract to address not on address page" do
transaction = %Transaction{to_address_hash: hash} = insert(:transaction)
assert %{
partial: "_link.html",
address_hash: ^hash,
contract: false,
truncate: false
} = AddressView.address_partial_selector(transaction, :to, nil)
end
test "for a non-contract to address non matching address page" do
transaction = %Transaction{to_address_hash: hash} = insert(:transaction)
assert %{
partial: "_link.html",
address_hash: ^hash,
contract: false,
truncate: false
} = AddressView.address_partial_selector(transaction, :to, nil)
end
test "for a non-contract to address matching address page" do
transaction = %Transaction{to_address_hash: hash} = insert(:transaction)
assert %{
partial: "_responsive_hash.html",
address_hash: ^hash,
contract: false,
truncate: false
} = AddressView.address_partial_selector(transaction, :to, transaction.to_address)
end
test "for a contract to address non matching address page" do
contract = %Address{hash: hash} = insert(:contract_address)
transaction = insert(:transaction, to_address: nil, created_contract_address: contract)
assert %{
partial: "_link.html",
address_hash: ^hash,
contract: true,
truncate: false
} = AddressView.address_partial_selector(transaction, :to, transaction.to_address)
end
test "for a contract to address matching address page" do
contract = %Address{hash: hash} = insert(:contract_address)
transaction = insert(:transaction, to_address: nil, created_contract_address: contract)
assert %{
partial: "_responsive_hash.html",
address_hash: ^hash,
contract: true,
truncate: false
} = AddressView.address_partial_selector(transaction, :to, contract)
end
test "for a non-contract from address not on address page" do
transaction = %Transaction{to_address_hash: hash} = insert(:transaction)
assert %{
partial: "_link.html",
address_hash: ^hash,
contract: false,
truncate: false
} = AddressView.address_partial_selector(transaction, :to, nil)
end
test "for a non-contract from address matching address page" do
transaction = %Transaction{from_address_hash: hash} = insert(:transaction)
assert %{
partial: "_responsive_hash.html",
address_hash: ^hash,
contract: false,
truncate: false
} = AddressView.address_partial_selector(transaction, :from, transaction.from_address)
end
end
describe "contract?/1" do
test "with a smart contract" do
{:ok, code} = Data.cast("0x000000000000000000000000862d67cb0773ee3f8ce7ea89b328ffea861ab3ef")
@ -15,6 +112,10 @@ defmodule BlockScoutWeb.AddressViewTest do
address = insert(:address, contract_code: nil)
refute AddressView.contract?(address)
end
test "with nil address" do
assert AddressView.contract?(nil)
end
end
describe "qr_code/1" do
@ -24,6 +125,27 @@ defmodule BlockScoutWeb.AddressViewTest do
end
end
describe "render_partial/1" do
test "renders _link partial" do
%Address{hash: hash} = build(:address)
assert {:safe, _} =
AddressView.render_partial(%{partial: "_link.html", address_hash: hash, contract: false, truncate: false})
end
test "renders _responsive_hash partial" do
%Address{hash: hash} = build(:address)
assert {:safe, _} =
AddressView.render_partial(%{
partial: "_responsive_hash.html",
address_hash: hash,
contract: false,
truncate: false
})
end
end
describe "smart_contract_verified?/1" do
test "returns true when smart contract is verified" do
smart_contract = insert(:smart_contract)

@ -0,0 +1,40 @@
defmodule BlockScoutWeb.Tokens.HolderViewTest do
use BlockScoutWeb.ConnCase, async: true
alias BlockScoutWeb.Tokens.HolderView
alias Explorer.Chain.{Address.TokenBalance, Token}
doctest BlockScoutWeb.Tokens.HolderView, import: true
describe "total_supply_percentage/2" do
test "returns the percentage of the Token total supply" do
%Token{total_supply: total_supply} = build(:token, total_supply: 1000)
%TokenBalance{value: value} = build(:token_balance, value: 200)
assert HolderView.total_supply_percentage(value, total_supply) == "20.0000%"
end
test "considers 4 decimals" do
%Token{total_supply: total_supply} = build(:token, total_supply: 100_000_009)
%TokenBalance{value: value} = build(:token_balance, value: 500)
assert HolderView.total_supply_percentage(value, total_supply) == "0.0005%"
end
end
describe "format_token_balance_value/1" do
test "formats according to token decimals when it's a ERC-20" do
token = build(:token, type: "ERC-20", decimals: 2)
token_balance = build(:token_balance, value: 2_000_000)
assert HolderView.format_token_balance_value(token_balance.value, 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
end
end
end

@ -5,6 +5,35 @@ defmodule BlockScoutWeb.TransactionViewTest do
alias Explorer.Repo
alias BlockScoutWeb.TransactionView
describe "confirmations/2" do
test "returns 0 if pending transaction" do
transaction = build(:transaction, block: nil)
assert 0 == TransactionView.confirmations(transaction, [])
end
test "returns string of number of blocks validated since subject block" do
block = insert(:block)
transaction =
:transaction
|> insert()
|> with_block(block)
assert "1" == TransactionView.confirmations(transaction, max_block_number: block.number + 1)
end
end
describe "contract_creation?/1" do
test "returns true if contract creation transaction" do
assert TransactionView.contract_creation?(build(:transaction, to_address: nil))
end
test "returns false if not contract" do
refute TransactionView.contract_creation?(build(:transaction))
end
end
describe "formatted_fee/2" do
test "pending transaction with no Receipt" do
{:ok, gas_price} = Wei.cast(3_000_000_000)
@ -78,10 +107,32 @@ defmodule BlockScoutWeb.TransactionViewTest do
end
end
test "gas/1 returns the gas as a string" do
assert "2" == TransactionView.gas(build(:transaction, gas: 2))
end
test "hash/1 returns the hash as a string" do
assert "test" == TransactionView.hash(build(:transaction, hash: "test"))
end
describe "qr_code/1" do
test "it returns an encoded value" do
transaction = build(:transaction)
assert {:ok, _} = Base.decode64(TransactionView.qr_code(transaction))
end
end
describe "to_address_hash/1" do
test "returns contract address for created contract transaction" do
contract = insert(:contract_address)
transaction = insert(:transaction, to_address: nil, created_contract_address: contract)
assert contract.hash == TransactionView.to_address_hash(transaction)
end
test "returns hash for transaction" do
address = insert(:address)
transaction = insert(:transaction, to_address: address)
assert address.hash == TransactionView.to_address_hash(transaction)
end
end
end

@ -1635,11 +1635,6 @@ defmodule Explorer.Chain do
TokenTransfer.count_token_transfers_from_token_hash(token_address_hash)
end
@spec count_addresses_in_token_transfers_from_token_hash(Hash.t()) :: non_neg_integer()
def count_addresses_in_token_transfers_from_token_hash(token_address_hash) do
TokenTransfer.count_addresses_in_token_transfers_from_token_hash(token_address_hash)
end
@spec transaction_has_token_transfers?(Hash.t()) :: boolean()
def transaction_has_token_transfers?(transaction_hash) do
query = from(tt in TokenTransfer, where: tt.transaction_hash == ^transaction_hash, limit: 1, select: 1)
@ -1718,4 +1713,18 @@ defmodule Explorer.Chain do
|> TokenBalance.last_token_balances()
|> Repo.all()
end
@spec fetch_token_holders_from_token_hash(Hash.Address.t(), [paging_options]) :: [TokenBalance.t()]
def fetch_token_holders_from_token_hash(contract_address_hash, options) do
contract_address_hash
|> TokenBalance.token_holders_ordered_by_value(options)
|> Repo.all()
end
@spec count_token_holders_from_token_hash(Hash.Address.t()) :: non_neg_integer()
def count_token_holders_from_token_hash(contract_address_hash) do
contract_address_hash
|> TokenBalance.token_holders_from_token_hash()
|> Repo.aggregate(:count, :address_hash)
end
end

@ -5,11 +5,14 @@ defmodule Explorer.Chain.Address.TokenBalance do
use Ecto.Schema
import Ecto.Changeset
import Ecto.Query, only: [from: 2]
import Ecto.Query, only: [from: 2, limit: 2, where: 3, subquery: 1, order_by: 3, preload: 2]
alias Explorer.{Chain, PagingOptions}
alias Explorer.Chain.Address.TokenBalance
alias Explorer.Chain.{Address, Block, Hash, Token}
@default_paging_options %PagingOptions{page_size: 50}
@typedoc """
* `address` - The `t:Explorer.Chain.Address.t/0` that is the balance's owner.
* `address_hash` - The address hash foreign key.
@ -62,17 +65,68 @@ defmodule Explorer.Chain.Address.TokenBalance do
end
@doc """
Builds an `Ecto.Query` to fetch the last token balances.
Builds an `Ecto.Query` to fetch the last token balances that have value greater than 0.
The last token balances from an Address is the last block indexed.
"""
def last_token_balances(address_hash) do
query =
from(
tb in TokenBalance,
where: tb.address_hash == ^address_hash,
distinct: :token_contract_address_hash,
order_by: [desc: :block_number]
)
from(tb in subquery(query), where: tb.value > 0, preload: :token)
end
@doc """
Builds an `Ecto.Query` to fetch the token holders from the given token contract address hash.
The Token Holders are the addresses that own a positive amount of the Token. So this query is
considering the following conditions:
* The token balance from the last block.
* Balances greater than 0.
* Excluding the burn address (0x0000000000000000000000000000000000000000).
"""
def token_holders_from_token_hash(token_contract_address_hash) do
query = token_holders_query(token_contract_address_hash)
from(tb in subquery(query), where: tb.value > 0)
end
def token_holders_ordered_by_value(token_contract_address_hash, options) do
paging_options = Keyword.get(options, :paging_options, @default_paging_options)
token_contract_address_hash
|> token_holders_from_token_hash()
|> order_by([tb], desc: tb.value, desc: tb.address_hash)
|> preload(:address)
|> page_token_balances(paging_options)
|> limit(^paging_options.page_size)
end
defp token_holders_query(contract_address_hash) do
{:ok, burn_address_hash} = Chain.string_to_address_hash("0x0000000000000000000000000000000000000000")
from(
tb in TokenBalance,
where: tb.address_hash == ^address_hash and tb.value > 0,
distinct: :token_contract_address_hash,
order_by: [desc: :block_number],
preload: :token
distinct: :address_hash,
where: tb.token_contract_address_hash == ^contract_address_hash and tb.address_hash != ^burn_address_hash,
order_by: [desc: :block_number]
)
end
defp page_token_balances(query, %PagingOptions{key: nil}), do: query
defp page_token_balances(query, %PagingOptions{key: {value, address_hash}}) do
where(
query,
[tb],
tb.value < ^value or (tb.value == ^value and tb.address_hash < ^address_hash)
)
end
end

@ -28,7 +28,6 @@ defmodule Explorer.Chain.TokenTransfer do
alias Explorer.Chain.{Address, Block, Hash, Transaction, TokenTransfer}
alias Explorer.{PagingOptions, Repo}
alias Ecto.Adapters.SQL
@default_paging_options %PagingOptions{page_size: 50}
@ -140,32 +139,6 @@ defmodule Explorer.Chain.TokenTransfer do
Repo.one(query)
end
@spec count_addresses_in_token_transfers_from_token_hash(Hash.t()) :: non_neg_integer()
def count_addresses_in_token_transfers_from_token_hash(token_address_hash) do
{:ok, %{rows: [[result]]}} =
SQL.query(
Repo,
"""
select count(*) as "addresses"
from
(
select to_address_hash as "address_hash"
from token_transfers tt1
where tt1.token_contract_address_hash = $1
union
select from_address_hash as "address_hash"
from token_transfers tt2
where tt2.token_contract_address_hash = $1
) as addresses_count
""",
[token_address_hash.bytes]
)
result
end
def page_token_transfer(query, %PagingOptions{key: nil}), do: query
def page_token_transfer(query, %PagingOptions{key: inserted_at}) do

@ -142,53 +142,4 @@ defmodule Explorer.Chain.TokenTransferTest do
assert TokenTransfer.count_token_transfers_from_token_hash(token_contract_address.hash) == 2
end
end
describe "count_addresses_in_transfers/1" do
test "counts how many unique addresses that appeared at `to` or `from`" do
token_contract_address = insert(:contract_address)
transaction =
:transaction
|> insert()
|> with_block()
john_address = insert(:address)
jane_address = insert(:address)
bob_address = insert(:address)
insert(
:token_transfer,
from_address: jane_address,
to_address: john_address,
transaction: transaction,
token_contract_address: token_contract_address
)
insert(
:token_transfer,
from_address: john_address,
to_address: jane_address,
transaction: transaction,
token_contract_address: token_contract_address
)
insert(
:token_transfer,
from_address: bob_address,
to_address: jane_address,
transaction: transaction,
token_contract_address: token_contract_address
)
insert(
:token_transfer,
from_address: jane_address,
to_address: bob_address,
transaction: transaction,
token_contract_address: token_contract_address
)
assert TokenTransfer.count_addresses_in_token_transfers_from_token_hash(token_contract_address.hash) == 3
end
end
end

@ -507,28 +507,6 @@ defmodule Explorer.ChainTest do
end
end
describe "count_addresses_in_token_transfers_from_token_hash/1" do
test "without token transfers" do
%Token{contract_address_hash: contract_address_hash} = insert(:token)
assert Chain.count_addresses_in_token_transfers_from_token_hash(contract_address_hash) == 0
end
test "with token transfers" do
address = insert(:address)
transaction =
:transaction
|> insert()
|> with_block()
%TokenTransfer{token_contract_address_hash: token_contract_address_hash} =
insert(:token_transfer, to_address: address, transaction: transaction)
assert Chain.count_addresses_in_token_transfers_from_token_hash(token_contract_address_hash) == 2
end
end
describe "gas_price/2" do
test ":wei unit" do
assert Chain.gas_price(%Transaction{gas_price: %Wei{value: Decimal.new(1)}}, :wei) == Decimal.new(1)
@ -2687,5 +2665,274 @@ defmodule Explorer.ChainTest do
assert Chain.fetch_last_token_balances(address.hash) == []
end
test "does not consider other blocks when the last block has the value 0" do
address = insert(:address)
token = insert(:token, contract_address: build(:contract_address))
insert(
:token_balance,
address: address,
block_number: 1000,
token_contract_address_hash: token.contract_address_hash,
value: 5000
)
insert(
:token_balance,
address: address,
block_number: 1001,
token_contract_address_hash: token.contract_address_hash,
value: 0
)
assert Chain.fetch_last_token_balances(address.hash) == []
end
end
describe "fetch_token_holders_from_token_hash/2" do
test "returns the last value for each address" do
%Token{contract_address_hash: contract_address_hash} = insert(:token)
address = insert(:address)
insert(
:token_balance,
address: address,
block_number: 1000,
token_contract_address_hash: contract_address_hash,
value: 5000
)
insert(
:token_balance,
block_number: 1001,
token_contract_address_hash: contract_address_hash,
value: 4000
)
insert(
:token_balance,
address: address,
block_number: 1002,
token_contract_address_hash: contract_address_hash,
value: 2000
)
values =
contract_address_hash
|> Chain.fetch_token_holders_from_token_hash([])
|> Enum.map(&Decimal.to_integer(&1.value))
assert values == [4000, 2000]
end
test "sort by the hightest value" do
%Token{contract_address_hash: contract_address_hash} = insert(:token)
insert(
:token_balance,
block_number: 1000,
token_contract_address_hash: contract_address_hash,
value: 2000
)
insert(
:token_balance,
block_number: 1001,
token_contract_address_hash: contract_address_hash,
value: 1000
)
insert(
:token_balance,
block_number: 1002,
token_contract_address_hash: contract_address_hash,
value: 4000
)
insert(
:token_balance,
block_number: 1002,
token_contract_address_hash: contract_address_hash,
value: 3000
)
values =
contract_address_hash
|> Chain.fetch_token_holders_from_token_hash([])
|> Enum.map(&Decimal.to_integer(&1.value))
assert values == [4000, 3000, 2000, 1000]
end
test "returns only token balances that have value" do
%Token{contract_address_hash: contract_address_hash} = insert(:token)
insert(
:token_balance,
token_contract_address_hash: contract_address_hash,
value: 0
)
assert Chain.fetch_token_holders_from_token_hash(contract_address_hash, []) == []
end
test "returns an empty list when there are no address with value greater than 0" do
%Token{contract_address_hash: contract_address_hash} = insert(:token)
insert(:token_balance, value: 1000)
assert Chain.fetch_token_holders_from_token_hash(contract_address_hash, []) == []
end
test "ignores the burn address" do
{:ok, burn_address_hash} = Chain.string_to_address_hash("0x0000000000000000000000000000000000000000")
burn_address = insert(:address, hash: burn_address_hash)
%Token{contract_address_hash: contract_address_hash} = insert(:token)
insert(
:token_balance,
address: burn_address,
token_contract_address_hash: contract_address_hash,
value: 1000
)
assert Chain.fetch_token_holders_from_token_hash(contract_address_hash, []) == []
end
test "paginates the result by value and different address" do
address_a = build(:address, hash: "0xcb2cf1fd3199584ac5faa16c6aca49472dc6495a")
address_b = build(:address, hash: "0x5f26097334b6a32b7951df61fd0c5803ec5d8354")
%Token{contract_address_hash: contract_address_hash} = insert(:token)
first_page =
insert(
:token_balance,
address: address_a,
token_contract_address_hash: contract_address_hash,
value: 4000
)
second_page =
insert(
:token_balance,
address: address_b,
token_contract_address_hash: contract_address_hash,
value: 4000
)
paging_options = %PagingOptions{
key: {first_page.value, first_page.address_hash},
page_size: 2
}
holders_paginated =
contract_address_hash
|> Chain.fetch_token_holders_from_token_hash(paging_options: paging_options)
|> Enum.map(& &1.address_hash)
assert holders_paginated == [second_page.address_hash]
end
test "considers the last block only if it has value" do
address = insert(:address, hash: "0x5f26097334b6a32b7951df61fd0c5803ec5d8354")
%Token{contract_address_hash: contract_address_hash} = insert(:token)
insert(
:token_balance,
address: address,
block_number: 1000,
token_contract_address_hash: contract_address_hash,
value: 5000
)
insert(
:token_balance,
address: address,
block_number: 1002,
token_contract_address_hash: contract_address_hash,
value: 0
)
assert Chain.fetch_token_holders_from_token_hash(contract_address_hash, []) == []
end
end
describe "count_token_holders_from_token_hash" do
test "counts different addresses that have the token" do
address_a = insert(:address, hash: "0xe49fedd93960a0267b3c3b2c1e2d66028e013fee")
address_b = insert(:address, hash: "0x5f26097334b6a32b7951df61fd0c5803ec5d8354")
%Token{contract_address_hash: contract_address_hash} = insert(:token)
insert(
:token_balance,
address: address_a,
block_number: 1000,
token_contract_address_hash: contract_address_hash,
value: 5000
)
insert(
:token_balance,
address: address_b,
block_number: 1002,
token_contract_address_hash: contract_address_hash,
value: 1000
)
assert Chain.count_token_holders_from_token_hash(contract_address_hash) == 2
end
test "counts only the last block" do
address = insert(:address, hash: "0xe49fedd93960a0267b3c3b2c1e2d66028e013fee")
%Token{contract_address_hash: contract_address_hash} = insert(:token)
insert(
:token_balance,
address: address,
block_number: 1000,
token_contract_address_hash: contract_address_hash,
value: 5000
)
insert(
:token_balance,
address: address,
block_number: 1002,
token_contract_address_hash: contract_address_hash,
value: 1000
)
assert Chain.count_token_holders_from_token_hash(contract_address_hash) == 1
end
test "counts only the last block that has value greater than 0" do
address = insert(:address, hash: "0xe49fedd93960a0267b3c3b2c1e2d66028e013fee")
%Token{contract_address_hash: contract_address_hash} = insert(:token)
insert(
:token_balance,
address: address,
block_number: 1000,
token_contract_address_hash: contract_address_hash,
value: 5000
)
insert(
:token_balance,
address: address,
block_number: 1002,
token_contract_address_hash: contract_address_hash,
value: 0
)
assert Chain.count_token_holders_from_token_hash(contract_address_hash) == 0
end
end
end

Loading…
Cancel
Save