Merge pull request #534 from poanetwork/frg-list-tokens-on-address-page

List of tokens associated with the Address on the Address' page
pull/556/head
Andrew Cravenho 6 years ago committed by GitHub
commit 67b415ddae
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 3
      apps/block_scout_web/assets/css/app.scss
  2. 35
      apps/block_scout_web/assets/css/components/_token-balance-dropdown.scss
  3. 10
      apps/block_scout_web/assets/css/components/address-overview.scss
  4. 1
      apps/block_scout_web/assets/js/app.js
  5. 19
      apps/block_scout_web/assets/js/lib/token_balance_dropdown.js
  6. 10
      apps/block_scout_web/lib/block_scout_web/controller.ex
  7. 24
      apps/block_scout_web/lib/block_scout_web/controllers/address_token_balance_controller.ex
  8. 7
      apps/block_scout_web/lib/block_scout_web/controllers/smart_contract_controller.ex
  9. 7
      apps/block_scout_web/lib/block_scout_web/router.ex
  10. 20
      apps/block_scout_web/lib/block_scout_web/templates/address/_token_holdings.html.eex
  11. 9
      apps/block_scout_web/lib/block_scout_web/templates/address/overview.html.eex
  12. 36
      apps/block_scout_web/lib/block_scout_web/templates/address_token_balance/_token_balances.html.eex
  13. 17
      apps/block_scout_web/lib/block_scout_web/templates/address_token_balance/_tokens.html.eex
  14. 20
      apps/block_scout_web/lib/block_scout_web/views/address_token_balance_view.ex
  15. 14
      apps/block_scout_web/lib/block_scout_web/views/currency_helpers.ex
  16. 26
      apps/block_scout_web/lib/block_scout_web/views/tokens/helpers.ex
  17. 26
      apps/block_scout_web/priv/gettext/default.pot
  18. 26
      apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po
  19. 30
      apps/block_scout_web/test/block_scout_web/views/address_token_balance_view_test.exs
  20. 14
      apps/block_scout_web/test/block_scout_web/views/currency_helpers_test.exs
  21. 22
      apps/block_scout_web/test/block_scout_web/views/tokens/helpers_test.exs
  22. 7
      apps/explorer/lib/explorer/chain.ex
  23. 21
      apps/explorer/lib/explorer/chain/token.ex
  24. 74
      apps/explorer/lib/explorer/token/balance_reader.ex
  25. 97
      apps/explorer/test/explorer/chain_test.exs
  26. 130
      apps/explorer/test/explorer/token/balance_reader_test.exs

