Add general elements to Claim Reward dialog popup

staking
Vadim 5 years ago committed by Victor Baranov
parent 8db83a720a
commit f581780558
  1. 39
      apps/block_scout_web/assets/css/components/stakes/_modal_claim_reward.scss
  2. 1
      apps/block_scout_web/assets/css/stakes.scss
  3. 44
      apps/block_scout_web/assets/js/pages/stakes/claim_reward.js
  4. 70
      apps/block_scout_web/lib/block_scout_web/channels/stakes_channel.ex
  5. 2
      apps/block_scout_web/lib/block_scout_web/templates/stakes/_stakes_btn_stake.html.eex
  6. 2
      apps/block_scout_web/lib/block_scout_web/templates/stakes/_stakes_btn_withdraw.html.eex
  7. 3
      apps/block_scout_web/lib/block_scout_web/templates/stakes/_stakes_modal_claim_reward.html.eex
  8. 35
      apps/block_scout_web/lib/block_scout_web/templates/stakes/_stakes_modal_claim_reward_content.html.eex
  9. 57
      apps/explorer/lib/explorer/staking/contract_reader.ex

@ -0,0 +1,39 @@
.modal-claim-reward {
max-width: 400px;
width: 100%;
@include media-breakpoint-down(sm) {
margin-left: auto;
margin-right: auto;
}
}
.modal-claim-reward {
label {
margin-bottom: 0;
}
p {
margin-bottom: 0.3rem;
width: 100%;
&.m-b-0 {
margin-bottom: 0;
}
&.left {
width: 49%;
float: left;
}
&.right {
width: 49%;
float: right;
}
}
textarea {
background: #fff!important;
height: 38px;
}
}

@ -16,6 +16,7 @@
@import "components/stakes/stakes_progress";
@import "components/stakes/modal_stake";
@import "components/stakes/modal_become_candidate";
@import "components/stakes/modal_claim_reward";
@import "components/stakes/modal_validator_info";
@import "components/stakes/modal_delegators_info";
@import "components/stakes/modal_bottom_disclaimer";

