UNPKG

lisk-framework

Version:

Lisk blockchain application platform

188 lines 9.74 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.StakeCommand = void 0; const lisk_utils_1 = require("@liskhq/lisk-utils"); const errors_1 = require("../../../errors"); const state_machine_1 = require("../../../state_machine"); const base_command_1 = require("../../base_command"); const constants_1 = require("../constants"); const validator_staked_1 = require("../events/validator_staked"); const schemas_1 = require("../schemas"); const validator_1 = require("../stores/validator"); const eligible_validators_1 = require("../stores/eligible_validators"); const staker_1 = require("../stores/staker"); const utils_1 = require("../utils"); class StakeCommand extends base_command_1.BaseCommand { constructor() { super(...arguments); this.schema = schemas_1.stakeCommandParamsSchema; } addDependencies(args) { this._tokenMethod = args.tokenMethod; this._internalMethod = args.internalMethod; } init(args) { this._posTokenID = args.posTokenID; this._factorSelfStakes = args.factorSelfStakes; this._baseStakeAmount = args.baseStakeAmount; this._maxNumberPendingUnlocks = args.maxNumberPendingUnlocks; this._maxNumberSentStakes = args.maxNumberSentStakes; } async verify(context) { const { params: { stakes }, } = context; let upstakeCount = 0; let downstakeCount = 0; const validatorAddressSet = new lisk_utils_1.dataStructures.BufferSet(); for (const stake of stakes) { if (validatorAddressSet.has(stake.validatorAddress)) { return { status: state_machine_1.VerifyStatus.FAIL, error: new errors_1.ValidationError('Validator address must be unique.', stake.validatorAddress.toString('hex')), }; } validatorAddressSet.add(stake.validatorAddress); if (stake.amount === BigInt(0)) { return { status: state_machine_1.VerifyStatus.FAIL, error: new errors_1.ValidationError('Amount cannot be 0.', ''), }; } if (stake.amount % this._baseStakeAmount !== BigInt(0)) { return { status: state_machine_1.VerifyStatus.FAIL, error: new errors_1.ValidationError('Amount should be multiple of 10 * 10^8.', stake.amount.toString()), }; } if (stake.amount > BigInt(0)) { upstakeCount += 1; } else if (stake.amount < BigInt(0)) { downstakeCount += 1; } } if (upstakeCount > this._maxNumberSentStakes) { return { status: state_machine_1.VerifyStatus.FAIL, error: new errors_1.ValidationError(`Upstake can only be casted up to ${this._maxNumberSentStakes}.`, upstakeCount.toString()), }; } if (downstakeCount > this._maxNumberSentStakes) { return { status: state_machine_1.VerifyStatus.FAIL, error: new errors_1.ValidationError(`Downstake can only be casted up to ${this._maxNumberSentStakes}.`, downstakeCount.toString()), }; } return { status: state_machine_1.VerifyStatus.OK, }; } async execute(context) { const { transaction: { senderAddress }, params: { stakes }, getMethodContext, header: { height }, } = context; stakes.sort((a, b) => { const diff = a.amount - b.amount; if (diff > BigInt(0)) { return 1; } if (diff < BigInt(0)) { return -1; } return 0; }); const stakerStore = this.stores.get(staker_1.StakerStore); const validatorStore = this.stores.get(validator_1.ValidatorStore); for (const stake of stakes) { const stakerData = await stakerStore.getOrDefault(context, senderAddress); const validatorExists = await validatorStore.has(context, stake.validatorAddress); if (!validatorExists) { this.events.get(validator_staked_1.ValidatorStakedEvent).error(context, { senderAddress, validatorAddress: stake.validatorAddress, amount: stake.amount, }, 1); throw new Error('Invalid stake: no registered validator with the specified address'); } const validatorData = await validatorStore.get(context, stake.validatorAddress); const existingStakeIndex = stakerData.stakes.findIndex(senderStake => senderStake.validatorAddress.equals(stake.validatorAddress)); if (stake.amount < BigInt(0)) { if (existingStakeIndex < 0) { this.events.get(validator_staked_1.ValidatorStakedEvent).error(context, { senderAddress, validatorAddress: stake.validatorAddress, amount: stake.amount, }, 2); throw new Error('Invalid unstake: Cannot cast downstake to validator who is not upstaked.'); } if (stakerData.stakes[existingStakeIndex].amount + stake.amount < BigInt(0)) { this.events.get(validator_staked_1.ValidatorStakedEvent).error(context, { senderAddress, validatorAddress: stake.validatorAddress, amount: stake.amount, }, 2); throw new Error('Invalid unstake: The unstake amount exceeds the staked amount for this validator.'); } await this._internalMethod.assignStakeRewards(context, senderAddress, stakerData.stakes[existingStakeIndex], validatorData); stakerData.stakes[existingStakeIndex].amount += stake.amount; stakerData.stakes[existingStakeIndex].sharingCoefficients = validatorData.sharingCoefficients; if (stakerData.stakes[existingStakeIndex].amount === BigInt(0)) { stakerData.stakes = stakerData.stakes.filter(senderStake => !senderStake.validatorAddress.equals(stake.validatorAddress)); } stakerData.pendingUnlocks.push({ validatorAddress: stake.validatorAddress, amount: BigInt(-1) * stake.amount, unstakeHeight: height, }); (0, utils_1.sortUnlocking)(stakerData.pendingUnlocks); if (stakerData.pendingUnlocks.length > this._maxNumberPendingUnlocks) { this.events.get(validator_staked_1.ValidatorStakedEvent).error(context, { senderAddress, validatorAddress: stake.validatorAddress, amount: stake.amount, }, 3); throw new Error(`Pending unlocks cannot exceed ${this._maxNumberPendingUnlocks}.`); } } else { await this._tokenMethod.lock(getMethodContext(), senderAddress, constants_1.MODULE_NAME_POS, this._posTokenID, stake.amount); if (existingStakeIndex > -1) { await this._internalMethod.assignStakeRewards(context.getMethodContext(), senderAddress, stakerData.stakes[existingStakeIndex], validatorData); stakerData.stakes[existingStakeIndex].amount += stake.amount; stakerData.stakes[existingStakeIndex].sharingCoefficients = validatorData.sharingCoefficients; } else { stakerData.stakes.push({ validatorAddress: stake.validatorAddress, amount: stake.amount, sharingCoefficients: validatorData.sharingCoefficients, }); } stakerData.stakes.sort((a, b) => a.validatorAddress.compare(b.validatorAddress)); if (stakerData.stakes.length > this._maxNumberSentStakes) { this.events.get(validator_staked_1.ValidatorStakedEvent).error(context, { senderAddress, validatorAddress: stake.validatorAddress, amount: stake.amount, }, 4); throw new Error(`Sender can only stake upto ${this._maxNumberSentStakes}.`); } } const previousValidatorWeight = (0, utils_1.getValidatorWeight)(this._factorSelfStakes, validatorData.selfStake, validatorData.totalStake); if (senderAddress.equals(stake.validatorAddress)) { validatorData.selfStake += stake.amount; } validatorData.totalStake += stake.amount; const eligibleValidatorsStore = this.stores.get(eligible_validators_1.EligibleValidatorsStore); await eligibleValidatorsStore.update(context, stake.validatorAddress, previousValidatorWeight, validatorData); await stakerStore.set(context, senderAddress, stakerData); await validatorStore.set(context, stake.validatorAddress, validatorData); this.events.get(validator_staked_1.ValidatorStakedEvent).log(context, { senderAddress, validatorAddress: stake.validatorAddress, amount: stake.amount, }); } } } exports.StakeCommand = StakeCommand; //# sourceMappingURL=stake.js.map