Merge branch 'master' into 742-redesign-top-menu

pull/749/head
Andrew Cravenho 6 years ago committed by GitHub
commit b77d00a77a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 6
      apps/block_scout_web/assets/js/pages/address.js
  2. 4
      apps/block_scout_web/assets/js/pages/block.js
  3. 4
      apps/block_scout_web/assets/js/pages/transaction.js
  4. 40
      apps/block_scout_web/assets/js/utils.js
  5. 3
      apps/block_scout_web/lib/block_scout_web/chain.ex
  6. 2
      apps/block_scout_web/lib/block_scout_web/controllers/address_token_controller.ex
  7. 13
      apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/transaction_controller.ex
  8. 9
      apps/block_scout_web/lib/block_scout_web/templates/address_token/_tokens.html.eex
  9. 2
      apps/block_scout_web/lib/block_scout_web/templates/layout/_topnav.html.eex
  10. 2
      apps/block_scout_web/lib/block_scout_web/views/address_token_view.ex
  11. 11
      apps/block_scout_web/lib/block_scout_web/views/api/rpc/transaction_view.ex
  12. 9
      apps/block_scout_web/lib/block_scout_web/views/tokens/helpers.ex
  13. 42
      apps/block_scout_web/test/block_scout_web/controllers/address_token_controller_test.exs
  14. 49
      apps/block_scout_web/test/block_scout_web/controllers/api/rpc/transaction_controller_test.exs
  15. 6
      apps/block_scout_web/test/block_scout_web/views/address_token_view_test.exs
  16. 31
      apps/explorer/lib/explorer/chain.ex
  17. 106
      apps/explorer/lib/explorer/chain/address/token.ex
  18. 54
      apps/explorer/lib/explorer/chain/token.ex
  19. 18
      apps/explorer/lib/explorer/etherscan.ex
  20. 382
      apps/explorer/test/explorer/chain/address/token_test.exs
  21. 279
      apps/explorer/test/explorer/chain_test.exs
  22. 32
      apps/explorer/test/explorer/etherscan_test.exs

@ -3,7 +3,7 @@ import URI from 'urijs'
import humps from 'humps'
import numeral from 'numeral'
import socket from '../socket'
import { batchChannel, initRedux } from '../utils'
import { batchChannel, initRedux, prependWithClingBottom } from '../utils'
import { updateAllAges } from '../lib/from_now'
import { updateAllCalculatedUsdValues } from '../lib/currency.js'
import { loadTokenBalanceDropdown } from '../lib/token_balance_dropdown'
@ -152,11 +152,11 @@ if ($addressDetailsPage.length) {
$channelBatching.hide()
}
if (oldState.newInternalTransactions !== state.newInternalTransactions && $internalTransactionsList.length) {
$internalTransactionsList.prepend(state.newInternalTransactions.slice(oldState.newInternalTransactions.length).reverse().join(''))
prependWithClingBottom($internalTransactionsList, state.newInternalTransactions.slice(oldState.newInternalTransactions.length).reverse().join(''))
updateAllAges()
}
if (oldState.newTransactions !== state.newTransactions && $transactionsList.length) {
$transactionsList.prepend(state.newTransactions.slice(oldState.newTransactions.length).reverse().join(''))
prependWithClingBottom($transactionsList, state.newTransactions.slice(oldState.newTransactions.length).reverse().join(''))
updateAllAges()
}
}

