create Address.TokenCard to do the querying of tokens from an address

pull/747/head
Gustavo Santos Ferreira 6 years ago
parent 7e8974c4fb
commit 958a277bad
  1. 3
      apps/block_scout_web/lib/block_scout_web/chain.ex
  2. 2
      apps/block_scout_web/lib/block_scout_web/controllers/address_token_controller.ex
  3. 9
      apps/block_scout_web/lib/block_scout_web/templates/address_token/_tokens.html.eex
  4. 2
      apps/block_scout_web/lib/block_scout_web/views/address_token_view.ex
  5. 9
      apps/block_scout_web/lib/block_scout_web/views/tokens/helpers.ex
  6. 34
      apps/block_scout_web/test/block_scout_web/controllers/address_token_controller_test.exs
  7. 6
      apps/block_scout_web/test/block_scout_web/views/address_token_view_test.exs
  8. 6
      apps/explorer/lib/explorer/chain.ex
  9. 106
      apps/explorer/lib/explorer/chain/address/token.ex
  10. 73
      apps/explorer/lib/explorer/chain/token.ex
  11. 382
      apps/explorer/test/explorer/chain/address/token_test.exs
  12. 145
      apps/explorer/test/explorer/chain_test.exs

@ -18,7 +18,6 @@ defmodule BlockScoutWeb.Chain do
Block,
InternalTransaction,
Log,
Token,
TokenTransfer,
Transaction
}
@ -184,7 +183,7 @@ defmodule BlockScoutWeb.Chain do
%{"inserted_at" => inserted_at_datetime}
end
defp paging_params(%Token{name: name, type: type, inserted_at: inserted_at}) do
defp paging_params(%Address.Token{name: name, type: type, inserted_at: inserted_at}) do
inserted_at_datetime =
inserted_at
|> DateTime.from_naive!("Etc/UTC")

