@celo/contractkit
Version:
Celo's ContractKit to interact with Celo network
514 lines • 27.5 kB
JavaScript
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ValidatorsWrapper = void 0;
const address_1 = require("@celo/base/lib/address");
const async_1 = require("@celo/base/lib/async");
const collections_1 = require("@celo/base/lib/collections");
const connect_1 = require("@celo/connect");
const fixidity_1 = require("@celo/utils/lib/fixidity");
const bignumber_js_1 = __importDefault(require("bignumber.js"));
const BaseWrapper_1 = require("./BaseWrapper");
const BaseWrapperForGoverning_1 = require("./BaseWrapperForGoverning");
/**
* Contract for voting for validators and managing validator groups.
*/
// TODO(asa): Support validator signers
class ValidatorsWrapper extends BaseWrapperForGoverning_1.BaseWrapperForGoverning {
constructor() {
super(...arguments);
/**
* Queues an update to a validator group's commission.
* @param commission Fixidity representation of the commission this group receives on epoch
* payments made to its members. Must be in the range [0, 1.0].
*/
this.setNextCommissionUpdate = (0, BaseWrapper_1.proxySend)(this.connection, this.contract.methods.setNextCommissionUpdate, (0, BaseWrapper_1.tupleParser)(BaseWrapper_1.valueToFixidityString));
/**
* Updates a validator group's commission based on the previously queued update
*/
this.updateCommission = (0, BaseWrapper_1.proxySend)(this.connection, this.contract.methods.updateCommission);
/**
* Returns the Locked Gold requirements for specific account.
* @returns The Locked Gold requirements for a specific account.
*/
this.getAccountLockedGoldRequirement = (0, BaseWrapper_1.proxyCall)(this.contract.methods.getAccountLockedGoldRequirement, undefined, BaseWrapper_1.valueToBigNumber);
/**
* Returns the reset period, in seconds, for slashing multiplier.
*/
this.getSlashingMultiplierResetPeriod = (0, BaseWrapper_1.proxyCall)(this.contract.methods.slashingMultiplierResetPeriod, undefined, BaseWrapper_1.valueToBigNumber);
/**
* Returns the update delay, in blocks, for the group commission.
*/
this.getCommissionUpdateDelay = (0, BaseWrapper_1.proxyCall)(this.contract.methods.commissionUpdateDelay, undefined, BaseWrapper_1.valueToBigNumber);
/**
* Returns the validator downtime grace period
*/
this.getDowntimeGracePeriod = (0, BaseWrapper_1.proxyCall)(this.contract.methods.deprecated_downtimeGracePeriod, undefined, BaseWrapper_1.valueToBigNumber);
/**
* Returns whether a particular account has a registered validator.
* @param account The account.
* @return Whether a particular address is a registered validator.
*/
this.isValidator = (0, BaseWrapper_1.proxyCall)(this.contract.methods.isValidator);
/**
* Returns whether a particular account has a registered validator group.
* @param account The account.
* @return Whether a particular address is a registered validator group.
*/
this.isValidatorGroup = (0, BaseWrapper_1.proxyCall)(this.contract.methods.isValidatorGroup);
/**
* Returns whether an account meets the requirements to register a validator.
* @param account The account.
* @return Whether an account meets the requirements to register a validator.
*/
this.meetsValidatorBalanceRequirements = (address) => __awaiter(this, void 0, void 0, function* () {
const lockedGold = yield this.contracts.getLockedGold();
const total = yield lockedGold.getAccountTotalLockedGold(address);
const reqs = yield this.getValidatorLockedGoldRequirements();
return reqs.value.lte(total);
});
/**
* Returns whether an account meets the requirements to register a group.
* @param account The account.
* @return Whether an account meets the requirements to register a group.
*/
this.meetsValidatorGroupBalanceRequirements = (address) => __awaiter(this, void 0, void 0, function* () {
const lockedGold = yield this.contracts.getLockedGold();
const total = yield lockedGold.getAccountTotalLockedGold(address);
const reqs = yield this.getGroupLockedGoldRequirements();
return reqs.value.lte(total);
});
/**
* Returns the Validator's group membership history
* @param validator The validator whose membership history to return.
* @return The group membership history of a validator.
*/
this.getValidatorMembershipHistory = (0, BaseWrapper_1.proxyCall)(this.contract.methods.getMembershipHistory, undefined, (res) => (0, collections_1.zip)((epoch, group) => ({ epoch: (0, BaseWrapper_1.valueToInt)(epoch), group }), res[0], res[1]));
/**
* Returns extra data from the Validator's group membership history
* @param validator The validator whose membership history to return.
* @return The group membership history of a validator.
*/
this.getValidatorMembershipHistoryExtraData = (0, BaseWrapper_1.proxyCall)(this.contract.methods.getMembershipHistory, undefined, (res) => ({ lastRemovedFromGroupTimestamp: (0, BaseWrapper_1.valueToInt)(res[2]), tail: (0, BaseWrapper_1.valueToInt)(res[3]) }));
/** Get the size (amount of members) of a ValidatorGroup */
this.getValidatorGroupSize = (0, BaseWrapper_1.proxyCall)(this.contract.methods.getGroupNumMembers, undefined, BaseWrapper_1.valueToInt);
/** Get list of registered validator group addresses */
this.getRegisteredValidatorGroupsAddresses = (0, BaseWrapper_1.proxyCall)(this.contract.methods.getRegisteredValidatorGroups);
/**
* Registers a validator unaffiliated with any validator group.
*
* Fails if the account is already a validator or validator group.
*
* @param validatorAddress The address that the validator is using for consensus, should match
* the validator signer.
* @param ecdsaPublicKey The ECDSA public key that the validator is using for consensus. 64 bytes.
*/
this.registerValidator = (0, BaseWrapper_1.proxySend)(this.connection, this.contract.methods.registerValidator, (0, BaseWrapper_1.tupleParser)(BaseWrapper_1.stringToSolidityBytes));
this.registerValidatorNoBls = (0, BaseWrapper_1.proxySend)(this.connection, this.contract.methods.registerValidatorNoBls, (0, BaseWrapper_1.tupleParser)(BaseWrapper_1.stringToSolidityBytes));
this.getEpochNumber = (0, BaseWrapper_1.proxyCall)(this.contract.methods.getEpochNumber, undefined, BaseWrapper_1.valueToBigNumber);
this.getEpochSize = (0, BaseWrapper_1.proxyCall)(this.contract.methods.getEpochSize, undefined, BaseWrapper_1.valueToBigNumber);
/**
* Affiliates a validator with a group, allowing it to be added as a member.
* De-affiliates with the previously affiliated group if present.
* @param group The validator group with which to affiliate.
*/
this.affiliate = (0, BaseWrapper_1.proxySend)(this.connection, this.contract.methods.affiliate);
/**
* De-affiliates a validator, removing it from the group for which it is a member.
* Fails if the account is not a validator with non-zero affiliation.
*/
this.deaffiliate = (0, BaseWrapper_1.proxySend)(this.connection, this.contract.methods.deaffiliate);
/**
* Removes a validator from the group for which it is a member.
* @param validatorAccount The validator to deaffiliate from their affiliated validator group.
*/
this.forceDeaffiliateIfValidator = (0, BaseWrapper_1.proxySend)(this.connection, this.contract.methods.forceDeaffiliateIfValidator);
/**
* Resets a group's slashing multiplier if it has been >= the reset period since
* the last time the group was slashed.
*/
this.resetSlashingMultiplier = (0, BaseWrapper_1.proxySend)(this.connection, this.contract.methods.resetSlashingMultiplier);
/**
* Removes a member from a ValidatorGroup
* The ValidatorGroup is specified by the `from` of the tx.
*
* @param validator The Validator to remove from the group
*/
this.removeMember = (0, BaseWrapper_1.proxySend)(this.connection, this.contract.methods.removeMember);
}
/**
* Returns the Locked Gold requirements for validators.
* @returns The Locked Gold requirements for validators.
*/
getValidatorLockedGoldRequirements() {
return __awaiter(this, void 0, void 0, function* () {
const res = yield this.contract.methods.getValidatorLockedGoldRequirements().call();
return {
value: (0, BaseWrapper_1.valueToBigNumber)(res[0]),
duration: (0, BaseWrapper_1.valueToBigNumber)(res[1]),
};
});
}
/**
* Returns the Locked Gold requirements for validator groups.
* @returns The Locked Gold requirements for validator groups.
*/
getGroupLockedGoldRequirements() {
return __awaiter(this, void 0, void 0, function* () {
const res = yield this.contract.methods.getGroupLockedGoldRequirements().call();
return {
value: (0, BaseWrapper_1.valueToBigNumber)(res[0]),
duration: (0, BaseWrapper_1.valueToBigNumber)(res[1]),
};
});
}
/**
* Returns current configuration parameters.
*/
getConfig() {
return __awaiter(this, void 0, void 0, function* () {
const res = yield Promise.all([
this.getValidatorLockedGoldRequirements(),
this.getGroupLockedGoldRequirements(),
this.contract.methods.maxGroupSize().call(),
this.contract.methods.membershipHistoryLength().call(),
this.getSlashingMultiplierResetPeriod(),
this.getCommissionUpdateDelay(),
this.getDowntimeGracePeriod(),
]);
return {
validatorLockedGoldRequirements: res[0],
groupLockedGoldRequirements: res[1],
maxGroupSize: (0, BaseWrapper_1.valueToBigNumber)(res[2]),
membershipHistoryLength: (0, BaseWrapper_1.valueToBigNumber)(res[3]),
slashingMultiplierResetPeriod: res[4],
commissionUpdateDelay: res[5],
downtimeGracePeriod: res[6],
};
});
}
/**
* @dev Returns human readable configuration of the validators contract
* @return ValidatorsConfig object
*/
getHumanReadableConfig() {
return __awaiter(this, void 0, void 0, function* () {
const config = yield this.getConfig();
const validatorLockedGoldRequirements = Object.assign(Object.assign({}, config.validatorLockedGoldRequirements), { duration: (0, BaseWrapper_1.secondsToDurationString)(config.validatorLockedGoldRequirements.duration) });
const groupLockedGoldRequirements = Object.assign(Object.assign({}, config.groupLockedGoldRequirements), { duration: (0, BaseWrapper_1.secondsToDurationString)(config.groupLockedGoldRequirements.duration) });
return Object.assign(Object.assign({}, config), { slashingMultiplierResetPeriod: (0, BaseWrapper_1.secondsToDurationString)(config.slashingMultiplierResetPeriod), commissionUpdateDelay: (0, BaseWrapper_1.blocksToDurationString)(config.commissionUpdateDelay), validatorLockedGoldRequirements,
groupLockedGoldRequirements });
});
}
/**
* Returns the account associated with `signer`.
* @param signer The address of an account or currently authorized validator signer.
* @dev Fails if the `signer` is not an account or currently authorized validator.
* @return The associated account.
*/
validatorSignerToAccount(signerAddress) {
return __awaiter(this, void 0, void 0, function* () {
const accounts = yield this.contracts.getAccounts();
return accounts.validatorSignerToAccount(signerAddress);
});
}
/**
* Returns the account associated with `signer`.
* @param signer The address of the account or previously authorized signer.
* @dev Fails if the `signer` is not an account or previously authorized signer.
* @return The associated account.
*/
signerToAccount(signerAddress) {
return __awaiter(this, void 0, void 0, function* () {
const accounts = yield this.contracts.getAccounts();
return accounts.signerToAccount(signerAddress);
});
}
/** Get Validator information */
getValidator(address, blockNumber) {
return __awaiter(this, void 0, void 0, function* () {
// @ts-ignore: Expected 0-1 arguments, but got 2
const res = yield this.contract.methods.getValidator(address).call({}, blockNumber);
const accounts = yield this.contracts.getAccounts();
const name = (yield accounts.getName(address, blockNumber)) || '';
return {
name,
address,
ecdsaPublicKey: res.ecdsaPublicKey,
affiliation: res.affiliation,
score: (0, fixidity_1.fromFixed)(new bignumber_js_1.default(res.score)),
signer: res.signer,
};
});
}
getValidatorsGroup(address) {
return __awaiter(this, void 0, void 0, function* () {
return this.contract.methods.getValidatorsGroup(address).call();
});
}
getMembershipInLastEpoch(address) {
return __awaiter(this, void 0, void 0, function* () {
return this.contract.methods.getMembershipInLastEpoch(address).call();
});
}
getValidatorFromSigner(address, blockNumber) {
return __awaiter(this, void 0, void 0, function* () {
const account = yield this.signerToAccount(address);
if ((0, address_1.eqAddress)(account, address_1.NULL_ADDRESS) || !(yield this.isValidator(account))) {
return {
name: 'Unregistered validator',
address,
ecdsaPublicKey: '',
affiliation: '',
score: new bignumber_js_1.default(0),
signer: address,
};
}
else {
return this.getValidator(account, blockNumber);
}
});
}
/** Get ValidatorGroup information */
getValidatorGroup(address, getAffiliates = true, blockNumber) {
return __awaiter(this, void 0, void 0, function* () {
// @ts-ignore: Expected 0-1 arguments, but got 2
const res = yield this.contract.methods.getValidatorGroup(address).call({}, blockNumber);
const accounts = yield this.contracts.getAccounts();
const name = (yield accounts.getName(address, blockNumber)) || '';
let affiliates = [];
if (getAffiliates) {
const validators = yield this.getRegisteredValidators(blockNumber);
affiliates = validators
.filter((v) => v.affiliation && (0, address_1.eqAddress)(v.affiliation, address))
.filter((v) => !res[0].includes(v.address));
}
return {
name,
address,
members: Array.from(res[0]),
commission: (0, fixidity_1.fromFixed)(new bignumber_js_1.default(res[1])),
nextCommission: (0, fixidity_1.fromFixed)(new bignumber_js_1.default(res[2])),
nextCommissionBlock: new bignumber_js_1.default(res[3]),
membersUpdated: res[4].reduce((a, b) => Math.max(a, new bignumber_js_1.default(b).toNumber()), 0),
affiliates: affiliates.map((v) => v.address),
slashingMultiplier: (0, fixidity_1.fromFixed)(new bignumber_js_1.default(res[5])),
lastSlashed: (0, BaseWrapper_1.valueToBigNumber)(res[6]),
};
});
}
/** Get list of registered validator addresses */
getRegisteredValidatorsAddresses(blockNumber) {
return __awaiter(this, void 0, void 0, function* () {
// @ts-ignore: Expected 0-1 arguments, but got 2
return this.contract.methods.getRegisteredValidators().call({}, blockNumber);
});
}
/** Get list of registered validators */
getRegisteredValidators(blockNumber) {
return __awaiter(this, void 0, void 0, function* () {
const vgAddresses = yield this.getRegisteredValidatorsAddresses(blockNumber);
return (0, async_1.concurrentMap)(10, vgAddresses, (addr) => this.getValidator(addr, blockNumber));
});
}
/** Get list of registered validator groups */
getRegisteredValidatorGroups() {
return __awaiter(this, void 0, void 0, function* () {
const vgAddresses = yield this.getRegisteredValidatorGroupsAddresses();
return (0, async_1.concurrentMap)(10, vgAddresses, (addr) => this.getValidatorGroup(addr, false));
});
}
/**
* De-registers a validator, removing it from the group for which it is a member.
* @param validatorAddress Address of the validator to deregister
*/
deregisterValidator(validatorAddress) {
return __awaiter(this, void 0, void 0, function* () {
const allValidators = yield this.getRegisteredValidatorsAddresses();
const idx = (0, address_1.findAddressIndex)(validatorAddress, allValidators);
if (idx < 0) {
throw new Error(`${validatorAddress} is not a registered validator`);
}
return (0, connect_1.toTransactionObject)(this.connection, this.contract.methods.deregisterValidator(idx));
});
}
/**
* Registers a validator group with no member validators.
* Fails if the account is already a validator or validator group.
* Fails if the account does not have sufficient weight.
*
* @param commission the commission this group receives on epoch payments made to its members.
*/
registerValidatorGroup(commission) {
return __awaiter(this, void 0, void 0, function* () {
return (0, connect_1.toTransactionObject)(this.connection, this.contract.methods.registerValidatorGroup((0, fixidity_1.toFixed)(commission).toFixed()));
});
}
/**
* De-registers a validator Group
* @param validatorGroupAddress Address of the validator group to deregister
*/
deregisterValidatorGroup(validatorGroupAddress) {
return __awaiter(this, void 0, void 0, function* () {
const allGroups = yield this.getRegisteredValidatorGroupsAddresses();
const idx = (0, address_1.findAddressIndex)(validatorGroupAddress, allGroups);
if (idx < 0) {
throw new Error(`${validatorGroupAddress} is not a registered validator`);
}
return (0, connect_1.toTransactionObject)(this.connection, this.contract.methods.deregisterValidatorGroup(idx));
});
}
/**
* Adds a member to the end of a validator group's list of members.
* Fails if `validator` has not set their affiliation to this account.
* @param validator The validator to add to the group
*/
addMember(group, validator) {
return __awaiter(this, void 0, void 0, function* () {
const numMembers = yield this.getValidatorGroupSize(group);
if (numMembers === 0) {
const election = yield this.contracts.getElection();
const voteWeight = yield election.getTotalVotesForGroup(group);
const { lesser, greater } = yield election.findLesserAndGreaterAfterVote(group, voteWeight);
return (0, connect_1.toTransactionObject)(this.connection, this.contract.methods.addFirstMember(validator, lesser, greater));
}
else {
return (0, connect_1.toTransactionObject)(this.connection, this.contract.methods.addMember(validator));
}
});
}
/**
* Reorders a member within a validator group.
* Fails if `validator` is not a member of the account's validator group.
* @param groupAddr The validator group
* @param validator The validator to reorder.
* @param newIndex New position for the validator
*/
reorderMember(groupAddr, validator, newIndex) {
return __awaiter(this, void 0, void 0, function* () {
const group = yield this.getValidatorGroup(groupAddr);
if (newIndex < 0 || newIndex >= group.members.length) {
throw new Error(`Invalid index ${newIndex}; max index is ${group.members.length - 1}`);
}
const currentIdx = (0, address_1.findAddressIndex)(validator, group.members);
if (currentIdx < 0) {
throw new Error(`ValidatorGroup ${groupAddr} does not include ${validator}`);
}
else if (currentIdx === newIndex) {
throw new Error(`Validator is already in position ${newIndex}`);
}
// remove the element
group.members.splice(currentIdx, 1);
// add it on new position
group.members.splice(newIndex, 0, validator);
const nextMember = newIndex === group.members.length - 1 ? address_1.NULL_ADDRESS : group.members[newIndex + 1];
const prevMember = newIndex === 0 ? address_1.NULL_ADDRESS : group.members[newIndex - 1];
return (0, connect_1.toTransactionObject)(this.connection, this.contract.methods.reorderMember(validator, nextMember, prevMember));
});
}
getEpochSizeNumber() {
return __awaiter(this, void 0, void 0, function* () {
const epochSize = yield this.getEpochSize();
return epochSize.toNumber();
});
}
getLastBlockNumberForEpoch(epochNumber) {
return __awaiter(this, void 0, void 0, function* () {
const epochManagerWrapper = yield this.contracts.getEpochManager();
return epochManagerWrapper.getLastBlockAtEpoch(epochNumber);
});
}
getEpochNumberOfBlock(blockNumber) {
return __awaiter(this, void 0, void 0, function* () {
const epochManagerWrapper = yield this.contracts.getEpochManager();
return epochManagerWrapper.getEpochNumberOfBlock(blockNumber);
});
}
/**
* Retrieves ValidatorRewards for epochNumber.
* @param epochNumber The epoch to retrieve ValidatorRewards at.
*/
getValidatorRewards(epochNumber, useBlockNumber) {
return __awaiter(this, void 0, void 0, function* () {
const blockNumber = yield this.getLastBlockNumberForEpoch(epochNumber);
const epochManager = yield this.contracts.getEpochManager();
const events = yield epochManager.getPastEvents('ValidatorEpochPaymentDistributed', {
fromBlock: blockNumber,
toBlock: blockNumber,
});
const validator = yield (0, async_1.concurrentMap)(10, events, (e) => {
return this.getValidator(e.returnValues.validator, useBlockNumber ? blockNumber : undefined);
});
const validatorGroup = yield (0, async_1.concurrentMap)(10, events, (e) => {
return this.getValidatorGroup(e.returnValues.group, false, useBlockNumber ? blockNumber : undefined);
});
return events.map((e, index) => ({
epochNumber,
validator: validator[index],
validatorPayment: (0, BaseWrapper_1.valueToBigNumber)(e.returnValues.validatorPayment),
group: validatorGroup[index],
groupPayment: (0, BaseWrapper_1.valueToBigNumber)(e.returnValues.groupPayment),
}));
});
}
/**
* Returns the current set of validator signer addresses
*/
currentSignerSet() {
return __awaiter(this, void 0, void 0, function* () {
const n = (0, BaseWrapper_1.valueToInt)(yield this.contract.methods.numberValidatorsInCurrentSet().call());
return (0, async_1.concurrentMap)(5, (0, collections_1.zeroRange)(n), (idx) => this.contract.methods.validatorSignerAddressFromCurrentSet(idx).call());
});
}
/**
* Returns the current set of validator signer and account addresses
*/
currentValidatorAccountsSet() {
return __awaiter(this, void 0, void 0, function* () {
const signerAddresses = yield this.currentSignerSet();
const accountAddresses = yield (0, async_1.concurrentMap)(5, signerAddresses, (signer) => this.validatorSignerToAccount(signer));
return (0, collections_1.zip)((signer, account) => ({ signer, account }), signerAddresses, accountAddresses);
});
}
/**
* Returns the group membership for validator account.
* @param account Address of validator account to retrieve group membership for.
* @param blockNumber Block number to retrieve group membership at.
* @return Group and membership history index for `validator`.
*/
getValidatorMembershipHistoryIndex(account, blockNumber) {
return __awaiter(this, void 0, void 0, function* () {
const blockEpoch = yield this.getEpochNumberOfBlock(blockNumber || (yield this.connection.getBlockNumber()));
const membershipHistory = yield this.getValidatorMembershipHistory(account);
const historyIndex = this.findValidatorMembershipHistoryIndex(blockEpoch, membershipHistory);
const group = membershipHistory[historyIndex].group;
return { group, historyIndex };
});
}
/**
* Returns the index into `history` for `epoch`.
* @param epoch The needle.
* @param history The haystack.
* @return Index for epoch or -1.
*/
findValidatorMembershipHistoryIndex(epoch, history) {
const revIndex = history
.slice()
.reverse()
.findIndex((x) => x.epoch <= epoch);
return revIndex < 0 ? -1 : history.length - revIndex - 1;
}
}
exports.ValidatorsWrapper = ValidatorsWrapper;
//# sourceMappingURL=Validators.js.map