feat: Celo API updates (#10629)

* fix: use atom for matching the `:chain_type`

* feat: add celo epoch number to `/api/v2/stats` response

* chore: display base fee in wei

* feat: add token info to base fee section

* fix: process review comments
pull/10673/head
Fedor Ivanov 3 months ago committed by GitHub
parent dcf88c0c81
commit acd60fb4bf
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 8
      apps/block_scout_web/lib/block_scout_web/controllers/api/v2/stats_controller.ex
  2. 68
      apps/block_scout_web/lib/block_scout_web/views/api/v2/celo_view.ex
  3. 6
      apps/explorer/lib/explorer/chain.ex
  4. 28
      apps/explorer/lib/explorer/chain/celo/reader.ex

@ -190,12 +190,18 @@ defmodule BlockScoutWeb.API.V2.StatsController do
end end
end end
"optimism" -> :optimism ->
defp add_chain_type_fields(response) do defp add_chain_type_fields(response) do
import Explorer.Counters.LastOutputRootSizeCounter, only: [fetch: 1] import Explorer.Counters.LastOutputRootSizeCounter, only: [fetch: 1]
response |> Map.put("last_output_root_size", fetch(@api_true)) response |> Map.put("last_output_root_size", fetch(@api_true))
end end
:celo ->
defp add_chain_type_fields(response) do
import Explorer.Chain.Celo.Reader, only: [last_block_epoch_number: 0]
response |> Map.put("celo", %{"epoch_number" => last_block_epoch_number()})
end
_ -> _ ->
defp add_chain_type_fields(response), do: response defp add_chain_type_fields(response), do: response
end end

@ -14,7 +14,7 @@ defmodule BlockScoutWeb.API.V2.CeloView do
alias Explorer.Chain.Celo.Helper, as: CeloHelper alias Explorer.Chain.Celo.Helper, as: CeloHelper
alias Explorer.Chain.Celo.{ElectionReward, EpochReward} alias Explorer.Chain.Celo.{ElectionReward, EpochReward}
alias Explorer.Chain.Hash alias Explorer.Chain.Hash
alias Explorer.Chain.{Block, Transaction} alias Explorer.Chain.{Block, Token, Transaction, Wei}
@address_params [ @address_params [
necessity_by_association: %{ necessity_by_association: %{
@ -97,20 +97,26 @@ defmodule BlockScoutWeb.API.V2.CeloView do
end end
def render("celo_base_fee.json", %Block{} = block) do def render("celo_base_fee.json", %Block{} = block) do
# For the blocks, where both FeeHandler and Governance contracts aren't block.transactions
# deployed, the base fee is not burnt, but refunded to transaction sender, |> Block.burnt_fees(block.base_fee_per_gas)
# so we return nil in this case. |> Wei.cast()
|> case do
base_fee = Block.burnt_fees(block.transactions, block.base_fee_per_gas) {:ok, base_fee} ->
# For the blocks, where both FeeHandler and Governance contracts aren't
fee_handler_base_fee_breakdown( # deployed, the base fee is not burnt, but refunded to transaction sender,
base_fee, # so we return nil in this case.
block.number fee_handler_base_fee_breakdown(
) || base_fee,
governance_base_fee_breakdown( block.number
base_fee, ) ||
block.number governance_base_fee_breakdown(
) base_fee,
block.number
)
_ ->
nil
end
end end
def render("celo_election_rewards.json", %{ def render("celo_election_rewards.json", %{
@ -246,7 +252,7 @@ defmodule BlockScoutWeb.API.V2.CeloView do
# Get the breakdown of the base fee for the case when FeeHandler is a contract # Get the breakdown of the base fee for the case when FeeHandler is a contract
# that receives the base fee. # that receives the base fee.
@spec fee_handler_base_fee_breakdown(Decimal.t(), Block.block_number()) :: @spec fee_handler_base_fee_breakdown(Wei.t(), Block.block_number()) ::
%{ %{
:recipient => %{optional(String.t()) => any()}, :recipient => %{optional(String.t()) => any()},
:amount => float(), :amount => float(),
@ -265,13 +271,14 @@ defmodule BlockScoutWeb.API.V2.CeloView do
{:ok, %{"address" => fee_beneficiary_address_hash}} <- {:ok, %{"address" => fee_beneficiary_address_hash}} <-
CeloCoreContracts.get_event(:fee_handler, :fee_beneficiary_set, block_number), CeloCoreContracts.get_event(:fee_handler, :fee_beneficiary_set, block_number),
{:ok, %{"value" => burn_fraction_fixidity_lib}} <- {:ok, %{"value" => burn_fraction_fixidity_lib}} <-
CeloCoreContracts.get_event(:fee_handler, :burn_fraction_set, block_number) do CeloCoreContracts.get_event(:fee_handler, :burn_fraction_set, block_number),
{:ok, celo_token_address_hash} <- CeloCoreContracts.get_address(:celo_token, block_number) do
burn_fraction = burn_fraction_decimal(burn_fraction_fixidity_lib) burn_fraction = burn_fraction_decimal(burn_fraction_fixidity_lib)
burnt_amount = Decimal.mult(base_fee, burn_fraction) burnt_amount = Wei.mult(base_fee, burn_fraction)
burnt_percentage = Decimal.mult(burn_fraction, 100) burnt_percentage = Decimal.mult(burn_fraction, 100)
carbon_offsetting_amount = Decimal.sub(base_fee, burnt_amount) carbon_offsetting_amount = Wei.sub(base_fee, burnt_amount)
carbon_offsetting_percentage = Decimal.sub(100, burnt_percentage) carbon_offsetting_percentage = Decimal.sub(100, burnt_percentage)
celo_burn_address_hash_string = dead_address_hash_string() celo_burn_address_hash_string = dead_address_hash_string()
@ -311,18 +318,25 @@ defmodule BlockScoutWeb.API.V2.CeloView do
} }
) )
celo_token = Token.get_by_contract_address_hash(celo_token_address_hash, api?: true)
%{ %{
recipient: fee_handler_contract_address_info, recipient: fee_handler_contract_address_info,
amount: base_fee, amount: base_fee,
token:
TokenView.render("token.json", %{
token: celo_token,
contract_address_hash: celo_token.contract_address_hash
}),
breakdown: [ breakdown: [
%{ %{
address: burn_address_info, address: burn_address_info,
amount: Decimal.to_float(burnt_amount), amount: burnt_amount,
percentage: Decimal.to_float(burnt_percentage) percentage: Decimal.to_float(burnt_percentage)
}, },
%{ %{
address: fee_beneficiary_address_info, address: fee_beneficiary_address_info,
amount: Decimal.to_float(carbon_offsetting_amount), amount: carbon_offsetting_amount,
percentage: Decimal.to_float(carbon_offsetting_percentage) percentage: Decimal.to_float(carbon_offsetting_percentage)
} }
] ]
@ -337,7 +351,7 @@ defmodule BlockScoutWeb.API.V2.CeloView do
# #
# Note that the base fee is not burnt in this case, but simply kept on the # Note that the base fee is not burnt in this case, but simply kept on the
# contract balance. # contract balance.
@spec governance_base_fee_breakdown(Decimal.t(), Block.block_number()) :: @spec governance_base_fee_breakdown(Wei.t(), Block.block_number()) ::
%{ %{
:recipient => %{optional(String.t()) => any()}, :recipient => %{optional(String.t()) => any()},
:amount => float(), :amount => float(),
@ -353,7 +367,8 @@ defmodule BlockScoutWeb.API.V2.CeloView do
defp governance_base_fee_breakdown(base_fee, block_number) do defp governance_base_fee_breakdown(base_fee, block_number) do
with {:ok, address_hash_string} when not is_nil(address_hash_string) <- with {:ok, address_hash_string} when not is_nil(address_hash_string) <-
CeloCoreContracts.get_address(:governance, block_number), CeloCoreContracts.get_address(:governance, block_number),
{:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string) do {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string),
{:ok, celo_token_address_hash} <- CeloCoreContracts.get_address(:celo_token, block_number) do
address = address =
address_hash address_hash
# todo: Querying database in the view is not a good practice. Consider # todo: Querying database in the view is not a good practice. Consider
@ -370,9 +385,16 @@ defmodule BlockScoutWeb.API.V2.CeloView do
address_hash address_hash
) )
celo_token = Token.get_by_contract_address_hash(celo_token_address_hash, api?: true)
%{ %{
recipient: address_with_info, recipient: address_with_info,
amount: base_fee, amount: base_fee,
token:
TokenView.render("token.json", %{
token: celo_token,
contract_address_hash: celo_token.contract_address_hash
}),
breakdown: [] breakdown: []
} }
else else

@ -2105,11 +2105,11 @@ defmodule Explorer.Chain do
{:error, :not_found} {:error, :not_found}
""" """
@spec max_consensus_block_number() :: {:ok, Block.block_number()} | {:error, :not_found} @spec max_consensus_block_number(Keyword.t()) :: {:ok, Block.block_number()} | {:error, :not_found}
def max_consensus_block_number do def max_consensus_block_number(options \\ []) do
Block Block
|> where(consensus: true) |> where(consensus: true)
|> Repo.aggregate(:max, :number) |> select_repo(options).aggregate(:max, :number)
|> case do |> case do
nil -> {:error, :not_found} nil -> {:error, :not_found}
number -> {:ok, number} number -> {:ok, number}

@ -9,11 +9,13 @@ defmodule Explorer.Chain.Celo.Reader do
only: [ only: [
select_repo: 1, select_repo: 1,
join_associations: 2, join_associations: 2,
default_paging_options: 0 default_paging_options: 0,
max_consensus_block_number: 1
] ]
alias Explorer.Chain.Cache.CeloCoreContracts alias Explorer.Chain.Block
alias Explorer.Chain.Celo.ElectionReward alias Explorer.Chain.Cache.{Blocks, CeloCoreContracts}
alias Explorer.Chain.Celo.{ElectionReward, Helper}
alias Explorer.Chain.{Hash, Token, Wei} alias Explorer.Chain.{Hash, Token, Wei}
@election_reward_types ElectionReward.types() @election_reward_types ElectionReward.types()
@ -190,4 +192,24 @@ defmodule Explorer.Chain.Celo.Reader do
{reward_type_atom, Map.get(token_atom_to_token, token_atom)} {reward_type_atom, Map.get(token_atom_to_token, token_atom)}
end) end)
end end
@doc """
Retrieves the epoch number of the last fetched block.
"""
@spec last_block_epoch_number(Keyword.t()) :: Block.block_number() | nil
def last_block_epoch_number(options \\ []) do
block_number =
1
|> Blocks.atomic_take_enough()
|> case do
[%Block{number: number}] -> {:ok, number}
nil -> max_consensus_block_number(options)
end
|> case do
{:ok, number} -> number
_ -> nil
end
block_number && Helper.block_number_to_epoch_number(block_number)
end
end end

Loading…
Cancel
Save