@celo/contractkit
Version:
Celo's ContractKit to interact with Celo network
430 lines • 22.5 kB
JavaScript
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
;