Improve 'Become a Candidate' appearance, checkups, and connectivity control

staking
Vadim 5 years ago committed by Victor Baranov
parent 7763c9ead2
commit 7d44866e9c
  1. 18
      apps/block_scout_web/assets/js/pages/stakes.js
  2. 55
      apps/block_scout_web/assets/js/pages/stakes/become_candidate.js
  3. 2
      apps/block_scout_web/assets/js/pages/stakes/claim_reward.js
  4. 23
      apps/block_scout_web/assets/js/pages/stakes/make_stake.js
  5. 8
      apps/block_scout_web/assets/js/pages/stakes/utils.js
  6. 12
      apps/block_scout_web/lib/block_scout_web/channels/stakes_channel.ex
  7. 15
      apps/block_scout_web/lib/block_scout_web/controllers/stakes_controller.ex
  8. 4
      apps/block_scout_web/lib/block_scout_web/templates/stakes/_stakes_top.html.eex
  9. 1
      apps/explorer/lib/explorer/staking/contract_reader.ex
  10. 8
      apps/explorer/lib/explorer/staking/contract_state.ex

@ -8,12 +8,12 @@ import { createAsyncLoadStore, refreshPage } from '../lib/async_listing_load'
import Web3 from 'web3'
import { openPoolInfoModal } from './stakes/validator_info'
import { openDelegatorsListModal } from './stakes/delegators_list'
import { openBecomeCandidateModal } from './stakes/become_candidate'
import { openBecomeCandidateModal, becomeCandidateConnectionLost } from './stakes/become_candidate'
import { openRemovePoolModal } from './stakes/remove_pool'
import { openMakeStakeModal } from './stakes/make_stake'
import { openMoveStakeModal } from './stakes/move_stake'
import { openWithdrawStakeModal } from './stakes/withdraw_stake'
import { openClaimRewardModal, connectionLost } from './stakes/claim_reward'
import { openClaimRewardModal, claimRewardConnectionLost } from './stakes/claim_reward'
import { openClaimWithdrawalModal } from './stakes/claim_withdrawal'
import { checkForTokenDefinition } from './stakes/utils'
import { currentModal, openWarningModal, openErrorModal } from '../lib/modals'
@ -35,6 +35,7 @@ export const initialState = {
tokenDecimals: 0,
tokenSymbol: '',
validatorSetApplyBlock: 0,
validatorSetContract: null,
web3: null
}
@ -99,6 +100,7 @@ export function reducer (state = initialState, action) {
return Object.assign({}, state, {
stakingContract: action.stakingContract,
blockRewardContract: action.blockRewardContract,
validatorSetContract: action.validatorSetContract,
tokenDecimals: action.tokenDecimals,
tokenSymbol: action.tokenSymbol
})
@ -191,23 +193,27 @@ if ($stakesPage.length) {
})
})
channel.on('contracts', msg => {
channel.on('contracts', async (msg) => {
const web3 = store.getState().web3
const stakingContract =
new web3.eth.Contract(msg.staking_contract.abi, msg.staking_contract.address)
const blockRewardContract =
new web3.eth.Contract(msg.block_reward_contract.abi, msg.block_reward_contract.address)
const validatorSetContract =
new web3.eth.Contract(msg.validator_set_contract.abi, msg.validator_set_contract.address)
store.dispatch({
type: 'RECEIVED_CONTRACTS',
stakingContract,
blockRewardContract,
validatorSetContract,
tokenDecimals: parseInt(msg.token_decimals, 10),
tokenSymbol: msg.token_symbol
})
})
channel.onError(connectionLost)
channel.onError(becomeCandidateConnectionLost)
channel.onError(claimRewardConnectionLost)
$(document.body)
.on('click', '.js-pool-info', event => {
@ -218,9 +224,9 @@ if ($stakesPage.length) {
.on('click', '.js-delegators-list', event => {
openDelegatorsListModal(event, store)
})
.on('click', '.js-become-candidate', () => {
.on('click', '.js-become-candidate', event => {
if (checkForTokenDefinition(store)) {
openBecomeCandidateModal(store)
openBecomeCandidateModal(event, store)
}
})
.on('click', '.js-remove-pool', () => {

@ -2,19 +2,27 @@ import $ from 'jquery'
import { BigNumber } from 'bignumber.js'
import { openModal, openErrorModal, openWarningModal, lockModal } from '../../lib/modals'
import { setupValidation } from '../../lib/validation'
import { makeContractCall, isSupportedNetwork } from './utils'
import { makeContractCall, isSupportedNetwork, isStakingAllowed } from './utils'
export function openBecomeCandidateModal (store) {
if (!store.getState().account) {
openWarningModal('Unauthorized', 'Please login with MetaMask')
let status = 'modalClosed'
export async function openBecomeCandidateModal (event, store) {
const state = store.getState()
if (!state.account) {
openWarningModal('Unauthorized', 'You haven\'t approved the reading of account list from your MetaMask or MetaMask is not installed.')
return
}
if (!isSupportedNetwork(store)) return
if (!isStakingAllowed(state)) return
store.getState().channel
$(event.currentTarget).prop('disabled', true)
state.channel
.push('render_become_candidate')
.receive('ok', msg => {
$(event.currentTarget).prop('disabled', false)
const $modal = $(msg.html)
const $form = $modal.find('form')
@ -39,23 +47,42 @@ export function openBecomeCandidateModal (store) {
return false
})
$modal.on('shown.bs.modal', () => {
status = 'modalOpened'
})
$modal.on('hidden.bs.modal', () => {
status = 'modalClosed'
$modal.remove()
})
openModal($modal)
})
.receive('timeout', () => {
$(event.currentTarget).prop('disabled', false)
openErrorModal('Become a Candidate', 'Connection timeout')
})
}
async function becomeCandidate ($modal, store, msg) {
export function becomeCandidateConnectionLost () {
const errorMsg = 'Connection with server is lost. Please, reload the page.'
if (status === 'modalOpened') {
status = 'modalClosed'
openErrorModal('Become a Candidate', errorMsg, true)
}
}
function becomeCandidate ($modal, store, msg) {
lockModal($modal)
const stakingContract = store.getState().stakingContract
const decimals = store.getState().tokenDecimals
const state = store.getState()
const stakingContract = state.stakingContract
const decimals = state.tokenDecimals
const stake = new BigNumber($modal.find('[candidate-stake]').val().replace(',', '.').trim()).shiftedBy(decimals).integerValue()
const miningAddress = $modal.find('[mining-address]').val().trim().toLowerCase()
try {
if (!await stakingContract.methods.areStakeAndWithdrawAllowed().call()) {
openErrorModal('Error', 'The current staking epoch is ending, and staking actions are temporarily restricted. Please try again when the new epoch starts.')
return false
}
if (!isSupportedNetwork(store)) return false
if (!isStakingAllowed(state)) return false
makeContractCall(stakingContract.methods.addPool(stake.toString(), miningAddress), store)
} catch (err) {
@ -84,8 +111,10 @@ function isMiningAddressValid (value, store) {
const web3 = store.getState().web3
const miningAddress = value.trim().toLowerCase()
if (miningAddress === store.getState().account.toLowerCase() || !web3.utils.isAddress(miningAddress)) {
if (!web3.utils.isAddress(miningAddress)) {
return 'Invalid mining address'
} else if (miningAddress === store.getState().account.toLowerCase()) {
return 'The mining address cannot match the staking address'
}
return true

@ -90,7 +90,7 @@ export function openClaimRewardModal (event, store) {
})
}
export function connectionLost () {
export function claimRewardConnectionLost () {
const errorMsg = 'Connection with server is lost. Please, reload the page.'
if (status === 'modalOpened') {
status = 'modalClosed'

@ -1,12 +1,12 @@
import $ from 'jquery'
import { BigNumber } from 'bignumber.js'
import { openModal, openWarningModal, lockModal } from '../../lib/modals'
import { openErrorModal, openModal, openWarningModal, lockModal } from '../../lib/modals'
import { setupValidation } from '../../lib/validation'
import { makeContractCall, setupChart, isSupportedNetwork } from './utils'
export function openMakeStakeModal (event, store) {
if (!store.getState().account) {
openWarningModal('Unauthorized', 'Please login with MetaMask')
openWarningModal('Unauthorized', 'You haven\'t approved the reading of account list from your MetaMask or MetaMask is not installed.')
return
}
@ -46,14 +46,27 @@ export function openMakeStakeModal (event, store) {
})
}
function makeStake ($modal, address, store, msg) {
async function makeStake ($modal, address, store, msg) {
lockModal($modal)
const stakingContract = store.getState().stakingContract
const decimals = store.getState().tokenDecimals
const state = store.getState()
const stakingContract = state.stakingContract
const validatorSetContract = state.validatorSetContract
const decimals = state.tokenDecimals
const stake = new BigNumber($modal.find('[delegator-stake]').val().replace(',', '.').trim()).shiftedBy(decimals).integerValue()
let miningAddress = msg.mining_address
if (!miningAddress || miningAddress === '0x0000000000000000000000000000000000000000') {
miningAddress = await validatorSetContract.methods.miningByStakingAddress(address).call()
}
const isBanned = await validatorSetContract.methods.isValidatorBanned(miningAddress).call()
if (isBanned) {
openErrorModal('This pool is banned', 'You cannot stake into a banned pool.')
return
}
makeContractCall(stakingContract.methods.stake(address, stake.toString()), store)
}

@ -91,6 +91,14 @@ export function checkForTokenDefinition (store) {
return false
}
export function isStakingAllowed (state) {
if (!state.stakingAllowed) {
openErrorModal('Actions temporarily disallowed', 'The current staking epoch is ending, and staking actions are temporarily restricted. Please try again after the new epoch starts.')
return false
}
return true
}
export function isSupportedNetwork (store) {
const state = store.getState()
if (state.network && state.network.authorized) {

@ -47,7 +47,8 @@ defmodule BlockScoutWeb.StakesChannel do
%{validator_set: validator_set_contract.address},
validator_set_contract.abi
).mining_address
after
rescue
_ -> nil
end
# convert zero address to nil
@ -175,6 +176,7 @@ defmodule BlockScoutWeb.StakesChannel do
is_active: false,
is_deleted: true,
self_staked_amount: 0,
mining_address_hash: nil,
staking_address_hash: staking_address,
total_staked_amount: 0
}
@ -192,6 +194,7 @@ defmodule BlockScoutWeb.StakesChannel do
html: html,
balance: balance,
delegator_staked: delegator_staked,
mining_address: nil,
min_stake: min_stake,
self_staked_amount: pool.self_staked_amount,
total_staked_amount: pool.total_staked_amount
@ -301,7 +304,8 @@ defmodule BlockScoutWeb.StakesChannel do
staking_contract_address =
try do
ContractState.get(:staking_contract).address
after
rescue
_ -> nil
end
empty_staker = staker == nil || staker == "" || staker == "0x0000000000000000000000000000000000000000"
@ -328,7 +332,8 @@ defmodule BlockScoutWeb.StakesChannel do
staking_contract_address =
try do
ContractState.get(:staking_contract).address
after
rescue
_ -> nil
end
empty_pool_staking_address =
@ -698,6 +703,7 @@ defmodule BlockScoutWeb.StakesChannel do
push(socket, "contracts", %{
staking_contract: ContractState.get(:staking_contract),
block_reward_contract: ContractState.get(:block_reward_contract),
validator_set_contract: ContractState.get(:validator_set_contract),
token_decimals: to_string(token.decimals),
token_symbol: token.symbol
})

@ -19,11 +19,12 @@ defmodule BlockScoutWeb.StakesController do
# when a new block appears (see `staking_update` event handled in `StakesChannel`),
# or when the page is loaded for the first time or reloaded by a user (i.e. it is called by the `render_template(filter, conn, _)`)
def render_top(conn) do
epoch_number = ContractState.get(:epoch_number, 0)
epoch_end_block = ContractState.get(:epoch_end_block, 0)
active_pools_length = ContractState.get(:active_pools_length, 0)
block_number = BlockNumber.get_max()
epoch_end_block = ContractState.get(:epoch_end_block, 0)
epoch_number = ContractState.get(:epoch_number, 0)
max_candidates = ContractState.get(:max_candidates, 0)
token = ContractState.get(:token, %Token{})
staking_allowed = ContractState.get(:staking_allowed, false)
account =
if account_address = conn.assigns[:account] do
@ -38,11 +39,11 @@ defmodule BlockScoutWeb.StakesController do
end
View.render_to_string(StakesView, "_stakes_top.html",
epoch_number: epoch_number,
epoch_end_in: epoch_end_block - block_number,
staking_allowed: staking_allowed,
block_number: block_number,
account: account,
block_number: block_number,
candidates_limit_reached: active_pools_length >= max_candidates,
epoch_end_in: epoch_end_block - block_number,
epoch_number: epoch_number,
token: token
)
end

@ -7,7 +7,6 @@
<%= render BlockScoutWeb.StakesView, "_stakes_stats_item_account.html", account: @account, token: @token %>
<!-- Buttons -->
<div class="stakes-top-buttons">
<%= if @staking_allowed do %>
<%= if @account[:pool] && @account.pool.is_active do %>
<%= unless @account.pool.is_unremovable do %>
<%= render BlockScoutWeb.StakesView, "_stakes_btn_remove_pool.html", text: gettext("Remove My Pool"), extra_class: "js-remove-pool" %>
@ -24,10 +23,9 @@
render BlockScoutWeb.CommonComponentsView, "_btn_add_full.html",
text: gettext("Become a Candidate"),
extra_class: button_class,
disabled: @account[:pool] && @account.pool.is_banned
disabled: @account[:pool] && @account.pool.is_banned || @candidates_limit_reached
%>
<% end %>
<% end %>
<%= render BlockScoutWeb.StakesView, "_stakes_btn_claim_reward.html", text: gettext("Claim Reward"), extra_class: "full-width" %>
</div>

@ -12,6 +12,7 @@ defmodule Explorer.Staking.ContractReader do
epoch_number: {:staking, "stakingEpoch", []},
epoch_start_block: {:staking, "stakingEpochStartBlock", []},
inactive_pools: {:staking, "getPoolsInactive", []},
max_candidates: {:staking, "MAX_CANDIDATES", []},
min_candidate_stake: {:staking, "candidateMinStake", []},
min_delegator_stake: {:staking, "delegatorMinStake", []},
pools_likelihood: {:staking, "getPoolsLikelihood", []},

@ -17,11 +17,13 @@ defmodule Explorer.Staking.ContractState do
@table_name __MODULE__
@table_keys [
:active_pools_length,
:block_reward_contract,
:epoch_end_block,
:epoch_number,
:epoch_start_block,
:is_snapshotting,
:max_candidates,
:min_candidate_stake,
:min_delegator_stake,
:snapshotted_epoch_number,
@ -139,7 +141,10 @@ defmodule Explorer.Staking.ContractState do
not get(:is_snapshotting)
# save the general info to ETS (excluding pool list and validator list)
settings = get_settings(global_responses, validator_min_reward_percent, block_number)
settings =
global_responses
|> get_settings(validator_min_reward_percent, block_number)
|> Enum.concat(active_pools_length: Enum.count(global_responses.active_pools))
:ets.insert(@table_name, settings)
@ -384,6 +389,7 @@ defmodule Explorer.Staking.ContractState do
global_responses
|> Map.take([
:token_contract_address,
:max_candidates,
:min_candidate_stake,
:min_delegator_stake,
:epoch_number,

Loading…
Cancel
Save