UNPKG

@celo/contractkit

Version:

Celo's ContractKit to interact with Celo network

514 lines 27.5 kB
"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