Fetch unremovable staking and disallow staking to empty pool (#2608)

is_unremovable field is added to staking pools and filled using contract
getter unremovableValidator.
If such validator hasn't staked to itself yet, Stake button is disabled.
Also, Remove My Pool button is disabled for such validator.

Also, fetching is_validator field is optimized, and is now performed using
one getValidators call instead of one isValidator call per pool.
staking
Paul Tsupikoff 5 years ago committed by Victor Baranov
parent 31b90102de
commit 2344222930
  1. 10
      apps/block_scout_web/lib/block_scout_web/controllers/stakes_controller.ex
  2. 4
      apps/block_scout_web/lib/block_scout_web/templates/stakes/_stakes_modal_move.html.eex
  3. 4
      apps/block_scout_web/lib/block_scout_web/templates/stakes/_stakes_top.html.eex
  4. 1
      apps/explorer/lib/explorer/chain/import/runner/staking_pools.ex
  5. 5
      apps/explorer/lib/explorer/chain/staking_pool.ex
  6. 4
      apps/explorer/lib/explorer/staking/contract_reader.ex
  7. 12
      apps/explorer/lib/explorer/staking/contract_state.ex
  8. 9
      apps/explorer/priv/repo/migrations/20190820212841_add_unremovable_to_staking_pools.exs
  9. 11
      apps/explorer/test/explorer/staking/contract_state_test.exs

@ -92,7 +92,7 @@ defmodule BlockScoutWeb.StakesController do
average_block_time: average_block_time, average_block_time: average_block_time,
pools_type: filter, pools_type: filter,
buttons: %{ buttons: %{
stake: true, stake: stake_allowed?(pool, delegator),
move: move_allowed?(delegator), move: move_allowed?(delegator),
withdraw: withdraw_allowed?(delegator, epoch_number) withdraw: withdraw_allowed?(delegator, epoch_number)
} }
@ -130,6 +130,14 @@ defmodule BlockScoutWeb.StakesController do
inactive_pools_path(conn, :index, params) inactive_pools_path(conn, :index, params)
end end
defp stake_allowed?(pool, nil) do
Decimal.positive?(pool.self_staked_amount)
end
defp stake_allowed?(pool, delegator) do
Decimal.positive?(pool.self_staked_amount) or delegator.delegator_address_hash == pool.staking_address_hash
end
defp move_allowed?(nil), do: false defp move_allowed?(nil), do: false
defp move_allowed?(delegator) do defp move_allowed?(delegator) do

@ -26,7 +26,9 @@
<div class="input-group form-group"> <div class="input-group form-group">
<select pool-select class="form-control"> <select pool-select class="form-control">
<option disabled <%= unless @pool_to do "selected" end %>><%= gettext("Choose Pool") %></option> <option disabled <%= unless @pool_to do "selected" end %>><%= gettext("Choose Pool") %></option>
<%= for %{pool: pool} <- @pools, pool.staking_address_hash != @pool_from.staking_address_hash do %> <%= for %{pool: pool} <- @pools,
pool.staking_address_hash != @pool_from.staking_address_hash,
Decimal.positive?(pool.self_staked_amount) or pool.staking_address_hash == @delegator.delegator_address_hash do %>
<option <option
value="<%= pool.staking_address_hash %>" value="<%= pool.staking_address_hash %>"
<%= if @pool_to && pool.staking_address_hash == @pool_to.staking_address_hash do "selected" end %> <%= if @pool_to && pool.staking_address_hash == @pool_to.staking_address_hash do "selected" end %>

@ -8,7 +8,9 @@
<!-- Buttons --> <!-- Buttons -->
<div class="stakes-top-buttons"> <div class="stakes-top-buttons">
<%= if @account[:pool] && @account.pool.is_active do %> <%= if @account[:pool] && @account.pool.is_active do %>
<%= render BlockScoutWeb.StakesView, "_stakes_btn_remove_pool.html", text: gettext("Remove My Pool"), extra_class: "js-remove-pool" %> <%= unless @account.pool.is_unremovable do %>
<%= render BlockScoutWeb.StakesView, "_stakes_btn_remove_pool.html", text: gettext("Remove My Pool"), extra_class: "js-remove-pool" %>
<% end %>
<% else %> <% else %>
<%= render BlockScoutWeb.CommonComponentsView, "_btn_add_full.html", text: gettext("Become a Candidate"), extra_class: "js-become-candidate" %> <%= render BlockScoutWeb.CommonComponentsView, "_btn_add_full.html", text: gettext("Become a Candidate"), extra_class: "js-become-candidate" %>
<% end %> <% end %>

@ -127,6 +127,7 @@ defmodule Explorer.Chain.Import.Runner.StakingPools do
is_active: fragment("EXCLUDED.is_active"), is_active: fragment("EXCLUDED.is_active"),
is_banned: fragment("EXCLUDED.is_banned"), is_banned: fragment("EXCLUDED.is_banned"),
is_validator: fragment("EXCLUDED.is_validator"), is_validator: fragment("EXCLUDED.is_validator"),
is_unremovable: fragment("EXCLUDED.is_unremovable"),
likelihood: fragment("EXCLUDED.likelihood"), likelihood: fragment("EXCLUDED.likelihood"),
block_reward_ratio: fragment("EXCLUDED.block_reward_ratio"), block_reward_ratio: fragment("EXCLUDED.block_reward_ratio"),
staked_ratio: fragment("EXCLUDED.staked_ratio"), staked_ratio: fragment("EXCLUDED.staked_ratio"),

@ -20,6 +20,7 @@ defmodule Explorer.Chain.StakingPool do
is_active: boolean, is_active: boolean,
is_banned: boolean, is_banned: boolean,
is_validator: boolean, is_validator: boolean,
is_unremovable: boolean,
likelihood: Decimal.t(), likelihood: Decimal.t(),
block_reward_ratio: Decimal.t(), block_reward_ratio: Decimal.t(),
staked_ratio: Decimal.t(), staked_ratio: Decimal.t(),
@ -34,11 +35,12 @@ defmodule Explorer.Chain.StakingPool do
is_active delegators_count staked_amount self_staked_amount is_validator is_active delegators_count staked_amount self_staked_amount is_validator
was_validator_count is_banned was_banned_count banned_until likelihood was_validator_count is_banned was_banned_count banned_until likelihood
staked_ratio staking_address_hash mining_address_hash block_reward_ratio staked_ratio staking_address_hash mining_address_hash block_reward_ratio
is_unremovable
)a )a
@req_attrs ~w( @req_attrs ~w(
is_active delegators_count staked_amount self_staked_amount is_validator is_active delegators_count staked_amount self_staked_amount is_validator
was_validator_count is_banned was_banned_count banned_until was_validator_count is_banned was_banned_count banned_until
staking_address_hash mining_address_hash staking_address_hash mining_address_hash is_unremovable
)a )a
schema "staking_pools" do schema "staking_pools" do
@ -47,6 +49,7 @@ defmodule Explorer.Chain.StakingPool do
field(:is_active, :boolean, default: false) field(:is_active, :boolean, default: false)
field(:is_banned, :boolean, default: false) field(:is_banned, :boolean, default: false)
field(:is_validator, :boolean, default: false) field(:is_validator, :boolean, default: false)
field(:is_unremovable, :boolean, default: false)
field(:likelihood, :decimal) field(:likelihood, :decimal)
field(:block_reward_ratio, :decimal) field(:block_reward_ratio, :decimal)
field(:staked_ratio, :decimal) field(:staked_ratio, :decimal)

