diff --git a/src/i18n/en-US/index.ts b/src/i18n/en-US/index.ts index a04a217a6..8d0a2195a 100644 --- a/src/i18n/en-US/index.ts +++ b/src/i18n/en-US/index.ts @@ -694,13 +694,16 @@ export default { Disabled: 'Pallet is disabled/in maintenance mode.', NoExpiredEntries: 'There are no expired entries to clean up.', NoStakingInfo: 'Account has no staking information for the contract', + NotOperatedDApp: 'dApp is part of dApp staking but is not active anymore.', PeriodEndsNextEra: 'Period ends in the next era.', TooManyStakedContracts: 'There are too many contract stake entries for the account. This can be cleaned up by either unstaking or cleaning expired entries.', + TooManyUnlockingChunks: + 'Contract has too many unlocking chunks. Withdraw the existing chunks if possible or wait for current chunks to complete unlocking process to withdraw them.', UnavailableStakeFunds: 'The staking amount surpasses the current balance available for staking.', - UnclaimedRewardsFromPastPeriods: - 'There are unclaimed rewards remaining from past periods. They should be claimed before staking again.', + UnclaimedRewards: + 'There are unclaimed rewards remaining from past eras or periods. They should be claimed before attempting any stake modification again.', UnstakeAmountTooLarge: 'Unstake amount is greater than the staked amount.', UnstakeFromPastPeriod: 'Unstaking is rejected since the period in which past stake was active has passed.', diff --git a/src/staking-v3/hooks/useDappStaking.ts b/src/staking-v3/hooks/useDappStaking.ts index f0f93b893..5ee678c29 100644 --- a/src/staking-v3/hooks/useDappStaking.ts +++ b/src/staking-v3/hooks/useDappStaking.ts @@ -33,7 +33,7 @@ export function useDappStaking() { const { t } = useI18n(); const store = useStore(); const { currentAccount } = useAccount(); - const { registeredDapps, fetchStakeAmountsToStore } = useDapps(); + const { registeredDapps, fetchStakeAmountsToStore, getDapp } = useDapps(); const { decimal } = useChainMetadata(); const { useableBalance } = useBalance(currentAccount); @@ -138,7 +138,7 @@ export function useDappStaking() { : true; const stake = async (dappAddress: string, amount: number): Promise => { - const [result, error] = await canStake(amount); + const [result, error] = await canStake(dappAddress, amount); if (!result) { throw error; } @@ -281,22 +281,29 @@ export function useDappStaking() { store.commit('stakingV3/setCurrentEraInfo', eraInfo); }; - const canStake = async (amount: number): Promise<[boolean, string]> => { + const canStake = async ( + dappAddress: string, + amount: number, + ignoreCanClaim = false + ): Promise<[boolean, string]> => { const stakeAmount = new BN(ethers.utils.parseEther(amount.toString()).toString()); const balanceBN = new BN(useableBalance.value.toString()); const stakingRepo = container.get(Symbols.DappStakingRepositoryV3); const constants = await stakingRepo.getConstants(); - if (amount <= 0) { + if (!dappAddress) { + // Prevents NoDappSelected + return [false, t('stakingV3.noDappSelected')]; + } else if (amount <= 0) { // Prevents dappStaking.ZeroAmount return [false, t('stakingV3.dappStaking.ZeroAmount')]; } else if ((ledger.value?.contractStakeCount ?? 0) >= constants.maxNumberOfStakedContracts) { // Prevents dappStaking.TooManyStakedContracts return [false, t('stakingV3.dappStaking.TooManyStakedContracts')]; - } else if (hasRewards.value) { - // Prevents dappStaking.UnclaimedRewardsFromPastPeriods + } else if (hasRewards.value && !ignoreCanClaim) { + // Prevents dappStaking.UnclaimedRewards // May want to auto claim rewards here - return [false, t('stakingV3.dappStaking.UnclaimedRewardsFromPastPeriods')]; + return [false, t('stakingV3.dappStaking.UnclaimedRewards')]; } else if (protocolState.value?.maintenance) { // Prevents dappStaking.Disabled return [false, t('stakingV3.dappStaking.Disabled')]; @@ -304,19 +311,30 @@ export function useDappStaking() { // Prevents dappStaking.UnavailableStakeFunds return [false, t('stakingV3.dappStaking.UnavailableStakeFunds')]; } else if ( - // Prevents dappStaking.PeriodEndsInNextEra protocolState.value?.periodInfo.subperiod === PeriodType.BuildAndEarn && protocolState.value.periodInfo.nextSubperiodStartEra <= protocolState.value.era + 1 ) { + // Prevents dappStaking.PeriodEndsInNextEra return [false, t('stakingV3.dappStaking.PeriodEndsNextEra')]; + } else if (getDapp(dappAddress)?.chain?.state === 'Unregistered') { + // Prevents dappStaking.NotOperatedDApp + return [false, t('stakingV3.dappStaking.NotOperatedDApp')]; } return [true, '']; }; - const canUnStake = async (address: string, amount: number): Promise<[boolean, string]> => { + const canUnStake = async (dappAddress: string, amount: number): Promise<[boolean, string]> => { const stakeAmount = new BN(ethers.utils.parseEther(amount.toString()).toString()); const stakedAmount = new BN(ledger.value?.locked?.toString() ?? 0); + const stakingRepo = container.get(Symbols.DappStakingRepositoryV3); + const [constants, stakerInfo] = await Promise.all([ + stakingRepo.getConstants(), + stakingRepo.getStakerInfo(currentAccount.value, false), + ]); + + console.log('ledger.value?.unlocking?.length)', ledger.value?.unlocking?.length); + console.log('constants.maxNumberOfUnlockingChunks', constants.maxUnlockingChunks); if (amount <= 0) { // Prevents dappStaking.ZeroAmount @@ -324,15 +342,22 @@ export function useDappStaking() { } else if (stakeAmount.gt(stakedAmount)) { // Prevents dappStaking.UnstakeAmountTooLarge return [false, t('stakingV3.dappStaking.UnstakeAmountTooLarge')]; + } else if (hasRewards.value) { + // Prevents dappStaking.UnclaimedRewards + // May want to auto claim rewards here + return [false, t('stakingV3.dappStaking.UnclaimedRewards')]; } else if (protocolState.value?.maintenance) { // Prevents dappStaking.Disabled return [false, t('stakingV3.dappStaking.Disabled')]; - } else if (!address) { + } else if (!stakerInfo.get(dappAddress)) { // Prevents dappStaking.NoStakingInfo return [false, t('stakingV3.dappStaking.NoStakingInfo')]; } else if (!amount) { // Prevents dappStaking.UnstakeFromPastPeriod return [false, t('stakingV3.dappStaking.UnstakeFromPastPeriod')]; + } else if ((ledger.value?.unlocking?.length ?? 0) >= constants.maxUnlockingChunks) { + // Prevents dappStaking.TooManyUnlockingChunks + return [false, t('stakingV3.dappStaking.TooManyUnlockingChunks')]; } return [true, '']; diff --git a/src/staking-v3/logic/models/DappStaking.ts b/src/staking-v3/logic/models/DappStaking.ts index 380fa8abb..f2680138d 100644 --- a/src/staking-v3/logic/models/DappStaking.ts +++ b/src/staking-v3/logic/models/DappStaking.ts @@ -105,6 +105,7 @@ export interface Constants { minBalanceAfterStaking: number; maxNumberOfStakedContracts: number; maxNumberOfContracts: number; + maxUnlockingChunks: number; standardErasPerBuildAndEarnPeriod: number; standardErasPerVotingPeriod: number; unlockingPeriod: number; diff --git a/src/staking-v3/logic/repositories/DappStakingRepository.ts b/src/staking-v3/logic/repositories/DappStakingRepository.ts index e61833d2a..da1391de8 100644 --- a/src/staking-v3/logic/repositories/DappStakingRepository.ts +++ b/src/staking-v3/logic/repositories/DappStakingRepository.ts @@ -292,6 +292,7 @@ export class DappStakingRepository implements IDappStakingRepository { api.consts.dappStaking.maxNumberOfStakedContracts )).toNumber(), maxNumberOfContracts: (api.consts.dappStaking.maxNumberOfContracts).toNumber(), + maxUnlockingChunks: (api.consts.dappStaking.maxUnlockingChunks).toNumber(), standardErasPerBuildAndEarnPeriod: 10, // (( // api.consts.dappStaking.standardErasPerBuildAndEarnPeriod // )).toNumber(),