@ -10,7 +10,7 @@ defmodule BlockScoutWeb.AddressTokenController do
def index(conn, %{"address_id" => address_hash_string} = params) do
with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string),
{:ok, address} <- Chain.hash_to_address(address_hash) do
tokens_plus_one = Chain.tokens_with_number_of_transfers_from_address(address_hash, paging_options(params))
tokens_plus_one = Chain.address_tokens_with_balance(address_hash, paging_options(params))
{tokens, next_page} = split_list_by_page(tokens_plus_one)
render(

@ -1,10 +1,15 @@
<div class="tile tile-type-token">
<div class="row justify-content">
<div class="col-md-12 d-flex flex-column tile-label">
<div class="row justify-content align-items-center">
<div class="col-md-7 d-flex flex-column mt-3 mt-md-0">
<%= link(to: token_path(@conn, :show, @token.contract_address_hash), class: "tile-title-lg") do %>
<%= token_name(@token) %>
<% end %>
<span><%= @token.type %> - <%= number_of_transfers(@token) %></span>
</div>
<div class="col-md-5 d-flex flex-column text-md-right mt-3 mt-md-0">
<span class="tile-title-lg text-md-right align-bottom">
<%= format_according_to_decimals(@token.balance, @token.decimals) %> <%= @token.symbol %>
</span>
</div>
</div>
</div>

@ -4,6 +4,6 @@ defmodule BlockScoutWeb.AddressTokenView do
alias BlockScoutWeb.AddressView
def number_of_transfers(token) do
ngettext("%{count} transfer", "%{count} transfers", token.number_of_transfers)
ngettext("%{count} transfer", "%{count} transfers", token.transfers_count)
end
end

@ -3,7 +3,7 @@ defmodule BlockScoutWeb.Tokens.Helpers do
Helper functions for intereacting with `t:BlockScoutWeb.Chain.Token` attributes.
"""
alias Explorer.Chain.{Token, TokenTransfer}
alias Explorer.Chain.{Token, TokenTransfer, Address}
alias BlockScoutWeb.{CurrencyHelpers}
@doc """
@ -58,11 +58,14 @@ defmodule BlockScoutWeb.Tokens.Helpers do
When the token's name is nil, the function will return the contract address hash.
"""
def token_name(%Token{name: nil, contract_address_hash: address_hash}) do
def token_name(%Token{} = token), do: build_token_name(token)
def token_name(%Address.Token{} = address_token), do: build_token_name(address_token)
defp build_token_name(%{name: nil, contract_address_hash: address_hash}) do
"#{contract_address_hash_truncated(address_hash)}..."
end
def token_name(%Token{name: name}) do
defp build_token_name(%{name: name}) do
name
end

@ -18,7 +18,7 @@ defmodule BlockScoutWeb.AddressTokenControllerTest do
assert html_response(conn, 404)
end
test "returns tokens for the address", %{conn: conn} do
test "returns tokens that have balance for the address", %{conn: conn} do
address = insert(:address)
token1 =
@ -29,6 +29,20 @@ defmodule BlockScoutWeb.AddressTokenControllerTest do
:token
|> insert(name: "token2")
insert(
:token_balance,
address: address,
token_contract_address_hash: token1.contract_address_hash,
value: 1000
)
insert(
:token_balance,
address: address,
token_contract_address_hash: token2.contract_address_hash,
value: 0
)
insert(
:token_transfer,
token_contract_address: token1.contract_address,
@ -51,7 +65,7 @@ defmodule BlockScoutWeb.AddressTokenControllerTest do
assert html_response(conn, 200)
assert Enum.member?(actual_token_hashes, token1.contract_address_hash)
assert Enum.member?(actual_token_hashes, token2.contract_address_hash)
refute Enum.member?(actual_token_hashes, token2.contract_address_hash)
end
test "returns next page of results based on last seen token", %{conn: conn} do
@ -61,12 +75,28 @@ defmodule BlockScoutWeb.AddressTokenControllerTest do
1..50
|> Enum.reduce([], fn i, acc ->
token = insert(:token, name: "A Token#{i}", type: "ERC-20")
insert(
:token_balance,
token_contract_address_hash: token.contract_address_hash,
address: address,
value: 1000
)
insert(:token_transfer, token_contract_address: token.contract_address, from_address: address)
acc ++ [token.name]
end)
|> Enum.sort()
token = insert(:token, name: "Another Token", type: "ERC-721")
insert(
:token_balance,
token_contract_address_hash: token.contract_address_hash,
address: address,
value: 1000
)
insert(:token_transfer, token: token, from_address: address)
%Token{name: name, type: type} = token

@ -5,19 +5,19 @@ defmodule BlockScoutWeb.AddressTokenViewTest do
describe "number_of_transfers/1" do
test "returns the singular form when there is only one transfer" do
token = %{number_of_transfers: 1}
token = %{transfers_count: 1}
assert AddressTokenView.number_of_transfers(token) == "1 transfer"
end
test "returns the plural form when there is more than one transfer" do
token = %{number_of_transfers: 2}
token = %{transfers_count: 2}
assert AddressTokenView.number_of_transfers(token) == "2 transfers"
end
test "returns the plural form when there are 0 transfers" do
token = %{number_of_transfers: 0}
token = %{transfers_count: 0}
assert AddressTokenView.number_of_transfers(token) == "0 transfers"
end

@ -1712,10 +1712,10 @@ defmodule Explorer.Chain do
Repo.one(query) != nil
end
@spec tokens_with_number_of_transfers_from_address(Hash.Address.t(), [any()]) :: []
def tokens_with_number_of_transfers_from_address(address_hash, paging_options \\ []) do
@spec address_tokens_with_balance(Hash.Address.t(), [any()]) :: []
def address_tokens_with_balance(address_hash, paging_options \\ []) do
address_hash
|> Token.with_transfers_by_address(paging_options)
|> Address.Token.list_address_tokens_with_balance(paging_options)
|> Repo.all()
end

@ -0,0 +1,106 @@
defmodule Explorer.Chain.Address.Token do
@moduledoc """
A projection that represents the relation between a Token and a specific Address.
This representation is expressed by the following attributes:
- contract_address_hash - Address of a Token's contract.
- name - Token's name.
- symbol - Token's symbol.
- type - Token's type.
- decimals - Token's decimals.
- balance - how much tokens (TokenBalance) the Address has from the Token.
- transfer_count - a count of how many TokenTransfers of the Token the Address was involved.
"""
@enforce_keys [:contract_address_hash, :inserted_at, :name, :symbol, :balance, :decimals, :type, :transfers_count]
defstruct @enforce_keys
import Ecto.Query
alias Explorer.{PagingOptions, Chain}
alias Explorer.Chain.{Hash, Address, Address.TokenBalance}
@default_paging_options %PagingOptions{page_size: 50}
@typep paging_options :: {:paging_options, PagingOptions.t()}
@doc """
It builds a paginated query of Address.Tokens that have a balance higher than zero ordered by type and name.
"""
@spec list_address_tokens_with_balance(Hash.t(), [paging_options()]) :: %Ecto.Query{}
def list_address_tokens_with_balance(address_hash, options \\ []) do
paging_options = Keyword.get(options, :paging_options, @default_paging_options)
Chain.Token
|> Chain.Token.join_with_transfers()
|> join_with_last_balance(address_hash)
|> order_filter_and_group(address_hash)
|> page_tokens(paging_options)
|> limit(^paging_options.page_size)
end
defp order_filter_and_group(query, address_hash) do
from(
[token, transfer, balance] in query,
order_by: fragment("? DESC, LOWER(?) ASC NULLS LAST", token.type, token.name),
where:
(transfer.to_address_hash == ^address_hash or transfer.from_address_hash == ^address_hash) and balance.value > 0,
group_by: [token.name, token.symbol, balance.value, token.type, token.contract_address_hash],
select: %Address.Token{
contract_address_hash: token.contract_address_hash,
inserted_at: max(token.inserted_at),
name: token.name,
symbol: token.symbol,
balance: balance.value,
decimals: max(token.decimals),
type: token.type,
transfers_count: count(token.contract_address_hash)
}
)
end
defp join_with_last_balance(queryable, address_hash) do
last_balance_query =
from(
tb in TokenBalance,
where: tb.address_hash == ^address_hash,
distinct: :token_contract_address_hash,
order_by: [desc: :block_number],
select: %{value: tb.value, token_contract_address_hash: tb.token_contract_address_hash}
)
from(
t in queryable,
join: tb in subquery(last_balance_query),
on: tb.token_contract_address_hash == t.contract_address_hash
)
end
@doc """
Builds the pagination according to the given key within `PagingOptions`.
* it just returns the given query when the key is nil.
* it composes another where clause considering the `type`, `name` and `inserted_at`.
"""
def page_tokens(query, %PagingOptions{key: nil}), do: query
def page_tokens(query, %PagingOptions{key: {nil, type, inserted_at}}) do
where(
query,
[token],
token.type < ^type or (token.type == ^type and is_nil(token.name) and token.inserted_at < ^inserted_at)
)
end
def page_tokens(query, %PagingOptions{key: {name, type, inserted_at}}) do
upper_name = String.downcase(name)
where(
query,
[token],
token.type < ^type or
(token.type == ^type and (fragment("LOWER(?)", token.name) > ^upper_name or is_nil(token.name))) or
(token.type == ^type and fragment("LOWER(?)", token.name) == ^upper_name and token.inserted_at < ^inserted_at)
)
end
end

@ -20,11 +20,7 @@ defmodule Explorer.Chain.Token do
use Ecto.Schema
import Ecto.{Changeset, Query}
alias Explorer.PagingOptions
alias Explorer.Chain.{Address, Hash, Token, TokenTransfer}
alias Explorer.Chain.Address.TokenBalance
@default_paging_options %PagingOptions{page_size: 50}
@typedoc """
* `:name` - Name of the token
@ -47,8 +43,6 @@ defmodule Explorer.Chain.Token do
contract_address_hash: Hash.Address.t()
}
@typep paging_options :: {:paging_options, PagingOptions.t()}
@primary_key false
schema "tokens" do
field(:name, :string)
@ -81,72 +75,11 @@ defmodule Explorer.Chain.Token do
|> unique_constraint(:contract_address_hash)
end
@doc """
Builds an `Ecto.Query` to fetch tokens that the given address has interacted with
along with the latest balance of each token for that address and the count of
transfers the address had with each token
warning: tokens with a latest balance of zero will be ignored
In order to fetch a token, the given address must have transfered tokens to or received tokens
from another address. This quey orders by the token type and name.
"""
@spec with_transfers_by_address(Hash.t(), [paging_options()]) :: %Ecto.Query{}
def with_transfers_by_address(address_hash, options \\ []) do
paging_options = Keyword.get(options, :paging_options, @default_paging_options)
query =
from(
tb in TokenBalance,
where: tb.address_hash == ^address_hash,
distinct: :token_contract_address_hash,
order_by: [desc: :block_number],
select: %{value: tb.value, token_contract_address_hash: tb.token_contract_address_hash}
)
query
|> with_last_balance_and_transfers_count(address_hash)
|> page_token(paging_options)
|> limit(^paging_options.page_size)
end
defp with_last_balance_and_transfers_count(last_balance_query, address_hash) do
def join_with_transfers(queryable \\ Token) do
from(
t in Token,
t in queryable,
join: tt in TokenTransfer,
on: tt.token_contract_address_hash == t.contract_address_hash,
join: tb in subquery(last_balance_query),
on: tb.token_contract_address_hash == t.contract_address_hash,
order_by: [desc: t.type, asc: fragment("UPPER(?)", t.name)],
where: (tt.to_address_hash == ^address_hash or tt.from_address_hash == ^address_hash) and tb.value > 0,
group_by: [t.name, t.symbol, tb.value, t.type, t.contract_address_hash],
select: %{
contract_address_hash: t.contract_address_hash,
inserted_at: max(t.inserted_at),
name: t.name,
symbol: t.symbol,
value: tb.value,
decimals: max(t.decimals),
type: t.type,
transfers: count(t.name)
}
)
end
@doc """
adds to the passed `Ecto.Query` a criteria indicating the first token of the current page
returns the original query if the received `PagingOptions` has a nil value for the key attribute
"""
def page_token(query, %PagingOptions{key: nil}), do: query
def page_token(query, %PagingOptions{key: {name, type, inserted_at}}) do
upper_name = String.upcase(name)
where(
query,
[token],
token.type < ^type or (token.type == ^type and fragment("UPPER(?)", token.name) > ^upper_name) or
(token.type == ^type and fragment("UPPER(?)", token.name) == ^upper_name and token.inserted_at < ^inserted_at)
on: tt.token_contract_address_hash == t.contract_address_hash
)
end
end

