From cfaa2d7a3ff5f4b26cda8a3b66f15547effd98b2 Mon Sep 17 00:00:00 2001 From: POA <33550681+poa@users.noreply.github.com> Date: Tue, 9 Mar 2021 10:40:16 +0300 Subject: [PATCH] Sort validator pools by APY descending in Staking DApp --- .dialyzer-ignore | 2 +- .../controllers/stakes_controller.ex | 93 ++++++++++++++----- .../lib/explorer/staking/contract_state.ex | 5 +- .../explorer/staking/stake_snapshotting.ex | 56 ++++++++--- 4 files changed, 116 insertions(+), 40 deletions(-) diff --git a/.dialyzer-ignore b/.dialyzer-ignore index a75410c17d..9858a6a111 100644 --- a/.dialyzer-ignore +++ b/.dialyzer-ignore @@ -25,4 +25,4 @@ lib/explorer/exchange_rates/source.ex:113 lib/explorer/smart_contract/verifier.ex:89 lib/block_scout_web/templates/address_contract/index.html.eex:118 lib/explorer/staking/stake_snapshotting.ex:15: Function do_snapshotting/6 has no local return -lib/explorer/staking/stake_snapshotting.ex:202 +lib/explorer/staking/stake_snapshotting.ex:214 diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/stakes_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/stakes_controller.ex index 8c8e534ecf..fea1928320 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/stakes_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/stakes_controller.ex @@ -70,10 +70,15 @@ defmodule BlockScoutWeb.StakesController do if Map.has_key?(params, "filterMy") do [paging_options: options] = paging_options(params) - last_index = - params - |> Map.get("position", "0") - |> String.to_integer() + # turn off paging for Validators page as we sort by APY below + # and max number of validators is no more than 19 + # (for the current POSDAO implementation) + options = + if is_page_unlimited?(filter) do + Map.put(options, :page_size, 1_000_000) + else + options + end pools_plus_one = Chain.staking_pools( @@ -86,21 +91,7 @@ defmodule BlockScoutWeb.StakesController do params["filterMy"] == "true" ) - {pools, next_page} = split_list_by_page(pools_plus_one) - - next_page_path = - case next_page_params(next_page, pools, params) do - nil -> - nil - - next_page_params -> - updated_page_params = - next_page_params - |> Map.delete("type") - |> Map.put("position", last_index + 1) - - next_page_path(filter, conn, updated_page_params) - end + {pools, next_page_path, last_index} = get_one_page(filter, conn, params, pools_plus_one) average_block_time = AverageBlockTime.average_block_time() @@ -119,14 +110,13 @@ defmodule BlockScoutWeb.StakesController do calc_apy_enabled = ContractState.calc_apy_enabled?() snapshotted_delegator_data = snapshotted_delegator_data(filter, calc_apy_enabled) - items = + pools = pools - |> Enum.with_index(last_index + 1) - |> Enum.map(fn {%{pool: pool, delegator: delegator}, index} -> + |> Enum.map(fn %{pool: pool} = item -> apy = - if calc_apy_enabled and snapshotted_delegator_data != nil do + if calc_apy_enabled and snapshotted_delegator_data !== nil do calc_apy( - pool, + item.pool, pool_rewards, snapshotted_delegator_data, average_block_time_seconds, @@ -134,11 +124,22 @@ defmodule BlockScoutWeb.StakesController do ) end + pool = Map.put(pool, :apy, apy) + Map.put(item, :pool, pool) + end) + + # sort pools on Validators page by descending APY if all APYs known + pools = sort_pools_by_apy(pools) + + items = + pools + |> Enum.with_index(last_index + 1) + |> Enum.map(fn {%{pool: pool, delegator: delegator}, index} -> View.render_to_string( StakesView, "_rows.html", token: token, - pool: Map.put(pool, :apy, apy), + pool: pool, delegator: delegator, index: index, average_block_time: average_block_time, @@ -227,8 +228,50 @@ defmodule BlockScoutWeb.StakesController do end end + defp sort_pools_by_apy(pools) do + if Enum.all?(pools, fn item -> item.pool.apy !== nil end) do + Enum.sort(pools, fn item1, item2 -> item1.pool.apy.apy_raw >= item2.pool.apy.apy_raw end) + else + pools + end + end + defp address_bytes_to_string(hash), do: "0x" <> Base.encode16(hash, case: :lower) + defp get_one_page(filter, conn, params, pools_plus_one) do + last_index = + params + |> Map.get("position", "0") + |> String.to_integer() + + {pools, next_page} = + if is_page_unlimited?(filter) do + {pools_plus_one, []} + else + split_list_by_page(pools_plus_one) + end + + next_page_path = + case next_page_params(next_page, pools, params) do + nil -> + nil + + next_page_params -> + updated_page_params = + next_page_params + |> Map.delete("type") + |> Map.put("position", last_index + 1) + + next_page_path(filter, conn, updated_page_params) + end + + {pools, next_page_path, last_index} + end + + defp is_page_unlimited?(filter) do + filter == :validator + end + defp next_page_path(:validator, conn, params) do validators_path(conn, :index, params) end diff --git a/apps/explorer/lib/explorer/staking/contract_state.ex b/apps/explorer/lib/explorer/staking/contract_state.ex index a0e5b92588..158d770244 100644 --- a/apps/explorer/lib/explorer/staking/contract_state.ex +++ b/apps/explorer/lib/explorer/staking/contract_state.ex @@ -296,7 +296,8 @@ defmodule Explorer.Staking.ContractState do epochs_per_year = floor(31_536_000 / average_block_time / staking_epoch_duration) predicted_reward = decimal_to_float(reward_ratio) * pool_reward / 100 apy = predicted_reward / decimal_to_integer(stake_amount) * epochs_per_year * 100 - %{apy: "#{floor(apy * 100) / 100}%", predicted_reward: floor(predicted_reward)} + apy_raw = floor(apy * 100) / 100 + %{apy: "#{apy_raw}%", predicted_reward: floor(predicted_reward), apy_raw: apy_raw} end end @@ -349,7 +350,7 @@ defmodule Explorer.Staking.ContractState do defp start_snapshotting?(global_responses) do global_responses.epoch_number > get(:snapshotted_epoch_number) && global_responses.epoch_number > 0 && - not get(:is_snapshotting) && (global_responses.epoch_number >= 49 || global_responses.epoch_number < 47) + not get(:is_snapshotting) end defp update_database( diff --git a/apps/explorer/lib/explorer/staking/stake_snapshotting.ex b/apps/explorer/lib/explorer/staking/stake_snapshotting.ex index d6db33d150..e46dd91ce3 100644 --- a/apps/explorer/lib/explorer/staking/stake_snapshotting.ex +++ b/apps/explorer/lib/explorer/staking/stake_snapshotting.ex @@ -21,6 +21,12 @@ defmodule Explorer.Staking.StakeSnapshotting do mining_address_to_id, block_number ) do + # temporary code (should be removed after staking epoch #48 on xDai is finished). + # this is a block number from which POSDAO on xDai chain started to use other signatures + # in the StakingAuRa contract + {:ok, net_version} = EthereumJSONRPC.fetch_net_version(Application.get_env(:explorer, :json_rpc_named_arguments)) + new_signatures = (net_version == 100 and block_number > 14_994_040) or net_version != 100 + # get pool ids and staking addresses for the pending validators pool_ids = pools_mining_addresses @@ -51,7 +57,7 @@ defmodule Explorer.Staking.StakeSnapshotting do Map.merge( resp, ContractReader.perform_requests( - snapshotted_pool_amounts_requests(pool_id, resp.staking_address_hash, block_number), + snapshotted_pool_amounts_requests(pool_id, resp.staking_address_hash, block_number, new_signatures), contracts, abi ) @@ -66,7 +72,7 @@ defmodule Explorer.Staking.StakeSnapshotting do }, ContractReader.perform_requests( ContractReader.active_delegators_request(pool_id, block_number) ++ - snapshotted_pool_amounts_requests(pool_id, pool_staking_address, block_number), + snapshotted_pool_amounts_requests(pool_id, pool_staking_address, block_number, new_signatures), contracts, abi ) @@ -89,7 +95,13 @@ defmodule Explorer.Staking.StakeSnapshotting do stakers |> Enum.map(fn {pool_id, pool_staking_address, staker_address} -> ContractReader.perform_requests( - snapshotted_staker_amount_request(pool_id, pool_staking_address, staker_address, block_number), + snapshotted_staker_amount_request( + pool_id, + pool_staking_address, + staker_address, + block_number, + new_signatures + ), contracts, abi ) @@ -219,18 +231,30 @@ defmodule Explorer.Staking.StakeSnapshotting do defp address_bytes_to_string(hash), do: "0x" <> Base.encode16(hash, case: :lower) - defp snapshotted_pool_amounts_requests(pool_id, pool_staking_address, block_number) do + defp snapshotted_pool_amounts_requests(pool_id, pool_staking_address, block_number, new_signatures) do + stake_amount_total_signature = + if new_signatures do + # keccak256(stakeAmountTotal(uint256)) + "2a8f6ecd" + else + # keccak256(stakeAmountTotal(address)) + "5267e1d6" + end + [ - # 2a8f6ecd = keccak256(stakeAmountTotal(uint256)) - snapshotted_total_staked_amount: {:staking, "2a8f6ecd", [pool_id], block_number}, + snapshotted_total_staked_amount: {:staking, stake_amount_total_signature, [pool_id], block_number}, snapshotted_self_staked_amount: - snapshotted_staker_amount_request(pool_id, pool_staking_address, pool_staking_address, block_number)[ - :snapshotted_stake_amount - ] + snapshotted_staker_amount_request( + pool_id, + pool_staking_address, + pool_staking_address, + block_number, + new_signatures + )[:snapshotted_stake_amount] ] end - defp snapshotted_staker_amount_request(pool_id, pool_staking_address, staker_address, block_number) do + defp snapshotted_staker_amount_request(pool_id, pool_staking_address, staker_address, block_number, new_signatures) do delegator_or_zero = if staker_address == pool_staking_address do "0x0000000000000000000000000000000000000000" @@ -238,9 +262,17 @@ defmodule Explorer.Staking.StakeSnapshotting do staker_address end + stake_amount_signature = + if new_signatures do + # keccak256(stakeAmount(uint256,address)) + "3fb1a1e4" + else + # keccak256(stakeAmount(address,address)) + "a697ecff" + end + [ - # 3fb1a1e4 = keccak256(stakeAmount(uint256,address)) - snapshotted_stake_amount: {:staking, "3fb1a1e4", [pool_id, delegator_or_zero], block_number} + snapshotted_stake_amount: {:staking, stake_amount_signature, [pool_id, delegator_or_zero], block_number} ] end