Merge pull request #417 from poanetwork/frg-show-token-transfers-in-transactions

Contributor sees Token Transfers in the Transactions tab in the Address page
pull/482/head
Felipe Renan 6 years ago committed by GitHub
commit fdcd352846
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 39
      apps/explorer/lib/explorer/chain.ex
  2. 3
      apps/explorer/lib/explorer/chain/address.ex
  3. 33
      apps/explorer/lib/explorer/chain/internal_transaction.ex
  4. 2
      apps/explorer/lib/explorer/chain/token_transfer.ex
  5. 97
      apps/explorer/lib/explorer/chain/transaction.ex
  6. 124
      apps/explorer/test/explorer/chain_test.exs
  7. 24
      apps/explorer/test/support/factory.ex
  8. 7
      apps/explorer_web/lib/explorer_web/notifier.ex
  9. 38
      apps/explorer_web/lib/explorer_web/templates/address_transaction/_transaction.html.eex
  10. 33
      apps/explorer_web/lib/explorer_web/views/address_transaction_view.ex
  11. 18
      apps/explorer_web/lib/explorer_web/views/address_view.ex
  12. 6
      apps/explorer_web/lib/explorer_web/views/transaction_view.ex
  13. 34
      apps/explorer_web/priv/gettext/default.pot
  14. 34
      apps/explorer_web/priv/gettext/en/LC_MESSAGES/default.po
  15. 1
      apps/explorer_web/test/explorer_web/controllers/address_transaction_controller_test.exs
  16. 12
      apps/explorer_web/test/explorer_web/features/pages/address_page.ex
  17. 85
      apps/explorer_web/test/explorer_web/features/viewing_addresses_test.exs
  18. 37
      apps/explorer_web/test/explorer_web/views/address_transaction_view_test.exs