@ -10,29 +10,30 @@ export function openClaimRewardModal(store) {
channel.push('render_claim_reward', { preload: true }).receive('ok', msg => {
const $modal = $(msg.html)
const closeButton = $modal.find('.close-modal')
const modalBody = $('.modal-body', $modal)
const waitingMessageContainer = modalBody.find('p')
const $closeButton = $modal.find('.close-modal')
const $modalBody = $('.modal-body', $modal)
const $waitingMessageContainer = $modalBody.find('p')
let dotCounter = 0
const dotCounterInterval = setInterval(() => {
let waitingMessage = $.trim(waitingMessageContainer.text())
let waitingMessage = $.trim($waitingMessageContainer.text())
if (!waitingMessage.endsWith('.')) {
waitingMessage = waitingMessage + '.'
}
waitingMessage = waitingMessage.replace(/\.+$/g, " " + ".".repeat(dotCounter))
waitingMessageContainer.text(waitingMessage)
$waitingMessageContainer.text(waitingMessage)
dotCounter = (dotCounter + 1) % 4
}, 500)
closeButton.hide()
$closeButton.hide()
lockModal($modal)
channel.on('claim_reward_pools', msg_pools => {
channel.off('claim_reward_pools')
closeButton.show()
$closeButton.show()
unlockModal($modal)
clearInterval(dotCounterInterval)
modalBody.html(msg_pools.html)
$modalBody.html(msg_pools.html)
onPoolsFound($modal, $modalBody)
})
$modal.on('shown.bs.modal', () => {
channel.push('render_claim_reward', {}).receive('error', (error) => {
@ -40,7 +41,7 @@ export function openClaimRewardModal(store) {
})
})
$modal.on('hidden.bs.modal', () => {
$(this).remove()
$modal.remove()
})
openModal($modal);
@ -48,3 +49,28 @@ export function openClaimRewardModal(store) {
openErrorModal('Claim Reward', error.reason)
})
}
function onPoolsFound($modal, $modalBody) {
const $poolsDropdown = $('[pool-select]', $modalBody)
const $epochChoiceRadio = $('input[name="epoch_choice"]', $modalBody)
const $specifiedEpochsText = $('.specified-epochs', $modalBody)
$poolsDropdown.on('change', () => {
const data = $('option:selected', this).data()
const $poolInfo = $('.selected-pool-info', $modalBody)
$poolsDropdown.blur()
$('textarea', $poolInfo).val(data.epochs ? data.epochs : '')
$('#token-reward-sum', $poolInfo).html(data.tokenRewardSum ? data.tokenRewardSum : '0')
$('#native-reward-sum', $poolInfo).html(data.nativeRewardSum ? data.nativeRewardSum : '0')
$('#tx-gas-limit', $poolInfo).html(data.gasLimit ? '~' + data.gasLimit : '0')
$('#epoch-choice-all', $poolInfo).click()
$specifiedEpochsText.val('')
$poolInfo.removeClass('hidden')
$('.modal-bottom-disclaimer', $modal).removeClass('hidden')
})
$epochChoiceRadio.on('change', () => {
$specifiedEpochsText.toggleClass('hidden')
})
}

@ -375,29 +375,61 @@ defmodule BlockScoutWeb.StakesChannel do
end
end)
pools = if error != nil do
%{}
{error, pools} = if error != nil do
{error, %{}}
else
block_reward_contract = ContractState.get(:block_reward_contract)
Enum.map(pools_amounts, fn {_, amounts} -> amounts end)
|> Enum.zip(pools)
|> Enum.filter(fn {amounts, _} -> amounts.token_reward_sum > 0 || amounts.native_reward_sum > 0 end)
|> Enum.map(fn {amounts, pool_staking_address} ->
responses =
ContractReader.epochs_to_claim_reward_from_request(pool_staking_address, staker)
|> ContractReader.perform_requests(%{block_reward: block_reward_contract.address}, block_reward_contract.abi)
epochs =
array_to_ranges(responses[:epochs])
|> Enum.map(fn {first, last} ->
Integer.to_string(first) <> (if first != last, do: "-" <> Integer.to_string(last), else: "")
end)
data = Map.put(amounts, :epochs, Enum.join(epochs, ","))
{data, pool_staking_address}
pools =
pools_amounts
|> Enum.map(fn {_, amounts} -> amounts end)
|> Enum.zip(pools)
|> Enum.filter(fn {amounts, _} -> amounts.token_reward_sum > 0 || amounts.native_reward_sum > 0 end)
|> Enum.map(fn {amounts, pool_staking_address} ->
responses =
ContractReader.epochs_to_claim_reward_from_request(pool_staking_address, staker)
|> ContractReader.perform_requests(%{block_reward: block_reward_contract.address}, block_reward_contract.abi)
epochs =
array_to_ranges(responses[:epochs])
|> Enum.map(fn {first, last} ->
Integer.to_string(first) <> (if first != last, do: "-" <> Integer.to_string(last), else: "")
end)
data = Map.put(amounts, :epochs, Enum.join(epochs, ","))
{data, pool_staking_address}
end)
pools_gas_estimates = Enum.map(pools, fn {_data, pool_staking_address} ->
result = ContractReader.claim_reward_estimate_gas(
staking_contract_address,
[],
pool_staking_address,
staker,
json_rpc_named_arguments
)
{pool_staking_address, result}
end)
|> Map.new(fn {val, key} -> {key, val} end)
error = Enum.find_value(pools_gas_estimates, fn {_, result} ->
case result do
{:error, reason} -> error_reason_to_string(reason)
_ -> nil
end
end)
pools = if error == nil do
pools_gas_estimates = Map.new(pools_gas_estimates)
Map.new(pools, fn {data, pool_staking_address} ->
{:ok, estimate} = pools_gas_estimates[pool_staking_address]
data = Map.put(data, :gas_estimate, estimate)
{pool_staking_address, data}
end)
else
%{}
end
{error, pools}
end
{error, pools}

@ -1,5 +1,5 @@
<button
class="btn-stake-more-full btn-full-primary <%= if assigns[:extra_class] do @extra_class end %>"
class="btn-full-primary <%= if assigns[:extra_class] do @extra_class end %>"
<%= if assigns[:disabled] do "disabled" end %>
>
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16">

@ -1,5 +1,5 @@
<button
class="btn-stake-more-full btn-full-primary <%= if assigns[:extra_class] do @extra_class end %>"
class="btn-full-primary <%= if assigns[:extra_class] do @extra_class end %>"
<%= if assigns[:disabled] do "disabled" end %>
>
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16">

@ -1,5 +1,5 @@
<div class="modal modal-fullwidth-xs fade" tabindex="-1" role="dialog" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered modal-stake" role="document">
<div class="modal-dialog modal-dialog-centered modal-claim-reward" role="document">
<div class="modal-content">
<%= render BlockScoutWeb.CommonComponentsView, "_modal_close_button.html" %>
<div class="modal-header">
@ -8,6 +8,7 @@
<div class="modal-body">
<p><%= gettext("Searching for pools you have ever staked into. Please, wait...") %></p>
</div>
<%= render BlockScoutWeb.CommonComponentsView, "_modal_bottom_disclaimer.html", text: gettext("You can get your reward for all staking epochs during which the pool was a validator or specify separate epochs if <b>Tx Gas Limit</b> is too high. <b>Tx Gas Limit</b> depends on how long the pool was a validator and how many staking epochs you held your stake in the pool without movement.") |> raw(), extra_class: "hidden" %>
</div>
</div>
</div>

@ -1,5 +1,5 @@
<%= if map_size(@pools) > 0 do %>
<p><%= gettext("We found the following pools you can claim your reward from:") %></p>
<p class="form-p"><%= gettext("We found the following pools you can claim reward from:") %></p>
<form>
<div class="input-group form-group">
<select pool-select class="form-control">
@ -9,12 +9,12 @@
token_reward_sum = format_token_amount(data.token_reward_sum, @token, digits: 5, ellipsize: false, symbol: false)
native_reward_sum = format_token_amount(data.native_reward_sum, @coin, digits: 5, ellipsize: false, symbol: false)
%>
<option value="<%= pool_staking_address %>" data-token-reward-sum="<%= token_reward_sum %>" data-native-reward-sum="<%= native_reward_sum %>" data-epochs="<%= data.epochs %>">
<option value="<%= pool_staking_address %>" data-token-reward-sum="<%= token_reward_sum %>" data-native-reward-sum="<%= native_reward_sum %>" data-epochs="<%= data.epochs %>" data-gas-limit="<%= data.gas_estimate %>">
<%=
BlockScoutWeb.AddressView.trimmed_hash(pool_staking_address) <>
" (" <>
token_reward_sum <> " " <> @token.symbol <>
", " <>
"; " <>
native_reward_sum <> " " <> @coin.symbol <>
")"
%>
@ -22,6 +22,35 @@
<% end %>
</select>
</div>
<div class="selected-pool-info hidden">
<p class="form-p"><%= gettext("The staking epochs for which the reward could be claimed:") %></p>
<div class="input-group form-group">
<textarea class="form-control" readonly="readonly"></textarea>
</div>
<div class="input-group form-group">
<p class="form-p">
<%= gettext("Claim for") %>&nbsp;
<label><input type="radio" name="epoch_choice" value="all" id="epoch-choice-all" checked="checked" />&nbsp;<%= gettext("all epochs") %></label>&nbsp;
<label><input type="radio" name="epoch_choice" value="specified" />&nbsp;<%= gettext("specified epochs only") %></label>
</p>
<input type="text" class="form-control specified-epochs hidden" placeholder='<%= gettext("Epochs range(s) or enum, e.g.: 5-9,23-27,47,50") %>' />
</div>
<div class="clearfix form-group">
<p class="form-p left">
<%= gettext("You will receive:") %><br />
<span class="text-dark" id="token-reward-sum"></span> <span class="text-dark"><%= @token.symbol %></span><br />
<span class="text-dark" id="native-reward-sum"></span> <span class="text-dark"><%= @coin.symbol %></span><br />
<a href="#" class="recalculate hidden"><%= gettext("Recalculate") %></a>
</p>
<p class="form-p right">
<%= gettext("Tx Gas Limit:") %><br />
<span class="text-dark" id="tx-gas-limit"></span><br />
<a href="#" class="recalculate hidden"><%= gettext("Recalculate") %></a>
</p>
</div>
<%= render BlockScoutWeb.StakesView, "_stakes_btn_withdraw.html", text: gettext("Claim Reward"), extra_class: "full-width" %>
</div>
</form>
<% else %>
<p>

@ -92,6 +92,63 @@ defmodule Explorer.Staking.ContractReader do
end
end
# makes a raw `eth_estimateGas` for the `claimReward` function of the Staking contract:
# function claimReward(
# uint256[] memory _stakingEpochs,
# address _poolStakingAddress
# ) public;
def claim_reward_estimate_gas(
staking_contract_address,
staking_epochs,
pool_staking_address,
staker,
json_rpc_named_arguments
) do
staking_epochs_joint =
staking_epochs
|> Enum.map(fn epoch ->
epoch
|> Integer.to_string(16)
|> String.pad_leading(64, ["0"])
end)
|> Enum.join("")
pool_staking_address = address_pad_to_64(pool_staking_address)
staking_epochs_length =
Enum.count(staking_epochs)
|> Integer.to_string(16)
|> String.pad_leading(64, ["0"])
data = "0x3ea15d62" # `claimReward` function signature
data = data <> String.pad_leading("40", 64, ["0"]) # offset to the `_stakingEpochs` array
data = data <> pool_staking_address # `_poolStakingAddress` parameter
data = data <> staking_epochs_length # the length of `_stakingEpochs` array
data = data <> staking_epochs_joint # encoded `_stakingEpochs` array
result = EthereumJSONRPC.request(%{
id: 0,
method: "eth_estimateGas",
params: [%{
from: staker,
to: staking_contract_address,
gasPrice: "0x3B9ACA00", # 1 gwei
data: data
}]
}) |> EthereumJSONRPC.json_rpc(json_rpc_named_arguments)
case result do
{:ok, response} ->
estimate =
response
|> String.replace_leading("0x", "")
|> String.to_integer(16)
{:ok, estimate}
{:error, reason} ->
{:error, reason}
end
end
# args = [staking_epoch, delegator_staked, validator_staked, total_staked, pool_reward \\ 10_00000]
def delegator_reward_request(args) do
[

Loading…
Cancel
Save