@ -0,0 +1,382 @@
defmodule Explorer.Chain.Address.TokenTest do
use Explorer.DataCase
alias Explorer.Repo
alias Explorer.Chain.Address
alias Explorer.Chain.Token
alias Explorer.PagingOptions
describe "list_address_tokens_with_balance/2" do
test "returns tokens with number of transfers and balance value attached" do
address = insert(:address)
token =
:token
|> insert(name: "token-c", type: "ERC-721", decimals: 0, symbol: "TC")
|> Repo.preload(:contract_address)
insert(
:token_balance,
address: address,
token_contract_address_hash: token.contract_address_hash,
value: 1000
)
insert(
:token_transfer,
token_contract_address: token.contract_address,
from_address: address,
to_address: build(:address)
)
insert(
:token_transfer,
token_contract_address: token.contract_address,
from_address: build(:address),
to_address: address
)
fetched_token =
address.hash
|> Address.Token.list_address_tokens_with_balance()
|> Repo.all()
|> List.first()
assert fetched_token == %Explorer.Chain.Address.Token{
contract_address_hash: token.contract_address_hash,
inserted_at: token.inserted_at,
name: "token-c",
symbol: "TC",
balance: Decimal.new(1000),
decimals: 0,
type: "ERC-721",
transfers_count: 2
}
end
test "returns tokens ordered by type in reverse alphabetical order" do
address = insert(:address)
token =
:token
|> insert(name: nil, type: "ERC-721", decimals: nil, symbol: nil)
|> Repo.preload(:contract_address)
insert(
:token_balance,
address: address,
token_contract_address_hash: token.contract_address_hash,
value: 1000
)
insert(
:token_transfer,
token_contract_address: token.contract_address,
from_address: address,
to_address: build(:address)
)
token2 =
:token
|> insert(name: "token-c", type: "ERC-20", decimals: 0, symbol: "TC")
|> Repo.preload(:contract_address)
insert(
:token_balance,
address: address,
token_contract_address_hash: token2.contract_address_hash,
value: 1000
)
insert(
:token_transfer,
token_contract_address: token2.contract_address,
from_address: address,
to_address: build(:address)
)
fetched_tokens =
address.hash
|> Address.Token.list_address_tokens_with_balance()
|> Repo.all()
|> Enum.map(& &1.contract_address_hash)
assert fetched_tokens == [token.contract_address_hash, token2.contract_address_hash]
end
test "returns tokens of same type by name in lowercase ascending" do
address = insert(:address)
token =
:token
|> insert(name: "atoken", type: "ERC-721", decimals: nil, symbol: nil)
|> Repo.preload(:contract_address)
insert(
:token_balance,
address: address,
token_contract_address_hash: token.contract_address_hash,
value: 1000
)
insert(
:token_transfer,
token_contract_address: token.contract_address,
from_address: address,
to_address: build(:address)
)
token2 =
:token
|> insert(name: "1token-c", type: "ERC-721", decimals: 0, symbol: "TC")
|> Repo.preload(:contract_address)
insert(
:token_balance,
address: address,
token_contract_address_hash: token2.contract_address_hash,
value: 1000
)
insert(
:token_transfer,
token_contract_address: token2.contract_address,
from_address: address,
to_address: build(:address)
)
token3 =
:token
|> insert(name: "token-c", type: "ERC-721", decimals: 0, symbol: "TC")
|> Repo.preload(:contract_address)
insert(
:token_balance,
address: address,
token_contract_address_hash: token3.contract_address_hash,
value: 1000
)
insert(
:token_transfer,
token_contract_address: token3.contract_address,
from_address: address,
to_address: build(:address)
)
fetched_tokens =
address.hash
|> Address.Token.list_address_tokens_with_balance()
|> Repo.all()
|> Enum.map(& &1.contract_address_hash)
assert fetched_tokens == [token2.contract_address_hash, token.contract_address_hash, token3.contract_address_hash]
end
test "returns tokens with null name after all the others of same type" do
address = insert(:address)
token =
:token
|> insert(name: nil, type: "ERC-721", decimals: nil, symbol: nil)
|> Repo.preload(:contract_address)
insert(
:token_balance,
address: address,
token_contract_address_hash: token.contract_address_hash,
value: 1000
)
insert(
:token_transfer,
token_contract_address: token.contract_address,
from_address: address,
to_address: build(:address)
)
token2 =
:token
|> insert(name: "token-c", type: "ERC-721", decimals: 0, symbol: "TC")
|> Repo.preload(:contract_address)
insert(
:token_balance,
address: address,
token_contract_address_hash: token2.contract_address_hash,
value: 1000
)
insert(
:token_transfer,
token_contract_address: token2.contract_address,
from_address: address,
to_address: build(:address)
)
token3 =
:token
|> insert(name: "token-c", type: "ERC-721", decimals: 0, symbol: "TC")
|> Repo.preload(:contract_address)
insert(
:token_balance,
address: address,
token_contract_address_hash: token3.contract_address_hash,
value: 1000
)
insert(
:token_transfer,
token_contract_address: token3.contract_address,
from_address: address,
to_address: build(:address)
)
fetched_tokens =
address.hash
|> Address.Token.list_address_tokens_with_balance()
|> Repo.all()
|> Enum.map(& &1.contract_address_hash)
assert fetched_tokens == [token2.contract_address_hash, token3.contract_address_hash, token.contract_address_hash]
end
test "does not return tokens with zero balance" do
address = insert(:address)
token =
:token
|> insert(name: "atoken", type: "ERC-721", decimals: 0, symbol: "AT")
|> Repo.preload(:contract_address)
insert(
:token_balance,
address: address,
token_contract_address_hash: token.contract_address_hash,
value: 0
)
fetched_token =
address.hash
|> Address.Token.list_address_tokens_with_balance()
|> Repo.all()
|> Enum.find(fn t -> t.name == "atoken" end)
assert fetched_token == nil
end
test "brings the value of the last balance" do
address = insert(:address)
token =
:token
|> insert(name: "atoken", type: "ERC-721", decimals: 0, symbol: "AT")
|> Repo.preload(:contract_address)
insert(
:token_balance,
address: address,
token_contract_address_hash: token.contract_address_hash,
value: 1000
)
insert(
:token_balance,
address: address,
token_contract_address_hash: token.contract_address_hash,
value: 1234
)
insert(
:token_transfer,
token_contract_address: token.contract_address,
from_address: address,
to_address: build(:address)
)
fetched_token =
address.hash
|> Address.Token.list_address_tokens_with_balance()
|> Repo.all()
|> List.first()
assert fetched_token.balance == Decimal.new(1234)
end
test "ignores token if the last balance is zero" do
address = insert(:address)
token =
:token
|> insert(name: "atoken", type: "ERC-721", decimals: 0, symbol: "AT")
|> Repo.preload(:contract_address)
insert(
:token_balance,
address: address,
token_contract_address_hash: token.contract_address_hash,
value: 1000
)
insert(
:token_balance,
address: address,
token_contract_address_hash: token.contract_address_hash,
value: 0
)
insert(
:token_transfer,
token_contract_address: token.contract_address,
from_address: address,
to_address: build(:address)
)
fetched_token =
address.hash
|> Address.Token.list_address_tokens_with_balance()
|> Repo.all()
|> List.first()
assert fetched_token == nil
end
end
describe "page_tokens/2" do
test "just bring the normal query when PagingOptions.key is nil" do
options = %PagingOptions{key: nil}
query = Ecto.Query.from(t in Token)
assert Address.Token.page_tokens(query, options) == query
end
test "add more conditions to the query when PagingOptions.key is not nil" do
token1 = insert(:token, name: "token-a", type: "ERC-20", decimals: 0, symbol: "TA")
token2 = insert(:token, name: "token-c", type: "ERC-721", decimals: 0, symbol: "TC")
options = %PagingOptions{key: {token2.name, token2.type, token2.inserted_at}}
query = Ecto.Query.from(t in Token, order_by: t.type, preload: :contract_address)
fetched_token = hd(Repo.all(Address.Token.page_tokens(query, options)))
refute Address.Token.page_tokens(query, options) == query
assert fetched_token == token1
end
test "tokens with nil name come after other tokens of same type" do
token1 = insert(:token, name: "token-a", type: "ERC-20", decimals: 0, symbol: "TA")
token2 = insert(:token, name: nil, type: "ERC-20", decimals: 0, symbol: "TC")
options = %PagingOptions{key: {token1.name, token1.type, token1.inserted_at}}
query = Ecto.Query.from(t in Token, order_by: t.type, preload: :contract_address)
fetched_token = hd(Repo.all(Address.Token.page_tokens(query, options)))
assert fetched_token == token2
end
end
end