@ -3,7 +3,7 @@ import URI from 'urijs'
import humps from 'humps'
import socket from '../socket'
import { updateAllAges } from '../lib/from_now'
import { initRedux } from '../utils'
import { initRedux, prependWithClingBottom } from '../utils'
export const initialState = {
beyondPageOne: null,
@ -58,7 +58,7 @@ if ($blockListPage.length) {
if (state.channelDisconnected) $channelDisconnected.show()
if (oldState.newBlock !== state.newBlock) {
$blocksList.prepend(state.newBlock)
prependWithClingBottom($blocksList, state.newBlock)
updateAllAges()
}
}

@ -4,7 +4,7 @@ import humps from 'humps'
import numeral from 'numeral'
import socket from '../socket'
import { updateAllAges } from '../lib/from_now'
import { batchChannel, initRedux } from '../utils'
import { batchChannel, initRedux, prependWithClingBottom } from '../utils'
const BATCH_THRESHOLD = 10
@ -125,7 +125,7 @@ if ($transactionListPage.length) {
.children()
.slice($transactionsList.children().length - newTransactionsToInsert.length, $transactionsList.children().length)
.remove()
$transactionsList.prepend(newTransactionsToInsert.reverse().join(''))
prependWithClingBottom($transactionsList, newTransactionsToInsert.reverse().join(''))
updateAllAges()
}

@ -1,3 +1,4 @@
import $ from 'jquery'
import _ from 'lodash'
import { createStore } from 'redux'
@ -32,3 +33,42 @@ export function initRedux (reducer, { main, render, debug } = {}) {
}
if (main) main(store)
}
export function prependWithClingBottom ($el, content) {
function userAtTop () {
return window.scrollY < $('[data-selector="navbar"]').outerHeight()
}
if (userAtTop()) return $el.prepend(content)
let isAnimating
function setIsAnimating () {
isAnimating = true
}
$el.on('animationstart', setIsAnimating)
let startingScrollPosition = window.scrollY
let endingScrollPosition = window.scrollY
function userIsScrolling () {
return window.scrollY < startingScrollPosition || endingScrollPosition < window.scrollY
}
const clingDistanceFromBottom = document.body.scrollHeight - window.scrollY
let clingBottomLoop = window.requestAnimationFrame(function clingBottom () {
if (userIsScrolling()) return stopClinging()
startingScrollPosition = window.scrollY
endingScrollPosition = document.body.scrollHeight - clingDistanceFromBottom
$(window).scrollTop(endingScrollPosition)
clingBottomLoop = window.requestAnimationFrame(clingBottom)
})
function stopClinging () {
window.cancelAnimationFrame(clingBottomLoop)
$el.off('animationstart', setIsAnimating)
$el.off('animationend animationcancel', stopClinging)
}
$el.on('animationend animationcancel', stopClinging)
setTimeout(() => !isAnimating && stopClinging(), 100)
return $el.prepend(content)
}

@ -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,7 +1,7 @@
defmodule BlockScoutWeb.API.RPC.TransactionController do
use BlockScoutWeb, :controller
alias Explorer.{Chain, Etherscan}
alias Explorer.Chain
def gettxreceiptstatus(conn, params) do
with {:txhash_param, {:ok, txhash_param}} <- fetch_txhash(params),
@ -20,7 +20,7 @@ defmodule BlockScoutWeb.API.RPC.TransactionController do
def getstatus(conn, params) do
with {:txhash_param, {:ok, txhash_param}} <- fetch_txhash(params),
{:format, {:ok, transaction_hash}} <- to_transaction_hash(txhash_param) do
error = Etherscan.get_transaction_error(transaction_hash)
error = to_transaction_error(transaction_hash)
render(conn, :getstatus, %{error: error})
else
{:txhash_param, :error} ->
@ -45,4 +45,13 @@ defmodule BlockScoutWeb.API.RPC.TransactionController do
{:ok, transaction} -> transaction.status
end
end
defp to_transaction_error(transaction_hash) do
with {:ok, transaction} <- Chain.hash_to_transaction(transaction_hash),
{:error, error} <- Chain.transaction_to_status(transaction) do
error
else
_ -> ""
end
end
end

@ -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>

@ -1,4 +1,4 @@
<nav class="navbar navbar-dark navbar-expand-lg navbar-primary">
<nav class="navbar navbar-dark navbar-expand-lg navbar-primary" data-selector="navbar">
<div class="container">
<%= link to: chain_path(@conn, :show), class: "navbar-brand", "data-test": "header_logo" do %>
<img class="navbar-logo" src="<%= Application.get_env(:block_scout_web, BlockScoutWeb.Chain)[:logo] %>" />

