Add Wei Ecto type (#145)

* Add Wei Ecto type

* Add view helper for manipulating Wei values

* Update templates to use Wei helpers
pull/175/head
Alex Garibay 7 years ago committed by GitHub
parent fcf0572b47
commit 025bec4226
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 16
      apps/explorer/lib/explorer/chain.ex
  2. 6
      apps/explorer/lib/explorer/chain/address.ex
  3. 5
      apps/explorer/lib/explorer/chain/credit.ex
  4. 4
      apps/explorer/lib/explorer/chain/debit.ex
  5. 2
      apps/explorer/lib/explorer/chain/internal_transaction.ex
  6. 6
      apps/explorer/lib/explorer/chain/transaction.ex
  7. 129
      apps/explorer/lib/explorer/chain/wei.ex
  8. 4
      apps/explorer/test/explorer/chain/credit_test.exs
  9. 4
      apps/explorer/test/explorer/chain/debit_test.exs
  10. 52
      apps/explorer/test/explorer/chain/wei_test.exs
  11. 58
      apps/explorer/test/explorer/chain_test.exs
  12. 4
      apps/explorer/test/explorer/importers/balance_importer_test.exs
  13. 6
      apps/explorer/test/explorer/importers/transaction_importer_test.exs
  14. 6
      apps/explorer/test/explorer/workers/import_balance_test.exs
  15. 10
      apps/explorer/test/explorer/workers/refresh_balance_test.exs
  16. 2
      apps/explorer_web/lib/explorer_web.ex
  17. 2
      apps/explorer_web/lib/explorer_web/templates/address/overview.html.eex
  18. 4
      apps/explorer_web/lib/explorer_web/templates/address_transaction/index.html.eex
  19. 8
      apps/explorer_web/lib/explorer_web/templates/block/index.html.eex
  20. 2
      apps/explorer_web/lib/explorer_web/templates/block_transaction/index.html.eex
  21. 2
      apps/explorer_web/lib/explorer_web/templates/chain/show.html.eex
  22. 2
      apps/explorer_web/lib/explorer_web/templates/pending_transaction/index.html.eex
  23. 2
      apps/explorer_web/lib/explorer_web/templates/transaction/index.html.eex
  24. 6
      apps/explorer_web/lib/explorer_web/templates/transaction/overview.html.eex
  25. 2
      apps/explorer_web/lib/explorer_web/templates/transaction/show.html.eex
  26. 5
      apps/explorer_web/lib/explorer_web/views/address_transaction_view.ex
  27. 16
      apps/explorer_web/lib/explorer_web/views/address_view.ex
  28. 1
      apps/explorer_web/lib/explorer_web/views/block_transaction_view.ex
  29. 17
      apps/explorer_web/lib/explorer_web/views/block_view.ex
  30. 4
      apps/explorer_web/lib/explorer_web/views/chain_view.ex
  31. 2
      apps/explorer_web/lib/explorer_web/views/pending_transaction_view.ex
  32. 42
      apps/explorer_web/lib/explorer_web/views/transaction_view.ex
  33. 93
      apps/explorer_web/lib/explorer_web/views/wei_helpers.ex
  34. 32
      apps/explorer_web/priv/gettext/default.pot
  35. 32
      apps/explorer_web/priv/gettext/en/LC_MESSAGES/default.po
  36. 60
      apps/explorer_web/test/explorer_web/views/address_transaction_view_test.exs
  37. 8
      apps/explorer_web/test/explorer_web/views/wei_helpers_test.exs

@ -108,7 +108,7 @@ defmodule Explorer.Chain do
@doc """ @doc """
The `t:Explorer.Chain.Address.t/0` `balance` in `unit`. The `t:Explorer.Chain.Address.t/0` `balance` in `unit`.
""" """
@spec balance(Address.t(), :wei) :: Wei.t() | nil @spec balance(Address.t(), :wei) :: Wei.wei() | nil
@spec balance(Address.t(), :gwei) :: Wei.gwei() | nil @spec balance(Address.t(), :gwei) :: Wei.gwei() | nil
@spec balance(Address.t(), :ether) :: Wei.ether() | nil @spec balance(Address.t(), :ether) :: Wei.ether() | nil
def balance(%Address{balance: balance}, unit) do def balance(%Address{balance: balance}, unit) do
@ -253,9 +253,9 @@ defmodule Explorer.Chain do
@spec fee(%Transaction{receipt: nil}, :ether | :gwei | :wei) :: {:maximum, Decimal.t()} @spec fee(%Transaction{receipt: nil}, :ether | :gwei | :wei) :: {:maximum, Decimal.t()}
def fee(%Transaction{gas: gas, gas_price: gas_price, receipt: nil}, unit) do def fee(%Transaction{gas: gas, gas_price: gas_price, receipt: nil}, unit) do
fee = fee =
gas gas_price
|> Decimal.mult(gas_price)
|> Wei.to(unit) |> Wei.to(unit)
|> Decimal.mult(gas)
{:maximum, fee} {:maximum, fee}
end end
@ -263,9 +263,9 @@ defmodule Explorer.Chain do
@spec fee(%Transaction{receipt: Receipt.t()}, :ether | :gwei | :wei) :: {:actual, Decimal.t()} @spec fee(%Transaction{receipt: Receipt.t()}, :ether | :gwei | :wei) :: {:actual, Decimal.t()}
def fee(%Transaction{gas_price: gas_price, receipt: %Receipt{gas_used: gas_used}}, unit) do def fee(%Transaction{gas_price: gas_price, receipt: %Receipt{gas_used: gas_used}}, unit) do
fee = fee =
gas_used gas_price
|> Decimal.mult(gas_price)
|> Wei.to(unit) |> Wei.to(unit)
|> Decimal.mult(gas_used)
{:actual, fee} {:actual, fee}
end end
@ -273,7 +273,7 @@ defmodule Explorer.Chain do
@doc """ @doc """
The `t:Explorer.Chain.Transaction.t/0` `gas_price` of the `transaction` in `unit`. The `t:Explorer.Chain.Transaction.t/0` `gas_price` of the `transaction` in `unit`.
""" """
@spec gas_price(Transaction.t(), :wei) :: Wei.t() @spec gas_price(Transaction.t(), :wei) :: Wei.wei()
@spec gas_price(Transaction.t(), :gwei) :: Wei.gwei() @spec gas_price(Transaction.t(), :gwei) :: Wei.gwei()
@spec gas_price(Transaction.t(), :ether) :: Wei.ether() @spec gas_price(Transaction.t(), :ether) :: Wei.ether()
def gas_price(%Transaction{gas_price: gas_price}, unit) do def gas_price(%Transaction{gas_price: gas_price}, unit) do
@ -570,10 +570,10 @@ defmodule Explorer.Chain do
The `t:Explorer.Chain.Transaction.t/0` or `t:Explorer.Chain.InternalTransaction.t/0` `value` of the `transaction` in The `t:Explorer.Chain.Transaction.t/0` or `t:Explorer.Chain.InternalTransaction.t/0` `value` of the `transaction` in
`unit`. `unit`.
""" """
@spec value(InternalTransaction.t(), :wei) :: Wei.t() @spec value(InternalTransaction.t(), :wei) :: Wei.wei()
@spec value(InternalTransaction.t(), :gwei) :: Wei.gwei() @spec value(InternalTransaction.t(), :gwei) :: Wei.gwei()
@spec value(InternalTransaction.t(), :ether) :: Wei.ether() @spec value(InternalTransaction.t(), :ether) :: Wei.ether()
@spec value(Transaction.t(), :wei) :: Wei.t() @spec value(Transaction.t(), :wei) :: Wei.wei()
@spec value(Transaction.t(), :gwei) :: Wei.gwei() @spec value(Transaction.t(), :gwei) :: Wei.gwei()
@spec value(Transaction.t(), :ether) :: Wei.ether() @spec value(Transaction.t(), :ether) :: Wei.ether()
def value(%type{value: value}, unit) when type in [InternalTransaction, Transaction] do def value(%type{value: value}, unit) when type in [InternalTransaction, Transaction] do

@ -5,7 +5,7 @@ defmodule Explorer.Chain.Address do
use Explorer.Schema use Explorer.Schema
alias Explorer.Chain.{Credit, Debit} alias Explorer.Chain.{Credit, Debit, Hash, Wei}
@typedoc """ @typedoc """
Hash of the public key for this address. Hash of the public key for this address.
@ -21,7 +21,7 @@ defmodule Explorer.Chain.Address do
* `updated_at` when this address was last updated * `updated_at` when this address was last updated
""" """
@type t :: %__MODULE__{ @type t :: %__MODULE__{
balance: Decimal.t(), balance: Wei.t(),
balance_updated_at: DateTime.t(), balance_updated_at: DateTime.t(),
credit: Ecto.Association.NotLoaded.t() | Credit.t() | nil, credit: Ecto.Association.NotLoaded.t() | Credit.t() | nil,
debit: Ecto.Association.NotLoaded.t() | Debit.t() | nil, debit: Ecto.Association.NotLoaded.t() | Debit.t() | nil,
@ -31,7 +31,7 @@ defmodule Explorer.Chain.Address do
} }
schema "addresses" do schema "addresses" do
field(:balance, :decimal) field(:balance, Wei)
field(:balance_updated_at, Timex.Ecto.DateTime) field(:balance_updated_at, Timex.Ecto.DateTime)
field(:hash, :string) field(:hash, :string)

@ -6,13 +6,14 @@ defmodule Explorer.Chain.Credit do
use Explorer.Schema use Explorer.Schema
alias Ecto.Adapters.SQL alias Ecto.Adapters.SQL
alias Explorer.Chain.Address alias Explorer.Chain.{Address, Wei}
alias Explorer.Repo alias Explorer.Repo
@primary_key false @primary_key false
schema "credits" do schema "credits" do
field(:count, :integer) field(:count, :integer)
field(:value, :decimal) field(:value, Wei)
timestamps() timestamps()

@ -6,13 +6,13 @@ defmodule Explorer.Chain.Debit do
use Explorer.Schema use Explorer.Schema
alias Ecto.Adapters.SQL alias Ecto.Adapters.SQL
alias Explorer.Chain.Address alias Explorer.Chain.{Address, Wei}
alias Explorer.Repo alias Explorer.Repo
@primary_key false @primary_key false
schema "debits" do schema "debits" do
field(:count, :integer) field(:count, :integer)
field(:value, :decimal) field(:value, Wei)
timestamps() timestamps()

@ -55,7 +55,7 @@ defmodule Explorer.Chain.InternalTransaction do
field(:input, :string) field(:input, :string)
field(:output, :string) field(:output, :string)
field(:trace_address, {:array, :integer}) field(:trace_address, {:array, :integer})
field(:value, :decimal) field(:value, Wei)
timestamps() timestamps()

@ -73,7 +73,7 @@ defmodule Explorer.Chain.Transaction do
@typedoc """ @typedoc """
How much the sender is willing to pay in wei per unit of gas. How much the sender is willing to pay in wei per unit of gas.
""" """
@type wei_per_gas :: non_neg_integer() @type wei_per_gas :: Wei.t()
@typedoc """ @typedoc """
* `block_transaction` - joins this transaction to its `block` * `block_transaction` - joins this transaction to its `block`
@ -125,7 +125,7 @@ defmodule Explorer.Chain.Transaction do
schema "transactions" do schema "transactions" do
field(:gas, :decimal) field(:gas, :decimal)
field(:gas_price, :decimal) field(:gas_price, Wei)
field(:hash, :string) field(:hash, :string)
field(:input, :string) field(:input, :string)
field(:nonce, :integer) field(:nonce, :integer)
@ -135,7 +135,7 @@ defmodule Explorer.Chain.Transaction do
field(:standard_v, :string) field(:standard_v, :string)
field(:transaction_index, :string) field(:transaction_index, :string)
field(:v, :string) field(:v, :string)
field(:value, :decimal) field(:value, Wei)
timestamps() timestamps()

@ -3,11 +3,82 @@ defmodule Explorer.Chain.Wei do
The smallest fractional unit of Ether. Using wei instead of ether allows code to do integer match instead of using The smallest fractional unit of Ether. Using wei instead of ether allows code to do integer match instead of using
floats. floats.
All values represented by the `Wei` struct are assumed to measured in the base unit of wei.
See [Etherum Homestead Documentation](http://ethdocs.org/en/latest/ether.html) for examples of various denominations of wei.
Etymology of "wei" comes from [Wei Dai ()](https://en.wikipedia.org/wiki/Wei_Dai), a Etymology of "wei" comes from [Wei Dai ()](https://en.wikipedia.org/wiki/Wei_Dai), a
[cypherpunk](https://en.wikipedia.org/wiki/Cypherpunk) who came up with b-money, which outlined modern [cypherpunk](https://en.wikipedia.org/wiki/Cypherpunk) who came up with b-money, which outlined modern
cryptocurrencies. cryptocurrencies.
## Interfacing With Ecto
You can define a field in a schema to be of type Wei for convinience when dealing with Wei values.
schema "my_schema" do
field :gas, Explorer.Chain.Wei
end
""" """
defstruct ~w(value)a
@behaviour Ecto.Type
@impl Ecto.Type
def type, do: :decimal
@impl Ecto.Type
def cast("0x" <> hex_wei) do
with {int_wei, ""} <- Integer.parse(hex_wei, 16) do
decimal = Decimal.new(int_wei)
{:ok, %__MODULE__{value: decimal}}
else
_ -> :error
end
end
@impl Ecto.Type
def cast(string_wei) when is_binary(string_wei) do
with {int_wei, ""} <- Integer.parse(string_wei) do
decimal = Decimal.new(int_wei)
{:ok, %__MODULE__{value: decimal}}
else
_ -> :error
end
end
@impl Ecto.Type
def cast(int_wei) when is_integer(int_wei) do
decimal = Decimal.new(int_wei)
{:ok, %__MODULE__{value: decimal}}
end
@impl Ecto.Type
def cast(%Decimal{} = decimal) do
{:ok, %__MODULE__{value: decimal}}
end
@impl Ecto.Type
def cast(%__MODULE__{} = wei) do
{:ok, wei}
end
@impl Ecto.Type
def cast(_), do: :error
@impl Ecto.Type
def dump(%__MODULE__{value: %Decimal{} = decimal}) do
{:ok, decimal}
end
@impl Ecto.Type
def dump(_), do: :error
@impl Ecto.Type
def load(%Decimal{} = decimal) do
{:ok, %__MODULE__{value: decimal}}
end
@typedoc """ @typedoc """
Ether is the default unit Ethereum and its side chains are measured in when displaying values to humans. Ether is the default unit Ethereum and its side chains are measured in when displaying values to humans.
@ -27,8 +98,15 @@ defmodule Explorer.Chain.Wei do
""" """
@type unit :: :wei | :gwei | :ether @type unit :: :wei | :gwei | :ether
@typedoc """
The smallest fractional unit of Ether.
"""
@type wei :: Decimal.t()
@typedoc @moduledoc @typedoc @moduledoc
@type t :: Decimal.t() @type t :: %__MODULE__{
value: Decimal.t()
}
# Constants # Constants
@ -38,68 +116,85 @@ defmodule Explorer.Chain.Wei do
## Functions ## Functions
@doc """ @doc """
Converts `Decimal` representations of various wei denominations (wei, Gwei, ether) to
a wei base unit.
## Examples
Convert wei to itself. Convert wei to itself.
iex> Explorer.Chain.Wei.from(Decimal.new(1), :wei) iex> Explorer.Chain.Wei.from(Decimal.new(1), :wei)
Decimal.new(1) %Explorer.Chain.Wei{value: Decimal.new(1)}
Convert `t:gwei/0` to wei. Convert `t:gwei/0` to wei.
iex> Explorer.Chain.Wei.from(Decimal.new(1), :gwei) iex> Explorer.Chain.Wei.from(Decimal.new(1), :gwei)
Decimal.new(1_000_000_000) %Explorer.Chain.Wei{value: Decimal.new(1_000_000_000)}
Convert `t:ether/0` to wei. Convert `t:ether/0` to wei.
iex> Explorer.Chain.Wei.from(Decimal.new(1), :ether) iex> Explorer.Chain.Wei.from(Decimal.new(1), :ether)
Decimal.new(1_000_000_000_000_000_000) %Explorer.Chain.Wei{value: Decimal.new(1_000_000_000_000_000_000)}
""" """
@spec from(ether(), :ether) :: t() @spec from(ether(), :ether) :: t()
def from(ether, :ether) do def from(%Decimal{} = ether, :ether) do
Decimal.mult(ether, @wei_per_ether) %__MODULE__{value: Decimal.mult(ether, @wei_per_ether)}
end end
@spec from(gwei(), :gwei) :: t() @spec from(gwei(), :gwei) :: t()
def from(gwei, :gwei) do def from(%Decimal{} = gwei, :gwei) do
Decimal.mult(gwei, @wei_per_gwei) %__MODULE__{value: Decimal.mult(gwei, @wei_per_gwei)}
end end
@spec from(t(), :wei) :: t() @spec from(t(), :wei) :: t()
def from(wei, :wei), do: wei def from(%Decimal{} = wei, :wei) do
%__MODULE__{value: wei}
end
@doc """ @doc """
Converts a `Wei` value to another denomination of wei represented in `Decimal`.
## Examples
Convert wei to itself. Convert wei to itself.
iex> Explorer.Chain.Wei.to(Decimal.new(1), :wei) iex> Explorer.Chain.Wei.to(%Explorer.Chain.Wei{value: Decimal.new(1)}, :wei)
Decimal.new(1) Decimal.new(1)
Convert wei to `t:gwei/0`. Convert wei to `t:gwei/0`.
iex> Explorer.Chain.Wei.to(Decimal.new(1), :gwei) iex> Explorer.Chain.Wei.to(%Explorer.Chain.Wei{value: Decimal.new(1)}, :gwei)
Decimal.new("1e-9") Decimal.new("1e-9")
iex> Explorer.Chain.Wei.to(Decimal.new("1e9"), :gwei) iex> Explorer.Chain.Wei.to(%Explorer.Chain.Wei{value: Decimal.new("1e9")}, :gwei)
Decimal.new(1) Decimal.new(1)
Convert wei to `t:ether/0`. Convert wei to `t:ether/0`.
iex> Explorer.Chain.Wei.to(Decimal.new(1), :ether) iex> Explorer.Chain.Wei.to(%Explorer.Chain.Wei{value: Decimal.new(1)}, :ether)
Decimal.new("1e-18") Decimal.new("1e-18")
iex> Explorer.Chain.Wei.to(Decimal.new("1e18"), :ether) iex> Explorer.Chain.Wei.to(%Explorer.Chain.Wei{value: Decimal.new("1e18")}, :ether)
Decimal.new(1) Decimal.new(1)
""" """
@spec to(t(), :ether) :: ether() @spec to(t(), :ether) :: ether()
def to(wei, :ether) do def to(%__MODULE__{value: wei}, :ether) do
Decimal.div(wei, @wei_per_ether) Decimal.div(wei, @wei_per_ether)
end end
@spec to(t(), :gwei) :: gwei() @spec to(t(), :gwei) :: gwei()
def to(wei, :gwei) do def to(%__MODULE__{value: wei}, :gwei) do
Decimal.div(wei, @wei_per_gwei) Decimal.div(wei, @wei_per_gwei)
end end
@spec to(t(), :wei) :: t() @spec to(t(), :wei) :: t()
def to(wei, :wei), do: wei def to(%__MODULE__{value: wei}, :wei), do: wei
end
defimpl Inspect, for: Explorer.Chain.Wei do
def inspect(wei, _) do
"#Explorer.Chain.Wei<#{Decimal.to_string(wei.value)}>"
end
end end

@ -1,7 +1,7 @@
defmodule Explorer.Chain.CreditTest do defmodule Explorer.Chain.CreditTest do
use Explorer.DataCase use Explorer.DataCase
alias Explorer.Chain.Credit alias Explorer.Chain.{Credit, Wei}
describe "Repo.all/1" do describe "Repo.all/1" do
test "returns no rows when there are no addresses" do test "returns no rows when there are no addresses" do
@ -47,7 +47,7 @@ defmodule Explorer.Chain.CreditTest do
address_id = recipient.id address_id = recipient.id
Credit.refresh() Credit.refresh()
credit = Credit |> where(address_id: ^address_id) |> Repo.one() credit = Credit |> where(address_id: ^address_id) |> Repo.one()
assert credit.value == Decimal.new(21) assert credit.value == %Wei{value: Decimal.new(21)}
end end
end end
end end

@ -1,7 +1,7 @@
defmodule Explorer.Chain.DebitTest do defmodule Explorer.Chain.DebitTest do
use Explorer.DataCase use Explorer.DataCase
alias Explorer.Chain.Debit alias Explorer.Chain.{Debit, Wei}
describe "Repo.all/1" do describe "Repo.all/1" do
test "returns no rows when there are no addresses" do test "returns no rows when there are no addresses" do
@ -34,7 +34,7 @@ defmodule Explorer.Chain.DebitTest do
address_id = sender.id address_id = sender.id
Debit.refresh() Debit.refresh()
debit = Debit |> where(address_id: ^address_id) |> Repo.one() debit = Debit |> where(address_id: ^address_id) |> Repo.one()
assert debit.value == Decimal.new(21) assert debit.value == %Wei{value: Decimal.new(21)}
end end
test "returns no debits against the recipient" do test "returns no debits against the recipient" do

@ -1,5 +1,57 @@
defmodule Explorer.Chain.WeiTest do defmodule Explorer.Chain.WeiTest do
use ExUnit.Case, async: true use ExUnit.Case, async: true
alias Explorer.Chain.Wei
doctest Explorer.Chain.Wei doctest Explorer.Chain.Wei
describe "cast/1" do
test "with hex string" do
assert Wei.cast("0x142") == {:ok, %Wei{value: Decimal.new(322)}}
assert Wei.cast("0xzzz") == :error
end
test "with integer string" do
assert Wei.cast("123") == {:ok, %Wei{value: Decimal.new(123)}}
assert Wei.cast("123.5") == :error
assert Wei.cast("invalid") == :error
end
test "with integer" do
assert Wei.cast(123) == {:ok, %Wei{value: Decimal.new(123)}}
end
test "with decimal" do
decimal = Decimal.new(123)
assert Wei.cast(decimal) == {:ok, %Wei{value: decimal}}
end
test "with Wei struct" do
wei = %Wei{value: Decimal.new(123)}
assert Wei.cast(wei) == {:ok, wei}
end
test "with unsupported type" do
assert Wei.cast(nil) == :error
end
end
describe "dump/1" do
test "with Wei struct" do
decimal = Decimal.new(123)
assert Wei.dump(%Wei{value: decimal}) == {:ok, decimal}
end
test "with invalid value" do
assert Wei.dump(123) == :error
end
end
test "load/1" do
decimal = Decimal.new(123)
assert Wei.load(decimal) == {:ok, %Wei{value: decimal}}
end
test "type/0" do
assert Wei.type() == :decimal
end
end end

@ -3,7 +3,7 @@ defmodule Explorer.ChainTest do
alias Explorer.{Chain, Repo} alias Explorer.{Chain, Repo}
alias Explorer.Chain.{Address, Block, InternalTransaction, Log, Receipt, Transaction} alias Explorer.Chain.{Address, Block, InternalTransaction, Log, Receipt, Transaction, Wei}
# Constants # Constants
@ -155,19 +155,19 @@ defmodule Explorer.ChainTest do
describe "balance/2" do describe "balance/2" do
test "with Address.t with :wei" do test "with Address.t with :wei" do
assert Chain.balance(%Address{balance: Decimal.new(1)}, :wei) == Decimal.new(1) assert Chain.balance(%Address{balance: %Wei{value: Decimal.new(1)}}, :wei) == Decimal.new(1)
assert Chain.balance(%Address{balance: nil}, :wei) == nil assert Chain.balance(%Address{balance: nil}, :wei) == nil
end end
test "with Address.t with :gwei" do test "with Address.t with :gwei" do
assert Chain.balance(%Address{balance: Decimal.new(1)}, :gwei) == Decimal.new("1e-9") assert Chain.balance(%Address{balance: %Wei{value: Decimal.new(1)}}, :gwei) == Decimal.new("1e-9")
assert Chain.balance(%Address{balance: Decimal.new("1e9")}, :gwei) == Decimal.new(1) assert Chain.balance(%Address{balance: %Wei{value: Decimal.new("1e9")}}, :gwei) == Decimal.new(1)
assert Chain.balance(%Address{balance: nil}, :gwei) == nil assert Chain.balance(%Address{balance: nil}, :gwei) == nil
end end
test "with Address.t with :ether" do test "with Address.t with :ether" do
assert Chain.balance(%Address{balance: Decimal.new(1)}, :ether) == Decimal.new("1e-18") assert Chain.balance(%Address{balance: %Wei{value: Decimal.new(1)}}, :ether) == Decimal.new("1e-18")
assert Chain.balance(%Address{balance: Decimal.new("1e18")}, :ether) == Decimal.new(1) assert Chain.balance(%Address{balance: %Wei{value: Decimal.new("1e18")}}, :ether) == Decimal.new(1)
assert Chain.balance(%Address{balance: nil}, :ether) == nil assert Chain.balance(%Address{balance: nil}, :ether) == nil
end end
end end
@ -342,17 +342,17 @@ defmodule Explorer.ChainTest do
describe "fee/2" do describe "fee/2" do
test "without receipt with :wei unit" do test "without receipt with :wei unit" do
assert Chain.fee(%Transaction{gas: Decimal.new(3), gas_price: Decimal.new(2), receipt: nil}, :wei) == assert Chain.fee(%Transaction{gas: Decimal.new(3), gas_price: %Wei{value: Decimal.new(2)}, receipt: nil}, :wei) ==
{:maximum, Decimal.new(6)} {:maximum, Decimal.new(6)}
end end
test "without receipt with :gwei unit" do test "without receipt with :gwei unit" do
assert Chain.fee(%Transaction{gas: Decimal.new(3), gas_price: Decimal.new(2), receipt: nil}, :gwei) == assert Chain.fee(%Transaction{gas: Decimal.new(3), gas_price: %Wei{value: Decimal.new(2)}, receipt: nil}, :gwei) ==
{:maximum, Decimal.new("6e-9")} {:maximum, Decimal.new("6e-9")}
end end
test "without receipt with :ether unit" do test "without receipt with :ether unit" do
assert Chain.fee(%Transaction{gas: Decimal.new(3), gas_price: Decimal.new(2), receipt: nil}, :ether) == assert Chain.fee(%Transaction{gas: Decimal.new(3), gas_price: %Wei{value: Decimal.new(2)}, receipt: nil}, :ether) ==
{:maximum, Decimal.new("6e-18")} {:maximum, Decimal.new("6e-18")}
end end
@ -360,7 +360,7 @@ defmodule Explorer.ChainTest do
assert Chain.fee( assert Chain.fee(
%Transaction{ %Transaction{
gas: Decimal.new(3), gas: Decimal.new(3),
gas_price: Decimal.new(2), gas_price: %Wei{value: Decimal.new(2)},
receipt: %Receipt{gas_used: Decimal.new(2)} receipt: %Receipt{gas_used: Decimal.new(2)}
}, },
:wei :wei
@ -371,7 +371,7 @@ defmodule Explorer.ChainTest do
assert Chain.fee( assert Chain.fee(
%Transaction{ %Transaction{
gas: Decimal.new(3), gas: Decimal.new(3),
gas_price: Decimal.new(2), gas_price: %Wei{value: Decimal.new(2)},
receipt: %Receipt{gas_used: Decimal.new(2)} receipt: %Receipt{gas_used: Decimal.new(2)}
}, },
:gwei :gwei
@ -382,7 +382,7 @@ defmodule Explorer.ChainTest do
assert Chain.fee( assert Chain.fee(
%Transaction{ %Transaction{
gas: Decimal.new(3), gas: Decimal.new(3),
gas_price: Decimal.new(2), gas_price: %Wei{value: Decimal.new(2)},
receipt: %Receipt{gas_used: Decimal.new(2)} receipt: %Receipt{gas_used: Decimal.new(2)}
}, },
:ether :ether
@ -392,19 +392,19 @@ defmodule Explorer.ChainTest do
describe "gas_price/2" do describe "gas_price/2" do
test ":wei unit" do test ":wei unit" do
assert Chain.gas_price(%Transaction{gas_price: Decimal.new(1)}, :wei) == Decimal.new(1) assert Chain.gas_price(%Transaction{gas_price: %Wei{value: Decimal.new(1)}}, :wei) == Decimal.new(1)
end end
test ":gwei unit" do test ":gwei unit" do
assert Chain.gas_price(%Transaction{gas_price: Decimal.new(1)}, :gwei) == Decimal.new("1e-9") assert Chain.gas_price(%Transaction{gas_price: %Wei{value: Decimal.new(1)}}, :gwei) == Decimal.new("1e-9")
assert Chain.gas_price(%Transaction{gas_price: Decimal.new("1e9")}, :gwei) == Decimal.new(1) assert Chain.gas_price(%Transaction{gas_price: %Wei{value: Decimal.new("1e9")}}, :gwei) == Decimal.new(1)
end end
test ":ether unit" do test ":ether unit" do
assert Chain.gas_price(%Transaction{gas_price: Decimal.new(1)}, :ether) == Decimal.new("1e-18") assert Chain.gas_price(%Transaction{gas_price: %Wei{value: Decimal.new(1)}}, :ether) == Decimal.new("1e-18")
assert Chain.gas_price(%Transaction{gas_price: Decimal.new("1e18")}, :ether) == Decimal.new(1) assert Chain.gas_price(%Transaction{gas_price: %Wei{value: Decimal.new("1e18")}}, :ether) == Decimal.new(1)
end end
end end
@ -911,7 +911,7 @@ defmodule Explorer.ChainTest do
Chain.update_balance(hash, 5) Chain.update_balance(hash, 5)
expected_balance = Decimal.new(5) expected_balance = %Wei{value: Decimal.new(5)}
assert {:ok, %Address{balance: ^expected_balance}} = Chain.hash_to_address(hash) assert {:ok, %Address{balance: ^expected_balance}} = Chain.hash_to_address(hash)
end end
@ -930,7 +930,7 @@ defmodule Explorer.ChainTest do
test "creates an address if one does not exist" do test "creates an address if one does not exist" do
Chain.update_balance("0xtwizzlers", 88) Chain.update_balance("0xtwizzlers", 88)
expected_balance = Decimal.new(88) expected_balance = %Wei{value: Decimal.new(88)}
assert {:ok, %Address{balance: ^expected_balance}} = Chain.hash_to_address("0xtwizzlers") assert {:ok, %Address{balance: ^expected_balance}} = Chain.hash_to_address("0xtwizzlers")
end end
@ -938,33 +938,33 @@ defmodule Explorer.ChainTest do
describe "value/2" do describe "value/2" do
test "with InternalTransaction.t with :wei" do test "with InternalTransaction.t with :wei" do
assert Chain.value(%InternalTransaction{value: Decimal.new(1)}, :wei) == Decimal.new(1) assert Chain.value(%InternalTransaction{value: %Wei{value: Decimal.new(1)}}, :wei) == Decimal.new(1)
end end
test "with InternalTransaction.t with :gwei" do test "with InternalTransaction.t with :gwei" do
assert Chain.value(%InternalTransaction{value: Decimal.new(1)}, :gwei) == Decimal.new("1e-9") assert Chain.value(%InternalTransaction{value: %Wei{value: Decimal.new(1)}}, :gwei) == Decimal.new("1e-9")
assert Chain.value(%InternalTransaction{value: Decimal.new("1e9")}, :gwei) == Decimal.new(1) assert Chain.value(%InternalTransaction{value: %Wei{value: Decimal.new("1e9")}}, :gwei) == Decimal.new(1)
end end
test "with InternalTransaction.t with :ether" do test "with InternalTransaction.t with :ether" do
assert Chain.value(%InternalTransaction{value: Decimal.new(1)}, :ether) == Decimal.new("1e-18") assert Chain.value(%InternalTransaction{value: %Wei{value: Decimal.new(1)}}, :ether) == Decimal.new("1e-18")
assert Chain.value(%InternalTransaction{value: Decimal.new("1e18")}, :ether) == Decimal.new(1) assert Chain.value(%InternalTransaction{value: %Wei{value: Decimal.new("1e18")}}, :ether) == Decimal.new(1)
end end
test "with Transaction.t with :wei" do test "with Transaction.t with :wei" do
assert Chain.value(%Transaction{value: Decimal.new(1)}, :wei) == Decimal.new(1) assert Chain.value(%Transaction{value: %Wei{value: Decimal.new(1)}}, :wei) == Decimal.new(1)
end end
test "with Transaction.t with :gwei" do test "with Transaction.t with :gwei" do
assert Chain.value(%Transaction{value: Decimal.new(1)}, :gwei) == Decimal.new("1e-9") assert Chain.value(%Transaction{value: %Wei{value: Decimal.new(1)}}, :gwei) == Decimal.new("1e-9")
assert Chain.value(%Transaction{value: Decimal.new("1e9")}, :gwei) == Decimal.new(1) assert Chain.value(%Transaction{value: %Wei{value: Decimal.new("1e9")}}, :gwei) == Decimal.new(1)
end end
test "with Transaction.t with :ether" do test "with Transaction.t with :ether" do
assert Chain.value(%Transaction{value: Decimal.new(1)}, :ether) == Decimal.new("1e-18") assert Chain.value(%Transaction{value: %Wei{value: Decimal.new(1)}}, :ether) == Decimal.new("1e-18")
assert Chain.value(%Transaction{value: Decimal.new("1e18")}, :ether) == Decimal.new(1) assert Chain.value(%Transaction{value: %Wei{value: Decimal.new("1e18")}}, :ether) == Decimal.new(1)
end end
end end
end end

@ -2,7 +2,7 @@ defmodule Explorer.BalanceImporterTest do
use Explorer.DataCase use Explorer.DataCase
alias Explorer.{Chain, BalanceImporter} alias Explorer.{Chain, BalanceImporter}
alias Explorer.Chain.Address alias Explorer.Chain.{Address, Wei}
describe "import/1" do describe "import/1" do
test "it updates the balance for an address" do test "it updates the balance for an address" do
@ -10,7 +10,7 @@ defmodule Explorer.BalanceImporterTest do
BalanceImporter.import("0x5cc18cc34175d358ff8e19b7f98566263c4106a0") BalanceImporter.import("0x5cc18cc34175d358ff8e19b7f98566263c4106a0")
expected_balance = Decimal.new(1_572_374_181_095_000_000) expected_balance = %Wei{value: Decimal.new(1_572_374_181_095_000_000)}
assert {:ok, %Address{balance: ^expected_balance}} = assert {:ok, %Address{balance: ^expected_balance}} =
Chain.hash_to_address("0x5cc18cc34175d358ff8e19b7f98566263c4106a0") Chain.hash_to_address("0x5cc18cc34175d358ff8e19b7f98566263c4106a0")

@ -1,7 +1,7 @@
defmodule Explorer.TransactionImporterTest do defmodule Explorer.TransactionImporterTest do
use Explorer.DataCase use Explorer.DataCase
alias Explorer.Chain.{Address, BlockTransaction, Transaction} alias Explorer.Chain.{Address, BlockTransaction, Transaction, Wei}
alias Explorer.TransactionImporter alias Explorer.TransactionImporter
@raw_transaction %{ @raw_transaction %{
@ -188,8 +188,8 @@ defmodule Explorer.TransactionImporterTest do
from_address = Address |> Repo.get_by(hash: "0xb2867180771b196518651c174c9240d5e8bd0ecd") from_address = Address |> Repo.get_by(hash: "0xb2867180771b196518651c174c9240d5e8bd0ecd")
to_address = Address |> Repo.get_by(hash: "0x24e5b8528fe83257d5fe3497ef616026713347f8") to_address = Address |> Repo.get_by(hash: "0x24e5b8528fe83257d5fe3497ef616026713347f8")
assert(from_address.balance == Decimal.new(1_572_374_181_095_000_000)) assert(from_address.balance == %Wei{value: Decimal.new(1_572_374_181_095_000_000)})
assert(to_address.balance == Decimal.new(1_572_374_181_095_000_000)) assert(to_address.balance == %Wei{value: Decimal.new(1_572_374_181_095_000_000)})
end end
end end

@ -2,7 +2,7 @@ defmodule Explorer.Workers.ImportBalanceTest do
import Mock import Mock
alias Explorer.Chain alias Explorer.Chain
alias Explorer.Chain.Address alias Explorer.Chain.{Address, Wei}
alias Explorer.Workers.ImportBalance alias Explorer.Workers.ImportBalance
use Explorer.DataCase use Explorer.DataCase
@ -11,7 +11,7 @@ defmodule Explorer.Workers.ImportBalanceTest do
test "imports the balance for an address" do test "imports the balance for an address" do
ImportBalance.perform("0x1d12e5716c593b156eb7152ca4360f6224ba3b0a") ImportBalance.perform("0x1d12e5716c593b156eb7152ca4360f6224ba3b0a")
expected_balance = Decimal.new(1_572_374_181_095_000_000) expected_balance = %Wei{value: Decimal.new(1_572_374_181_095_000_000)}
assert {:ok, %Address{balance: ^expected_balance}} = assert {:ok, %Address{balance: ^expected_balance}} =
Chain.hash_to_address("0x1d12e5716c593b156eb7152ca4360f6224ba3b0a") Chain.hash_to_address("0x1d12e5716c593b156eb7152ca4360f6224ba3b0a")
@ -30,7 +30,7 @@ defmodule Explorer.Workers.ImportBalanceTest do
end do end do
ImportBalance.perform_later("0xskateboards") ImportBalance.perform_later("0xskateboards")
expected_balance = Decimal.new(66) expected_balance = %Wei{value: Decimal.new(66)}
assert {:ok, %Address{balance: ^expected_balance}} = Chain.hash_to_address("0xskateboards") assert {:ok, %Address{balance: ^expected_balance}} = Chain.hash_to_address("0xskateboards")
end end

@ -3,7 +3,7 @@ defmodule Explorer.Workers.RefreshBalanceTest do
import Mock import Mock
alias Explorer.Chain.{Credit, Debit} alias Explorer.Chain.{Credit, Debit, Wei}
alias Explorer.Workers.RefreshBalance alias Explorer.Workers.RefreshBalance
describe "perform/0" do describe "perform/0" do
@ -14,7 +14,7 @@ defmodule Explorer.Workers.RefreshBalanceTest do
insert(:to_address, address: address, transaction: transaction) insert(:to_address, address: address, transaction: transaction)
insert(:receipt, transaction: transaction, status: 1) insert(:receipt, transaction: transaction, status: 1)
RefreshBalance.perform() RefreshBalance.perform()
assert Repo.one(Credit).value == Decimal.new(20) assert Repo.one(Credit).value == %Wei{value: Decimal.new(20)}
end end
end end
@ -25,7 +25,7 @@ defmodule Explorer.Workers.RefreshBalanceTest do
insert(:from_address, address: address, transaction: transaction) insert(:from_address, address: address, transaction: transaction)
insert(:receipt, transaction: transaction, status: 1) insert(:receipt, transaction: transaction, status: 1)
RefreshBalance.perform() RefreshBalance.perform()
assert Repo.one(Debit).value == Decimal.new(20) assert Repo.one(Debit).value == %Wei{value: Decimal.new(20)}
end end
end end
end end
@ -37,7 +37,7 @@ defmodule Explorer.Workers.RefreshBalanceTest do
insert(:to_address, address: address, transaction: transaction) insert(:to_address, address: address, transaction: transaction)
insert(:receipt, transaction: transaction, status: 1) insert(:receipt, transaction: transaction, status: 1)
RefreshBalance.perform("credit") RefreshBalance.perform("credit")
assert Repo.one(Credit).value == Decimal.new(20) assert Repo.one(Credit).value == %Wei{value: Decimal.new(20)}
end end
test "refreshes debit balances" do test "refreshes debit balances" do
@ -46,7 +46,7 @@ defmodule Explorer.Workers.RefreshBalanceTest do
insert(:from_address, address: address, transaction: transaction) insert(:from_address, address: address, transaction: transaction)
insert(:receipt, transaction: transaction, status: 1) insert(:receipt, transaction: transaction, status: 1)
RefreshBalance.perform("debit") RefreshBalance.perform("debit")
assert Repo.one(Debit).value == Decimal.new(20) assert Repo.one(Debit).value == %Wei{value: Decimal.new(20)}
end end
end end
end end

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

@ -8,7 +8,7 @@
<div class="address__item"> <div class="address__item">
<dt class="address__item-key"><%= gettext "Balance" %></dt> <dt class="address__item-key"><%= gettext "Balance" %></dt>
<dd class="address__item-value address__balance" title="<%= @address.hash %>" data-test="address_balance"> <dd class="address__item-value address__balance" title="<%= @address.hash %>" data-test="address_balance">
<%= balance(@address) %> <%= gettext "Ether" %> <%= balance(@address) %>
</dd> </dd>
</div> </div>
</dl> </dl>

@ -118,10 +118,10 @@
</div> </div>
</td> </td>
<td class="transactions__column transactions__column--value"> <td class="transactions__column transactions__column--value">
<%= value(transaction) %> <%= gettext "Ether" %> <%= ExplorerWeb.TransactionView.value(transaction) %>
</td> </td>
<td class="transactions__column transactions__column--value"> <td class="transactions__column transactions__column--value">
<%= fee(transaction) %> <%= gettext "Ether" %> <%= ExplorerWeb.TransactionView.fee(transaction) %>
</td> </td>
</tr> </tr>
<% end %> <% end %>

@ -57,13 +57,7 @@
<%= block.gas_limit |> Cldr.Number.to_string! %> <%= block.gas_limit |> Cldr.Number.to_string! %>
</td> </td>
<td class="blocks__column blocks__column--optional blocks__column--gas-price"> <td class="blocks__column blocks__column--optional blocks__column--gas-price">
<%= block.transactions <%= average_gas_price(block) %>
|> Enum.map(fn(transaction) ->
Decimal.to_float(Decimal.div(Decimal.new(transaction.gas_price), Decimal.new(1_000_000_000)))
end)
|> Math.Enum.mean()
|> Kernel.||(0)
|> Cldr.Number.to_string! %> Gwei
</td> </td>
</tr> </tr>
<% end %> <% end %>

@ -143,7 +143,7 @@
) %></div> ) %></div>
</td> </td>
<td class="transactions__column transactions__column--value"> <td class="transactions__column transactions__column--value">
<%= value(transaction) %> <%= gettext "Ether" %> <%= ExplorerWeb.TransactionView.value(transaction) %>
</td> </td>
</tr> </tr>
<% end %> <% end %>

@ -134,7 +134,7 @@
<%= transaction.block.timestamp |> Timex.from_now() %> <%= transaction.block.timestamp |> Timex.from_now() %>
</td> </td>
<td class="transactions__column transactions__column--value"> <td class="transactions__column transactions__column--value">
<%= value(transaction) %> <%= gettext "Ether" %> <%= ExplorerWeb.TransactionView.value(transaction) %>
</td> </td>
</tr> </tr>
<% end %> <% end %>

@ -77,7 +77,7 @@
<% end %> <% end %>
</td> </td>
<td class="transactions__column transactions__column--value"> <td class="transactions__column transactions__column--value">
<%= value(transaction) %> <%= gettext "Ether" %> <%= ExplorerWeb.TransactionView.value(transaction) %>
</td> </td>
</tr> </tr>
<% end %> <% end %>

@ -81,7 +81,7 @@
) %></div> ) %></div>
</td> </td>
<td class="transactions__column transactions__column--value"> <td class="transactions__column transactions__column--value">
<%= value(transaction) %> <%= gettext "Ether" %> <%= value(transaction) %>
</td> </td>
</tr> </tr>
<% end %> <% end %>

@ -41,7 +41,7 @@
</div> </div>
<div class="transaction__item"> <div class="transaction__item">
<dt class="transaction__item-key"><%= gettext "Value" %></dt> <dt class="transaction__item-key"><%= gettext "Value" %></dt>
<dd class="transaction__item-value"><%= value(@transaction) %> <%= gettext "Ether" %></dd> <dd class="transaction__item-value"><%= value(@transaction) %></dd>
</div> </div>
<div class="transaction__item"> <div class="transaction__item">
<dt class="transaction__item-key"><%= gettext "From" %></dt> <dt class="transaction__item-key"><%= gettext "From" %></dt>
@ -96,8 +96,8 @@
<div class="transaction__item"> <div class="transaction__item">
<dt class="transaction__item-key"><%= gettext "Gas Price" %></dt> <dt class="transaction__item-key"><%= gettext "Gas Price" %></dt>
<dd class="transaction__item-value"> <dd class="transaction__item-value">
<%= gas_price(@transaction, :wei) %> <%= gettext("Wei") %> <%= gas_price(@transaction, :wei) %>
(<%= gas_price(@transaction, :gwei) %> <%= gettext "Gwei" %>) (<%= gas_price(@transaction, :gwei) %>)
</dd> </dd>
</div> </div>
<div class="transaction__item"> <div class="transaction__item">

@ -42,7 +42,7 @@
to: address_path(@conn, :show, @conn.assigns.locale, transaction.to_address.hash), to: address_path(@conn, :show, @conn.assigns.locale, transaction.to_address.hash),
class: "transaction-log__link") %> class: "transaction-log__link") %>
</td> </td>
<td><%= value(transaction) %></td> <td><%= value(transaction, include_label: false) %></td>
<td><%= gas(transaction) %></td> <td><%= gas(transaction) %></td>
</tr> </tr>
</tgroup> </tgroup>

@ -3,8 +3,6 @@ defmodule ExplorerWeb.AddressTransactionView do
alias ExplorerWeb.TransactionView alias ExplorerWeb.TransactionView
defdelegate fee(transaction), to: TransactionView
def format_current_filter(filter) do def format_current_filter(filter) do
case filter do case filter do
"to" -> gettext("To") "to" -> gettext("To")
@ -13,6 +11,5 @@ defmodule ExplorerWeb.AddressTransactionView do
end end
end end
defdelegate status(transacton), to: TransactionView defdelegate status(transaction), to: TransactionView
defdelegate value(transaction), to: TransactionView
end end

@ -1,16 +1,16 @@
defmodule ExplorerWeb.AddressView do defmodule ExplorerWeb.AddressView do
use ExplorerWeb, :view use ExplorerWeb, :view
alias Explorer.Chain alias Explorer.Chain.Address
@dialyzer :no_match @dialyzer :no_match
def balance(address) do def balance(%Address{balance: nil}), do: ""
address
|> Chain.balance(:ether) @doc """
|> case do Returns a formatted address balance and includes the unit.
nil -> "" """
ether -> Cldr.Number.to_string!(ether, fractional_digits: 18) def balance(%Address{balance: balance}) do
end format_wei_value(balance, :ether, fractional_digits: 18)
end end
end end

@ -6,7 +6,6 @@ defmodule ExplorerWeb.BlockTransactionView do
# Functions # Functions
defdelegate status(transacton), to: TransactionView defdelegate status(transacton), to: TransactionView
defdelegate value(transaction), to: TransactionView
defdelegate age(block), to: BlockView defdelegate age(block), to: BlockView
defdelegate formatted_timestamp(block), to: BlockView defdelegate formatted_timestamp(block), to: BlockView
end end

@ -1,7 +1,9 @@
defmodule ExplorerWeb.BlockView do defmodule ExplorerWeb.BlockView do
use ExplorerWeb, :view use ExplorerWeb, :view
alias Explorer.Chain.Block import Math.Enum, only: [mean: 1]
alias Explorer.Chain.{Block, Wei}
@dialyzer :no_match @dialyzer :no_match
@ -14,4 +16,17 @@ defmodule ExplorerWeb.BlockView do
def formatted_timestamp(%Block{timestamp: timestamp}) do def formatted_timestamp(%Block{timestamp: timestamp}) do
Timex.format!(timestamp, "%b-%d-%Y %H:%M:%S %p %Z", :strftime) Timex.format!(timestamp, "%b-%d-%Y %H:%M:%S %p %Z", :strftime)
end end
def average_gas_price(%Block{transactions: transactions}) do
average =
transactions
|> Enum.map(&Decimal.to_float(Wei.to(&1.gas_price, :gwei)))
|> mean()
|> Kernel.||(0)
|> Cldr.Number.to_string!()
unit_text = gettext("Gwei")
"#{average} #{unit_text}"
end
end end

@ -1,7 +1,3 @@
defmodule ExplorerWeb.ChainView do defmodule ExplorerWeb.ChainView do
use ExplorerWeb, :view use ExplorerWeb, :view
alias ExplorerWeb.TransactionView
defdelegate value(transaction), to: TransactionView
end end

@ -23,6 +23,4 @@ defmodule ExplorerWeb.PendingTransactionView do
_ -> nil _ -> nil
end end
end end
defdelegate value(transaction), to: TransactionView
end end

@ -3,7 +3,7 @@ defmodule ExplorerWeb.TransactionView do
alias Cldr.Number alias Cldr.Number
alias Explorer.Chain alias Explorer.Chain
alias Explorer.Chain.{Block, InternalTransaction, Transaction} alias Explorer.Chain.{Block, InternalTransaction, Transaction, Wei}
alias ExplorerWeb.BlockView alias ExplorerWeb.BlockView
# Functions # Functions
@ -22,15 +22,16 @@ defmodule ExplorerWeb.TransactionView do
end end
end end
def fee(transaction) do @doc """
transaction Calculates the transaction fee and returns a formatted display value.
|> Chain.fee(:ether) """
|> case do def fee(%Transaction{} = transaction) do
case Chain.fee(transaction, :wei) do
{:actual, actual} -> {:actual, actual} ->
Cldr.Number.to_string!(actual, fractional_digits: 18) format_wei_value(Wei.from(actual, :wei), :ether, fractional_digits: 18)
{:maximum, maximum} -> {:maximum, maximum} ->
"<= " <> Cldr.Number.to_string!(maximum, fractional_digits: 18) "<= " <> format_wei_value(Wei.from(maximum, :wei), :ether, fractional_digits: 18)
end end
end end
@ -56,14 +57,17 @@ defmodule ExplorerWeb.TransactionView do
end end
end end
def gas(%type{gas: gas}) when type in [InternalTransaction, Transaction] do defguardp is_transaction_type(mod) when mod in [InternalTransaction, Transaction]
def gas(%type{gas: gas}) when is_transaction_type(type) do
Cldr.Number.to_string!(gas) Cldr.Number.to_string!(gas)
end end
def gas_price(transaction, unit) do @doc """
transaction Converts a transaction's gas price to a displayable value.
|> Chain.gas_price(unit) """
|> Cldr.Number.to_string!() def gas_price(%Transaction{gas_price: gas_price}, unit) when unit in ~w(wei gwei ether)a do
format_wei_value(gas_price, unit)
end end
def last_seen(%Transaction{updated_at: updated_at}) do def last_seen(%Transaction{updated_at: updated_at}) do
@ -85,9 +89,15 @@ defmodule ExplorerWeb.TransactionView do
end end
end end
def value(transaction) do @doc """
transaction Converts a transaction's Wei value to Ether and returns a formatted display value.
|> Chain.value(:ether)
|> Cldr.Number.to_string!() ## Options
* `:include_label` - Boolean. Defaults to true. Flag for displaying unit with value.
"""
def value(%mod{value: value}, opts \\ []) when is_transaction_type(mod) do
include_label? = Keyword.get(opts, :include_label, true)
format_wei_value(value, :ether, include_unit_label: include_label?)
end end
end end

@ -0,0 +1,93 @@
defmodule ExplorerWeb.WeiHelpers do
@moduledoc """
Helper functions for interacting with `t:Explorer.Chain.Wei.t/0` values.
"""
import ExplorerWeb.Gettext
alias Explorer.Chain.Wei
@valid_units ~w(wei gwei ether)a
@type format_option :: {:fractional_digits, pos_integer()} | {:include_unit_label, boolean()}
@type format_options :: [format_option()]
@doc """
Converts a `t:Explorer.Wei.t/0` value to the specified unit including a
translated unit label.
## Supported Formatting Options
The third argument allows for keyword options to be passed for formatting the
converted number.
* `:fractional_digits` - Integer. Number of fractional digits to include
* `:include_unit_label` - Boolean (Defaults to `true`). Flag for if the unit
label should be included in the returned string
## Examples
iex> format_wei_value(%Wei{value: Decimal.new(1)}, :wei)
"1 Wei"
iex> format_wei_value(%Wei{value: Decimal.new(1, 10, 12)}, :gwei)
"10,000 Gwei"
iex> format_wei_value(%Wei{value: Decimal.new(1, 10, 21)}, :ether)
"10,000 POA"
# With formatting options
iex> format_wei_value(
...> %Wei{value: Decimal.new(1)},
...> :wei,
...> fractional_digits: 3
...> )
"1.000 Wei"
iex> format_wei_value(
...> %Wei{value: Decimal.new(10)},
...> :wei,
...> include_unit_label: false
...> )
"10"
"""
@spec format_wei_value(Wei.t(), Wei.unit(), format_options()) :: String.t()
def format_wei_value(%Wei{} = wei, unit, options \\ []) when unit in @valid_units do
number_format_options = build_number_format_options(options)
converted_value =
wei
|> Wei.to(unit)
|> Cldr.Number.to_string!(number_format_options)
if Keyword.get(options, :include_unit_label, true) do
display_unit = display_unit(unit)
"#{converted_value} #{display_unit}"
else
converted_value
end
end
defp build_number_format_options(options) do
Enum.reduce(options, [], fn option, formatted_options ->
case parse_number_format_option(option) do
nil -> formatted_options
{key, value} -> Keyword.put(formatted_options, key, value)
end
end)
end
defp display_unit(:wei), do: gettext("Wei")
defp display_unit(:gwei), do: gettext("Gwei")
defp display_unit(:ether), do: gettext("Ether")
defguardp is_fractional_digit(digits) when is_integer(digits) and digits > 0
defp parse_number_format_option({:fractional_digits, digits}) when is_fractional_digit(digits) do
{:fractional_digits, digits}
end
defp parse_number_format_option(_), do: nil
end

@ -171,7 +171,7 @@ msgstr ""
#: lib/explorer_web/templates/transaction/overview.html.eex:47 #: lib/explorer_web/templates/transaction/overview.html.eex:47
#: lib/explorer_web/templates/transaction/show.html.eex:26 #: lib/explorer_web/templates/transaction/show.html.eex:26
#: lib/explorer_web/views/address_internal_transaction_view.ex:7 #: lib/explorer_web/views/address_internal_transaction_view.ex:7
#: lib/explorer_web/views/address_transaction_view.ex:11 #: lib/explorer_web/views/address_transaction_view.ex:9
msgid "From" msgid "From"
msgstr "" msgstr ""
@ -180,7 +180,7 @@ msgstr ""
msgid "Overview" msgid "Overview"
msgstr "" msgstr ""
#: lib/explorer_web/views/transaction_view.ex:84 #: lib/explorer_web/views/transaction_view.ex:88
msgid "Success" msgid "Success"
msgstr "" msgstr ""
@ -194,7 +194,7 @@ msgstr ""
#: lib/explorer_web/templates/transaction/overview.html.eex:61 #: lib/explorer_web/templates/transaction/overview.html.eex:61
#: lib/explorer_web/templates/transaction/show.html.eex:27 #: lib/explorer_web/templates/transaction/show.html.eex:27
#: lib/explorer_web/views/address_internal_transaction_view.ex:6 #: lib/explorer_web/views/address_internal_transaction_view.ex:6
#: lib/explorer_web/views/address_transaction_view.ex:10 #: lib/explorer_web/views/address_transaction_view.ex:8
msgid "To" msgid "To"
msgstr "" msgstr ""
@ -241,9 +241,9 @@ msgstr ""
#: lib/explorer_web/templates/transaction/overview.html.eex:56 #: lib/explorer_web/templates/transaction/overview.html.eex:56
#: lib/explorer_web/templates/transaction/overview.html.eex:70 #: lib/explorer_web/templates/transaction/overview.html.eex:70
#: lib/explorer_web/views/transaction_view.ex:20 #: lib/explorer_web/views/transaction_view.ex:20
#: lib/explorer_web/views/transaction_view.ex:47 #: lib/explorer_web/views/transaction_view.ex:48
#: lib/explorer_web/views/transaction_view.ex:54 #: lib/explorer_web/views/transaction_view.ex:55
#: lib/explorer_web/views/transaction_view.ex:83 #: lib/explorer_web/views/transaction_view.ex:87
msgid "Pending" msgid "Pending"
msgstr "" msgstr ""
@ -306,11 +306,11 @@ msgstr ""
msgid "Next Page" msgid "Next Page"
msgstr "" msgstr ""
#: lib/explorer_web/views/transaction_view.ex:81 #: lib/explorer_web/views/transaction_view.ex:85
msgid "Failed" msgid "Failed"
msgstr "" msgstr ""
#: lib/explorer_web/views/transaction_view.ex:82 #: lib/explorer_web/views/transaction_view.ex:86
msgid "Out of Gas" msgid "Out of Gas"
msgstr "" msgstr ""
@ -329,19 +329,13 @@ msgstr ""
msgid "Showing #%{number}" msgid "Showing #%{number}"
msgstr "" msgstr ""
#: lib/explorer_web/templates/address/overview.html.eex:11
#: lib/explorer_web/templates/address_transaction/index.html.eex:121
#: lib/explorer_web/templates/address_transaction/index.html.eex:124
#: lib/explorer_web/templates/block_transaction/index.html.eex:146
#: lib/explorer_web/templates/chain/show.html.eex:137
#: lib/explorer_web/templates/pending_transaction/index.html.eex:80
#: lib/explorer_web/templates/transaction/index.html.eex:84
#: lib/explorer_web/templates/transaction/overview.html.eex:44
#: lib/explorer_web/templates/transaction/show.html.eex:28 #: lib/explorer_web/templates/transaction/show.html.eex:28
#: lib/explorer_web/views/wei_helpers.ex:84
msgid "Ether" msgid "Ether"
msgstr "" msgstr ""
#: lib/explorer_web/templates/transaction/overview.html.eex:100 #: lib/explorer_web/views/block_view.ex:28
#: lib/explorer_web/views/wei_helpers.ex:83
msgid "Gwei" msgid "Gwei"
msgstr "" msgstr ""
@ -381,15 +375,15 @@ msgstr ""
msgid "Type" msgid "Type"
msgstr "" msgstr ""
#: lib/explorer_web/templates/transaction/overview.html.eex:99
#: lib/explorer_web/templates/transaction/overview.html.eex:105 #: lib/explorer_web/templates/transaction/overview.html.eex:105
#: lib/explorer_web/views/wei_helpers.ex:82
msgid "Wei" msgid "Wei"
msgstr "" msgstr ""
#: lib/explorer_web/templates/address_internal_transaction/index.html.eex:28 #: lib/explorer_web/templates/address_internal_transaction/index.html.eex:28
#: lib/explorer_web/templates/address_transaction/index.html.eex:29 #: lib/explorer_web/templates/address_transaction/index.html.eex:29
#: lib/explorer_web/views/address_internal_transaction_view.ex:8 #: lib/explorer_web/views/address_internal_transaction_view.ex:8
#: lib/explorer_web/views/address_transaction_view.ex:12 #: lib/explorer_web/views/address_transaction_view.ex:10
msgid "All" msgid "All"
msgstr "" msgstr ""

@ -183,7 +183,7 @@ msgstr "Address"
#: lib/explorer_web/templates/transaction/overview.html.eex:47 #: lib/explorer_web/templates/transaction/overview.html.eex:47
#: lib/explorer_web/templates/transaction/show.html.eex:26 #: lib/explorer_web/templates/transaction/show.html.eex:26
#: lib/explorer_web/views/address_internal_transaction_view.ex:7 #: lib/explorer_web/views/address_internal_transaction_view.ex:7
#: lib/explorer_web/views/address_transaction_view.ex:11 #: lib/explorer_web/views/address_transaction_view.ex:9
msgid "From" msgid "From"
msgstr "From" msgstr "From"
@ -192,7 +192,7 @@ msgstr "From"
msgid "Overview" msgid "Overview"
msgstr "Overview" msgstr "Overview"
#: lib/explorer_web/views/transaction_view.ex:84 #: lib/explorer_web/views/transaction_view.ex:88
msgid "Success" msgid "Success"
msgstr "Success" msgstr "Success"
@ -206,7 +206,7 @@ msgstr "Success"
#: lib/explorer_web/templates/transaction/overview.html.eex:61 #: lib/explorer_web/templates/transaction/overview.html.eex:61
#: lib/explorer_web/templates/transaction/show.html.eex:27 #: lib/explorer_web/templates/transaction/show.html.eex:27
#: lib/explorer_web/views/address_internal_transaction_view.ex:6 #: lib/explorer_web/views/address_internal_transaction_view.ex:6
#: lib/explorer_web/views/address_transaction_view.ex:10 #: lib/explorer_web/views/address_transaction_view.ex:8
msgid "To" msgid "To"
msgstr "To" msgstr "To"
@ -253,9 +253,9 @@ msgstr "Showing %{count} Transactions"
#: lib/explorer_web/templates/transaction/overview.html.eex:56 #: lib/explorer_web/templates/transaction/overview.html.eex:56
#: lib/explorer_web/templates/transaction/overview.html.eex:70 #: lib/explorer_web/templates/transaction/overview.html.eex:70
#: lib/explorer_web/views/transaction_view.ex:20 #: lib/explorer_web/views/transaction_view.ex:20
#: lib/explorer_web/views/transaction_view.ex:47 #: lib/explorer_web/views/transaction_view.ex:48
#: lib/explorer_web/views/transaction_view.ex:54 #: lib/explorer_web/views/transaction_view.ex:55
#: lib/explorer_web/views/transaction_view.ex:83 #: lib/explorer_web/views/transaction_view.ex:87
msgid "Pending" msgid "Pending"
msgstr "Pending" msgstr "Pending"
@ -318,11 +318,11 @@ msgstr ""
msgid "Next Page" msgid "Next Page"
msgstr "" msgstr ""
#: lib/explorer_web/views/transaction_view.ex:81 #: lib/explorer_web/views/transaction_view.ex:85
msgid "Failed" msgid "Failed"
msgstr "" msgstr ""
#: lib/explorer_web/views/transaction_view.ex:82 #: lib/explorer_web/views/transaction_view.ex:86
msgid "Out of Gas" msgid "Out of Gas"
msgstr "" msgstr ""
@ -341,19 +341,13 @@ msgstr ""
msgid "Showing #%{number}" msgid "Showing #%{number}"
msgstr "" msgstr ""
#: lib/explorer_web/templates/address/overview.html.eex:11
#: lib/explorer_web/templates/address_transaction/index.html.eex:121
#: lib/explorer_web/templates/address_transaction/index.html.eex:124
#: lib/explorer_web/templates/block_transaction/index.html.eex:146
#: lib/explorer_web/templates/chain/show.html.eex:137
#: lib/explorer_web/templates/pending_transaction/index.html.eex:80
#: lib/explorer_web/templates/transaction/index.html.eex:84
#: lib/explorer_web/templates/transaction/overview.html.eex:44
#: lib/explorer_web/templates/transaction/show.html.eex:28 #: lib/explorer_web/templates/transaction/show.html.eex:28
#: lib/explorer_web/views/wei_helpers.ex:84
msgid "Ether" msgid "Ether"
msgstr "POA" msgstr "POA"
#: lib/explorer_web/templates/transaction/overview.html.eex:100 #: lib/explorer_web/views/block_view.ex:28
#: lib/explorer_web/views/wei_helpers.ex:83
msgid "Gwei" msgid "Gwei"
msgstr "" msgstr ""
@ -393,15 +387,15 @@ msgstr ""
msgid "Type" msgid "Type"
msgstr "" msgstr ""
#: lib/explorer_web/templates/transaction/overview.html.eex:99
#: lib/explorer_web/templates/transaction/overview.html.eex:105 #: lib/explorer_web/templates/transaction/overview.html.eex:105
#: lib/explorer_web/views/wei_helpers.ex:82
msgid "Wei" msgid "Wei"
msgstr "" msgstr ""
#: lib/explorer_web/templates/address_internal_transaction/index.html.eex:28 #: lib/explorer_web/templates/address_internal_transaction/index.html.eex:28
#: lib/explorer_web/templates/address_transaction/index.html.eex:29 #: lib/explorer_web/templates/address_transaction/index.html.eex:29
#: lib/explorer_web/views/address_internal_transaction_view.ex:8 #: lib/explorer_web/views/address_internal_transaction_view.ex:8
#: lib/explorer_web/views/address_transaction_view.ex:12 #: lib/explorer_web/views/address_transaction_view.ex:10
msgid "All" msgid "All"
msgstr "" msgstr ""

@ -1,60 +0,0 @@
defmodule ExplorerWeb.AddressTransactionViewTest do
use Explorer.DataCase
alias ExplorerWeb.AddressTransactionView
describe "fee/0" do
test "formats the fee for a successful transaction" do
insert(:block, number: 24)
time = Timex.now() |> Timex.shift(hours: -2)
block =
insert(:block, %{
number: 1,
gas_used: 99523,
timestamp: time
})
to_address = insert(:address, hash: "0xsleepypuppy")
from_address = insert(:address, hash: "0xilovefrogs")
transaction =
insert(
:transaction,
inserted_at: Timex.parse!("1970-01-01T00:00:18-00:00", "{ISO:Extended}"),
updated_at: Timex.parse!("1980-01-01T00:00:18-00:00", "{ISO:Extended}"),
to_address_id: to_address.id,
from_address_id: from_address.id,
gas_price: Decimal.new(1_000_000_000.0)
)
|> with_block(block)
insert(:receipt, status: 1, gas_used: Decimal.new(435_334), transaction: transaction)
transaction =
transaction
|> Repo.preload([:receipt])
assert AddressTransactionView.fee(transaction) == "0.000,435,334,000,000,000"
end
test "fee returns max_gas for pending transaction" do
to_address = insert(:address, hash: "0xchadmuska")
from_address = insert(:address, hash: "0xtonyhawk")
transaction =
insert(
:transaction,
inserted_at: Timex.parse!("1970-01-01T00:00:18-00:00", "{ISO:Extended}"),
updated_at: Timex.parse!("1980-01-01T00:00:18-00:00", "{ISO:Extended}"),
to_address_id: to_address.id,
from_address_id: from_address.id,
gas: Decimal.new(21000.0),
gas_price: Decimal.new(1_000_000_000.0)
)
|> Repo.preload([:to_address, :from_address, :receipt])
assert AddressTransactionView.fee(transaction) == "<= 0.000,021,000,000,000,000"
end
end
end

@ -0,0 +1,8 @@
defmodule ExplorerWeb.WeiHelpersTest do
use ExUnit.Case
# Needed for doctest
alias Explorer.Chain.Wei
doctest ExplorerWeb.WeiHelpers, import: true
end
Loading…
Cancel
Save