@ -76,7 +76,8 @@ $fa-font-path: "~@fortawesome/fontawesome-free/webfonts";
@import "components/badge";
@import "components/description-list";
@import "components/nounderline-link";
@import "components/token-balance-dropdown";
@import "components/address-overview";
:export {
primary: $primary;

@ -0,0 +1,35 @@
.token-balance-dropdown {
min-width: 14.375rem;
margin-top: 1rem;
background-color: $gray-100;
box-shadow: 0px 2px 3px 2px $gray-200;
border: none;
// Overriding style added by Bootstrap dropdown via JS.
left: -17px !important;
.dropdown-items {
overflow-y: auto;
max-height: 18.5rem;
.dropdown-item:hover {
color: $white;
}
}
&:after, &:before {
bottom: 100%;
left: 14%;
border: solid transparent;
content: " ";
height: 0;
width: 0;
position: absolute;
}
&:before {
border-bottom-color: $gray-100;
border-width: 1rem;
margin-left: -1rem;
}
}

@ -0,0 +1,10 @@
.address-overview {
.card-section {
margin-bottom: 3rem;
}
.card {
margin-bottom: 0;
height: 100%;
}
}

@ -27,6 +27,7 @@ import './lib/tooltip'
import './lib/smart_contract/read_only_functions'
import './lib/pretty_json'
import './lib/try_api'
import './lib/token_balance_dropdown'
import './pages/address'
import './pages/block'

@ -0,0 +1,19 @@
import $ from 'jquery'
const tokenBalanceDropdown = (element) => {
const $element = $(element)
const $loading = $element.find('[data-loading]')
const $errorMessage = $element.find('[data-error-message]')
const apiPath = element.dataset.api_path
$loading.show()
$.get(apiPath)
.done(response => $element.html(response))
.fail(() => {
$loading.hide()
$errorMessage.show()
})
}
$('[data-token-balance-dropdown]').each((_index, element) => tokenBalanceDropdown(element))

@ -22,4 +22,14 @@ defmodule BlockScoutWeb.Controller do
|> put_view(BlockScoutWeb.ErrorView)
|> render("422.html")
end
@doc """
Checks if the request is AJAX or not.
"""
def ajax?(conn) do
case get_req_header(conn, "x-requested-with") do
[value] -> value in ["XMLHttpRequest", "xmlhttprequest"]
[] -> false
end
end
end

@ -0,0 +1,24 @@
defmodule BlockScoutWeb.AddressTokenBalanceController do
use BlockScoutWeb, :controller
alias Explorer.Chain
alias Explorer.Token.BalanceReader
def index(conn, %{"address_id" => address_hash_string}) do
with true <- ajax?(conn),
{:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string) do
token_balances =
address_hash
|> Chain.fetch_tokens_from_address_hash()
|> BalanceReader.fetch_token_balances_without_error(address_hash_string)
conn
|> put_status(200)
|> put_layout(false)
|> render("_token_balances.html", tokens: token_balances)
else
_ ->
not_found(conn)
end
end
end

@ -57,11 +57,4 @@ defmodule BlockScoutWeb.SmartContractController do
not_found(conn)
end
end
defp ajax?(conn) do
case get_req_header(conn, "x-requested-with") do
[value] -> value in ["XMLHttpRequest", "xmlhttprequest"]
[] -> false
end
end
end

@ -95,6 +95,13 @@ defmodule BlockScoutWeb.Router do
only: [:index, :show],
as: :read_contract
)
resources(
"/token_balances",
AddressTokenBalanceController,
only: [:index],
as: :token_balance
)
end
resources "/tokens", Tokens.TokenController, only: [:show], as: :token do

@ -0,0 +1,20 @@
<div class="card">
<div class="card-body">
<h2 class="card-title"><%= gettext "Token Holdings" %></h2>
<!-- Dropdown -->
<div data-token-balance-dropdown
data-api_path=<%= address_token_balance_path(@conn, :index, :en, @address.hash) %>
class="icon-links ml-3 mb-3"
>
<p data-loading class="mb-0" stytle="display: none">
<i class="fa fa-spinner fa-spin"></i>
<%= gettext("Fetching tokens...") %>
</p>
<p data-error-message class="mb-0" style="display: none">
<%= gettext("Error tryng to fetch balances.") %>
</p>
</div>
</div>
</div>

@ -1,6 +1,6 @@
<section>
<section class="address-overview">
<div class="row">
<div class="col-md-12 col-lg-8">
<div class="card-section col-md-12 col-lg-5">
<div class="card">
<div class="card-body">
<div class="icon-links float-right">
@ -51,9 +51,12 @@
</div>
</div>
</div>
<div class="col-md-6 col-lg-4" data-selector="balance-card">
<div class="card-section col-md-6 col-lg-4" data-selector="balance-card">
<%= render BlockScoutWeb.AddressView, "_balance_card.html", address: @address, exchange_rate: @exchange_rate %>
</div>
<div class="card-section col-md-6 col-lg-3">
<%= render BlockScoutWeb.AddressView, "_token_holdings.html", conn: @conn, address: @address %>
</div>
</div>
</section>

