Merge pull request #951 from poanetwork/gsf-dai-chain-supply-and-market-cap
dai chain supply and market cappull/955/head
commit
b92c632e2c
@ -0,0 +1,78 @@ |
|||||||
|
defmodule Explorer.Chain.Supply.TransactionAndLog do |
||||||
|
@moduledoc """ |
||||||
|
Defines the supply API for calculating the supply for smaller chains with |
||||||
|
specific mint and burn events |
||||||
|
""" |
||||||
|
use Explorer.Chain.Supply |
||||||
|
|
||||||
|
alias Explorer.Chain.{InternalTransaction, Log, Wei} |
||||||
|
alias Explorer.{Repo, Chain} |
||||||
|
|
||||||
|
{:ok, base_wei} = Wei.cast(0) |
||||||
|
@base_wei base_wei |
||||||
|
|
||||||
|
{:ok, burn_address} = Chain.string_to_address_hash("0x0000000000000000000000000000000000000000") |
||||||
|
|
||||||
|
@burn_address burn_address |
||||||
|
@bridge_edge "0x3c798bbcf33115b42c728b8504cff11dd58736e9fa789f1cda2738db7d696b2a" |
||||||
|
|
||||||
|
import Ecto.Query, only: [from: 2] |
||||||
|
|
||||||
|
def circulating, do: total(Timex.now()) |
||||||
|
|
||||||
|
def total, do: total(Timex.now()) |
||||||
|
|
||||||
|
@doc false |
||||||
|
@spec total(DateTime.t()) :: %Decimal{sign: 1} |
||||||
|
def total(on_date) do |
||||||
|
on_date |
||||||
|
|> minted_value |
||||||
|
|> Wei.sub(burned_value(on_date)) |
||||||
|
|> Wei.to(:ether) |
||||||
|
end |
||||||
|
|
||||||
|
def supply_for_days(days_count) when is_integer(days_count) and days_count > 0 do |
||||||
|
past_days = -(days_count - 1) |
||||||
|
|
||||||
|
result = |
||||||
|
for i <- past_days..0, into: %{} do |
||||||
|
datetime = Timex.shift(Timex.now(), days: i) |
||||||
|
{DateTime.to_date(datetime), total(datetime)} |
||||||
|
end |
||||||
|
|
||||||
|
{:ok, result} |
||||||
|
end |
||||||
|
|
||||||
|
defp minted_value(on_date) do |
||||||
|
query = |
||||||
|
from( |
||||||
|
l in Log, |
||||||
|
join: t in assoc(l, :transaction), |
||||||
|
join: b in assoc(t, :block), |
||||||
|
where: b.timestamp <= ^on_date and l.first_topic == @bridge_edge, |
||||||
|
select: fragment("concat('0x', encode(?, 'hex'))", l.data) |
||||||
|
) |
||||||
|
|
||||||
|
query |
||||||
|
|> Repo.all() |
||||||
|
|> Enum.reduce(@base_wei, fn data, acc -> |
||||||
|
{:ok, wei_value} = Wei.cast(data) |
||||||
|
Wei.sum(wei_value, acc) |
||||||
|
end) |
||||||
|
end |
||||||
|
|
||||||
|
defp burned_value(on_date) do |
||||||
|
query = |
||||||
|
from( |
||||||
|
it in InternalTransaction, |
||||||
|
join: t in assoc(it, :transaction), |
||||||
|
join: b in assoc(t, :block), |
||||||
|
where: b.timestamp <= ^on_date and it.to_address_hash == ^@burn_address, |
||||||
|
select: it.value |
||||||
|
) |
||||||
|
|
||||||
|
query |
||||||
|
|> Repo.all() |
||||||
|
|> Enum.reduce(@base_wei, fn data, acc -> Wei.sum(data, acc) end) |
||||||
|
end |
||||||
|
end |
@ -0,0 +1,50 @@ |
|||||||
|
defmodule Explorer.ExchangeRates.Source.TransactionAndLog do |
||||||
|
@moduledoc """ |
||||||
|
Adapter for calculating the market cap and total supply from logs and transactions |
||||||
|
while still getting other info like price in dollars and bitcoin from a secondary source |
||||||
|
""" |
||||||
|
|
||||||
|
alias Explorer.ExchangeRates.{Source, Token} |
||||||
|
alias Explorer.Chain |
||||||
|
|
||||||
|
@behaviour Source |
||||||
|
|
||||||
|
@impl Source |
||||||
|
def fetch_exchange_rates do |
||||||
|
token_data = |
||||||
|
secondary_source().fetch_exchange_rates() |
||||||
|
|> elem(1) |
||||||
|
|> Enum.find(fn token -> token.symbol == Explorer.coin() end) |
||||||
|
|> build_struct |
||||||
|
|
||||||
|
{:ok, [token_data]} |
||||||
|
end |
||||||
|
|
||||||
|
defp build_struct(original_token) do |
||||||
|
%Token{ |
||||||
|
available_supply: to_decimal(Chain.circulating_supply()), |
||||||
|
btc_value: original_token.btc_value, |
||||||
|
id: original_token.id, |
||||||
|
last_updated: original_token.last_updated, |
||||||
|
market_cap_usd: Decimal.mult(to_decimal(Chain.circulating_supply()), original_token.usd_value), |
||||||
|
name: original_token.name, |
||||||
|
symbol: original_token.symbol, |
||||||
|
usd_value: original_token.usd_value, |
||||||
|
volume_24h_usd: original_token.volume_24h_usd |
||||||
|
} |
||||||
|
end |
||||||
|
|
||||||
|
defp to_decimal(value) do |
||||||
|
Decimal.new(value) |
||||||
|
end |
||||||
|
|
||||||
|
@spec secondary_source() :: module() |
||||||
|
defp secondary_source do |
||||||
|
config(:secondary_source) || Explorer.ExchangeRates.Source.CoinMarketCap |
||||||
|
end |
||||||
|
|
||||||
|
@spec config(atom()) :: term |
||||||
|
defp config(key) do |
||||||
|
Application.get_env(:explorer, __MODULE__, [])[key] |
||||||
|
end |
||||||
|
end |
@ -0,0 +1,122 @@ |
|||||||
|
defmodule Explorer.Chain.Supply.TransactionAndLogTest do |
||||||
|
use Explorer.DataCase |
||||||
|
alias Explorer.Chain |
||||||
|
alias Explorer.Chain.Supply.TransactionAndLog |
||||||
|
|
||||||
|
setup do |
||||||
|
{:ok, burn_address_hash} = Chain.string_to_address_hash("0x0000000000000000000000000000000000000000") |
||||||
|
|
||||||
|
burn_address = |
||||||
|
case Chain.hash_to_address(burn_address_hash) do |
||||||
|
{:ok, burn_address} -> burn_address |
||||||
|
{:error, :not_found} -> insert(:address, hash: "0x0000000000000000000000000000000000000000") |
||||||
|
end |
||||||
|
|
||||||
|
{:ok, %{burn_address: burn_address}} |
||||||
|
end |
||||||
|
|
||||||
|
describe "total/1" do |
||||||
|
test "today with no mints or burns brings zero" do |
||||||
|
assert TransactionAndLog.total(Timex.now()) == Decimal.new(0) |
||||||
|
end |
||||||
|
|
||||||
|
test "today with mints and burns calculates a value", %{burn_address: burn_address} do |
||||||
|
old_block = insert(:block, timestamp: Timex.shift(Timex.now(), days: -1), number: 1000) |
||||||
|
|
||||||
|
insert(:log, |
||||||
|
transaction: |
||||||
|
insert(:transaction, block: old_block, block_number: 1000, cumulative_gas_used: 1, gas_used: 1, index: 2), |
||||||
|
first_topic: "0x3c798bbcf33115b42c728b8504cff11dd58736e9fa789f1cda2738db7d696b2a", |
||||||
|
data: "0x0000000000000000000000000000000000000000000000008ac7230489e80000" |
||||||
|
) |
||||||
|
|
||||||
|
insert(:internal_transaction, |
||||||
|
index: 527, |
||||||
|
transaction: |
||||||
|
insert(:transaction, block: old_block, block_number: 1000, cumulative_gas_used: 1, gas_used: 1, index: 3), |
||||||
|
to_address: burn_address, |
||||||
|
value: "0x0000000000000000000000000000000000000000000000000de0b6b3a7640000" |
||||||
|
) |
||||||
|
|
||||||
|
assert TransactionAndLog.total(Timex.now()) == Decimal.new(9) |
||||||
|
end |
||||||
|
|
||||||
|
test "yesterday with mints and burns calculates a value ignoring whatever happened today", %{ |
||||||
|
burn_address: burn_address |
||||||
|
} do |
||||||
|
old_block = insert(:block, timestamp: Timex.shift(Timex.now(), days: -1), number: 1000) |
||||||
|
|
||||||
|
insert(:log, |
||||||
|
transaction: |
||||||
|
insert(:transaction, block: old_block, block_number: 1000, cumulative_gas_used: 1, gas_used: 1, index: 2), |
||||||
|
first_topic: "0x3c798bbcf33115b42c728b8504cff11dd58736e9fa789f1cda2738db7d696b2a", |
||||||
|
data: "0x0000000000000000000000000000000000000000000000008ac7230489e80000" |
||||||
|
) |
||||||
|
|
||||||
|
new_block = insert(:block, timestamp: Timex.now(), number: 1001) |
||||||
|
|
||||||
|
insert(:internal_transaction, |
||||||
|
index: 527, |
||||||
|
transaction: |
||||||
|
insert(:transaction, block: new_block, block_number: 1000, cumulative_gas_used: 1, gas_used: 1, index: 3), |
||||||
|
to_address: burn_address, |
||||||
|
value: "0x0000000000000000000000000000000000000000000000000de0b6b3a7640000" |
||||||
|
) |
||||||
|
|
||||||
|
assert TransactionAndLog.total(Timex.shift(Timex.now(), days: -1)) == Decimal.new(10) |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
describe "total/0" do |
||||||
|
test "calculates the same value as total/1 receiving today's date", %{burn_address: burn_address} do |
||||||
|
old_block = insert(:block, timestamp: Timex.shift(Timex.now(), days: -1), number: 1000) |
||||||
|
|
||||||
|
insert(:log, |
||||||
|
transaction: |
||||||
|
insert(:transaction, block: old_block, block_number: 1000, cumulative_gas_used: 1, gas_used: 1, index: 2), |
||||||
|
first_topic: "0x3c798bbcf33115b42c728b8504cff11dd58736e9fa789f1cda2738db7d696b2a", |
||||||
|
data: "0x0000000000000000000000000000000000000000000000008ac7230489e80000" |
||||||
|
) |
||||||
|
|
||||||
|
insert(:internal_transaction, |
||||||
|
index: 527, |
||||||
|
transaction: |
||||||
|
insert(:transaction, block: old_block, block_number: 1000, cumulative_gas_used: 1, gas_used: 1, index: 3), |
||||||
|
to_address: burn_address, |
||||||
|
value: "0x0000000000000000000000000000000000000000000000000de0b6b3a7640000" |
||||||
|
) |
||||||
|
|
||||||
|
assert TransactionAndLog.total() == TransactionAndLog.total(Timex.now()) |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
describe "supply_for_days/1" do |
||||||
|
test "bring the supply of today and yesterday when receiving 2", %{burn_address: burn_address} do |
||||||
|
old_block = insert(:block, timestamp: Timex.shift(Timex.now(), days: -1), number: 1000) |
||||||
|
|
||||||
|
insert(:log, |
||||||
|
transaction: |
||||||
|
insert(:transaction, block: old_block, block_number: 1000, cumulative_gas_used: 1, gas_used: 1, index: 2), |
||||||
|
first_topic: "0x3c798bbcf33115b42c728b8504cff11dd58736e9fa789f1cda2738db7d696b2a", |
||||||
|
data: "0x0000000000000000000000000000000000000000000000008ac7230489e80000" |
||||||
|
) |
||||||
|
|
||||||
|
new_block = insert(:block, timestamp: Timex.now(), number: 1001) |
||||||
|
|
||||||
|
insert(:internal_transaction, |
||||||
|
index: 527, |
||||||
|
transaction: |
||||||
|
insert(:transaction, block: new_block, block_number: 1000, cumulative_gas_used: 1, gas_used: 1, index: 3), |
||||||
|
to_address: burn_address, |
||||||
|
value: "0x0000000000000000000000000000000000000000000000000de0b6b3a7640000" |
||||||
|
) |
||||||
|
|
||||||
|
expected_result = %{ |
||||||
|
Timex.shift(Timex.today(), days: -1) => Decimal.new(10), |
||||||
|
Timex.today() => Decimal.new(9) |
||||||
|
} |
||||||
|
|
||||||
|
assert TransactionAndLog.supply_for_days(2) == {:ok, expected_result} |
||||||
|
end |
||||||
|
end |
||||||
|
end |
@ -0,0 +1,11 @@ |
|||||||
|
defmodule Explorer.ExchangeRates.Source.TransactionAndLogTest do |
||||||
|
use Explorer.DataCase |
||||||
|
alias Explorer.ExchangeRates.Source.TransactionAndLog |
||||||
|
alias Explorer.ExchangeRates.Token |
||||||
|
|
||||||
|
describe "fetch_exchange_rates/1" do |
||||||
|
test "bring a list with one %Token{}" do |
||||||
|
assert {:ok, [%Token{}]} = TransactionAndLog.fetch_exchange_rates() |
||||||
|
end |
||||||
|
end |
||||||
|
end |
@ -0,0 +1,25 @@ |
|||||||
|
defmodule Explorer.ExchangeRates.Source.OneCoinSource do |
||||||
|
@moduledoc false |
||||||
|
|
||||||
|
alias Explorer.ExchangeRates.Source |
||||||
|
alias Explorer.ExchangeRates.Token |
||||||
|
|
||||||
|
@behaviour Source |
||||||
|
|
||||||
|
@impl Source |
||||||
|
def fetch_exchange_rates do |
||||||
|
pseudo_token = %Token{ |
||||||
|
available_supply: Decimal.new(10_000_000), |
||||||
|
btc_value: Decimal.new(1), |
||||||
|
id: "", |
||||||
|
last_updated: Timex.now(), |
||||||
|
name: "", |
||||||
|
market_cap_usd: Decimal.new(10_000_000), |
||||||
|
symbol: Explorer.coin(), |
||||||
|
usd_value: Decimal.new(1), |
||||||
|
volume_24h_usd: Decimal.new(1) |
||||||
|
} |
||||||
|
|
||||||
|
{:ok, [pseudo_token]} |
||||||
|
end |
||||||
|
end |
Loading…
Reference in new issue