@ -2319,151 +2319,6 @@ defmodule Explorer.ChainTest do
end
end
describe "tokens_with_number_of_transfers_from_address/2" do
test "returns tokens with number of transfers and balance value attached" do
address = insert(:address)
token =
:token
|> insert(name: "token-c", type: "ERC-721", decimals: 0, symbol: "TC")
|> Repo.preload(:contract_address)
insert(
:token_balance,
address: address,
token_contract_address_hash: token.contract_address_hash,
value: 1000
)
insert(
:token_transfer,
token_contract_address: token.contract_address,
from_address: address,
to_address: build(:address)
)
insert(
:token_transfer,
token_contract_address: token.contract_address,
from_address: build(:address),
to_address: address
)
fetched_token =
address.hash
|> Chain.tokens_with_number_of_transfers_from_address()
|> List.first()
assert fetched_token == %{
contract_address_hash: token.contract_address_hash,
inserted_at: token.inserted_at,
name: "token-c",
symbol: "TC",
value: Decimal.new(1000),
decimals: 0,
type: "ERC-721",
transfers: 2
}
end
test "does not return tokens with zero balance" do
address = insert(:address)
token =
:token
|> insert(name: "atoken", type: "ERC-721", decimals: 0, symbol: "AT")
|> Repo.preload(:contract_address)
insert(
:token_balance,
address: address,
token_contract_address_hash: token.contract_address_hash,
value: 0
)
fetched_token =
address.hash
|> Chain.tokens_with_number_of_transfers_from_address()
|> Enum.find(fn t -> t.name == "atoken" end)
assert fetched_token == nil
end
test "brings the value of the last balance" do
address = insert(:address)
token =
:token
|> insert(name: "atoken", type: "ERC-721", decimals: 0, symbol: "AT")
|> Repo.preload(:contract_address)
insert(
:token_balance,
address: address,
token_contract_address_hash: token.contract_address_hash,
value: 1000
)
insert(
:token_balance,
address: address,
token_contract_address_hash: token.contract_address_hash,
value: 1234
)
insert(
:token_transfer,
token_contract_address: token.contract_address,
from_address: address,
to_address: build(:address)
)
fetched_token =
address.hash
|> Chain.tokens_with_number_of_transfers_from_address()
|> List.first()
assert fetched_token.value == Decimal.new(1234)
end
test "ignores token if the last balance is zero" do
address = insert(:address)
token =
:token
|> insert(name: "atoken", type: "ERC-721", decimals: 0, symbol: "AT")
|> Repo.preload(:contract_address)
insert(
:token_balance,
address: address,
token_contract_address_hash: token.contract_address_hash,
value: 1000
)
insert(
:token_balance,
address: address,
token_contract_address_hash: token.contract_address_hash,
value: 0
)
insert(
:token_transfer,
token_contract_address: token.contract_address,
from_address: address,
to_address: build(:address)
)
fetched_token =
address.hash
|> Chain.tokens_with_number_of_transfers_from_address()
|> List.first()
assert fetched_token == nil
end
end
describe "update_token/2" do
test "updates a token's values" do
token = insert(:token, name: nil, symbol: nil, total_supply: nil, decimals: nil, cataloged: false)

Loading…
Cancel
Save