From 4a63b978fd6f93d7add5013d93199ea64b0ef297 Mon Sep 17 00:00:00 2001 From: zachdaniel Date: Wed, 19 Jun 2019 10:19:12 -0400 Subject: [PATCH] feat: calculate RSK market cap --- .../controllers/chain_controller.ex | 11 ++ .../templates/chain/show.html.eex | 2 +- .../lib/block_scout_web/views/chain_view.ex | 8 + apps/explorer/config/config.exs | 11 +- .../explorer/lib/explorer/chain/supply/rsk.ex | 103 +++++++++++++ .../test/explorer/chain/supply/rsk_test.exs | 139 ++++++++++++++++++ 6 files changed, 271 insertions(+), 3 deletions(-) create mode 100644 apps/explorer/lib/explorer/chain/supply/rsk.ex create mode 100644 apps/explorer/test/explorer/chain/supply/rsk_test.exs diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/chain_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/chain_controller.ex index 48d84dd5d1..40a0fd3bbb 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/chain_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/chain_controller.ex @@ -4,6 +4,7 @@ defmodule BlockScoutWeb.ChainController do alias BlockScoutWeb.ChainView alias Explorer.{Chain, PagingOptions, Repo} alias Explorer.Chain.{Address, Block, Transaction} + alias Explorer.Chain.Supply.RSK alias Explorer.Counters.AverageBlockTime alias Explorer.ExchangeRates.Token alias Explorer.Market @@ -13,6 +14,15 @@ defmodule BlockScoutWeb.ChainController do transaction_estimated_count = Chain.transaction_estimated_count() block_count = Chain.block_estimated_count() + market_cap_calculation = + case Application.get_env(:explorer, :supply) do + RSK -> + RSK + + _ -> + :standard + end + exchange_rate = Market.get_exchange_rate(Explorer.coin()) || Token.null() render( @@ -22,6 +32,7 @@ defmodule BlockScoutWeb.ChainController do average_block_time: AverageBlockTime.average_block_time(), exchange_rate: exchange_rate, chart_data_path: market_history_chart_path(conn, :show), + market_cap_calculation: market_cap_calculation, transaction_estimated_count: transaction_estimated_count, transactions_path: recent_transactions_path(conn, :index), block_count: block_count diff --git a/apps/block_scout_web/lib/block_scout_web/templates/chain/show.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/chain/show.html.eex index b5d7283a03..17bd9c4afe 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/chain/show.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/chain/show.html.eex @@ -30,7 +30,7 @@ <%= gettext "Market Cap" %> - + diff --git a/apps/block_scout_web/lib/block_scout_web/views/chain_view.ex b/apps/block_scout_web/lib/block_scout_web/views/chain_view.ex index 7f49d2d239..506d6ff3c8 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/chain_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/chain_view.ex @@ -2,4 +2,12 @@ defmodule BlockScoutWeb.ChainView do use BlockScoutWeb, :view alias BlockScoutWeb.LayoutView + + defp market_cap(:standard, exchange_rate) do + exchange_rate.market_cap_usd + end + + defp market_cap(module, exchange_rate) do + module.market_cap(exchange_rate) + end end diff --git a/apps/explorer/config/config.exs b/apps/explorer/config/config.exs index 9a7d9f4296..9a601e6c68 100644 --- a/apps/explorer/config/config.exs +++ b/apps/explorer/config/config.exs @@ -71,8 +71,15 @@ else config :explorer, Explorer.Staking.EpochCounter, enabled: false end -if System.get_env("SUPPLY_MODULE") == "TokenBridge" do - config :explorer, supply: Explorer.Chain.Supply.TokenBridge +case System.get_env("SUPPLY_MODULE") do + "TokenBridge" -> + config :explorer, supply: Explorer.Chain.Supply.TokenBridge + + "rsk" -> + config :explorer, supply: Explorer.Chain.Supply.RSK + + _ -> + :ok end if System.get_env("SOURCE_MODULE") == "TransactionAndLog" do diff --git a/apps/explorer/lib/explorer/chain/supply/rsk.ex b/apps/explorer/lib/explorer/chain/supply/rsk.ex new file mode 100644 index 0000000000..09a02252a2 --- /dev/null +++ b/apps/explorer/lib/explorer/chain/supply/rsk.ex @@ -0,0 +1,103 @@ +defmodule Explorer.Chain.Supply.RSK do + @moduledoc """ + Defines the supply API for calculating supply for coins from RSK. + """ + + use Explorer.Chain.Supply + + import Ecto.Query, only: [from: 2] + + alias Explorer.Chain.Address.CoinBalance + alias Explorer.Chain.{Block, Wei} + alias Explorer.ExchangeRates.Token + alias Explorer.{Market, Repo} + + def market_cap(exchange_rate) do + circulating() * exchange_rate.usd_value + end + + @doc "Equivalent to getting the circulating value " + def supply_for_days(days) do + now = Timex.now() + + balances_query = + from(balance in CoinBalance, + join: block in Block, + on: block.number == balance.block_number, + where: block.consensus == true, + where: balance.address_hash == ^"0x0000000000000000000000000000000001000006", + where: block.timestamp > ^Timex.shift(now, days: -days), + distinct: fragment("date_trunc('day', ?)", block.timestamp), + select: {block.timestamp, balance.value} + ) + + balance_before_query = + from(balance in CoinBalance, + join: block in Block, + on: block.number == balance.block_number, + where: block.consensus == true, + where: balance.address_hash == ^"0x0000000000000000000000000000000001000006", + where: block.timestamp <= ^Timex.shift(Timex.now(), days: -days), + order_by: [desc: block.timestamp], + limit: 1, + select: balance.value + ) + + by_day = + balances_query + |> Repo.all() + |> Enum.into(%{}, fn {timestamp, value} -> + {Timex.to_date(timestamp), value} + end) + + starting = Repo.one(balance_before_query) || wei!(0) + + result = + -days..0 + |> Enum.reduce({%{}, starting.value}, fn i, {days, last} -> + date = + now + |> Timex.shift(days: i) + |> Timex.to_date() + + case Map.get(by_day, date) do + nil -> + {Map.put(days, date, last), last} + + value -> + {Map.put(days, date, value.value), value.value} + end + end) + |> elem(0) + + {:ok, result} + end + + def circulating do + query = + from(balance in CoinBalance, + join: block in Block, + on: block.number == balance.block_number, + where: block.consensus == true, + where: balance.address_hash == ^"0x0000000000000000000000000000000001000006", + order_by: [desc: block.timestamp], + limit: 1, + select: balance.value + ) + + Repo.one(query) || wei!(0) + end + + defp wei!(value) do + {:ok, wei} = Wei.cast(value) + wei + end + + def total do + 21_000_000 + end + + def exchange_rate do + Market.get_exchange_rate(Explorer.coin()) || Token.null() + end +end diff --git a/apps/explorer/test/explorer/chain/supply/rsk_test.exs b/apps/explorer/test/explorer/chain/supply/rsk_test.exs new file mode 100644 index 0000000000..40aa0a831b --- /dev/null +++ b/apps/explorer/test/explorer/chain/supply/rsk_test.exs @@ -0,0 +1,139 @@ +defmodule Explorer.Chain.Supply.RSKTest do + use Explorer.DataCase + + alias Explorer.Chain.Supply.RSK + alias Explorer.Chain.Wei + + @coin_address "0x0000000000000000000000000000000001000006" + + defp wei!(value) do + {:ok, wei} = Wei.cast(value) + wei + end + + test "total is 21_000_000" do + assert RSK.total() == 21_000_000 + end + + describe "circulating/0" do + test "with no balance" do + assert RSK.circulating() == wei!(0) + end + + test "with a balance" do + address = insert(:address, hash: @coin_address) + insert(:block, number: 0) + + insert(:fetched_balance, value: 10, address_hash: address.hash, block_number: 0) + + assert RSK.circulating() == wei!(10) + end + end + + defp date(now, shift \\ []) do + now + |> Timex.shift(shift) + |> Timex.to_date() + end + + defp dec(number) do + Decimal.new(number) + end + + describe "supply_for_days/1" do + test "when there is no balance" do + now = Timex.now() + + assert RSK.supply_for_days(2) == + {:ok, + %{ + date(now, days: -2) => dec(0), + date(now, days: -1) => dec(0), + date(now) => dec(0) + }} + end + + test "when there is a single balance before the days, that balance is used" do + address = insert(:address, hash: @coin_address) + now = Timex.now() + + insert(:block, number: 0, timestamp: Timex.shift(now, days: -10)) + + insert(:fetched_balance, value: 10, address_hash: address.hash, block_number: 0) + + assert RSK.supply_for_days(2) == + {:ok, + %{ + date(now, days: -2) => dec(10), + date(now, days: -1) => dec(10), + date(now) => dec(10) + }} + end + + test "when there is a balance for one of the days, days after it use that balance" do + address = insert(:address, hash: @coin_address) + now = Timex.now() + + insert(:block, number: 0, timestamp: Timex.shift(now, days: -10)) + insert(:block, number: 1, timestamp: Timex.shift(now, days: -1)) + + insert(:fetched_balance, value: 10, address_hash: address.hash, block_number: 0) + + insert(:fetched_balance, value: 20, address_hash: address.hash, block_number: 1) + + assert RSK.supply_for_days(2) == + {:ok, + %{ + date(now, days: -2) => dec(10), + date(now, days: -1) => dec(20), + date(now) => dec(20) + }} + end + + test "when there is a balance for the first day, that balance is used" do + address = insert(:address, hash: @coin_address) + now = Timex.now() + + insert(:block, number: 0, timestamp: Timex.shift(now, days: -10)) + insert(:block, number: 1, timestamp: Timex.shift(now, days: -2)) + insert(:block, number: 2, timestamp: Timex.shift(now, days: -1)) + + insert(:fetched_balance, value: 5, address_hash: address.hash, block_number: 0) + + insert(:fetched_balance, value: 10, address_hash: address.hash, block_number: 1) + + insert(:fetched_balance, value: 20, address_hash: address.hash, block_number: 2) + + assert RSK.supply_for_days(2) == + {:ok, + %{ + date(now, days: -2) => dec(10), + date(now, days: -1) => dec(20), + date(now) => dec(20) + }} + end + + test "when there is a balance for all days, they are each used correctly" do + address = insert(:address, hash: @coin_address) + now = Timex.now() + + insert(:block, number: 0, timestamp: Timex.shift(now, days: -10)) + insert(:block, number: 1, timestamp: Timex.shift(now, days: -2)) + insert(:block, number: 2, timestamp: Timex.shift(now, days: -1)) + insert(:block, number: 3, timestamp: now) + + insert(:fetched_balance, value: 5, address_hash: address.hash, block_number: 0) + insert(:fetched_balance, value: 10, address_hash: address.hash, block_number: 1) + insert(:fetched_balance, value: 20, address_hash: address.hash, block_number: 2) + insert(:fetched_balance, value: 30, address_hash: address.hash, block_number: 3) + + assert RSK.supply_for_days(2) == + {:ok, + %{ + date(now, days: -2) => dec(10), + date(now, days: -1) => dec(20), + date(now) => dec(30) + }} + end + end +end