@ -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

@ -24,17 +24,24 @@ defmodule BlockScoutWeb.API.RPC.TransactionView do
defp prepare_tx_receipt_status(_), do: "0"
defp prepare_error(nil) do
defp prepare_error("") do
%{
"isError" => "0",
"errDescription" => ""
}
end
defp prepare_error(error) do
defp prepare_error(error) when is_binary(error) do
%{
"isError" => "1",
"errDescription" => error
}
end
defp prepare_error(error) when is_atom(error) do
%{
"isError" => "1",
"errDescription" => error |> Atom.to_string() |> String.replace("_", " ")
}
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
@ -89,6 +119,14 @@ defmodule BlockScoutWeb.AddressTokenControllerTest do
Enum.each(1..51, fn i ->
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)
end)

@ -209,17 +209,25 @@ defmodule BlockScoutWeb.API.RPC.TransactionControllerTest do
assert response["message"] == "OK"
end
test "with a txhash with error status", %{conn: conn} do
test "with a txhash with error", %{conn: conn} do
error = "some error"
transaction_details = [
status: :error,
error: error,
internal_transactions_indexed_at: DateTime.utc_now()
]
transaction =
:transaction
|> insert()
|> with_block(status: :error)
|> with_block(transaction_details)
internal_transaction_details = [
transaction: transaction,
index: 0,
type: :reward,
error: "some error"
error: error
]
insert(:internal_transaction, internal_transaction_details)
@ -232,7 +240,40 @@ defmodule BlockScoutWeb.API.RPC.TransactionControllerTest do
expected_result = %{
"isError" => "1",
"errDescription" => "some error"
"errDescription" => error
}
response =
conn
|> get("/api", params)
|> json_response(200)
assert response["result"] == expected_result
assert response["status"] == "1"
assert response["message"] == "OK"
end
test "with a txhash with failed status but awaiting internal transactions", %{conn: conn} do
transaction_details = [
status: :error,
error: nil,
internal_transactions_indexed_at: nil
]
transaction =
:transaction
|> insert()
|> with_block(transaction_details)
params = %{
"module" => "transaction",
"action" => "getstatus",
"txhash" => "#{transaction.hash}"
}
expected_result = %{
"isError" => "1",
"errDescription" => "awaiting internal transactions"
}
assert response =

@ -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,38 +1712,13 @@ 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
|> fetch_tokens_from_address_hash(paging_options)
|> add_number_of_transfers_to_tokens_from_address(address_hash)
end
@spec fetch_tokens_from_address_hash(Hash.Address.t(), [any()]) :: []
def fetch_tokens_from_address_hash(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
@spec add_number_of_transfers_to_tokens_from_address([Token], Hash.Address.t()) :: []
defp add_number_of_transfers_to_tokens_from_address(tokens, address_hash) do
Enum.map(tokens, fn token ->
Map.put(
token,
:number_of_transfers,
count_token_transfers_from_address_hash(token.contract_address_hash, address_hash)
)
end)
end
@spec count_token_transfers_from_address_hash(Hash.Address.t(), Hash.Address.t()) :: []
def count_token_transfers_from_address_hash(token_hash, address_hash) do
token_hash
|> Token.interactions_with_address(address_hash)
|> Repo.aggregate(:count, :name)
end
@doc """
Update a new `t:Token.t/0` record.

@ -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,8 @@ defmodule Explorer.Chain.Token do
use Ecto.Schema
import Ecto.{Changeset, Query}
alias Explorer.PagingOptions
alias Explorer.Chain.{Address, Hash, Token, TokenTransfer}
@default_paging_options %PagingOptions{page_size: 50}
@typedoc """
* `:name` - Name of the token
* `:symbol` - Trading symbol of the token
@ -46,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)
@ -80,54 +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.
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)
subquery =
from(
token in Token,
join: tt in TokenTransfer,
on: tt.token_contract_address_hash == token.contract_address_hash,
where: tt.to_address_hash == ^address_hash or tt.from_address_hash == ^address_hash,
distinct: [:contract_address_hash]
)
query = from(t in subquery(subquery), order_by: [desc: :type, asc: :name])
query
|> page_token(paging_options)
|> limit(^paging_options.page_size)
end
@doc """
Builds an `Ecto.Query` to fetch the transactions between a token and an address.
"""
def interactions_with_address(token_hash, 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,
where: t.contract_address_hash == ^token_hash,
where: tt.to_address_hash == ^address_hash or tt.from_address_hash == ^address_hash,
select: tt
)
end
def page_token(query, %PagingOptions{key: nil}), do: query
def page_token(query, %PagingOptions{key: {name, type, inserted_at}}) do
where(
query,
[token],
token.type < ^type or (token.type == ^type and token.name > ^name) or
(token.type == ^type and token.name == ^name and token.inserted_at < ^inserted_at)
on: tt.token_contract_address_hash == t.contract_address_hash
)
end
end

