diff --git a/apps/explorer/lib/explorer/chain/address/current_token_balance.ex b/apps/explorer/lib/explorer/chain/address/current_token_balance.ex new file mode 100644 index 0000000000..448ae7da34 --- /dev/null +++ b/apps/explorer/lib/explorer/chain/address/current_token_balance.ex @@ -0,0 +1,60 @@ +defmodule Explorer.Chain.Address.CurrentTokenBalance do + @moduledoc """ + Represents the current token balance from addresses according to the last block. + """ + + use Ecto.Schema + import Ecto.Changeset + + alias Explorer.Chain.{Address, Block, Hash, Token} + + @typedoc """ + * `address` - The `t:Explorer.Chain.Address.t/0` that is the balance's owner. + * `address_hash` - The address hash foreign key. + * `token` - The `t:Explorer.Chain.Token/0` so that the address has the balance. + * `token_contract_address_hash` - The contract address hash foreign key. + * `block_number` - The block's number that the transfer took place. + * `value` - The value that's represents the balance. + """ + @type t :: %__MODULE__{ + address: %Ecto.Association.NotLoaded{} | Address.t(), + address_hash: Hash.Address.t(), + token: %Ecto.Association.NotLoaded{} | Token.t(), + token_contract_address_hash: Hash.Address, + block_number: Block.block_number(), + inserted_at: DateTime.t(), + updated_at: DateTime.t(), + value: Decimal.t() | nil + } + + schema "address_current_token_balances" do + field(:value, :decimal) + field(:block_number, :integer) + field(:value_fetched_at, :utc_datetime) + + belongs_to(:address, Address, foreign_key: :address_hash, references: :hash, type: Hash.Address) + + belongs_to( + :token, + Token, + foreign_key: :token_contract_address_hash, + references: :contract_address_hash, + type: Hash.Address + ) + + timestamps() + end + + @optional_fields ~w(value value_fetched_at)a + @required_fields ~w(address_hash block_number token_contract_address_hash)a + @allowed_fields @optional_fields ++ @required_fields + + @doc false + def changeset(%__MODULE__{} = token_balance, attrs) do + token_balance + |> cast(attrs, @allowed_fields) + |> validate_required(@required_fields) + |> foreign_key_constraint(:address_hash) + |> foreign_key_constraint(:token_contract_address_hash) + end +end diff --git a/apps/explorer/priv/repo/migrations/20181026180921_create_address_current_token_balances.exs b/apps/explorer/priv/repo/migrations/20181026180921_create_address_current_token_balances.exs new file mode 100644 index 0000000000..d497d1feda --- /dev/null +++ b/apps/explorer/priv/repo/migrations/20181026180921_create_address_current_token_balances.exs @@ -0,0 +1,32 @@ +defmodule Explorer.Repo.Migrations.CreateAddressCurrentTokenBalances do + use Ecto.Migration + + def change do + create table(:address_current_token_balances) do + add(:address_hash, references(:addresses, column: :hash, type: :bytea), null: false) + add(:block_number, :bigint, null: false) + + add( + :token_contract_address_hash, + references(:tokens, column: :contract_address_hash, type: :bytea), + null: false + ) + + add(:value, :decimal, null: true) + add(:value_fetched_at, :utc_datetime, default: fragment("NULL"), null: true) + + timestamps(null: false, type: :utc_datetime) + end + + create(unique_index(:address_current_token_balances, ~w(address_hash token_contract_address_hash)a)) + + create( + index( + :address_current_token_balances, + [:value], + name: :address_current_token_balances_value, + where: "value IS NOT NULL" + ) + ) + end +end diff --git a/apps/explorer/test/support/factory.ex b/apps/explorer/test/support/factory.ex index f168091195..54f99731a8 100644 --- a/apps/explorer/test/support/factory.ex +++ b/apps/explorer/test/support/factory.ex @@ -13,6 +13,7 @@ defmodule Explorer.Factory do alias Explorer.Chain.{ Address, + Address.CurrentTokenBalance, Address.TokenBalance, Address.CoinBalance, Block, @@ -480,6 +481,16 @@ defmodule Explorer.Factory do } end + def address_current_token_balance_factory() do + %CurrentTokenBalance{ + address: build(:address), + token_contract_address_hash: insert(:token).contract_address_hash, + block_number: block_number(), + value: Enum.random(1..100_000), + value_fetched_at: DateTime.utc_now() + } + end + defmacrop left + right do quote do fragment("? + ?", unquote(left), unquote(right))