UNPKG

@celo/contractkit

Version:

Celo's ContractKit to interact with Celo network

430 lines 22.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.ElectionWrapper = 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 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. */ class ElectionWrapper extends BaseWrapperForGoverning_1.BaseWrapperForGoverning { constructor() { super(...arguments); /** * Returns the current election threshold. * @returns Election threshold. */ this.electabilityThreshold = (0, BaseWrapper_1.proxyCall)(this.contract.methods.getElectabilityThreshold, undefined, BaseWrapper_1.fixidityValueToBigNumber); /** * Gets a validator address from the validator set at the given block number. * @param index Index of requested validator in the validator set. * @param blockNumber Block number to retrieve the validator set from. * @return Address of validator at the requested index. */ this.validatorSignerAddressFromSet = (0, BaseWrapper_1.proxyCall)(this.contract.methods.validatorSignerAddressFromSet); /** * Gets a validator address from the current validator set. * @param index Index of requested validator in the validator set. * @return Address of validator at the requested index. */ this.validatorSignerAddressFromCurrentSet = (0, BaseWrapper_1.proxyCall)(this.contract.methods.validatorSignerAddressFromCurrentSet, (0, BaseWrapper_1.tupleParser)(BaseWrapper_1.identity)); /** * Gets the size of the validator set that must sign the given block number. * @param blockNumber Block number to retrieve the validator set from. * @return Size of the validator set. */ this.numberValidatorsInSet = (0, BaseWrapper_1.proxyCall)(this.contract.methods.numberValidatorsInSet, undefined, BaseWrapper_1.valueToInt); /** * Gets the size of the current elected validator set. * @return Size of the current elected validator set. */ this.numberValidatorsInCurrentSet = (0, BaseWrapper_1.proxyCall)(this.contract.methods.numberValidatorsInCurrentSet, undefined, BaseWrapper_1.valueToInt); /** * Returns the total votes received across all groups. * @return The total votes received across all groups. */ this.getTotalVotes = (0, BaseWrapper_1.proxyCall)(this.contract.methods.getTotalVotes, undefined, BaseWrapper_1.valueToBigNumber); /** * Returns the current validator signers using the precompiles. * @return List of current validator signers. * @deprecated use EpochManagerWrapper.getElectedSigners instead. see see https://specs.celo.org/smart_contract_updates_from_l1.html */ this.getCurrentValidatorSigners = (0, BaseWrapper_1.proxyCall)(this.contract.methods.getCurrentValidatorSigners); /** * Returns the total votes for `group` made by `account`. * @param group The address of the validator group. * @param account The address of the voting account. * @return The total votes for `group` made by `account`. */ this.getTotalVotesForGroupByAccount = (0, BaseWrapper_1.proxyCall)(this.contract.methods.getTotalVotesForGroupByAccount, undefined, BaseWrapper_1.valueToBigNumber); /** * Returns the groups that `account` has voted for. * @param account The address of the account casting votes. * @return The groups that `account` has voted for. */ this.getGroupsVotedForByAccount = (0, BaseWrapper_1.proxyCall)(this.contract.methods.getGroupsVotedForByAccount); this.getTotalVotesByAccount = (0, BaseWrapper_1.proxyCall)(this.contract.methods.getTotalVotesByAccount, undefined, BaseWrapper_1.valueToBigNumber); this._activate = (0, BaseWrapper_1.proxySend)(this.connection, this.contract.methods.activate); this._activateForAccount = (0, BaseWrapper_1.proxySend)(this.connection, this.contract.methods.activateForAccount); } /** * Returns the minimum and maximum number of validators that can be elected. * @returns The minimum and maximum number of validators that can be elected. */ electableValidators() { return __awaiter(this, void 0, void 0, function* () { const { min, max } = yield this.contract.methods.electableValidators().call(); return { min: (0, BaseWrapper_1.valueToBigNumber)(min), max: (0, BaseWrapper_1.valueToBigNumber)(max) }; }); } /** * Returns the validator signers for block `blockNumber`. * @param blockNumber Block number to retrieve signers for. * @return Address of each signer in the validator set. * @deprecated see https://specs.celo.org/smart_contract_updates_from_l1.html */ getValidatorSigners(blockNumber) { return __awaiter(this, void 0, void 0, function* () { const numValidators = yield this.numberValidatorsInSet(blockNumber); return (0, async_1.concurrentMap)(10, (0, collections_1.zeroRange)(numValidators), (i) => this.validatorSignerAddressFromSet(i, blockNumber)); }); } /** * Returns a list of elected validators with seats allocated to groups via the D'Hondt method. * @return The list of elected validators. * @dev See https://en.wikipedia.org/wiki/D%27Hondt_method#Allocation for more information. */ electValidatorSigners(min, max) { return __awaiter(this, void 0, void 0, function* () { if (min !== undefined || max !== undefined) { const config = yield this.getConfig(); const minArg = min === undefined ? config.electableValidators.min : min; const maxArg = max === undefined ? config.electableValidators.max : max; return this.contract.methods .electNValidatorSigners(minArg.toString(10), maxArg.toString(10)) .call(); } else { return this.contract.methods.electValidatorSigners().call(); } }); } /** * Returns the total votes for `group`. * @param group The address of the validator group. * @return The total votes for `group`. */ getTotalVotesForGroup(group, blockNumber) { return __awaiter(this, void 0, void 0, function* () { // @ts-ignore: Expected 0-1 arguments, but got 2 const votes = yield this.contract.methods.getTotalVotesForGroup(group).call({}, blockNumber); return (0, BaseWrapper_1.valueToBigNumber)(votes); }); } /** * Returns the active votes for `group`. * @param group The address of the validator group. * @return The active votes for `group`. */ getActiveVotesForGroup(group, blockNumber) { return __awaiter(this, void 0, void 0, function* () { // @ts-ignore: Expected 0-1 arguments, but got 2 const votes = yield this.contract.methods.getActiveVotesForGroup(group).call({}, blockNumber); return (0, BaseWrapper_1.valueToBigNumber)(votes); }); } getVotesForGroupByAccount(account, group, blockNumber) { return __awaiter(this, void 0, void 0, function* () { const pending = yield this.contract.methods .getPendingVotesForGroupByAccount(group, account) // @ts-ignore: Expected 0-1 arguments, but got 2 .call({}, blockNumber); const active = yield this.contract.methods .getActiveVotesForGroupByAccount(group, account) // @ts-ignore: Expected 0-1 arguments, but got 2 .call({}, blockNumber); return { group, pending: (0, BaseWrapper_1.valueToBigNumber)(pending), active: (0, BaseWrapper_1.valueToBigNumber)(active), }; }); } getVoter(account, blockNumber) { return __awaiter(this, void 0, void 0, function* () { const groups = yield this.contract.methods .getGroupsVotedForByAccount(account) // @ts-ignore: Expected 0-1 arguments, but got 2 .call({}, blockNumber); const votes = yield (0, async_1.concurrentMap)(10, groups, (g) => this.getVotesForGroupByAccount(account, g, blockNumber)); return { address: account, votes }; }); } /** * Returns whether or not the account has any pending votes. * @param account The address of the account casting votes. * @return The groups that `account` has voted for. */ hasPendingVotes(account) { return __awaiter(this, void 0, void 0, function* () { const groups = yield this.contract.methods.getGroupsVotedForByAccount(account).call(); const isPending = yield Promise.all(groups.map((g) => __awaiter(this, void 0, void 0, function* () { return (0, BaseWrapper_1.valueToBigNumber)(yield this.contract.methods.getPendingVotesForGroupByAccount(g, account).call()).isGreaterThan(0); }))); return isPending.some((a) => a); }); } hasActivatablePendingVotes(account) { return __awaiter(this, void 0, void 0, function* () { const groups = yield this.contract.methods.getGroupsVotedForByAccount(account).call(); const isActivatable = yield Promise.all(groups.map((g) => this.contract.methods.hasActivatablePendingVotes(account, g).call())); return isActivatable.some((a) => a); }); } /** * Returns current configuration parameters. */ getConfig() { return __awaiter(this, void 0, void 0, function* () { const res = yield Promise.all([ this.electableValidators(), this.electabilityThreshold(), this.contract.methods.maxNumGroupsVotedFor().call(), this.getTotalVotes(), ]); return { electableValidators: res[0], electabilityThreshold: res[1], maxNumGroupsVotedFor: (0, BaseWrapper_1.valueToBigNumber)(res[2]), totalVotes: res[3], currentThreshold: res[3].multipliedBy(res[1]), }; }); } getValidatorGroupVotes(address) { return __awaiter(this, void 0, void 0, function* () { const votes = yield this.contract.methods.getTotalVotesForGroup(address).call(); const eligible = yield this.contract.methods.getGroupEligibility(address).call(); const numVotesReceivable = yield this.contract.methods.getNumVotesReceivable(address).call(); const accounts = yield this.contracts.getAccounts(); const name = (yield accounts.getName(address)) || ''; return { address, name, votes: (0, BaseWrapper_1.valueToBigNumber)(votes), capacity: (0, BaseWrapper_1.valueToBigNumber)(numVotesReceivable).minus(votes), eligible, }; }); } /** * Returns the current registered validator groups and their total votes and eligibility. */ getValidatorGroupsVotes() { return __awaiter(this, void 0, void 0, function* () { const validators = yield this.contracts.getValidators(); const groups = yield validators.getRegisteredValidatorGroupsAddresses(); return (0, async_1.concurrentMap)(5, groups, (g) => this.getValidatorGroupVotes(g)); }); } /** * Activates any activatable pending votes. * @param account The account with pending votes to activate. */ activate(account, onBehalfOfAccount) { return __awaiter(this, void 0, void 0, function* () { const groups = yield this.contract.methods.getGroupsVotedForByAccount(account).call(); const isActivatable = yield Promise.all(groups.map((g) => this.contract.methods.hasActivatablePendingVotes(account, g).call())); const groupsActivatable = groups.filter((_, i) => isActivatable[i]); return groupsActivatable.map((g) => onBehalfOfAccount ? this._activateForAccount(g, account) : this._activate(g)); }); } revokePending(account, group, value) { return __awaiter(this, void 0, void 0, function* () { const groups = yield this.contract.methods.getGroupsVotedForByAccount(account).call(); const index = (0, address_1.findAddressIndex)(group, groups); const { lesser, greater } = yield this.findLesserAndGreaterAfterVote(group, value.times(-1)); return (0, connect_1.toTransactionObject)(this.connection, this.contract.methods.revokePending(group, value.toFixed(), lesser, greater, index)); }); } /** * Creates a transaction object for revoking active votes. * @param account Account to revoke votes for. * @param group Validator group to revoke votes from. * @param value Amount to be removed from active votes. * @param lesserAfterVote First group address with less vote than `account`. * @param greaterAfterVote First group address with more vote than `account`. * @dev Must pass both `lesserAfterVote` and `greaterAfterVote` or neither. */ revokeActive(account, group, value, lesserAfterVote, greaterAfterVote) { return __awaiter(this, void 0, void 0, function* () { let lesser, greater; const groups = yield this.contract.methods.getGroupsVotedForByAccount(account).call(); const index = (0, address_1.findAddressIndex)(group, groups); if (lesserAfterVote !== undefined && greaterAfterVote !== undefined) { lesser = lesserAfterVote; greater = greaterAfterVote; } else { const res = yield this.findLesserAndGreaterAfterVote(group, value.times(-1)); lesser = res.lesser; greater = res.greater; } return (0, connect_1.toTransactionObject)(this.connection, this.contract.methods.revokeActive(group, value.toFixed(), lesser, greater, index)); }); } revoke(account, group, value) { return __awaiter(this, void 0, void 0, function* () { const vote = yield this.getVotesForGroupByAccount(account, group); if (value.gt(vote.pending.plus(vote.active))) { throw new Error(`can't revoke more votes for ${group} than have been made by ${account}`); } const txos = []; const pendingValue = bignumber_js_1.default.minimum(vote.pending, value); if (!pendingValue.isZero()) { txos.push(yield this.revokePending(account, group, pendingValue)); } if (pendingValue.lt(value)) { const activeValue = value.minus(pendingValue); const { lesser, greater } = yield this.findLesserAndGreaterAfterVote(group, value.times(-1)); txos.push(yield this.revokeActive(account, group, activeValue, lesser, greater)); } return txos; }); } /** * Increments the number of total and pending votes for `group`. * @param validatorGroup The validator group to vote for. * @param value The amount of gold to use to vote. */ vote(validatorGroup, value) { return __awaiter(this, void 0, void 0, function* () { const { lesser, greater } = yield this.findLesserAndGreaterAfterVote(validatorGroup, value); return (0, connect_1.toTransactionObject)(this.connection, this.contract.methods.vote(validatorGroup, value.toFixed(), lesser, greater)); }); } /** * Returns the current eligible validator groups and their total votes. */ getEligibleValidatorGroupsVotes() { return __awaiter(this, void 0, void 0, function* () { const res = yield this.contract.methods.getTotalVotesForEligibleValidatorGroups().call(); return (0, collections_1.zip)((a, b) => ({ address: a, name: '', votes: new bignumber_js_1.default(b), capacity: new bignumber_js_1.default(0), eligible: true, }), res[0], res[1]); }); } findLesserAndGreaterAfterVote(votedGroup, voteWeight) { return __awaiter(this, void 0, void 0, function* () { const currentVotes = yield this.getEligibleValidatorGroupsVotes(); const selectedGroup = currentVotes.find((votes) => (0, address_1.eqAddress)(votes.address, votedGroup)); const voteTotal = selectedGroup ? selectedGroup.votes.plus(voteWeight) : voteWeight; let greaterKey = address_1.NULL_ADDRESS; let lesserKey = address_1.NULL_ADDRESS; // This leverages the fact that the currentVotes are already sorted from // greatest to lowest value for (const vote of currentVotes) { if (!(0, address_1.eqAddress)(vote.address, votedGroup)) { if (vote.votes.isLessThanOrEqualTo(voteTotal)) { lesserKey = vote.address; break; } greaterKey = vote.address; } } return { lesser: lesserKey, greater: greaterKey }; }); } /** * Retrieves GroupVoterRewards at epochNumber. * @param epochNumber The epoch to retrieve GroupVoterRewards at. */ getGroupVoterRewards(epochNumber, useBlockNumber) { return __awaiter(this, void 0, void 0, function* () { const epochManager = yield this.contracts.getEpochManager(); const blockNumber = yield epochManager.getLastBlockAtEpoch(epochNumber); const events = yield this.getPastEvents('EpochRewardsDistributedToVoters', { fromBlock: blockNumber, toBlock: blockNumber, }); const validators = yield this.contracts.getValidators(); const validatorGroup = yield (0, async_1.concurrentMap)(10, events, (e) => { return validators.getValidatorGroup(e.returnValues.group, false, useBlockNumber ? blockNumber : undefined); }); return events.map((e, index) => ({ epochNumber, group: validatorGroup[index], groupVoterPayment: (0, BaseWrapper_1.valueToBigNumber)(e.returnValues.value), })); }); } /** * Retrieves VoterRewards for address at epochNumber. * @param address The address to retrieve VoterRewards for. * @param epochNumber The epoch to retrieve VoterRewards at. * @param voterShare Optionally address' share of group rewards. */ getVoterRewards(address, epochNumber, useBlockNumber, voterShare) { return __awaiter(this, void 0, void 0, function* () { const activeVoteShare = voterShare || (yield this.getVoterShare(address, yield (yield this.contracts.getEpochManager()).getLastBlockAtEpoch(epochNumber))); const groupVoterRewards = yield this.getGroupVoterRewards(epochNumber, useBlockNumber); const voterRewards = groupVoterRewards.filter((e) => (0, address_1.normalizeAddressWith0x)(e.group.address) in activeVoteShare); return voterRewards.map((e) => { const group = (0, address_1.normalizeAddressWith0x)(e.group.address); return { address, addressPayment: e.groupVoterPayment.times(activeVoteShare[group]), group: e.group, epochNumber: e.epochNumber, }; }); }); } /** * Retrieves a voter's share of active votes. * @param address The voter to retrieve share for. * @param blockNumber The block to retrieve the voter's share at. */ getVoterShare(address, blockNumber) { return __awaiter(this, void 0, void 0, function* () { const activeVoterVotes = {}; const voter = yield this.getVoter(address, blockNumber); for (const vote of voter.votes) { const group = (0, address_1.normalizeAddressWith0x)(vote.group); activeVoterVotes[group] = vote.active; } return (0, async_1.concurrentValuesMap)(10, activeVoterVotes, (voterVotes, group) => __awaiter(this, void 0, void 0, function* () { return voterVotes.dividedBy(yield this.getActiveVotesForGroup(group, blockNumber)); })); }); } getGroupEpochRewards(group, totalEpochRewards, groupScore) { return __awaiter(this, void 0, void 0, function* () { const rewards = yield this.contract.methods .getGroupEpochRewardsBasedOnScore(group, totalEpochRewards.toFixed(), groupScore.toFixed()) .call(); return (0, BaseWrapper_1.valueToBigNumber)(rewards); }); } } exports.ElectionWrapper = ElectionWrapper; //# sourceMappingURL=Election.js.map