@ -0,0 +1,36 @@
<%= if Enum.any?(@tokens) do %>
<a href="#"
data-dropdown-toggle
data-toggle="dropdown"
role="button"
class="icon-link"
id="dropdown-tokens"
aria-haspopup="true"
aria-expanded="false"
style="text-decoration: none;">
<i class="fas fa-chevron-circle-down"></i>
</a>
<% end %>
<h4 data-tokens-count class="ml-2 text-dark"><%= tokens_count_title(@tokens) %></h4>
<div class="dropdown-menu p-0 token-balance-dropdown" aria-labelledby="dropdown-tokens">
<div data-dropdown-items class="dropdown-items">
<%= if Enum.any?(@tokens, & &1.type == "ERC-721") do %>
<%= render(
"_tokens.html",
conn: @conn,
tokens: filter_by_type(@tokens, "ERC-721"),
type: "ERC-721"
) %>
<% end %>
<%= if Enum.any?(@tokens, & &1.type == "ERC-20") do %>
<%= render(
"_tokens.html",
conn: @conn,
tokens: filter_by_type(@tokens, "ERC-20"),
type: "ERC-20"
) %>
<% end %>
</div>
</div>

@ -0,0 +1,17 @@
<h6 class="dropdown-header border-bottom">
<%= @type %> (<%= Enum.count(@tokens)%>)
</h6>
<%= for token <- sort_by_name(@tokens) do %>
<div class="border-bottom">
<%= link(
to: token_path(@conn, :show, :en, token.contract_address_hash),
class: "dropdown-item"
) do %>
<p class="mb-0"><%= token_name(token) %></p>
<p class="mb-0">
<%= format_according_to_decimals(token.balance, token.decimals) %> <%= token.symbol %>
</p>
<% end %>
</div>
<% end %>

@ -0,0 +1,20 @@
defmodule BlockScoutWeb.AddressTokenBalanceView do
use BlockScoutWeb, :view
def tokens_count_title(tokens) do
ngettext("%{count} token", "%{count} tokens", Enum.count(tokens))
end
def filter_by_type(tokens, type) do
Enum.filter(tokens, &(&1.type == type))
end
@doc """
Sorts the given list of tokens in alphabetically order considering nil values in the bottom of
the list.
"""
def sort_by_name(tokens) do
{unnamed, named} = Enum.split_with(tokens, &is_nil(&1.name))
Enum.sort_by(named, &String.downcase(&1.name)) ++ unnamed
end
end