@ -183,24 +183,6 @@ defmodule Explorer.Etherscan do
Repo.one(query)
end
@doc """
Gets the error for a given transaction hash
(`t:Explorer.Chain.Hash.Full.t/0`). Returns nil if no error is found.
"""
@spec get_transaction_error(Hash.Full.t()) :: String.t() | nil
def get_transaction_error(%Hash{byte_count: unquote(Hash.Full.byte_count())} = transaction_hash) do
query =
from(it in InternalTransaction,
where: it.transaction_hash == ^transaction_hash,
order_by: [desc: :index],
limit: 1,
select: it.error
)
Repo.one(query)
end
@transaction_fields ~w(
block_hash
block_number

@ -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,285 +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 attached" do
address = insert(:address)
token =
:token
|> insert(name: "token-c", type: "ERC-721")
|> Repo.preload(:contract_address)
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.name == "token-c"
assert fetched_token.number_of_transfers == 2
end
end
describe "fetch_tokens_from_address_hash/1" do
test "only returns tokens that a given address has interacted with" do
alice = insert(:address)
token_a =
:token
|> insert(name: "token-1")
|> Repo.preload(:contract_address)
token_b =
:token
|> insert(name: "token-2")
|> Repo.preload(:contract_address)
token_c =
:token
|> insert(name: "token-3")
|> Repo.preload(:contract_address)
insert(
:token_transfer,
token_contract_address: token_a.contract_address,
from_address: alice,
to_address: build(:address)
)
insert(
:token_transfer,
token_contract_address: token_b.contract_address,
from_address: build(:address),
to_address: alice
)
insert(
:token_transfer,
token_contract_address: token_c.contract_address,
from_address: build(:address),
to_address: build(:address)
)
expected_tokens =
alice.hash
|> Chain.fetch_tokens_from_address_hash()
|> Enum.map(& &1.name)
assert expected_tokens == [token_a.name, token_b.name]
end
test "returns an empty list when the given address hasn't interacted with any tokens" do
alice = insert(:address)
token =
:token
|> insert(name: "token-1")
|> Repo.preload(:contract_address)
insert(
:token_transfer,
token_contract_address: token.contract_address,
from_address: build(:address),
to_address: build(:address)
)
assert Chain.fetch_tokens_from_address_hash(alice.hash) == []
end
test "distinct tokens by contract_address_hash" do
alice = insert(:address)
token =
:token
|> insert(name: "token-1")
|> Repo.preload(:contract_address)
insert(
:token_transfer,
token_contract_address: token.contract_address,
from_address: alice,
to_address: build(:address)
)
insert(
:token_transfer,
token_contract_address: token.contract_address,
from_address: build(:address),
to_address: alice
)
expected_tokens =
alice.hash
|> Chain.fetch_tokens_from_address_hash()
|> Enum.map(& &1.name)
assert expected_tokens == [token.name]
end
test "orders by type, name and inserted_at time" do
address = insert(:address)
first_token =
:token
|> insert(name: "token-c", type: "ERC-721")
|> Repo.preload(:contract_address)
second_token =
:token
|> insert(name: "token-a", type: "ERC-20")
|> Repo.preload(:contract_address)
third_token =
:token
|> insert(name: "token-b", type: "ERC-20")
|> Repo.preload(:contract_address)
fourth_token =
:token
|> insert(name: "token-b", type: "ERC-20", inserted_at: third_token.inserted_at)
|> Repo.preload(:contract_address)
insert(
:token_transfer,
token_contract_address: first_token.contract_address,
from_address: address,
to_address: build(:address)
)
insert(
:token_transfer,
token_contract_address: second_token.contract_address,
from_address: address,
to_address: build(:address)
)
insert(
:token_transfer,
token_contract_address: third_token.contract_address,
from_address: build(:address),
to_address: address
)
insert(
:token_transfer,
token_contract_address: fourth_token.contract_address,
from_address: build(:address),
to_address: address
)
fetched_tokens =
address.hash
|> Chain.fetch_tokens_from_address_hash()
|> Enum.map(&Repo.preload(&1, :contract_address))
assert fetched_tokens == [first_token, second_token, third_token, fourth_token]
end
test "supports pagination" do
address = insert(:address)
first_token =
:token
|> insert(name: "token-c", type: "ERC-721")
|> Repo.preload(:contract_address)
second_token =
:token
|> insert(name: "token-a", type: "ERC-20")
|> Repo.preload(:contract_address)
third_token =
:token
|> insert(name: "token-b", type: "ERC-20")
|> Repo.preload(:contract_address)
paging_options = %PagingOptions{
page_size: 1,
key: {first_token.name, first_token.type, first_token.inserted_at}
}
insert(
:token_transfer,
token_contract_address: first_token.contract_address,
from_address: address,
to_address: build(:address)
)
insert(
:token_transfer,
token_contract_address: second_token.contract_address,
from_address: address,
to_address: build(:address)
)
insert(
:token_transfer,
token_contract_address: third_token.contract_address,
from_address: build(:address),
to_address: address
)
fetched_tokens =
address.hash
|> Chain.fetch_tokens_from_address_hash(paging_options: paging_options)
|> Enum.map(& &1.name)
assert fetched_tokens == [second_token.name]
end
end
describe "count_token_transfers_from_address_hash/2" do
test "returns the number of times an address has interacted with a token" do
address = insert(:address)
token =
:token
|> insert(name: "token")
|> Repo.preload(:contract_address)
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
)
assert Chain.count_token_transfers_from_address_hash(token.contract_address.hash, address.hash) == 2
end
test "returns 0 if no interaction is found" do
address = insert(:address)
token =
:token
|> insert(name: "token")
|> Repo.preload(:contract_address)
assert Chain.count_token_transfers_from_address_hash(token.contract_address.hash, address.hash) == 0
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)

@ -1103,36 +1103,4 @@ defmodule Explorer.EtherscanTest do
assert found_token_balance.id == token_balance2.id
end
end
describe "get_transaction_error/1" do
test "with a transaction that doesn't exist" do
transaction = build(:transaction)
refute Etherscan.get_transaction_error(transaction.hash)
end
test "with a transaction with no errors" do
transaction = insert(:transaction)
refute Etherscan.get_transaction_error(transaction.hash)
end
test "with a transaction with an error" do
transaction =
:transaction
|> insert()
|> with_block(status: :error)
internal_transaction_details = [
transaction: transaction,
index: 0,
type: :reward,
error: "some error"
]
insert(:internal_transaction, internal_transaction_details)
assert Etherscan.get_transaction_error(transaction.hash) == "some error"
end
end
end

Loading…
Cancel
Save