@celo/contractkit
Version:
Celo's ContractKit to interact with Celo network
317 lines • 16.4 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.LockedGoldWrapper = void 0;
const collections_1 = require("@celo/base/lib/collections");
const bignumber_js_1 = __importDefault(require("bignumber.js"));
const BaseWrapper_1 = require("../wrappers/BaseWrapper");
const BaseWrapperForGoverning_1 = require("./BaseWrapperForGoverning");
const bigNumberComparator = (a, b) => a.lt(b);
function linkedListChanges(groups, changed) {
return (0, collections_1.linkedListChanges)(groups, changed, bigNumberComparator);
}
/**
* Contract for handling deposits needed for voting.
*/
class LockedGoldWrapper extends BaseWrapperForGoverning_1.BaseWrapperForGoverning {
constructor() {
super(...arguments);
/**
* Withdraws a gold that has been unlocked after the unlocking period has passed.
* @param index The index of the pending withdrawal to withdraw.
*/
this.withdraw = (0, BaseWrapper_1.proxySend)(this.connection, this.contract.methods.withdraw);
/**
* Locks gold to be used for voting.
* The gold to be locked, must be specified as the `tx.value`
*/
this.lock = (0, BaseWrapper_1.proxySend)(this.connection, this.contract.methods.lock);
/**
* Delegates locked gold.
*/
this.delegate = (0, BaseWrapper_1.proxySend)(this.connection, this.contract.methods.delegateGovernanceVotes);
/**
* Updates the amount of delegated locked gold. There might be discrepancy between the amount of locked gold
* and the amount of delegated locked gold because of received rewards.
*/
this.updateDelegatedAmount = (0, BaseWrapper_1.proxySend)(this.connection, this.contract.methods.updateDelegatedAmount);
/**
* Revokes delegated locked gold.
*/
this.revokeDelegated = (0, BaseWrapper_1.proxySend)(this.connection, this.contract.methods.revokeDelegatedGovernanceVotes);
this.getMaxDelegateesCount = () => __awaiter(this, void 0, void 0, function* () {
const maxDelegateesCountHex = yield this.connection.web3.eth.getStorageAt(
// @ts-ignore
this.contract._address, 10);
return new bignumber_js_1.default(maxDelegateesCountHex, 16);
});
this.getDelegateInfo = (account) => __awaiter(this, void 0, void 0, function* () {
const totalDelegatedFractionPromise = this.contract.methods
.getAccountTotalDelegatedFraction(account)
.call();
const totalDelegatedCeloPromise = this.contract.methods.totalDelegatedCelo(account).call();
const delegateesPromise = this.contract.methods.getDelegateesOfDelegator(account).call();
const fixidity = new bignumber_js_1.default('1000000000000000000000000');
return {
totalPercentDelegated: new bignumber_js_1.default(yield totalDelegatedFractionPromise)
.multipliedBy(100)
.div(fixidity)
.toFixed() + '%',
delegatees: yield delegateesPromise,
totalVotesDelegatedToThisAccount: new bignumber_js_1.default(yield totalDelegatedCeloPromise),
};
});
/**
* Unlocks gold that becomes withdrawable after the unlocking period.
* @param value The amount of gold to unlock.
*/
this.unlock = (0, BaseWrapper_1.proxySend)(this.connection, this.contract.methods.unlock, (0, BaseWrapper_1.tupleParser)(BaseWrapper_1.valueToString));
/**
* Relocks gold that has been unlocked but not withdrawn.
* @param index The index of the pending withdrawal to relock from.
* @param value The value to relock from the specified pending withdrawal.
*/
this._relock = (0, BaseWrapper_1.proxySend)(this.connection, this.contract.methods.relock, (0, BaseWrapper_1.tupleParser)(BaseWrapper_1.valueToString, BaseWrapper_1.valueToString));
/**
* Returns the total amount of locked gold for an account.
* @param account The account.
* @return The total amount of locked gold for an account.
*/
this.getAccountTotalLockedGold = (0, BaseWrapper_1.proxyCall)(this.contract.methods.getAccountTotalLockedGold, undefined, BaseWrapper_1.valueToBigNumber);
/**
* Returns the total amount of locked gold in the system. Note that this does not include
* gold that has been unlocked but not yet withdrawn.
* @returns The total amount of locked gold in the system.
*/
this.getTotalLockedGold = (0, BaseWrapper_1.proxyCall)(this.contract.methods.getTotalLockedGold, undefined, BaseWrapper_1.valueToBigNumber);
/**
* Returns the total amount of non-voting locked gold for an account.
* @param account The account.
* @return The total amount of non-voting locked gold for an account.
*/
this.getAccountNonvotingLockedGold = (0, BaseWrapper_1.proxyCall)(this.contract.methods.getAccountNonvotingLockedGold, undefined, BaseWrapper_1.valueToBigNumber);
this._getTotalPendingWithdrawalsCount = (0, BaseWrapper_1.proxyCall)(this.contract.methods.getTotalPendingWithdrawalsCount, undefined, BaseWrapper_1.valueToBigNumber);
}
getPendingWithdrawalsTotalValue(account) {
return __awaiter(this, void 0, void 0, function* () {
const pendingWithdrawals = yield this.getPendingWithdrawals(account);
// Ensure there are enough pending withdrawals to relock.
const values = pendingWithdrawals.map((pw) => pw.value);
const reducer = (total, pw) => pw.plus(total);
return values.reduce(reducer, new bignumber_js_1.default(0));
});
}
/**
* Relocks gold that has been unlocked but not withdrawn.
* @param value The value to relock from pending withdrawals.
*/
relock(account, value) {
return __awaiter(this, void 0, void 0, function* () {
const pendingWithdrawals = yield this.getPendingWithdrawals(account);
// Ensure there are enough pending withdrawals to relock.
const totalValue = yield this.getPendingWithdrawalsTotalValue(account);
if (totalValue.isLessThan(value)) {
throw new Error(`Not enough pending withdrawals to relock ${value}`);
}
// Assert pending withdrawals are sorted by time (increasing), so that we can re-lock starting
// with those furthest away from being available (at the end).
const throwIfNotSorted = (pw, i) => {
if (i > 0 && !pw.time.isGreaterThanOrEqualTo(pendingWithdrawals[i - 1].time)) {
throw new Error('Pending withdrawals not sorted by timestamp');
}
};
pendingWithdrawals.forEach(throwIfNotSorted);
let remainingToRelock = new bignumber_js_1.default(value);
const relockPw = (acc, pw, i) => {
const valueToRelock = bignumber_js_1.default.minimum(pw.value, remainingToRelock);
if (!valueToRelock.isZero()) {
remainingToRelock = remainingToRelock.minus(valueToRelock);
acc.push(this._relock(i, valueToRelock));
}
return acc;
};
return pendingWithdrawals.reduceRight(relockPw, []);
});
}
/**
* Returns current configuration parameters.
*/
getConfig() {
return __awaiter(this, void 0, void 0, function* () {
return {
unlockingPeriod: (0, BaseWrapper_1.valueToBigNumber)(yield this.contract.methods.unlockingPeriod().call()),
totalLockedGold: yield this.getTotalLockedGold(),
};
});
}
/**
* @dev Returns human readable configuration of the lockedcelo contract
* @return LockedGoldConfig object
*/
getHumanReadableConfig() {
return __awaiter(this, void 0, void 0, function* () {
const config = yield this.getConfig();
return Object.assign(Object.assign({}, config), { unlockingPeriod: (0, BaseWrapper_1.secondsToDurationString)(config.unlockingPeriod) });
});
}
getAccountSummary(account) {
return __awaiter(this, void 0, void 0, function* () {
const validators = yield this.contracts.getValidators();
const nonvotingPromise = this.getAccountNonvotingLockedGold(account);
const totalPromise = this.getAccountTotalLockedGold(account);
const requirementPromise = validators.getAccountLockedGoldRequirement(account);
const pendingWithdrawalsPromise = this.getPendingWithdrawals(account);
const accountTotalGovernanceVotingPowerPromise = this.getAccountTotalGovernanceVotingPower(account);
return {
lockedGold: {
total: yield totalPromise,
nonvoting: yield nonvotingPromise,
requirement: yield requirementPromise,
},
totalGovernaneVotingPower: yield accountTotalGovernanceVotingPowerPromise,
pendingWithdrawals: yield pendingWithdrawalsPromise,
};
});
}
/**
* Returns the total amount of governance voting power for an account.
* @param account The address of the account.
* @return The total amount of governance voting power for an account.
*/
getAccountTotalGovernanceVotingPower(account) {
return __awaiter(this, void 0, void 0, function* () {
const totalGovernanceVotingPower = yield this.contract.methods
.getAccountTotalGovernanceVotingPower(account)
.call();
return new bignumber_js_1.default(totalGovernanceVotingPower);
});
}
/**
* Returns the pending withdrawals from unlocked gold for an account.
* @param account The address of the account.
* @return The value and timestamp for each pending withdrawal.
*/
getPendingWithdrawals(account) {
return __awaiter(this, void 0, void 0, function* () {
const withdrawals = yield this.contract.methods.getPendingWithdrawals(account).call();
return (0, collections_1.zip)((time, value) => ({
time: (0, BaseWrapper_1.valueToBigNumber)(time),
value: (0, BaseWrapper_1.valueToBigNumber)(value),
}), withdrawals[1], withdrawals[0]);
});
}
/**
* Returns the pending withdrawal at a given index for a given account.
* @param account The address of the account.
* @param index The index of the pending withdrawal.
* @return The value of the pending withdrawal.
* @return The timestamp of the pending withdrawal.
*/
getPendingWithdrawal(account, index) {
return __awaiter(this, void 0, void 0, function* () {
const response = yield this.contract.methods.getPendingWithdrawal(account, index).call();
return {
value: (0, BaseWrapper_1.valueToBigNumber)(response[0]),
time: (0, BaseWrapper_1.valueToBigNumber)(response[1]),
};
});
}
/**
* Retrieves AccountSlashed for epochNumber.
* @param epochNumber The epoch to retrieve AccountSlashed at.
*/
getAccountsSlashed(epochNumber) {
return __awaiter(this, void 0, void 0, function* () {
const epochManagerWrapper = yield this.contracts.getEpochManager();
const [fromBlock, toBlock] = yield Promise.all([
epochManagerWrapper.getFirstBlockAtEpoch(epochNumber),
epochManagerWrapper.getLastBlockAtEpoch(epochNumber),
]);
const events = yield this.getPastEvents('AccountSlashed', { fromBlock, toBlock });
return events.map((e) => ({
epochNumber,
slashed: e.returnValues.slashed,
penalty: (0, BaseWrapper_1.valueToBigNumber)(e.returnValues.penalty),
reporter: e.returnValues.reporter,
reward: (0, BaseWrapper_1.valueToBigNumber)(e.returnValues.reward),
}));
});
}
/**
* Computes parameters for slashing `penalty` from `account`.
* @param account The account to slash.
* @param penalty The amount to slash as penalty.
* @return List of (group, voting gold) to decrement from `account`.
*/
computeInitialParametersForSlashing(account, penalty) {
return __awaiter(this, void 0, void 0, function* () {
const election = yield this.contracts.getElection();
const eligible = yield election.getEligibleValidatorGroupsVotes();
const groups = eligible.map((x) => ({ address: x.address, value: x.votes }));
return this.computeParametersForSlashing(account, penalty, groups);
});
}
computeParametersForSlashing(account, penalty, groups) {
return __awaiter(this, void 0, void 0, function* () {
const changed = yield this.computeDecrementsForSlashing(account, penalty, groups);
const changes = linkedListChanges(groups, changed);
return Object.assign(Object.assign({}, changes), { indices: changed.map((a) => a.index) });
});
}
// Returns how much voting gold will be decremented from the groups voted by an account
// Implementation follows protocol/test/common/integration slashingOfGroups()
computeDecrementsForSlashing(account, penalty, allGroups) {
var _a;
return __awaiter(this, void 0, void 0, function* () {
// first check how much voting gold has to be slashed
const nonVoting = yield this.getAccountNonvotingLockedGold(account);
if (penalty.isLessThan(nonVoting)) {
return [];
}
let difference = penalty.minus(nonVoting);
// find voted groups
const election = yield this.contracts.getElection();
const groups = yield election.getGroupsVotedForByAccount(account);
const res = [];
//
for (let i = groups.length - 1; i >= 0; i--) {
const group = groups[i];
const totalVotes = (_a = allGroups.find((a) => a.address === group)) === null || _a === void 0 ? void 0 : _a.value;
if (!totalVotes) {
throw new Error(`Cannot find group ${group}`);
}
const votes = yield election.getTotalVotesForGroupByAccount(group, account);
const slashedVotes = votes.lt(difference) ? votes : difference;
res.push({ address: group, value: totalVotes.minus(slashedVotes), index: i });
difference = difference.minus(slashedVotes);
if (difference.eq(new bignumber_js_1.default(0))) {
break;
}
}
return res;
});
}
/**
* Returns the number of pending withdrawals for the specified account.
* @param account The account.
* @returns The count of pending withdrawals.
*/
getTotalPendingWithdrawalsCount(account) {
return __awaiter(this, void 0, void 0, function* () {
return this._getTotalPendingWithdrawalsCount(account);
});
}
}
exports.LockedGoldWrapper = LockedGoldWrapper;
//# sourceMappingURL=LockedGold.js.map