@ -65,7 +65,21 @@ defmodule BlockScoutWeb.CurrencyHelpers do
iex> format_according_to_decimals(Decimal.new(205000), 2)
"2,050"
iex> format_according_to_decimals(205000, 2)
"2,050"
"""
@spec format_according_to_decimals(non_neg_integer(), non_neg_integer()) :: String.t()
def format_according_to_decimals(value, nil) do
format_according_to_decimals(value, 0)
end
def format_according_to_decimals(value, decimals) when is_integer(value) do
value
|> Decimal.new()
|> format_according_to_decimals(decimals)
end
@spec format_according_to_decimals(Decimal.t(), non_neg_integer()) :: String.t()
def format_according_to_decimals(%Decimal{sign: sign, coef: coef, exp: exp}, decimals) do
sign

@ -46,15 +46,29 @@ defmodule BlockScoutWeb.Tokens.Helpers do
When the token's symbol is nil, the function will return the contract address hash.
"""
def token_symbol(%Token{symbol: nil, contract_address_hash: address_hash}) do
address_hash =
address_hash
|> to_string()
|> String.slice(0..6)
"#{address_hash}..."
"#{contract_address_hash_truncated(address_hash)}..."
end
def token_symbol(%Token{symbol: symbol}) do
symbol
end
@doc """
Returns the token's name.
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
"#{contract_address_hash_truncated(address_hash)}..."
end
def token_name(%Token{name: name}) do
name
end
defp contract_address_hash_truncated(address_hash) do
address_hash
|> to_string()
|> String.slice(0..6)
end
end

@ -599,7 +599,7 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/overview.html.eex:13
#: lib/block_scout_web/templates/address/overview.html.eex:65
#: lib/block_scout_web/templates/address/overview.html.eex:68
msgid "QR Code"
msgstr ""
@ -686,7 +686,7 @@ msgid "Block Height #%{height}"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/overview.html.eex:74
#: lib/block_scout_web/templates/address/overview.html.eex:77
msgid "Close"
msgstr ""
@ -816,3 +816,25 @@ msgstr ""
#: lib/block_scout_web/templates/tokens/read_contract/index.html.eex:52
msgid "loading..."
msgstr ""
#, elixir-format
#: lib/block_scout_web/views/address_token_balance_view.ex:5
msgid "%{count} token"
msgid_plural "%{count} tokens"
msgstr[0] ""
msgstr[1] ""
#, elixir-format
#: lib/block_scout_web/templates/address/_token_holdings.html.eex:16
msgid "Error tryng to fetch balances."
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/_token_holdings.html.eex:12
msgid "Fetching tokens..."
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/_token_holdings.html.eex:3
msgid "Token Holdings"
msgstr ""

@ -611,7 +611,7 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/overview.html.eex:13
#: lib/block_scout_web/templates/address/overview.html.eex:65
#: lib/block_scout_web/templates/address/overview.html.eex:68
msgid "QR Code"
msgstr ""
@ -698,7 +698,7 @@ msgid "Block Height #%{height}"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/overview.html.eex:74
#: lib/block_scout_web/templates/address/overview.html.eex:77
msgid "Close"
msgstr ""
@ -828,3 +828,25 @@ msgstr ""
#: lib/block_scout_web/templates/tokens/read_contract/index.html.eex:52
msgid "loading..."
msgstr ""
#, elixir-format
#: lib/block_scout_web/views/address_token_balance_view.ex:5
msgid "%{count} token"
msgid_plural "%{count} tokens"
msgstr[0] ""
msgstr[1] ""
#, elixir-format
#: lib/block_scout_web/templates/address/_token_holdings.html.eex:16
msgid "Error tryng to fetch balances."
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/_token_holdings.html.eex:12
msgid "Fetching tokens..."
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/_token_holdings.html.eex:3
msgid "Token Holdings"
msgstr ""

@ -0,0 +1,30 @@
defmodule BlockScoutWeb.AddressTokenBalanceViewTest do
use BlockScoutWeb.ConnCase, async: true
alias BlockScoutWeb.AddressTokenBalanceView
describe "sort_by_name/1" do
test "sorts the given tokens by its name" do
token_a = build(:token, name: "token name")
token_b = build(:token, name: "token")
token_c = build(:token, name: "atoken")
assert AddressTokenBalanceView.sort_by_name([token_a, token_b, token_c]) == [token_c, token_b, token_a]
end
test "considers nil values in the bottom of the list" do
token_a = build(:token, name: nil)
token_b = build(:token, name: "token name")
token_c = build(:token, name: "token")
assert AddressTokenBalanceView.sort_by_name([token_a, token_b, token_c]) == [token_c, token_b, token_a]
end
test "considers capitalization" do
token_a = build(:token, name: "Token")
token_b = build(:token, name: "atoken")
assert AddressTokenBalanceView.sort_by_name([token_a, token_b]) == [token_b, token_a]
end
end
end

@ -49,6 +49,20 @@ defmodule BlockScoutWeb.CurrencyHelpersTest do
assert CurrencyHelpers.format_according_to_decimals(amount, decimals) == "10,004.5"
end
test "supports value as integer" do
amount = 1_000_450
decimals = 2
assert CurrencyHelpers.format_according_to_decimals(amount, decimals) == "10,004.5"
end
test "considers 0 when decimals is nil" do
amount = 1_000_450
decimals = nil
assert CurrencyHelpers.format_according_to_decimals(amount, decimals) == "1,000,450"
end
end
describe "format_integer_to_currency/1" do

@ -48,15 +48,25 @@ defmodule BlockScoutWeb.Tokens.HelpersTest do
end
test "returns the token contract address hash when the symbol is nil" do
address = build(:address)
address = build(:address, hash: "de3fa0f9f8d47790ce88c2b2b82ab81f79f2e65f")
token = build(:token, symbol: nil, contract_address_hash: address.hash)
address_hash =
address.hash
|> Explorer.Chain.Hash.to_string()
|> String.slice(0..6)
assert Helpers.token_symbol(token) == "de3fa0f..."
end
end
describe "token_name/1" do
test "returns the token name" do
token = build(:token, name: "Batman")
assert Helpers.token_name(token) == "Batman"
end
test "returns the token contract address hash when the name is nil" do
address = build(:address, hash: "de3fa0f9f8d47790ce88c2b2b82ab81f79f2e65f")
token = build(:token, name: nil, contract_address_hash: address.hash)
assert Helpers.token_symbol(token) == "#{address_hash}..."
assert Helpers.token_name(token) == "de3fa0f..."
end
end
end

@ -1564,4 +1564,11 @@ defmodule Explorer.Chain do
Repo.one(query) != nil
end
@spec fetch_tokens_from_address_hash(Hash.Address.t()) :: []
def fetch_tokens_from_address_hash(address_hash) do
address_hash
|> Token.with_transfers_by_address()
|> Repo.all()
end
end

@ -19,8 +19,8 @@ defmodule Explorer.Chain.Token do
use Ecto.Schema
import Ecto.{Changeset}
alias Explorer.Chain.{Address, Hash, Token}
import Ecto.{Changeset, Query}
alias Explorer.Chain.{Address, Hash, Token, TokenTransfer}
@typedoc """
* `:name` - Name of the token
@ -74,4 +74,21 @@ defmodule Explorer.Chain.Token do
|> foreign_key_constraint(:contract_address)
|> 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 an another address.
"""
def with_transfers_by_address(address_hash) do
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: tt.token_contract_address_hash,
select: token
)
end
end

@ -0,0 +1,74 @@
defmodule Explorer.Token.BalanceReader do
@moduledoc """
Reads Token's balances using Smart Contract functions from the blockchain.
"""
alias Explorer.SmartContract.Reader
@balance_function_abi [
%{
"type" => "function",
"stateMutability" => "view",
"payable" => false,
"outputs" => [
%{
"type" => "uint256",
"name" => "balance"
}
],
"name" => "balanceOf",
"inputs" => [
%{
"type" => "address",
"name" => "tokenOwner"
}
],
"constant" => true
}
]
@doc """
Fetches the token balances that were fetched without error and have balances more than 0.
"""
def fetch_token_balances_without_error(tokens, address_hash_string) do
tokens
|> fetch_token_balances_from_blockchain(address_hash_string)
|> Stream.filter(&token_without_error?/1)
|> Stream.map(&format_result/1)
|> Enum.filter(&tokens_with_no_zero_balance?/1)
end
defp token_without_error?({:ok, _token}), do: true
defp token_without_error?({:error, _token}), do: false
defp format_result({:ok, token}), do: token
defp tokens_with_no_zero_balance?(%{balance: balance}), do: balance != 0
@doc """
Fetches the token balances given the tokens and the address hash as string.
This function is going to perform one request async for each token inside a list of tokens in
order to fetch the balance.
"""
@spec fetch_token_balances_from_blockchain([], String.t()) :: []
def fetch_token_balances_from_blockchain(tokens, address_hash_string) do
tokens
|> Task.async_stream(&fetch_from_blockchain(&1, address_hash_string))
|> Enum.map(&blockchain_result_from_tasks/1)
end
defp fetch_from_blockchain(%{contract_address_hash: address_hash} = token, address_hash_string) do
address_hash
|> Reader.query_unverified_contract(@balance_function_abi, %{"balanceOf" => [address_hash_string]})
|> format_blockchain_result(token)
end
defp format_blockchain_result(%{"balanceOf" => {:ok, balance}}, token) do
{:ok, Map.put(token, :balance, balance)}
end
defp format_blockchain_result(%{"balanceOf" => {:error, error}}, token) do
{:error, Map.put(token, :balance, error)}
end
defp blockchain_result_from_tasks({:ok, blockchain_result}), do: blockchain_result
end

@ -1604,4 +1604,101 @@ defmodule Explorer.ChainTest do
assert Chain.transaction_has_token_transfers?(transaction.hash) == false
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 a empty list when the given address hasn't interacted with one" 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
end
end

@ -0,0 +1,130 @@
defmodule Explorer.Token.BalanceReaderTest do
use EthereumJSONRPC.Case
use Explorer.DataCase
doctest Explorer.Token.BalanceReader
alias Explorer.Token.{BalanceReader}
alias Explorer.Chain.Hash
import Mox
setup :verify_on_exit!
setup :set_mox_global
describe "fetch_token_balances_from_blockchain/2" do
test "fetches balances of tokens given the address hash" do
address = insert(:address)
token = insert(:token, contract_address: build(:contract_address))
address_hash_string = Hash.to_string(address.hash)
get_balance_from_blockchain()
result =
[token]
|> BalanceReader.fetch_token_balances_from_blockchain(address_hash_string)
|> List.first()
assert result == {:ok, Map.put(token, :balance, 1_000_000_000_000_000_000_000_000)}
end
test "does not ignore calls that were returned with error" do
address = insert(:address)
token = insert(:token, contract_address: build(:contract_address))
address_hash_string = Hash.to_string(address.hash)
get_balance_from_blockchain_with_error()
result =
[token]
|> BalanceReader.fetch_token_balances_from_blockchain(address_hash_string)
|> List.first()
assert result == {:error, Map.put(token, :balance, "(-32015) VM execution error.")}
end
end
describe "fetch_token_balances_without_error/2" do
test "filters token balances that were fetched without error" do
address = insert(:address)
token_a = insert(:token, contract_address: build(:contract_address))
token_b = insert(:token, contract_address: build(:contract_address))
address_hash_string = Hash.to_string(address.hash)
get_balance_from_blockchain()
get_balance_from_blockchain_with_error()
results =
[token_a, token_b]
|> BalanceReader.fetch_token_balances_without_error(address_hash_string)
assert Enum.count(results) == 1
assert List.first(results) == Map.put(token_a, :balance, 1_000_000_000_000_000_000_000_000)
end
test "does not considers balances equal 0" do
address = insert(:address)
token = insert(:token, contract_address: build(:contract_address))
address_hash_string = Hash.to_string(address.hash)
get_balance_from_blockchain_with_balance_zero()
results =
[token]
|> BalanceReader.fetch_token_balances_without_error(address_hash_string)
assert Enum.count(results) == 0
end
end
defp get_balance_from_blockchain() do
expect(
EthereumJSONRPC.Mox,
:json_rpc,
fn [%{id: _, method: _, params: [%{data: _, to: _}]}], _options ->
{:ok,
[
%{
id: "balanceOf",
jsonrpc: "2.0",
result: "0x00000000000000000000000000000000000000000000d3c21bcecceda1000000"
}
]}
end
)
end
defp get_balance_from_blockchain_with_balance_zero() do
expect(
EthereumJSONRPC.Mox,
:json_rpc,
fn [%{id: _, method: _, params: [%{data: _, to: _}]}], _options ->
{:ok,
[
%{
id: "balanceOf",
jsonrpc: "2.0",
result: "0x0000000000000000000000000000000000000000000000000000000000000000"
}
]}
end
)
end
defp get_balance_from_blockchain_with_error() do
expect(
EthereumJSONRPC.Mox,
:json_rpc,
fn [%{id: _, method: _, params: [%{data: _, to: _}]}], _options ->
{:ok,
[
%{
error: %{code: -32015, data: "Reverted 0x", message: "VM execution error."},
id: "balanceOf",
jsonrpc: "2.0"
}
]}
end
)
end
end
Loading…
Cancel
Save