@ -112,7 +112,7 @@ defmodule Explorer.Chain do
transaction in assoc(internal_transaction, :transaction)
)
|> join(:left, [internal_transaction, transaction], block in assoc(transaction, :block))
|> where_address_fields_match(hash, direction)
|> InternalTransaction.where_address_fields_match(hash, direction)
|> where_transaction_has_multiple_internal_transactions()
|> page_internal_transaction(paging_options)
|> limit(^paging_options.page_size)
@ -182,7 +182,7 @@ defmodule Explorer.Chain do
options
|> Keyword.get(:paging_options, @default_paging_options)
|> fetch_transactions()
|> where_address_fields_match(address_hash, direction)
|> Transaction.where_address_fields_match(address_hash, direction)
|> join_associations(necessity_by_association)
|> Repo.all()
end
@ -1640,41 +1640,6 @@ defmodule Explorer.Chain do
where(query, [transaction], transaction.index < ^index)
end
defp where_address_fields_match(query, address_hash, :to) do
where(query, [t], t.to_address_hash == ^address_hash)
end
defp where_address_fields_match(query, address_hash, :from) do
where(query, [t], t.from_address_hash == ^address_hash)
end
defp where_address_fields_match(%Ecto.Query{from: {_table, InternalTransaction}} = query, address_hash, nil) do
where(
query,
[it],
it.to_address_hash == ^address_hash or it.from_address_hash == ^address_hash or
it.created_contract_address_hash == ^address_hash
)
end
defp where_address_fields_match(%Ecto.Query{from: {_table, Transaction}} = query, address_hash, nil) do
where(
query,
[t],
t.to_address_hash == ^address_hash or t.from_address_hash == ^address_hash or
(is_nil(t.to_address_hash) and
^address_hash.bytes in fragment(
~s[
(SELECT i."created_contract_address_hash"
FROM "internal_transactions" AS i
WHERE (i."transaction_hash" = ?) AND (i."type" = 'create')
LIMIT 1)
],
t.hash
))
)
end
defp where_transaction_has_multiple_internal_transactions(query) do
where(
query,

@ -6,7 +6,7 @@ defmodule Explorer.Chain.Address do
use Explorer.Schema
alias Ecto.Changeset
alias Explorer.Chain.{Block, Data, Hash, Wei, SmartContract, InternalTransaction}
alias Explorer.Chain.{Block, Data, Hash, Wei, SmartContract, InternalTransaction, Token}
@optional_attrs ~w(contract_code fetched_balance fetched_balance_block_number)a
@required_attrs ~w(hash)a
@ -42,6 +42,7 @@ defmodule Explorer.Chain.Address do
field(:contract_code, Data)
has_one(:smart_contract, SmartContract)
has_one(:token, Token, foreign_key: :contract_address_hash)
has_one(
:contracts_creation_internal_transaction,

@ -415,4 +415,37 @@ defmodule Explorer.Chain.InternalTransaction do
_ -> validate_disallowed(changeset, @create_success_fields, message: "can't be present for failed create")
end
end
@doc """
Adds to the given transaction's query a `where` with one of the conditions that the matched
function returns.
`where_address_fields_match(query, address_hash, :to)`
- returns a query considering that the given address_hash is equal to to_address_hash from
transactions' table.
`where_address_fields_match(query, address_hash, :from)`
- returns a query considering that the given address_hash is equal to from_address_hash from
transactions' table.
`where_address_fields_match(query, address_hash, nil)`
- returns a query considering that the given address_hash can be: to_address_hash,
from_address_hash, created_contract_address_hash from internal_transactions' table.
"""
def where_address_fields_match(query, address_hash, :to) do
where(query, [t], t.to_address_hash == ^address_hash)
end
def where_address_fields_match(query, address_hash, :from) do
where(query, [t], t.from_address_hash == ^address_hash)
end
def where_address_fields_match(query, address_hash, nil) do
where(
query,
[it],
it.to_address_hash == ^address_hash or it.from_address_hash == ^address_hash or
it.created_contract_address_hash == ^address_hash
)
end
end

@ -73,6 +73,8 @@ defmodule Explorer.Chain.TokenTransfer do
belongs_to(:transaction, Transaction, foreign_key: :transaction_hash, references: :hash, type: Hash.Full)
has_one(:token, through: [:token_contract_address, :token])
timestamps()
end

@ -4,7 +4,19 @@ defmodule Explorer.Chain.Transaction do
use Explorer.Schema
alias Ecto.Changeset
alias Explorer.Chain.{Address, Block, Data, Gas, Hash, InternalTransaction, Log, Wei}
alias Explorer.Chain.{
Address,
Block,
Data,
Gas,
Hash,
InternalTransaction,
Log,
TokenTransfer,
Wei
}
alias Explorer.Chain.Transaction.Status
@optional_attrs ~w(block_hash block_number cumulative_gas_used gas_used index internal_transactions_indexed_at status
@ -137,6 +149,7 @@ defmodule Explorer.Chain.Transaction do
has_many(:internal_transactions, InternalTransaction, foreign_key: :transaction_hash)
has_many(:logs, Log, foreign_key: :transaction_hash)
has_many(:token_transfers, TokenTransfer, foreign_key: :transaction_hash)
belongs_to(
:to_address,
@ -329,4 +342,86 @@ defmodule Explorer.Chain.Transaction do
_ -> changeset
end
end
defmacrop exists_contract_creation_with_matching_address_hash_fragment(transaction_hash, bytes) do
quote do
fragment(
~s[
EXISTS (
SELECT 1
FROM "internal_transactions" AS i
WHERE i."transaction_hash" = ? AND i."type" = 'create' AND i."created_contract_address_hash" = ?
)
],
unquote(transaction_hash),
unquote(bytes)
)
end
end
@doc """
Adds to the given transaction's query a `where` with one of the conditions that the matched
function returns.
`where_address_fields_match(query, address, :to)`
- returns a query considering that the given address_hash is equal to to_address_hash from
transactions' table or is equal to to_address_hash from token transfers' table.
`where_address_fields_match(query, address, :from)`
- returns a query considering that the given address_hash is equal to from_address_hash from
transactions' table or is equal to from_address_hash from token transfers't table.
`where_address_fields_match(query, address, nil)`
- returns a query considering that the given address_hash can be: to_address_hash,
from_address_hash, created_contract_address_hash from internal_transactions' table,
to_address_hash or from_address_hash from token_transfers' table.
### Token transfers' preload
Token transfers will be preloaded according to the given address_hash considering if it's equal
to token_contract_address_hash, to_address_hash or from_address_hash from Token Transfers's table.
"""
def where_address_fields_match(query, address_hash, :to) do
query
|> join_token_tranfers()
|> preload_token_transfers(address_hash)
|> where([t, tt], t.to_address_hash == ^address_hash or tt.to_address_hash == ^address_hash)
end
def where_address_fields_match(query, address_hash, :from) do
query
|> join_token_tranfers()
|> preload_token_transfers(address_hash)
|> where([t, tt], t.from_address_hash == ^address_hash or tt.from_address_hash == ^address_hash)
end
def where_address_fields_match(query, address_hash, nil) do
query
|> join_token_tranfers()
|> preload_token_transfers(address_hash)
|> where(
[t, tt],
t.to_address_hash == ^address_hash or t.from_address_hash == ^address_hash or tt.to_address_hash == ^address_hash or
tt.from_address_hash == ^address_hash or
(is_nil(t.to_address_hash) and
exists_contract_creation_with_matching_address_hash_fragment(t.hash, ^address_hash.bytes))
)
end
defp join_token_tranfers(query) do
join(query, :left, [t], tt in assoc(t, :token_transfers))
end
defp preload_token_transfers(query, address_hash) do
token_transfers_query =
from(
tt in TokenTransfer,
where:
tt.token_contract_address_hash == ^address_hash or tt.to_address_hash == ^address_hash or
tt.from_address_hash == ^address_hash,
preload: [:token, :from_address, :to_address]
)
preload(query, [tt], token_transfers: ^token_transfers_query)
end
end

@ -68,7 +68,12 @@ defmodule Explorer.ChainTest do
test "with from transactions" do
address = insert(:address)
transaction = :transaction |> insert(from_address: address) |> with_block()
transaction =
:transaction
|> insert(from_address: address)
|> with_block()
|> Repo.preload(:token_transfers)
assert [transaction] ==
Chain.address_to_transactions(address, direction: :from)
@ -77,7 +82,12 @@ defmodule Explorer.ChainTest do
test "with to transactions" do
address = insert(:address)
transaction = :transaction |> insert(to_address: address) |> with_block()
transaction =
:transaction
|> insert(to_address: address)
|> with_block()
|> Repo.preload(:token_transfers)
assert [transaction] ==
Chain.address_to_transactions(address, direction: :to)
@ -86,8 +96,12 @@ defmodule Explorer.ChainTest do
test "with to and from transactions and direction: :from" do
address = insert(:address)
transaction = :transaction |> insert(from_address: address) |> with_block()
:transaction |> insert(to_address: address) |> with_block()
transaction =
:transaction
|> insert(from_address: address)
|> with_block()
|> Repo.preload(:token_transfers)
# only contains "from" transaction
assert [transaction] ==
@ -97,10 +111,13 @@ defmodule Explorer.ChainTest do
test "with to and from transactions and direction: :to" do
address = insert(:address)
transaction = :transaction |> insert(to_address: address) |> with_block()
:transaction |> insert(from_address: address) |> with_block()
# only contains "to" transaction
transaction =
:transaction
|> insert(to_address: address)
|> with_block()
|> Repo.preload(:token_transfers)
assert [transaction] ==
Chain.address_to_transactions(address, direction: :to)
|> Repo.preload([:block, :to_address, :from_address])
@ -109,11 +126,22 @@ defmodule Explorer.ChainTest do
test "with to and from transactions and no :direction option" do
address = insert(:address)
block = insert(:block)
transaction1 = :transaction |> insert(to_address: address) |> with_block(block)
transaction2 = :transaction |> insert(from_address: address) |> with_block(block)
transaction1 =
:transaction
|> insert(to_address: address)
|> with_block(block)
|> Repo.preload(:token_transfers)
transaction2 =
:transaction
|> insert(from_address: address)
|> with_block(block)
|> Repo.preload(:token_transfers)
assert [transaction2, transaction1] ==
Chain.address_to_transactions(address) |> Repo.preload([:block, :to_address, :from_address])
Chain.address_to_transactions(address)
|> Repo.preload([:block, :to_address, :from_address])
end
test "does not include non-contract-creation parent transactions" do
@ -129,6 +157,82 @@ defmodule Explorer.ChainTest do
assert [] == Chain.address_to_transactions(address)
end
test "returns transactions that have token transfers for the given to_address" do
address = insert(:address)
transaction =
:transaction
|> insert()
|> with_block()
insert(:token_transfer, to_address: address, transaction: transaction)
transaction =
Transaction
|> Repo.get!(transaction.hash)
|> Repo.preload([:block, :to_address, :from_address, token_transfers: :token])
assert [transaction.hash] ==
Chain.address_to_transactions(address)
|> Enum.map(& &1.hash)
end
test "returns just the token transfers related to the given address" do
address = insert(:address)
transaction =
:transaction
|> insert()
|> with_block()
token_transfer = insert(:token_transfer, to_address: address, transaction: transaction)
insert(:token_transfer, to_address: build(:address), transaction: transaction)
transaction = Chain.address_to_transactions(address) |> List.first()
assert transaction.token_transfers |> Enum.map(& &1.id) == [token_transfer.id]
end
test "returns just the token transfers related to the given contract address" do
contract_address = insert(:address, contract_code: Factory.data("contract_code"))
transaction =
:transaction
|> insert()
|> with_block()
token_transfer = insert(:token_transfer, to_address: contract_address, transaction: transaction)
insert(:token_transfer, to_address: build(:address), transaction: transaction)
transaction = Chain.address_to_transactions(contract_address) |> List.first()
assert Enum.map(transaction.token_transfers, & &1.id) == [token_transfer.id]
end
test "returns all token transfers when the given address is the token contract address" do
contract_address = insert(:address, contract_code: Factory.data("contract_code"))
transaction =
:transaction
|> insert(to_address: contract_address)
|> with_block()
insert(
:token_transfer,
to_address: build(:address),
token_contract_address: contract_address,
transaction: transaction
)
insert(
:token_transfer,
to_address: build(:address),
token_contract_address: contract_address,
transaction: transaction
)
transaction = Chain.address_to_transactions(contract_address) |> List.first()
assert Enum.count(transaction.token_transfers) == 2
end
test "with transactions can be paginated" do
address = insert(:address)

@ -227,7 +227,7 @@ defmodule Explorer.Factory do
data: data(:log_data),
first_topic: nil,
fourth_topic: nil,
index: 0,
index: Enum.random(1..1000),
second_topic: nil,
third_topic: nil,
transaction: build(:transaction),
@ -278,23 +278,21 @@ defmodule Explorer.Factory do
end
def token_transfer_factory do
log = insert(:token_transfer_log)
log = build(:token_transfer_log)
to_address_hash = address_hash_from_zero_padded_hash_string(log.third_topic)
from_address_hash = address_hash_from_zero_padded_hash_string(log.second_topic)
# `to_address` is only the only thing that isn't created from the token_transfer_log_factory
insert(:address, hash: to_address_hash)
from_address_hash = address_hash_from_zero_padded_hash_string(log.second_topic)
to_address = build(:address, hash: to_address_hash)
from_address = build(:address, hash: from_address_hash)
contract_code = Map.fetch!(contract_code_info(), :bytecode)
%TokenTransfer{
amount: Decimal.new(1),
from_address: nil,
from_address_hash: from_address_hash,
to_address: nil,
to_address_hash: to_address_hash,
transaction: nil,
transaction_hash: log.transaction.hash,
from_address: from_address,
to_address: to_address,
token_contract_address: build(:address, contract_code: contract_code),
transaction: log.transaction,
log_index: log.index
}
end
@ -425,7 +423,7 @@ defmodule Explorer.Factory do
end
defp address_hash_from_zero_padded_hash_string("0x000000000000000000000000" <> hash_string) do
{:ok, hash} = Explorer.Chain.Hash.cast(Explorer.Chain.Hash.Truncated, "0x" <> hash_string)
{:ok, hash} = Explorer.Chain.Hash.cast(Explorer.Chain.Hash.Address, "0x" <> hash_string)
hash
end
end

@ -24,7 +24,12 @@ defmodule ExplorerWeb.Notifier do
def handle_event({:chain_event, :transactions, transaction_hashes}) do
transaction_hashes
|> Chain.hashes_to_transactions(
necessity_by_association: %{block: :required, from_address: :optional, to_address: :optional}
necessity_by_association: %{
block: :required,
from_address: :optional,
to_address: :optional,
token_transfers: :optional
}
)
|> Enum.each(&broadcast_transaction/1)
end

@ -28,10 +28,16 @@
<% end %>
</span>
<span>
<%= if @address.hash == @transaction.from_address_hash do %>
<span class="badge badge-danger tile-badge"><%= gettext "OUT" %></span>
<%= if transaction_from_or_to_current_address?(@transaction, @address.hash) do %>
<%= if @transaction.from_address_hash == @address.hash do %>
<span data-test="transaction_type" class="badge badge-danger tile-badge">
<%= gettext "OUT" %>
</span>
<% else %>
<span class="badge badge-success tile-badge"><%= gettext "IN" %></span>
<span data-test="transaction_type" class="badge badge-success tile-badge">
<%= gettext "IN" %>
</span>
<% end %>
<% end %>
<span class="ml-1" data-from-now="<%= @transaction.block.timestamp %>"></span>
<span class="ml-1">
@ -48,5 +54,31 @@
</span>
<span> <%= ExplorerWeb.TransactionView.formatted_fee(@transaction, denomination: :ether) %> <%= gettext "Fee" %></span>
</div>
<%= if ExplorerWeb.TransactionView.involves_token_transfers?(@transaction) do %>
<div class="offset-md-2 col-md-10">
<hr class="mt-3 mb-3 w-100" />
<p class="tile-title"><%= gettext "Transfers" %></p>
</div>
<% end %>
<%= for token_transfer <- @transaction.token_transfers do %>
<div class="offset-md-2 col-sm-7 col-lg-8 d-flex flex-column">
<span data-test="token_transfer">
<span data-test="token_transfer_address_hash">
<%= ExplorerWeb.AddressView.display_address_hash(@address, token_transfer.from_address, @locale) %>
</span>
&rarr;
<span data-test="token_transfer_address_hash">
<%= ExplorerWeb.AddressView.display_address_hash(@address, token_transfer.to_address, @locale) %>
</span>
</span>
</div>
<div class="col-sm-3 col-lg-2 d-flex flex-row flex-sm-column align-items-end">
<span class="tile-title">
<%= formatted_token_amount(token_transfer.amount, token_transfer.token.decimals) %> <%= token_transfer.token.symbol %>
</span>
</div>
<% end %>
</div>
</div>

@ -11,4 +11,37 @@ defmodule ExplorerWeb.AddressTransactionView do
_ -> gettext("All")
end
end
@doc """
Check if the given address is the to_address_hash or from_address_hash from the transaction.
When the transaction has token transfers, the transaction is going to be shown even when the
transaction is the to or from of the given address.
"""
def transaction_from_or_to_current_address?(transaction, address_hash) do
transaction.from_address_hash == address_hash || transaction.to_address_hash == address_hash
end
@doc """
Formats the given amount according to given decimals.
## Examples
iex> ExplorerWeb.AddressTransactionView.formatted_token_amount(Decimal.new(20500000), 5)
"205"
iex> ExplorerWeb.AddressTransactionView.formatted_token_amount(Decimal.new(20500000), 7)
"2.05"
iex> ExplorerWeb.AddressTransactionView.formatted_token_amount(Decimal.new(205000), 12)
"0.000000205"
"""
@spec formatted_token_amount(Decimal.t(), non_neg_integer()) :: String.t()
def formatted_token_amount(%Decimal{sign: sign, coef: coef, exp: exp}, decimals) do
sign
|> Decimal.new(coef, exp - decimals)
|> Decimal.reduce()
|> Decimal.to_string(:normal)
end
end

@ -78,4 +78,22 @@ defmodule ExplorerWeb.AddressView do
end
def smart_contract_with_read_only_functions?(%Address{smart_contract: nil}), do: false
def display_address_hash(current_address, another_address, locale) do
if current_address.hash == another_address.hash do
render(
"_responsive_hash.html",
address_hash: current_address.hash,
contract: contract?(current_address),
locale: locale
)
else
render(
"_link.html",
address_hash: another_address.hash,
contract: contract?(another_address),
locale: locale
)
end
end
end

@ -45,6 +45,10 @@ defmodule ExplorerWeb.TransactionView do
AddressView.contract?(from_address) || AddressView.contract?(to_address)
end
def involves_token_transfers?(%Transaction{} = transaction) do
Ecto.assoc_loaded?(transaction.token_transfers) && Enum.any?(transaction.token_transfers)
end
def contract_creation?(%Transaction{to_address: nil}), do: true
def contract_creation?(_), do: false
@ -100,6 +104,7 @@ defmodule ExplorerWeb.TransactionView do
def type_suffix(%Transaction{} = transaction) do
cond do
involves_token_transfers?(transaction) -> "token"
contract_creation?(transaction) -> "contract-creation"
involves_contract?(transaction) -> "contract-call"
true -> "transaction"
@ -108,6 +113,7 @@ defmodule ExplorerWeb.TransactionView do
def transaction_display_type(%Transaction{} = transaction) do
cond do
involves_token_transfers?(transaction) -> gettext("Token Transfer")
contract_creation?(transaction) -> gettext("Contract Creation")
involves_contract?(transaction) -> gettext("Contract Call")
true -> gettext("Transaction")

@ -147,7 +147,7 @@ msgstr ""
msgid "Overview"
msgstr ""
#: lib/explorer_web/views/transaction_view.ex:70
#: lib/explorer_web/views/transaction_view.ex:74
msgid "Success"
msgstr ""
@ -200,7 +200,7 @@ msgstr ""
#: lib/explorer_web/templates/transaction/index.html.eex:35
#: lib/explorer_web/templates/transaction/overview.html.eex:40
#: lib/explorer_web/views/transaction_view.ex:38
#: lib/explorer_web/views/transaction_view.ex:69
#: lib/explorer_web/views/transaction_view.ex:73
msgid "Pending"
msgstr ""
@ -263,11 +263,11 @@ msgstr ""
msgid "Next Page"
msgstr ""
#: lib/explorer_web/views/transaction_view.ex:67
#: lib/explorer_web/views/transaction_view.ex:71
msgid "Failed"
msgstr ""
#: lib/explorer_web/views/transaction_view.ex:68
#: lib/explorer_web/views/transaction_view.ex:72
msgid "Out of Gas"
msgstr ""
@ -285,7 +285,7 @@ msgstr ""
#:
#: lib/explorer_web/templates/address_internal_transaction/_internal_transaction.html.eex:29
#: lib/explorer_web/templates/address_transaction/_transaction.html.eex:47
#: lib/explorer_web/templates/address_transaction/_transaction.html.eex:53
#: lib/explorer_web/templates/block_transaction/_transaction.html.eex:33
#: lib/explorer_web/templates/chain/_transactions.html.eex:33
#: lib/explorer_web/templates/pending_transaction/index.html.eex:71
@ -355,7 +355,7 @@ msgstr ""
msgid "All"
msgstr ""
#: lib/explorer_web/templates/address_transaction/_transaction.html.eex:49
#: lib/explorer_web/templates/address_transaction/_transaction.html.eex:55
#: lib/explorer_web/templates/block_transaction/_transaction.html.eex:35
#: lib/explorer_web/templates/chain/_transactions.html.eex:35
#: lib/explorer_web/templates/pending_transaction/index.html.eex:72
@ -435,7 +435,7 @@ msgstr ""
msgid "Total Gas Used"
msgstr ""
#: lib/explorer_web/views/transaction_view.ex:113
#: lib/explorer_web/views/transaction_view.ex:119
msgid "Transaction"
msgstr ""
@ -513,7 +513,7 @@ msgid "Newer"
msgstr ""
#, elixir-format
#: lib/explorer_web/views/transaction_view.ex:111
#: lib/explorer_web/views/transaction_view.ex:117
msgid "Contract Creation"
msgstr ""
@ -613,7 +613,7 @@ msgid "Contract Address Pending"
msgstr ""
#, elixir-format
#: lib/explorer_web/views/transaction_view.ex:112
#: lib/explorer_web/views/transaction_view.ex:118
msgid "Contract Call"
msgstr ""
@ -628,7 +628,7 @@ msgid "at"
msgstr ""
#, elixir-format
#: lib/explorer_web/templates/address_transaction/_transaction.html.eex:39
#: lib/explorer_web/templates/address_transaction/_transaction.html.eex:45
#: lib/explorer_web/templates/block_transaction/_transaction.html.eex:25
#: lib/explorer_web/templates/chain/_transactions.html.eex:25
#: lib/explorer_web/templates/transaction/index.html.eex:69
@ -638,14 +638,14 @@ msgstr ""
#, elixir-format
#:
#: lib/explorer_web/templates/address_internal_transaction/_internal_transaction.html.eex:24
#: lib/explorer_web/templates/address_transaction/_transaction.html.eex:34
#: lib/explorer_web/templates/address_transaction/_transaction.html.eex:38
msgid "IN"
msgstr ""
#, elixir-format
#:
#: lib/explorer_web/templates/address_internal_transaction/_internal_transaction.html.eex:22
#: lib/explorer_web/templates/address_transaction/_transaction.html.eex:32
#: lib/explorer_web/templates/address_transaction/_transaction.html.eex:34
msgid "OUT"
msgstr ""
@ -711,3 +711,13 @@ msgstr ""
#: lib/explorer_web/templates/transaction/overview.html.eex:97
msgid "Used"
msgstr ""
#, elixir-format
#: lib/explorer_web/views/transaction_view.ex:116
msgid "Token Transfer"
msgstr ""
#, elixir-format
#: lib/explorer_web/templates/address_transaction/_transaction.html.eex:61
msgid "Transfers"
msgstr ""

@ -159,7 +159,7 @@ msgstr "From"
msgid "Overview"
msgstr "Overview"
#: lib/explorer_web/views/transaction_view.ex:70
#: lib/explorer_web/views/transaction_view.ex:74
msgid "Success"
msgstr "Success"
@ -212,7 +212,7 @@ msgstr "Showing %{count} Transactions"
#: lib/explorer_web/templates/transaction/index.html.eex:35
#: lib/explorer_web/templates/transaction/overview.html.eex:40
#: lib/explorer_web/views/transaction_view.ex:38
#: lib/explorer_web/views/transaction_view.ex:69
#: lib/explorer_web/views/transaction_view.ex:73
msgid "Pending"
msgstr "Pending"
@ -275,11 +275,11 @@ msgstr ""
msgid "Next Page"
msgstr ""
#: lib/explorer_web/views/transaction_view.ex:67
#: lib/explorer_web/views/transaction_view.ex:71
msgid "Failed"
msgstr ""
#: lib/explorer_web/views/transaction_view.ex:68
#: lib/explorer_web/views/transaction_view.ex:72
msgid "Out of Gas"
msgstr ""
@ -297,7 +297,7 @@ msgstr ""
#:
#: lib/explorer_web/templates/address_internal_transaction/_internal_transaction.html.eex:29
#: lib/explorer_web/templates/address_transaction/_transaction.html.eex:47
#: lib/explorer_web/templates/address_transaction/_transaction.html.eex:53
#: lib/explorer_web/templates/block_transaction/_transaction.html.eex:33
#: lib/explorer_web/templates/chain/_transactions.html.eex:33
#: lib/explorer_web/templates/pending_transaction/index.html.eex:71
@ -367,7 +367,7 @@ msgstr ""
msgid "All"
msgstr ""
#: lib/explorer_web/templates/address_transaction/_transaction.html.eex:49
#: lib/explorer_web/templates/address_transaction/_transaction.html.eex:55
#: lib/explorer_web/templates/block_transaction/_transaction.html.eex:35
#: lib/explorer_web/templates/chain/_transactions.html.eex:35
#: lib/explorer_web/templates/pending_transaction/index.html.eex:72
@ -447,7 +447,7 @@ msgstr ""
msgid "Total Gas Used"
msgstr ""
#: lib/explorer_web/views/transaction_view.ex:113
#: lib/explorer_web/views/transaction_view.ex:119
msgid "Transaction"
msgstr ""
@ -525,7 +525,7 @@ msgid "Newer"
msgstr ""
#, elixir-format
#: lib/explorer_web/views/transaction_view.ex:111
#: lib/explorer_web/views/transaction_view.ex:117
msgid "Contract Creation"
msgstr ""
@ -625,7 +625,7 @@ msgid "Contract Address Pending"
msgstr ""
#, elixir-format
#: lib/explorer_web/views/transaction_view.ex:112
#: lib/explorer_web/views/transaction_view.ex:118
msgid "Contract Call"
msgstr ""
@ -640,7 +640,7 @@ msgid "at"
msgstr ""
#, elixir-format
#: lib/explorer_web/templates/address_transaction/_transaction.html.eex:39
#: lib/explorer_web/templates/address_transaction/_transaction.html.eex:45
#: lib/explorer_web/templates/block_transaction/_transaction.html.eex:25
#: lib/explorer_web/templates/chain/_transactions.html.eex:25
#: lib/explorer_web/templates/transaction/index.html.eex:69
@ -650,14 +650,14 @@ msgstr ""
#, elixir-format
#:
#: lib/explorer_web/templates/address_internal_transaction/_internal_transaction.html.eex:24
#: lib/explorer_web/templates/address_transaction/_transaction.html.eex:34
#: lib/explorer_web/templates/address_transaction/_transaction.html.eex:38
msgid "IN"
msgstr ""
#, elixir-format
#:
#: lib/explorer_web/templates/address_internal_transaction/_internal_transaction.html.eex:22
#: lib/explorer_web/templates/address_transaction/_transaction.html.eex:32
#: lib/explorer_web/templates/address_transaction/_transaction.html.eex:34
msgid "OUT"
msgstr ""
@ -723,3 +723,13 @@ msgstr ""
#: lib/explorer_web/templates/transaction/overview.html.eex:97
msgid "Used"
msgstr ""
#, elixir-format
#: lib/explorer_web/views/transaction_view.ex:116
msgid "Token Transfer"
msgstr ""
#, elixir-format
#: lib/explorer_web/templates/address_transaction/_transaction.html.eex:61
msgid "Transfers"
msgstr ""

@ -128,6 +128,7 @@ defmodule ExplorerWeb.AddressTransactionControllerTest do
:transaction
|> insert(to_address: nil, created_contract_address_hash: address.hash)
|> with_block(block)
|> Explorer.Repo.preload(:token_transfers)
insert(
:internal_transaction_create,

@ -76,4 +76,16 @@ defmodule ExplorerWeb.AddressPage do
def visit_page(session, address_hash) do
visit(session, "/en/addresses/#{address_hash}")
end
def token_transfers(count: count) do
css("[data-test='token_transfer']", count: count)
end
def token_transfer(address_hash, count: count) do
css("[data-test='token_transfer_address_hash']", count: count, text: to_string(address_hash))
end
def transaction_type do
css("[data-test='transaction_type']")
end
end

@ -237,6 +237,7 @@ defmodule ExplorerWeb.ViewingAddressesTest do
2
|> insert_list(:transaction, from_address: addresses.lincoln)
|> with_block()
|> Repo.preload([:block, :from_address, :to_address])
session
|> AddressPage.visit_page(addresses.lincoln)
@ -332,4 +333,88 @@ defmodule ExplorerWeb.ViewingAddressesTest do
|> AddressPage.click_internal_transactions()
|> assert_has(AddressPage.contract_creation(internal_transaction))
end
describe "viewing token transfers" do
test "contributor can see all token transfers that he sent", %{
addresses: addresses,
block: block,
session: session
} do
lincoln = addresses.lincoln
taft = addresses.taft
contract_token_address =
insert(
:address,
contract_code: Explorer.Factory.data("contract_code")
)
insert(:token, contract_address: contract_token_address)
transaction =
:transaction
|> insert(from_address: lincoln, to_address: contract_token_address)
|> with_block(block)
insert(
:token_transfer,
from_address: lincoln,
to_address: taft,
transaction: transaction,
token_contract_address: contract_token_address
)
session
|> AddressPage.visit_page(lincoln)
|> assert_has(AddressPage.token_transfers(count: 1))
|> assert_has(AddressPage.token_transfer(lincoln.hash, count: 1))
|> assert_has(AddressPage.token_transfer(taft.hash, count: 1))
end
test "contributor can see only token transfers related to him", %{
addresses: addresses,
block: block,
session: session
} do
lincoln = addresses.lincoln
taft = addresses.taft
morty = build(:address)
contract_token_address =
insert(
:address,
contract_code: Explorer.Factory.data("contract_code")
)
insert(:token, contract_address: contract_token_address)
transaction =
:transaction
|> insert(from_address: lincoln, to_address: contract_token_address)
|> with_block(block)
insert(
:token_transfer,
from_address: lincoln,
to_address: taft,
transaction: transaction,
token_contract_address: contract_token_address
)
insert(
:token_transfer,
from_address: lincoln,
to_address: morty,
transaction: transaction,
token_contract_address: contract_token_address
)
session
|> AddressPage.visit_page(morty)
|> assert_has(AddressPage.token_transfers(count: 1))
|> assert_has(AddressPage.token_transfer(lincoln.hash, count: 1))
|> assert_has(AddressPage.token_transfer(morty.hash, count: 1))
|> refute_has(AddressPage.token_transfer(taft.hash, count: 1))
end
end
end

@ -0,0 +1,37 @@
defmodule ExplorerWeb.AddresstransactionViewTest do
use ExplorerWeb.ConnCase, async: true
alias ExplorerWeb.AddressTransactionView
doctest ExplorerWeb.AddressTransactionView
describe "formatted_token_amount/1" do
test "formats the amount as value considering the given decimals" do
amount = Decimal.new(205_000_000_000_000)
decimals = 12
assert AddressTransactionView.formatted_token_amount(amount, decimals) == "205"
end
test "considers the decimal places according to the given decimals" do
amount = Decimal.new(205_000)
decimals = 12
assert AddressTransactionView.formatted_token_amount(amount, decimals) == "0.000000205"
end
test "does not consider right zeros in decimal places" do
amount = Decimal.new(90_000_000)
decimals = 6
assert AddressTransactionView.formatted_token_amount(amount, decimals) == "90"
end
test "returns the full number when there is no right zeros in decimal places" do
amount = Decimal.new(9_324_876)
decimals = 6
assert AddressTransactionView.formatted_token_amount(amount, decimals) == "9.324876"
end
end
end
Loading…
Cancel
Save