Add CurrencyHelpers for non-wei currency formatting

pull/201/head
Tim Mecklem 7 years ago
parent 309a5bc5cb
commit 3befa706ec
  1. 2
      apps/explorer_web/lib/explorer_web.ex
  2. 36
      apps/explorer_web/lib/explorer_web/exchange_rates/usd.ex
  3. 2
      apps/explorer_web/lib/explorer_web/templates/address/overview.html.eex
  4. 4
      apps/explorer_web/lib/explorer_web/templates/transaction/overview.html.eex
  5. 20
      apps/explorer_web/lib/explorer_web/views/address_view.ex
  6. 36
      apps/explorer_web/lib/explorer_web/views/currency_helpers.ex
  7. 4
      apps/explorer_web/lib/explorer_web/views/transaction_log_view.ex
  8. 48
      apps/explorer_web/lib/explorer_web/views/transaction_view.ex
  9. 9
      apps/explorer_web/test/explorer_web/controllers/transaction_internal_transaction_controller_test.exs
  10. 42
      apps/explorer_web/test/explorer_web/exchange_rates/usd_test.exs
  11. 26
      apps/explorer_web/test/explorer_web/views/address_view_test.exs
  12. 16
      apps/explorer_web/test/explorer_web/views/currency_helpers_test.exs
  13. 2
      apps/explorer_web/test/explorer_web/views/transaction_view_test.exs

@ -40,7 +40,7 @@ defmodule ExplorerWeb do
# Use all HTML functionality (forms, tags, etc)
use Phoenix.HTML
import ExplorerWeb.{Gettext, Router.Helpers, WeiHelpers}
import ExplorerWeb.{CurrencyHelpers, Gettext, Router.Helpers, WeiHelpers}
import Scrivener.HTML
end
end

@ -0,0 +1,36 @@
defmodule ExplorerWeb.ExchangeRates.USD do
@moduledoc """
Struct and associated conversion functions for USD currency
"""
@typedoc """
Represents USD currency
* `:value` - value in USD
"""
@type t :: %__MODULE__{
value: Decimal.t() | nil
}
defstruct ~w(value)a
alias Explorer.Chain.Wei
alias Explorer.ExchangeRates.Token
def from(nil, _), do: null()
def from(_, nil), do: null()
def from(%Wei{value: nil}, _), do: null()
def from(_, %Token{usd_value: nil}), do: null()
def from(%Wei{} = wei, %Token{usd_value: exchange_rate}) do
ether = Wei.to(wei, :ether)
%__MODULE__{value: Decimal.mult(ether, exchange_rate)}
end
def null do
%__MODULE__{value: nil}
end
end

@ -9,7 +9,7 @@
<dt class="address__item-key"><%= gettext "Balance" %></dt>
<dd class="address__item-value address__balance u-text-right" title="<%= @address %>" data-test="address_balance">
<div><%= balance(@address) %> </div>
<div><%= format_usd(@address, @exchange_rate) %> </div>
<div><%= formatted_usd(@address, @exchange_rate) %> </div>
</dd>
</div>
<div class="address__item">

@ -44,7 +44,7 @@
</th>
<td>
<div><%= value(@transaction) %> </div>
<div><%= format_usd_value(@transaction, @exchange_rate) %></div>
<div><%= formatted_usd_value(@transaction, @exchange_rate) %></div>
</td>
</tr>
<tr>
@ -133,7 +133,7 @@
<%= gettext "TX Fee" %>
</th>
<td>
<%= formatted_fee(@transaction, denomination: :ether) %> <%= formatted_fee(@transaction, exchange_rate: @exchange_rate) %>
<%= formatted_fee(@transaction, denomination: :ether) %> (<%= formatted_fee(@transaction, exchange_rate: @exchange_rate) %>)
</td>
</tr>
<tr>

