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