@ -17,7 +17,8 @@ defmodule Explorer.Staking.ContractReader do
inactive_pools: {:staking, "getPoolsInactive", []}, inactive_pools: {:staking, "getPoolsInactive", []},
pools_likely: {:staking, "getPoolsToBeElected", []}, pools_likely: {:staking, "getPoolsToBeElected", []},
pools_likelihood: {:staking, "getPoolsLikelihood", []}, pools_likelihood: {:staking, "getPoolsLikelihood", []},
validators: {:validator_set, "getValidators", []} validators: {:validator_set, "getValidators", []},
unremovable_validator: {:validator_set, "unremovableValidator", []}
] ]
end end
@ -37,7 +38,6 @@ defmodule Explorer.Staking.ContractReader do
def pool_mining_requests(mining_address) do def pool_mining_requests(mining_address) do
[ [
is_validator: {:validator_set, "isValidator", [mining_address]},
was_validator_count: {:validator_set, "validatorCounter", [mining_address]}, was_validator_count: {:validator_set, "validatorCounter", [mining_address]},
is_banned: {:validator_set, "isValidatorBanned", [mining_address]}, is_banned: {:validator_set, "isValidatorBanned", [mining_address]},
banned_until: {:validator_set, "bannedUntil", [mining_address]}, banned_until: {:validator_set, "bannedUntil", [mining_address]},

@ -128,6 +128,8 @@ defmodule Explorer.Staking.ContractState do
:ets.insert(@table_name, settings) :ets.insert(@table_name, settings)
pools = global_responses.active_pools ++ global_responses.inactive_pools pools = global_responses.active_pools ++ global_responses.inactive_pools
is_validator = Enum.into(global_responses.validators, %{}, &{hash_to_string(&1), true})
unremovable_validator = global_responses.unremovable_validator
pool_staking_responses = pool_staking_responses =
pools pools
@ -179,7 +181,9 @@ defmodule Explorer.Staking.ContractState do
end, end,
likelihood: ratio(likelihood[staking_address] || 0, total_likelihood), likelihood: ratio(likelihood[staking_address] || 0, total_likelihood),
block_reward_ratio: staking_response.block_reward / 10_000, block_reward_ratio: staking_response.block_reward / 10_000,
is_deleted: false is_deleted: false,
is_validator: is_validator[staking_response.mining_address_hash] || false,
is_unremovable: hash_to_string(staking_address) == unremovable_validator
} }
|> Map.merge( |> Map.merge(
Map.take(staking_response, [ Map.take(staking_response, [
@ -191,7 +195,6 @@ defmodule Explorer.Staking.ContractState do
) )
|> Map.merge( |> Map.merge(
Map.take(mining_response, [ Map.take(mining_response, [
:is_validator,
:was_validator_count, :was_validator_count,
:is_banned, :is_banned,
:banned_until, :banned_until,
@ -203,10 +206,9 @@ defmodule Explorer.Staking.ContractState do
delegator_entries = delegator_entries =
Enum.map(delegator_responses, fn {{pool_address, delegator_address, is_active}, response} -> Enum.map(delegator_responses, fn {{pool_address, delegator_address, is_active}, response} ->
staking_response = pool_staking_responses[pool_address] staking_response = pool_staking_responses[pool_address]
mining_response = pool_mining_responses[pool_address]
reward_ratio = reward_ratio =
if mining_response.is_validator do if is_validator[staking_response.mining_address_hash] do
reward_ratio = delegator_rewards[pool_address][delegator_address] reward_ratio = delegator_rewards[pool_address][delegator_address]
if reward_ratio do if reward_ratio do
@ -304,6 +306,8 @@ defmodule Explorer.Staking.ContractState do
defp ratio(_numerator, 0), do: 0 defp ratio(_numerator, 0), do: 0
defp ratio(numerator, denominator), do: numerator / denominator * 100 defp ratio(numerator, denominator), do: numerator / denominator * 100
defp hash_to_string(hash), do: "0x" <> Base.encode16(hash, case: :lower)
# sobelow_skip ["Traversal"] # sobelow_skip ["Traversal"]
defp abi(file_name) do defp abi(file_name) do
:explorer :explorer

@ -0,0 +1,9 @@
defmodule Explorer.Repo.Migrations.AddUnremovableToStakingPools do
use Ecto.Migration
def change do
alter table(:staking_pools) do
add(:is_unremovable, :boolean, default: false, null: false)
end
end
end

@ -76,7 +76,7 @@ defmodule Explorer.Staking.ContractStateTest do
EthereumJSONRPC.Mox, EthereumJSONRPC.Mox,
:json_rpc, :json_rpc,
fn requests, _opts -> fn requests, _opts ->
assert length(requests) == 11 assert length(requests) == 12
{:ok, {:ok,
format_responses([ format_responses([
@ -90,7 +90,8 @@ defmodule Explorer.Staking.ContractStateTest do
"0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000b6695f5c2e3f5eff8036b5f5f3a9d83a5310e51e", "0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000b6695f5c2e3f5eff8036b5f5f3a9d83a5310e51e",
"0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000b916e7e1f4bcb13549602ed042d36746fd0d96c9000000000000000000000000db9cb2478d917719c53862008672166808258577", "0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000b916e7e1f4bcb13549602ed042d36746fd0d96c9000000000000000000000000db9cb2478d917719c53862008672166808258577",
"0x00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000514000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000c8000000000000000000000000000000000000000000000000000000000000044c", "0x00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000514000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000c8000000000000000000000000000000000000000000000000000000000000044c",
"0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000003000000000000000000000000bbcaa8d48289bb1ffcf9808d9aa4b1d215054c78000000000000000000000000f67cc5231c5858ad6cc87b105217426e17b824bb000000000000000000000000be69eb0968226a1808975e1a1f2127667f2bffb3" "0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000003000000000000000000000000bbcaa8d48289bb1ffcf9808d9aa4b1d215054c78000000000000000000000000f67cc5231c5858ad6cc87b105217426e17b824bb000000000000000000000000be69eb0968226a1808975e1a1f2127667f2bffb3",
"0x0000000000000000000000000b2f5e2f3cbd864eaa2c642e3769c1582361caf6"
])} ])}
end end
) )
@ -147,26 +148,22 @@ defmodule Explorer.Staking.ContractStateTest do
EthereumJSONRPC.Mox, EthereumJSONRPC.Mox,
:json_rpc, :json_rpc,
fn requests, _opts -> fn requests, _opts ->
assert length(requests) == 20 assert length(requests) == 16
{:ok, {:ok,
format_responses([ format_responses([
"0x0000000000000000000000000000000000000000000000000000000000000001",
"0x000000000000000000000000000000000000000000000000000000000000004b", "0x000000000000000000000000000000000000000000000000000000000000004b",
"0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000",
"0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000",
"0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000",
"0x0000000000000000000000000000000000000000000000000000000000000000",
"0x0000000000000000000000000000000000000000000000000000000000000002", "0x0000000000000000000000000000000000000000000000000000000000000002",
"0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000",
"0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000",
"0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000",
"0x0000000000000000000000000000000000000000000000000000000000000001",
"0x000000000000000000000000000000000000000000000000000000000000004a", "0x000000000000000000000000000000000000000000000000000000000000004a",
"0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000",
"0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000",
"0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000",
"0x0000000000000000000000000000000000000000000000000000000000000001",
"0x000000000000000000000000000000000000000000000000000000000000004a", "0x000000000000000000000000000000000000000000000000000000000000004a",
"0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000",
"0x0000000000000000000000000000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000",

Loading…
Cancel
Save