@ -3,6 +3,7 @@ defmodule ExplorerWeb.AddressView do
alias Explorer.Chain.{Address, Wei}
alias Explorer.ExchangeRates.Token
alias ExplorerWeb.ExchangeRates.USD
@dialyzer :no_match
@ -15,16 +16,17 @@ defmodule ExplorerWeb.AddressView do
format_wei_value(balance, :ether, fractional_digits: 18)
end
def format_usd(_, %Token{usd_value: nil}), do: nil
def format_usd(%Address{fetched_balance: nil}, _), do: nil
def formatted_usd(%Address{fetched_balance: nil}, _), do: nil
def format_usd(%Address{fetched_balance: balance}, %Token{usd_value: usd_value}) do
with {:ok, wei} <- Wei.cast(balance),
ether <- Wei.to(wei, :ether),
usd <- Decimal.mult(ether, usd_value) do
"$#{usd} " <> gettext("USD")
else
_ -> nil
def formatted_usd(%Address{fetched_balance: balance}, %Token{} = exchange_rate) do
case Wei.cast(balance) do
{:ok, wei} ->
wei
|> USD.from(exchange_rate)
|> format_usd_value()
_ ->
nil
end
end

@ -0,0 +1,36 @@
defmodule ExplorerWeb.CurrencyHelpers do
@moduledoc """
Helper functions for interacting with `t:ExplorerWeb.ExchangeRates.USD.t/0` values.
"""
import ExplorerWeb.Gettext
alias ExplorerWeb.ExchangeRates.USD
alias Cldr.Number
@doc """
Formats a `ExplorerWeb.ExchangeRates.USD` value into USD and applies a unit label.
## Examples
iex> format_usd_value(%USD{value: Decimal.new(5)})
"$5 USD"
iex> format_usd_value(%USD{value: Decimal.new(5000)})
"$5,000 USD"
iex> format_usd_value(%USD{value: Decimal.new(0.000005)})
"$0.000005 USD"
"""
@spec format_usd_value(USD.t() | nil) :: binary() | nil
def format_usd_value(nil), do: nil
def format_usd_value(%USD{value: nil}), do: nil
def format_usd_value(%USD{value: value}) do
case Number.to_string(value, format: "#,##0.##################") do
{:ok, formatted} -> "$#{formatted} " <> gettext("USD")
_ -> nil
end
end
end

@ -1,8 +1,4 @@
defmodule ExplorerWeb.TransactionLogView do
use ExplorerWeb, :view
@dialyzer :no_match
alias ExplorerWeb.TransactionView
defdelegate format_usd(txn, token), to: TransactionView
end

@ -6,6 +6,7 @@ defmodule ExplorerWeb.TransactionView do
alias Explorer.Chain.{Block, InternalTransaction, Transaction, Wei}
alias Explorer.ExchangeRates.Token
alias ExplorerWeb.BlockView
alias ExplorerWeb.ExchangeRates.USD
def confirmations(%Transaction{block: block}, named_arguments) when is_list(named_arguments) do
case block do
@ -26,9 +27,9 @@ defmodule ExplorerWeb.TransactionView do
|> Chain.fee(:wei)
|> fee_to_currency(opts)
|> case do
{_, nil} -> nil
{:actual, value} -> value
{:maximum, value} -> "<= " <> value
nil -> nil
end
end
@ -36,17 +37,14 @@ defmodule ExplorerWeb.TransactionView do
{fee_type, format_wei_value(Wei.from(fee, :wei), denomination, fractional_digits: 18)}
end
defp fee_to_currency(_, exchange_rate: %Token{usd_value: nil}), do: nil
defp fee_to_currency({fee_type, fee}, exchange_rate: %Token{usd_value: usd_value}) do
usd =
defp fee_to_currency({fee_type, fee}, exchange_rate: %Token{} = exchange_rate) do
formatted =
fee
|> Wei.from(:wei)
|> Wei.to(:ether)
|> Decimal.mult(usd_value)
|> USD.from(exchange_rate)
|> format_usd_value()
currency = gettext("USD")
{fee_type, "$#{usd} #{currency}"}
{fee_type, formatted}
end
def first_seen(%Transaction{inserted_at: inserted_at}) do
@ -57,36 +55,10 @@ defmodule ExplorerWeb.TransactionView do
Number.to_string!(gas)
end
def format_usd(_, %Token{usd_value: nil}), do: nil
def format_usd(nil, _), do: nil
def format_usd(value, %Token{usd_value: usd_value}) do
with {:ok, wei} <- Wei.cast(value),
ether <- Wei.to(wei, :ether),
usd <- Decimal.mult(ether, usd_value) do
currency = gettext("USD")
"$#{usd} #{currency}"
else
_ -> "HMMMM"
end
end
def format_usd_transaction_fee(nil, _token), do: nil
def format_usd_transaction_fee(%Transaction{} = transaction, token) do
transaction
|> Chain.fee(:wei)
|> case do
{:actual, actual} -> actual
{:maximum, maximum} -> maximum
end
|> format_usd(token)
end
def format_usd_value(%Transaction{value: nil}, _token), do: nil
def formatted_usd_value(%Transaction{value: nil}, _token), do: nil
def format_usd_value(%Transaction{value: value}, token) do
format_usd(value, token)
def formatted_usd_value(%Transaction{value: value}, token) do
format_usd_value(USD.from(value, token))
end
def formatted_age(%Transaction{block: block}) do

