lisk-framework
Version:
Lisk blockchain application platform
188 lines • 9.74 kB
JavaScript
"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