@ -6,7 +6,14 @@ defmodule ExplorerWeb.TransactionInternalTransactionControllerTest do
alias Explorer.ExchangeRates.Token
describe "GET index/3" do
test "without transaction", %{conn: conn} do
test "with missing transaction", %{conn: conn} do
hash = transaction_hash()
conn = get(conn, transaction_internal_transaction_path(ExplorerWeb.Endpoint, :index, :en, hash))
assert html_response(conn, 404)
end
test "with invalid transaction hash", %{conn: conn} do
conn = get(conn, transaction_internal_transaction_path(ExplorerWeb.Endpoint, :index, :en, "nope"))
assert html_response(conn, 404)

@ -0,0 +1,42 @@
defmodule ExplorerWeb.ExchangeRates.USDTest do
use ExUnit.Case, async: true
alias ExplorerWeb.ExchangeRates.USD
alias Explorer.ExchangeRates.Token
alias Explorer.Chain.Wei
describe "from/2" do
test "with nil wei returns null object" do
token = %Token{usd_value: Decimal.new(0.5)}
assert USD.null() == USD.from(nil, token)
end
test "with nil token returns nil" do
wei = %Wei{value: Decimal.new(10_000_000_000_000)}
assert USD.null() == USD.from(wei, nil)
end
test "without a wei value returns nil" do
wei = %Wei{value: nil}
token = %Token{usd_value: Decimal.new(0.5)}
assert USD.null() == USD.from(wei, token)
end
test "without an exchange rate returns nil" do
wei = %Wei{value: Decimal.new(10_000_000_000_000)}
token = %Token{usd_value: nil}
assert USD.null() == USD.from(wei, token)
end
test "returns formatted usd value" do
wei = %Wei{value: Decimal.new(10_000_000_000_000)}
token = %Token{usd_value: Decimal.new(0.5)}
assert %USD{value: Decimal.new(0.000005)} == USD.from(wei, token)
end
end
end

@ -0,0 +1,26 @@
defmodule ExplorerWeb.AddressViewTest do
use ExplorerWeb.ConnCase, async: true
alias ExplorerWeb.AddressView
alias Explorer.ExchangeRates.Token
describe "formatted_usd/2" do
test "without a fetched_balance returns nil" do
address = build(:address, fetched_balance: nil)
token = %Token{usd_value: Decimal.new(0.5)}
assert nil == AddressView.formatted_usd(address, token)
end
test "without a usd_value returns nil" do
address = build(:address)
token = %Token{usd_value: nil}
assert nil == AddressView.formatted_usd(address, token)
end
test "returns formatted usd value" do
address = build(:address, fetched_balance: 10_000_000_000_000)
token = %Token{usd_value: Decimal.new(0.5)}
assert "$0.000005 USD" == AddressView.formatted_usd(address, token)
end
end
end

@ -0,0 +1,16 @@
defmodule ExplorerWeb.CurrencyHelpersTest do
use ExUnit.Case
alias ExplorerWeb.CurrencyHelpers
alias ExplorerWeb.ExchangeRates.USD
doctest ExplorerWeb.CurrencyHelpers, import: true
test "with nil it returns nil" do
assert nil == CurrencyHelpers.format_usd_value(nil)
end
test "with USD.null() it returns nil" do
assert nil == CurrencyHelpers.format_usd_value(USD.null())
end
end

@ -33,7 +33,7 @@ defmodule ExplorerWeb.TransactionViewTest do
expected_value = "0.003,102,702,000,000,000 POA"
assert expected_value == TransactionView.formatted_fee(transaction, denomination: :ether)
assert "$0.0015513510 USD" == TransactionView.formatted_fee(transaction, exchange_rate: token)
assert "$0.001551351 USD" == TransactionView.formatted_fee(transaction, exchange_rate: token)
end
test "with fee but no available exchange_rate" do

Loading…
Cancel
Save