From 02f39db4f6b3abef4fd095bd39b43751c3d27cf0 Mon Sep 17 00:00:00 2001
From: Asa Oines
Date: Wed, 18 Sep 2019 17:44:17 -0700
Subject: [PATCH 001/149] Most things that don't touch incentives
---
.../contracts/governance/Election.sol | 542 ++++++++++++
.../contracts/governance/LockedGold.sol | 809 ++++--------------
.../contracts/governance/Validators.sol | 471 ++--------
3 files changed, 785 insertions(+), 1037 deletions(-)
create mode 100644 packages/protocol/contracts/governance/Election.sol
diff --git a/packages/protocol/contracts/governance/Election.sol b/packages/protocol/contracts/governance/Election.sol
new file mode 100644
index 00000000000..0ceb736ca54
--- /dev/null
+++ b/packages/protocol/contracts/governance/Election.sol
@@ -0,0 +1,542 @@
+pragma solidity ^0.5.3;
+
+import "openzeppelin-solidity/contracts/math/SafeMath.sol";
+import "openzeppelin-solidity/contracts/ownership/Ownable.sol";
+import "openzeppelin-solidity/contracts/utils/ReentrancyGuard.sol";
+import "solidity-bytes-utils/contracts/BytesLib.sol";
+
+import "./UsingLockedGold.sol";
+import "./interfaces/IValidators.sol";
+import "../common/Initializable.sol";
+import "../common/FixidityLib.sol";
+import "../common/linkedlists/AddressLinkedList.sol";
+import "../common/linkedlists/AddressSortedLinkedList.sol";
+
+
+contract Election is Ownable, ReentrancyGuard, Initializable, UsingLockedGold {
+
+ using FixidityLib for FixidityLib.Fraction;
+ using AddressSortedLinkedList for SortedLinkedList.List;
+ using SafeMath for uint256;
+
+ // Pending votes are those for which no following elections have been held.
+ // These votes have yet to contribute to the election of validators and thus do not accrue
+ // rewards.
+ struct PendingVotes {
+ // Maps groups to accounts to pending voting balance.
+ mapping(address => mapping(address => uint256)) value;
+ // Maps groups to accounts to timestamp of the account's most recent vote for the group.
+ mapping(address => mapping(address => uint256)) timestamp;
+ }
+
+ // Active votes are those for which at least one following election has been held.
+ // These votes have contributed to the election of validators and thus accrue rewards.
+ struct ActiveVotes {
+ // Maps groups to accounts to the numerator of the account's fraction of the group's
+ // total active votes.
+ mapping(address => mapping(address => uint256)) numerators;
+ // Maps groups to the denominator of all accounts' fraction of the group's total active votes.
+ mapping(address => uint256) denominators;
+ }
+
+ struct Votes {
+ PendingVotes pending;
+ ActiveVotes active;
+ // A sorted list of ValidatorGroups by total votes.
+ SortedLinkedList.List totals;
+ // Maps an account to the list of groups it's voting for.
+ mapping(address => address[]) lists;
+ }
+
+ Votes public votes;
+ uint256 public minElectableValidators;
+ uint256 public maxElectableValidators;
+ uint256 public maxVotesPerAccount;
+ uint256 public totalVotes;
+ FixidityLib.Fraction public electabilityThreshold;
+
+ event MinElectableValidatorsSet(
+ uint256 minElectableValidators
+ );
+
+ event MaxElectableValidatorsSet(
+ uint256 maxElectableValidators
+ );
+
+ event MaxVotesPerAccountSet(
+ uint256 maxVotesPerAccount
+ );
+
+ event ValidatorGroupVoteCast(
+ address indexed account,
+ address indexed group,
+ uint256 weight
+ );
+
+ event ValidatorGroupVoteRevoked(
+ address indexed account,
+ address indexed group,
+ uint256 weight
+ );
+
+ /**
+ * @notice Initializes critical variables.
+ * @param registryAddress The address of the registry contract.
+ * @param _minElectableValidators The minimum number of validators that can be elected.
+ * @param _maxVotesPerAccount The maximum number of groups that an acconut can vote for at once.
+ * @dev Should be called only once.
+ */
+ function initialize(
+ address registryAddress,
+ uint256 _minElectableValidators,
+ uint256 _maxElectableValidators,
+ uint256 _maxVotesPerAccount
+ )
+ external
+ initializer
+ {
+ require(_minElectableValidators > 0 && _maxElectableValidators >= _minElectableValidators);
+ _transferOwnership(msg.sender);
+ setRegistry(registryAddress);
+ minElectableValidators = _minElectableValidators;
+ maxElectableValidators = _maxElectableValidators;
+ maxVotesPerAccount = _maxVotesPerAccount;
+ }
+
+ /**
+ * @notice Updates the minimum number of validators that can be elected.
+ * @param _minElectableValidators The minimum number of validators that can be elected.
+ * @return True upon success.
+ */
+ function setMinElectableValidators(
+ uint256 _minElectableValidators
+ )
+ external
+ onlyOwner
+ returns (bool)
+ {
+ require(
+ _minElectableValidators > 0 &&
+ _minElectableValidators != minElectableValidators &&
+ _minElectableValidators <= maxElectableValidators
+ );
+ minElectableValidators = _minElectableValidators;
+ emit MinElectableValidatorsSet(_minElectableValidators);
+ return true;
+ }
+
+ /**
+ * @notice Updates the maximum number of validators that can be elected.
+ * @param _maxElectableValidators The maximum number of validators that can be elected.
+ * @return True upon success.
+ */
+ function setMaxElectableValidators(
+ uint256 _maxElectableValidators
+ )
+ external
+ onlyOwner
+ returns (bool)
+ {
+ require(
+ _maxElectableValidators != maxElectableValidators &&
+ _maxElectableValidators >= minElectableValidators
+ );
+ maxElectableValidators = _maxElectableValidators;
+ emit MaxElectableValidatorsSet(_maxElectableValidators);
+ return true;
+ }
+
+ /**
+ * @notice Updates the maximum number of groups an account can be voting for at once.
+ * @param _maxVotesPerAccount The maximum number of groups an account can vote for.
+ * @return True upon success.
+ */
+ function setMaxVotesPerAccount(uint256 _maxVotesPerAccount) external onlyOwner returns (bool) {
+ require(_maxVotesPerAccount != maxVotesPerAccount);
+ maxVotesPerAccount = _maxVotesPerAccount;
+ emit MaxVotesPerAccountSet(_maxVotePerAccount);
+ return true;
+ }
+
+ /**
+ * @notice Increments the number of total and pending votes for `group`.
+ * @param group The validator group to vote for.
+ * @param value The amount of gold to use to vote.
+ * @param lesser The group receiving fewer votes than `group`, or 0 if `group` has the
+ * fewest votes of any validator group.
+ * @param greater The group receiving more votes than `group`, or 0 if `group` has the
+ * most votes of any validator group.
+ * @return True upon success.
+ * @dev Fails if `group` is empty or not a validator group.
+ */
+ function vote(
+ address group,
+ uint256 value,
+ address lesser,
+ address greater
+ )
+ external
+ nonReentrant
+ returns (bool)
+ {
+ require(0 < value && value <= getNumVotesReceivable(group));
+ address account = getAccountFromVoter(msg.sender);
+ address[] storage list = votes.lists[account];
+ require(list.length < maxVotesPerAccount);
+ for (uint256 i = 0; i < list.length; i = i.add(1)) {
+ require(list[i] != group);
+ }
+ list.push(group);
+ incrementPendingVotes(group, account, value);
+ incrementTotalVotes(group, value);
+ decrementNonvotingAccountBalance(account, value);
+ emit ValidatorGroupVoteCast(account, group, value);
+ return true;
+ }
+
+ /**
+ * @notice Converts `account`'s pending votes for `group` to active votes.
+ * @param group The validator group to vote for.
+ * @return True upon success.
+ */
+ function activate(address group) external nonReentrant returns (bool) {
+ address account = getAccountFromVoter(msg.sender);
+ PendingVotes storage pending = votes.pending;
+ uint256 pendingValue = pending.values[group][account];
+ require(0 < pendingValue);
+ decrementPendingVotes(group, account, pendingValue);
+ incrementActiveVotes(group, account, pendingValue);
+ }
+
+ /**
+ * @notice Revokes `value` pending votes for `group`
+ * @param group The validator group to revoke votes from.
+ * @param value The number of votes to revoke.
+ * @param lesser The group receiving fewer votes than the group for which the vote was revoked,
+ * or 0 if that group has the fewest votes of any validator group.
+ * @param greater The group receiving more votes than the group for which the vote was revoked,
+ * or 0 if that group has the most votes of any validator group.
+ * @param index The index of the group in the account's voting list.
+ * @return True upon success.
+ * @dev Fails if the account has not voted on a validator group.
+ */
+ function revokePending(
+ address group,
+ uint256 value,
+ address lesser,
+ address greater,
+ uint256 index
+ )
+ external
+ nonReentrant
+ returns (bool)
+ {
+ require(group != address(0));
+ address account = getAccountFromVoter(msg.sender);
+ require(0 < value && value <= getAccountPendingVotesForGroup(group, account));
+ decrementPendingVotes(group, account, value);
+ decrementTotalVotes(group, value, lesser, greater);
+ incrementNonvotingAccountBalance(account, value);
+ if (getAccountTotalVotesForGroup(group, account) == 0) {
+ deleteElement(votes.lists[account], group, index);
+ }
+ emit ValidatorGroupVoteRevoked(account, group, weight);
+ return true;
+ }
+
+ /**
+ * @notice Revokes `value` active votes for `group`
+ * @param group The validator group to revoke votes from.
+ * @param value The number of votes to revoke.
+ * @param lesser The group receiving fewer votes than the group for which the vote was revoked,
+ * or 0 if that group has the fewest votes of any validator group.
+ * @param greater The group receiving more votes than the group for which the vote was revoked,
+ * or 0 if that group has the most votes of any validator group.
+ * @param index The index of the group in the account's voting list.
+ * @return True upon success.
+ * @dev Fails if the account has not voted on a validator group.
+ */
+ function revokeActive(
+ address group,
+ uint256 value,
+ address lesser,
+ address greater,
+ uint256 index
+ )
+ external
+ nonReentrant
+ returns (bool)
+ {
+ require(group != address(0));
+ address account = getAccountFromVoter(msg.sender);
+ require(0 < value && value <= getAccountActiveVotesForGroup(group, account));
+ decrementActiveVotes(group, account, value);
+ decrementTotalVotes(group, value, lesser, greater);
+ incrementNonvotingAccountBalance(account, value);
+ if (getAccountTotalVotesForGroup(group, account) == 0) {
+ deleteElement(votes.lists[account], group, index);
+ }
+ emit ValidatorGroupVoteRevoked(account, group, weight);
+ return true;
+ }
+
+ function getAccountTotalVotes(address account) external view returns (uint256) {
+ uint256 total = 0;
+ address[] memory groups = votes.lists[account];
+ for (uint256 i = 0; i < groups.length; i = i.add(1)) {
+ total = total.add(getAccountTotalVotesForGroup(groups[i], account));
+ }
+ return total;
+ }
+
+ function getAccountPendingVotesForGroup(address group, address account) external view returns (uint256) {
+ return votes.pending.values[group][account];
+ }
+
+ function getAccountActiveVotesForGroup(address group, address account) external view returns (uint256) {
+ uint256 numerator = votes.active.numerators[group][account].mul(votes.total.getValue(group));
+ uint256 denominator = votes.total.getValue(group);
+ return numerator.div(denominator);
+ }
+
+ function getAccountTotalVotesForGroup(address group, address account) external view returns (uint256) {
+ uint256 pending = getAccountPendingVotesForGroup(group, account)
+ uint256 active = getAccountActiveVotesForGroup(group, account);
+ return pending.add(active);
+ }
+
+ /**
+ * @notice Increments the number of total votes for `group` by `value`.
+ * @param group The validator group whose vote total should be incremented.
+ * @param value The number of votes to increment.
+ * @param lesser The group receiving fewer votes than the group for which the vote was cast,
+ * or 0 if that group has the fewest votes of any validator group.
+ * @param greater The group receiving more votes than the group for which the vote was cast,
+ * or 0 if that group has the most votes of any validator group.
+ */
+ function incrementTotalVotes(
+ address group,
+ uint256 value,
+ address lesser,
+ address greater
+ )
+ private
+ {
+ if (votes.contains(group)) {
+ votes.totals.update(group, votes.getValue(group).add(value), lesser, greater);
+ } else {
+ votes.totals.insert(group, value, lesser, greater);
+ }
+ totalVotes = totalVotes.add(value);
+ }
+
+
+ /**
+ * @notice Decrements the number of total votes for `group` by `value`.
+ * @param group The validator group whose vote total should be decremented.
+ * @param value The number of votes to decrement.
+ * @param lesser The group receiving fewer votes than the group for which the vote was revoked,
+ * or 0 if that group has the fewest votes of any validator group.
+ * @param greater The group receiving more votes than the group for which the vote was revoked,
+ * or 0 if that group has the most votes of any validator group.
+ */
+ function decrementTotalVotes(
+ address group,
+ uint256 value,
+ address lesser,
+ address greater
+ )
+ private
+ {
+ if (votes.totals.contains(group)) {
+ uint256 newVoteTotal = votes.totals.getValue(group).sub(value);
+ if (newVoteTotal > 0) {
+ votes.totals.update(group, newVoteTotal, lesser, greater);
+ } else {
+ // Groups receiving no votes are not electable.
+ votes.totals.remove(group);
+ }
+ }
+ totalVotes = totalVotes.sub(value);
+ }
+
+ function incrementActiveVotes(address group, address account, uint256 value) private {
+ uint256 delta = getActiveVotesDelta(group, account, value);
+ ActiveVotes storage active = votes.active;
+ active.denominators[group] = active.denominators[group].add(delta);
+ active.numerators[group][account] = active.numerators[group][account].add(delta);
+ }
+
+ function decrementActiveVotes(address group, address account, uint256 value) private {
+ uint256 delta = getActiveVotesDelta(group, account, value);
+ ActiveVotes storage active = votes.active;
+ active.denominators[group] = active.denominators[group].sub(delta);
+ active.numerators[group][account] = active.numerators[group][account].sub(delta);
+ }
+
+ function incrementPendingVotes(address group, address account, uint256 value) private {
+ PendingVotes storage pending = votes.pending;
+ pending.values[group][account] = pending.values[group][account].add(value);
+ pending.timestamps[group][account] = now;
+ }
+
+ function decrementPendingVotes(address group, address account, uint256 value) private {
+ PendingVotes storage pending = votes.pending;
+ uint256 newValue = pending.values[group][account].sub(value);
+ pending.values[group][account] = newValue;
+ if (newValue == 0) {
+ pending.timestamps[group][account] = 0;
+ }
+ }
+
+ function getActiveVotesDelta(address group, address account, uint256 value) private {
+ uint256 total = votes.totals.getValue(group);
+ // Preserve delta * total = value * denominator
+ uint256 delta = value.mul(active.denominators[group]).div(total);
+ }
+
+ /**
+ * @notice Deletes an element from a list of addresses.
+ * @param list The list of addresses.
+ * @param element The address to delete.
+ * @param index The index of `element` in the list.
+ */
+ function deleteElement(address[] storage list, address element, uint256 index) private {
+ require(index < list.length && list[index] == element);
+ uint256 lastIndex = list.length.sub(1);
+ list[index] = list[lastIndex];
+ list[lastIndex] = address(0);
+ list.length = lastIndex;
+ }
+
+ function getNumVotesReceivable(address group) public view returns (uint256) {
+ uint256 numerator = getNumGroupMembers(group).add(1).mul(getTotalLockedGold());
+ uint256 denominator = Math.min(maxElectableValidators, getNumRegisteredValidators());
+ return numerator.div(denominator);
+ }
+
+ function validatorAddressFromCurrentSet(uint256 index) external view returns (address) {
+ address validatorAddress;
+ assembly {
+ let newCallDataPosition := mload(0x40)
+ mstore(newCallDataPosition, index)
+ let success := staticcall(5000, 0xfa, newCallDataPosition, 32, 0, 0)
+ returndatacopy(add(newCallDataPosition, 64), 0, 32)
+ validatorAddress := mload(add(newCallDataPosition, 64))
+ }
+
+ return validatorAddress;
+ }
+
+ function numberValidatorsInCurrentSet() external view returns (uint256) {
+ uint256 numberValidators;
+ assembly {
+ let success := staticcall(5000, 0xf9, 0, 0, 0, 0)
+ let returnData := mload(0x40)
+ returndatacopy(returnData, 0, 32)
+ numberValidators := mload(returnData)
+ }
+
+ return numberValidators;
+ }
+
+ /**
+ * @notice Returns electable validator group addresses and their vote totals.
+ * @return Electable validator group addresses and their vote totals.
+ */
+ function getValidatorGroupVotes() external view returns (address[] memory, uint256[] memory) {
+ return votes.getElements();
+ }
+
+ /**
+ * @notice Returns the number of votes a particular validator group has received.
+ * @param group The account that registered the validator group.
+ * @return The number of votes a particular validator group has received.
+ */
+ function getVotesReceived(address group) external view returns (uint256) {
+ return votes.getValue(group);
+ }
+
+ /**
+ * @notice 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.
+ */
+ /* solhint-disable code-complexity */
+ function electValidators() external view returns (address[] memory) {
+ // Only members of these validator groups are eligible for election.
+ uint256 maxNumElectionGroups = Math.min(maxElectableValidators, votes.totals.list.numElements);
+ uint256 requiredVotes = electabilityThreshold.multiply(FixidityLib.newFixed(totalVotes)).fromFixed();
+ address[] memory electionGroups = votes.totals.list.headN(maxNumElectionGroups, requiredVotes);
+ // Holds the number of members elected for each of the eligible validator groups.
+ uint256[] memory numMembersElected = new uint256[](electionGroups.length);
+ uint256 totalNumMembersElected = 0;
+ bool memberElectedInRound = true;
+ // Assign a number of seats to each validator group.
+ while (totalNumMembersElected < maxElectableValidators && memberElectedInRound) {
+ memberElectedInRound = false;
+ uint256 groupIndex = 0;
+ FixidityLib.Fraction memory maxN = FixidityLib.wrap(0);
+ for (uint256 i = 0; i < electionGroups.length; i = i.add(1)) {
+ bool isWinningestGroupInRound = false;
+ (maxN, isWinningestGroupInRound) = dHondt(maxN, electionGroups[i], numMembersElected[i]);
+ if (isWinningestGroupInRound) {
+ memberElectedInRound = true;
+ groupIndex = i;
+ }
+ }
+
+ if (memberElectedInRound) {
+ numMembersElected[groupIndex] = numMembersElected[groupIndex].add(1);
+ totalNumMembersElected = totalNumMembersElected.add(1);
+ }
+ }
+ require(totalNumMembersElected >= minElectableValidators);
+ // Grab the top validators from each group that won seats.
+ address[] memory electedValidators = new address[](totalNumMembersElected);
+ totalNumMembersElected = 0;
+ for (uint256 i = 0; i < electionGroups.length; i = i.add(1)) {
+ address[] memory electedGroupMembers = groups[electionGroups[i]].members.headN(
+ numMembersElected[i]
+ );
+ for (uint256 j = 0; j < electedGroupMembers.length; j = j.add(1)) {
+ // We use the validating delegate if one is set.
+ electedValidators[totalNumMembersElected] = getValidatorFromAccount(electedGroupMembers[j]);
+ totalNumMembersElected = totalNumMembersElected.add(1);
+ }
+ }
+ return electedValidators;
+ }
+ /* solhint-enable code-complexity */
+
+ /**
+ * @notice Runs D'Hondt for a validator group.
+ * @param maxN The maximum number of votes per elected seat for a group in this round.
+ * @param groupAddress The address of the validator group.
+ * @param numMembersElected The number of members elected so far for this group.
+ * @dev See https://en.wikipedia.org/wiki/D%27Hondt_method#Allocation for more information.
+ * @return The new `maxN` and whether or not the group should win a seat in this round thus far.
+ */
+ function dHondt(
+ FixidityLib.Fraction memory maxN,
+ address groupAddress,
+ uint256 numMembersElected
+ )
+ private
+ view
+ returns (FixidityLib.Fraction memory, bool)
+ {
+ ValidatorGroup storage group = groups[groupAddress];
+ // Only consider groups with members left to be elected.
+ if (group.members.numElements > numMembersElected) {
+ FixidityLib.Fraction memory n = FixidityLib.newFixed(votes.getValue(groupAddress)).divide(
+ FixidityLib.newFixed(numMembersElected.add(1))
+ );
+ if (n.gt(maxN)) {
+ return (n, true);
+ }
+ }
+ return (maxN, false);
+ }
+}
diff --git a/packages/protocol/contracts/governance/LockedGold.sol b/packages/protocol/contracts/governance/LockedGold.sol
index 4d430956429..91afae461a7 100644
--- a/packages/protocol/contracts/governance/LockedGold.sol
+++ b/packages/protocol/contracts/governance/LockedGold.sol
@@ -17,708 +17,261 @@ import "../common/FractionUtil.sol";
contract LockedGold is ILockedGold, ReentrancyGuard, Initializable, UsingRegistry {
- using FixidityLib for FixidityLib.Fraction;
- using FractionUtil for FractionUtil.Fraction;
- using SafeMath for uint256;
-
- // TODO(asa): Remove index for gas efficiency if two updates to the same slot costs extra gas.
- struct Commitment {
- uint128 value;
- uint128 index;
+ // TODO(asa): How do adjust for updated requirements?
+ // Have a refreshRequirements function validators and groups can call
+ struct MustMaintain {
+ uint256 value;
+ uint256 timestamp;
}
- struct Commitments {
- // Maps a notice period in seconds to a Locked Gold commitment.
- mapping(uint256 => Commitment) locked;
- // Maps an availability time in seconds since epoch to a notified commitment.
- mapping(uint256 => Commitment) notified;
- uint256[] noticePeriods;
- uint256[] availabilityTimes;
+ struct Authorizations {
+ address voting;
+ address validating;
}
- struct Account {
- bool exists;
- // The weight of the account in validator elections, governance, and block rewards.
- uint256 weight;
- // Each account may delegate their right to receive rewards, vote, and register a Validator or
- // Validator group to exactly one address each, respectively. This address must not hold an
- // account and must not be delegated to by any other account or by the same account for any
- // other purpose.
- address[3] delegates;
- // Frozen accounts may not vote, but may redact votes.
- bool votingFrozen;
- // The timestamp of the last time that rewards were redeemed.
- uint96 rewardsLastRedeemed;
- Commitments commitments;
+ struct PendingWithdrawal {
+ uint256 value;
+ uint256 timestamp;
}
- // TODO(asa): Add minNoticePeriod
- uint256 public maxNoticePeriod;
- uint256 public totalWeight;
- mapping(address => Account) private accounts;
- // Maps voting, rewards, and validating delegates to the account that delegated these rights.
- mapping(address => address) public delegations;
- // Maps a block number to the cumulative reward for an account with weight 1 since genesis.
- mapping(uint256 => FixidityLib.Fraction) public cumulativeRewardWeights;
-
- event MaxNoticePeriodSet(
- uint256 maxNoticePeriod
- );
-
- event RoleDelegated(
- DelegateRole role,
- address indexed account,
- address delegate
- );
-
- event VotingFrozen(
- address indexed account
- );
-
- event VotingUnfrozen(
- address indexed account
- );
-
- event NewCommitment(
- address indexed account,
- uint256 value,
- uint256 noticePeriod
- );
-
- event CommitmentNotified(
- address indexed account,
- uint256 value,
- uint256 noticePeriod,
- uint256 availabilityTime
- );
+ struct Balances {
+ // This contract does not store an account's locked gold that is being used in electing
+ // validators.
+ uint256 nonvoting;
+ PendingWithdrawal[] pendingWithdrawals;
+ MustMaintain requirements;
+ }
- event CommitmentExtended(
- address indexed account,
- uint256 value,
- uint256 noticePeriod,
- uint256 availabilityTime
- );
+ struct Account {
+ bool exists;
+ // Each account may authorize additional keys to use for voting or valdiating.
+ // These keys may not be keys of other accounts, and may not be authorized by any other
+ // account for any purpose.
+ Authorizations authorizations;
+ Balances balances;
+ }
- event Withdrawal(
- address indexed account,
- uint256 value
- );
+ mapping(address => Account) public accounts;
+ // Maps voting and validating keys to the account that provided the authorization.
+ mapping(address => address) public authorizations;
+ uint256 public nonvotingTotal;
+ uint256 public unlockingPeriod;
- event NoticePeriodIncreased(
- address indexed account,
- uint256 value,
- uint256 noticePeriod,
- uint256 increase
- );
+ event VoterAuthorized(address indexed account, address voter);
+ event ValidatorAuthorized(address indexed account, address validator);
- function initialize(address registryAddress, uint256 _maxNoticePeriod) external initializer {
+ function initialize(address registryAddress) external initializer {
_transferOwnership(msg.sender);
setRegistry(registryAddress);
- maxNoticePeriod = _maxNoticePeriod;
- }
-
- /**
- * @notice Sets the cumulative block reward for 1 unit of account weight.
- * @param blockReward The total reward allocated to bonders for this block.
- * @dev Called by the EVM at the end of the block.
- */
- function setCumulativeRewardWeight(uint256 blockReward) external {
- return;
- // TODO(asa): Modify ganache to set cumulativeRewardWeights.
- // TODO(asa): Make inheritable `onlyVm` modifier.
- // Only callable by the EVM.
- // require(msg.sender == address(0), "sender was not vm (reserved addr 0x0)");
- // FractionUtil.Fraction storage previousCumulativeRewardWeight = cumulativeRewardWeights[
- // block.number.sub(1)
- // ];
-
- // // This will be true the first time this is called by the EVM.
- // if (!previousCumulativeRewardWeight.exists()) {
- // previousCumulativeRewardWeight.denominator = 1;
- // }
-
- // if (totalWeight > 0) {
- // FractionUtil.Fraction memory currentRewardWeight = FractionUtil.Fraction(
- // blockReward,
- // totalWeight
- // ).reduce();
- // cumulativeRewardWeights[block.number] = previousCumulativeRewardWeight.add(
- // currentRewardWeight
- // );
- // } else {
- // cumulativeRewardWeights[block.number] = previousCumulativeRewardWeight;
- // }
- }
-
- /**
- * @notice Sets the maximum notice period for an account.
- * @param _maxNoticePeriod The new maximum notice period.
- */
- function setMaxNoticePeriod(uint256 _maxNoticePeriod) external onlyOwner {
- maxNoticePeriod = _maxNoticePeriod;
- emit MaxNoticePeriodSet(maxNoticePeriod);
}
/**
* @notice Creates an account.
* @return True if account creation succeeded.
*/
- function createAccount()
- external
- returns (bool)
- {
- require(isNotAccount(msg.sender) && isNotDelegate(msg.sender));
+ function createAccount() external returns (bool) {
+ require(isNotAccount(msg.sender) && isNotAuthorized(msg.sender));
Account storage account = accounts[msg.sender];
account.exists = true;
- account.rewardsLastRedeemed = uint96(block.number);
return true;
}
- /**
- * @notice Redeems rewards accrued since the last redemption for the specified account.
- * @return The amount of accrued rewards.
- * @dev Fails if `msg.sender` is not the owner or rewards recipient of the account.
- */
- function redeemRewards() external nonReentrant returns (uint256) {
- require(false, "Disabled");
- address account = getAccountFromDelegateAndRole(msg.sender, DelegateRole.Rewards);
- return _redeemRewards(account);
- }
-
- /**
- * @notice Freezes the voting power of `msg.sender`'s account.
- */
- function freezeVoting() external {
- require(isAccount(msg.sender));
- Account storage account = accounts[msg.sender];
- require(account.votingFrozen == false);
- account.votingFrozen = true;
- emit VotingFrozen(msg.sender);
- }
-
- /**
- * @notice Unfreezes the voting power of `msg.sender`'s account.
- */
- function unfreezeVoting() external {
- require(isAccount(msg.sender));
- Account storage account = accounts[msg.sender];
- require(account.votingFrozen == true);
- account.votingFrozen = false;
- emit VotingUnfrozen(msg.sender);
- }
-
- /**
- * @notice Delegates the validating power of `msg.sender`'s account to another address.
- * @param delegate The address to delegate to.
- * @param v The recovery id of the incoming ECDSA signature.
- * @param r Output value r of the ECDSA signature.
- * @param s Output value s of the ECDSA signature.
- * @dev Fails if the address is already a delegate or has an account .
- * @dev Fails if the current account is already participating in validation.
- * @dev v, r, s constitute `delegate`'s signature on `msg.sender`.
- */
- function delegateRole(
- DelegateRole role,
- address delegate,
+ function authorizeVoter(
+ address voter,
uint8 v,
bytes32 r,
bytes32 s
- )
external
nonReentrant
{
- require(isAccount(msg.sender) && isNotAccount(delegate) && isNotDelegate(delegate));
-
- address signer = Signatures.getSignerOfAddress(msg.sender, v, r, s);
- require(signer == delegate);
-
- if (role == DelegateRole.Validating) {
- require(isNotValidating(msg.sender));
- } else if (role == DelegateRole.Voting) {
- require(!isVoting(msg.sender));
- } else if (role == DelegateRole.Rewards) {
- _redeemRewards(msg.sender);
- }
-
Account storage account = accounts[msg.sender];
- delegations[account.delegates[uint256(role)]] = address(0);
- account.delegates[uint256(role)] = delegate;
- delegations[delegate] = msg.sender;
- emit RoleDelegated(role, msg.sender, delegate);
+ authorize(voter, account.authorizations.voting, v, r, s);
+ account.authorizations.voting = voter;
+ emit VoterAuthorized(msg.sender, voter);
}
- /**
- * @notice Adds a Locked Gold commitment to `msg.sender`'s account.
- * @param noticePeriod The notice period for the commitment.
- * @return The account's new weight.
- */
- function newCommitment(
- uint256 noticePeriod
- )
+ function authorizeValidator(
+ address validator,
+ uint8 v,
+ bytes32 r,
+ bytes32 s
external
nonReentrant
- payable
- returns (uint256)
{
- require(isAccount(msg.sender) && !isVoting(msg.sender));
-
- // _redeemRewards(msg.sender);
- require(msg.value > 0 && noticePeriod <= maxNoticePeriod);
Account storage account = accounts[msg.sender];
- Commitment storage locked = account.commitments.locked[noticePeriod];
- updateLockedCommitment(account, uint256(locked.value).add(msg.value), noticePeriod);
- emit NewCommitment(msg.sender, msg.value, noticePeriod);
- return account.weight;
+ authorize(validator, account.authorizations.validating, v, r, s);
+ account.authorizations.validating = validator;
+ emit ValidatorAuthorized(msg.sender, validator);
}
/**
- * @notice Notifies a Locked Gold commitment, allowing funds to be withdrawn after the notice
- * period.
- * @param value The amount of the commitment to eventually withdraw.
- * @param noticePeriod The notice period of the Locked Gold commitment.
- * @return The account's new weight.
+ * @notice Locks gold to be used for voting.
+ * @param value The amount of gold to be locked.
*/
- function notifyCommitment(
- uint256 value,
- uint256 noticePeriod
- )
- external
- nonReentrant
- returns (uint256)
- {
- require(isAccount(msg.sender) && isNotValidating(msg.sender) && !isVoting(msg.sender));
- // _redeemRewards(msg.sender);
- Account storage account = accounts[msg.sender];
- Commitment storage locked = account.commitments.locked[noticePeriod];
- require(locked.value >= value && value > 0);
- updateLockedCommitment(account, uint256(locked.value).sub(value), noticePeriod);
+ function lock(uint256 value) external nonReentrant {
+ require(isAccount(msg.sender));
+ require(msg.value == value && value > 0);
+ incrementNonvotingAccountBalance(msg.sender, value)
+ emit GoldLocked(msg.sender, value);
+ }
- // solhint-disable-next-line not-rely-on-time
- uint256 availabilityTime = now.add(noticePeriod);
- Commitment storage notified = account.commitments.notified[availabilityTime];
- updateNotifiedDeposit(account, uint256(notified.value).add(value), availabilityTime);
+ function incrementNonvotingAccountBalance(address account, uint256 value) private {
+ Account storage account = accounts[account];
+ account.gold.nonvoting = account.gold.nonvoting.add(value);
+ totalNonvoting = totalNonvoting.add(value);
+ }
- emit CommitmentNotified(msg.sender, value, noticePeriod, availabilityTime);
- return account.weight;
+ function decrementNonvotingAccountBalance(address account, uint256 value) private {
+ Account storage account = accounts[account];
+ account.gold.nonvoting = account.gold.nonvoting.sub(value);
+ totalNonvoting = totalNonvoting.sub(value);
}
- /**
- * @notice Rebonds a notified commitment, with notice period >= the remaining time to
- * availability.
- * @param value The amount of the commitment to rebond.
- * @param availabilityTime The availability time of the notified commitment.
- * @return The account's new weight.
- */
- function extendCommitment(
- uint256 value,
- uint256 availabilityTime
- )
- external
- nonReentrant
- returns (uint256)
- {
- require(isAccount(msg.sender) && !isVoting(msg.sender));
- // solhint-disable-next-line not-rely-on-time
- require(availabilityTime > now);
- // _redeemRewards(msg.sender);
+ // TODO: Can't unlock if voting in governance.
+ function unlock(uint256 value) external nonReentrant {
+ require(isAccount(msg.sender));
Account storage account = accounts[msg.sender];
- Commitment storage notified = account.commitments.notified[availabilityTime];
- require(notified.value >= value && value > 0);
- updateNotifiedDeposit(account, uint256(notified.value).sub(value), availabilityTime);
- // solhint-disable-next-line not-rely-on-time
- uint256 noticePeriod = availabilityTime.sub(now);
- Commitment storage locked = account.commitments.locked[noticePeriod];
- updateLockedCommitment(account, uint256(locked.value).add(value), noticePeriod);
- emit CommitmentExtended(msg.sender, value, noticePeriod, availabilityTime);
- return account.weight;
+ MustMaintain memory requirement = account.requirement;
+ require(
+ now >= requirement.timestamp ||
+ getAccountTotalLockedGold(msg.sender).sub(value) >= requirement.value
+ );
+ decrementNonvotingAccountBalance(msg.sender, value);
+ uint256 available = now.add(unlockingPeriod);
+ account.balances.pendingWithdrawals.push(PendingWithdrawal(value, available));
+ emit GoldUnlocked(msg.sender, value, available);
}
- /**
- * @notice Withdraws a notified commitment after the duration of the notice period.
- * @param availabilityTime The availability time of the notified commitment.
- * @return The account's new weight.
- */
- function withdrawCommitment(
- uint256 availabilityTime
- )
- external
- nonReentrant
- returns (uint256)
- {
- require(isAccount(msg.sender) && !isVoting(msg.sender));
- // _redeemRewards(msg.sender);
- // solhint-disable-next-line not-rely-on-time
- require(now >= availabilityTime);
- _redeemRewards(msg.sender);
+ function relock(uint256 value, uint256 index) external nonReentrant {
+ require(isAccount(msg.sender));
Account storage account = accounts[msg.sender];
- Commitment storage notified = account.commitments.notified[availabilityTime];
- uint256 value = notified.value;
- require(value > 0);
- updateNotifiedDeposit(account, 0, availabilityTime);
+ require(index < account.gold.unlocking.length);
+ uint256 value = account.gold.unlocking[index].value;
+ incrementNonvotingAccountBalance(msg.sender, value);
+ deletePendingWithdrawal(account.gold.unlocking, index);
+ emit GoldLocked(msg.sender, value);
+ }
+ function withdraw(uint256 value, uint256 index) external nonReentrant {
+ require(isAccount(msg.sender));
+ Account storage account = accounts[msg.sender];
+ require(index < account.gold.unlocking.length);
+ PendingWithdrawal memory unlocking = account.gold.unlocking[index];
+ require(now >= unlocking.available);
+ uint256 value = unlocking.value;
+ deletePendingWithdrawal(account.gold.unlocking, index);
IERC20Token goldToken = IERC20Token(registry.getAddressFor(GOLD_TOKEN_REGISTRY_ID));
require(goldToken.transfer(msg.sender, value));
- emit Withdrawal(msg.sender, value);
- return account.weight;
+ emit GoldWithdrawn(msg.sender, value);
}
- /**
- * @notice Increases the notice period for all or part of a Locked Gold commitment.
- * @param value The amount of the Locked Gold commitment to increase the notice period for.
- * @param noticePeriod The notice period of the Locked Gold commitment.
- * @param increase The amount to increase the notice period by.
- * @return The account's new weight.
- */
- function increaseNoticePeriod(
+ function setAccountMustMaintain(
+ address account,
uint256 value,
- uint256 noticePeriod,
- uint256 increase
+ uint256 timestamp
)
- external
+ public
+ onlyRegisteredContract('Election', msg.sender)
nonReentrant
- returns (uint256)
- {
- require(isAccount(msg.sender) && !isVoting(msg.sender));
- // _redeemRewards(msg.sender);
- require(value > 0 && increase > 0);
- Account storage account = accounts[msg.sender];
- Commitment storage locked = account.commitments.locked[noticePeriod];
- require(locked.value >= value);
- updateLockedCommitment(account, uint256(locked.value).sub(value), noticePeriod);
- uint256 increasedNoticePeriod = noticePeriod.add(increase);
- uint256 increasedValue = account.commitments.locked[increasedNoticePeriod].value;
- updateLockedCommitment(account, increasedValue.add(value), increasedNoticePeriod);
- emit NoticePeriodIncreased(msg.sender, value, noticePeriod, increase);
- return account.weight;
- }
-
- /**
- * @notice Returns whether or not an account's voting power is frozen.
- * @param account The address of the account.
- * @return Whether or not the account's voting power is frozen.
- * @dev Frozen accounts can retract existing votes but not make future votes.
- */
- function isVotingFrozen(address account) external view returns (bool) {
- return accounts[account].votingFrozen;
- }
-
- /**
- * @notice Returns the timestamp of the last time the account redeemed block rewards.
- * @param _account The address of the account.
- * @return The timestamp of the last time `_account` redeemed block rewards.
- */
- function getRewardsLastRedeemed(address _account) external view returns (uint96) {
- Account storage account = accounts[_account];
- return account.rewardsLastRedeemed;
- }
-
- function isValidating(address validator) external view returns (bool) {
- IValidators validators = IValidators(registry.getAddressFor(VALIDATORS_REGISTRY_ID));
- return validators.isValidating(validator);
- }
-
- /**
- * @notice Returns the notice periods of all Locked Gold for an account.
- * @param _account The address of the account.
- * @return The notice periods of all Locked Gold for `_account`.
- */
- function getNoticePeriods(address _account) external view returns (uint256[] memory) {
- Account storage account = accounts[_account];
- return account.commitments.noticePeriods;
- }
-
- /**
- * @notice Returns the availability times of all notified commitments for an account.
- * @param _account The address of the account.
- * @return The availability times of all notified commitments for `_account`.
- */
- function getAvailabilityTimes(address _account) external view returns (uint256[] memory) {
- Account storage account = accounts[_account];
- return account.commitments.availabilityTimes;
- }
-
- /**
- * @notice Returns the value and index of a specified Locked Gold commitment.
- * @param _account The address of the account.
- * @param noticePeriod The notice period of the Locked Gold commitment.
- * @return The value and index of the specified Locked Gold commitment.
- */
- function getLockedCommitment(
- address _account,
- uint256 noticePeriod
- )
- external
- view
- returns (uint256, uint256)
- {
- Account storage account = accounts[_account];
- Commitment storage locked = account.commitments.locked[noticePeriod];
- return (locked.value, locked.index);
- }
-
- /**
- * @notice Returns the value and index of a specified notified commitment.
- * @param _account The address of the account.
- * @param availabilityTime The availability time of the notified commitment.
- * @return The value and index of the specified notified commitment.
- */
- function getNotifiedCommitment(
- address _account,
- uint256 availabilityTime
- )
- external
- view
- returns (uint256, uint256)
+ returns (bool)
{
- Account storage account = accounts[_account];
- Commitment storage notified = account.commitments.notified[availabilityTime];
- return (notified.value, notified.index);
+ accounts[account].requirement = MustMaintain(value, timestamp);
+ emit AccountMustMaintainSet(account, value, timestamp);
}
+ // TODO(asa): Dedup
/**
- * @notice Returns the account associated with the provided delegate and role.
- * @param accountOrDelegate The address of the account or voting delegate.
- * @param role The delegate role to query for.
- * @dev Fails if the `accountOrDelegate` is a non-voting delegate.
+ * @notice Returns the account associated with the `voter` address.
+ * @param accountOrVoter The address of the account or authorized voter.
+ * @dev Fails if the `accountOrVoter` is not an account or authorized voter.
* @return The associated account.
*/
- function getAccountFromDelegateAndRole(
- address accountOrDelegate,
- DelegateRole role
- )
- public
- view
- returns (address)
- {
- address delegatingAccount = delegations[accountOrDelegate];
- if (delegatingAccount != address(0)) {
- require(accounts[delegatingAccount].delegates[uint256(role)] == accountOrDelegate);
- return delegatingAccount;
+ function getAccountFromVoter(address accountOrVoter) public view returns (address) {
+ address authorizingAccount = authorizations[voter];
+ if (authorizingAccount != address(0)) {
+ require(accounts[authorizingAccount].authorizations.voter == accountOrVoter);
+ return authorizingAccount;
} else {
- return accountOrDelegate;
+ require(isAccount(accountOrVoter));
+ return accountOrVoter;
}
}
- /**
- * @notice Returns the weight of a specified account.
- * @param _account The address of the account.
- * @return The weight of the specified account.
- */
- function getAccountWeight(address _account) external view returns (uint256) {
- Account storage account = accounts[_account];
- return account.weight;
+ function getTotalLockedGold() public view returns (uint256) {
+ return nonvotingTotal.add(getTotalVotes());
}
- /**
- * @notice Returns whether or not a specified account is voting.
- * @param account The address of the account.
- * @return Whether or not the account is voting.
- */
- function isVoting(address account) public view returns (bool) {
- address voter = getDelegateFromAccountAndRole(account, DelegateRole.Voting);
- IGovernance governance = IGovernance(registry.getAddressFor(GOVERNANCE_REGISTRY_ID));
- IValidators validators = IValidators(registry.getAddressFor(VALIDATORS_REGISTRY_ID));
- return (governance.isVoting(voter) || validators.isVoting(voter));
+ function getAccountTotalLockedGold(address account) public view returns (uint256) {
+ uint256 total = accounts[account].balances.nonvoting;
+ return total.add(getAccountTotalVotes(account));
}
/**
- * @notice Returns the weight of a commitment for a given notice period.
- * @param value The value of the commitment.
- * @param noticePeriod The notice period of the commitment.
- * @return The weight of the commitment.
- * @dev A commitment's weight is (1 + sqrt(noticePeriodDays) / 30) * value.
- */
- function getCommitmentWeight(uint256 value, uint256 noticePeriod) public pure returns (uint256) {
- uint256 precision = 10000;
- uint256 noticeDays = noticePeriod.div(1 days);
- uint256 preciseMultiplier = sqrt(noticeDays).mul(precision).div(30).add(precision);
- return preciseMultiplier.mul(value).div(precision);
- }
-
- /**
- * @notice Returns the delegate for a specified account and role.
- * @param account The address of the account.
- * @param role The role to query for.
- * @return The rewards recipient for the account.
+ * @notice Returns the account associated with the `validator` address.
+ * @param accountOrValidator The address of the account or authorized validator.
+ * @dev Fails if the `accountOrValidator` is not an account or authorized validator.
+ * @return The associated account.
*/
- function getDelegateFromAccountAndRole(
- address account,
- DelegateRole role
- )
- public
- view
- returns (address)
- {
- address delegate = accounts[account].delegates[uint256(role)];
- if (delegate == address(0)) {
- return account;
+ function getAccountFromValidator(address accountOrValidator) public view returns (address) {
+ address authorizingAccount = authorizations[validator];
+ if (authorizingAccount != address(0)) {
+ require(accounts[authorizingAccount].authorizations.validator == accountOrVoter);
+ return authorizingAccount;
} else {
- return delegate;
+ require(isAccount(accountOrVoter));
+ return accountOrVoter;
}
}
- // TODO(asa): Factor in governance, validator election participation.
/**
- * @notice Redeems rewards accrued since the last redemption for a specified account.
- * @param _account The address of the account to redeem rewards for.
- * @return The amount of accrued rewards.
+ * @notice Returns the voter for the specified account.
+ * @param account The address of the account.
+ * @return The address with which the account can vote.
*/
- function _redeemRewards(address _account) private returns (uint256) {
- Account storage account = accounts[_account];
- uint256 rewardBlockNumber = block.number.sub(1);
- FixidityLib.Fraction memory previousCumulativeRewardWeight = cumulativeRewardWeights[
- account.rewardsLastRedeemed
- ];
- FixidityLib.Fraction memory cumulativeRewardWeight = cumulativeRewardWeights[
- rewardBlockNumber
- ];
- // We should never get here except in testing, where cumulativeRewardWeight will not be set.
- if (previousCumulativeRewardWeight.unwrap() == 0 || cumulativeRewardWeight.unwrap() == 0) {
- return 0;
- }
-
- FixidityLib.Fraction memory rewardWeight = cumulativeRewardWeight.subtract(
- previousCumulativeRewardWeight
- );
- require(rewardWeight.unwrap() != 0, "Rewards weight does not exist");
- uint256 value = rewardWeight.multiply(FixidityLib.wrap(account.weight)).fromFixed();
- account.rewardsLastRedeemed = uint96(rewardBlockNumber);
- if (value > 0) {
- address recipient = getDelegateFromAccountAndRole(_account, DelegateRole.Rewards);
- IERC20Token goldToken = IERC20Token(registry.getAddressFor(GOLD_TOKEN_REGISTRY_ID));
- require(goldToken.transfer(recipient, value));
- emit Withdrawal(recipient, value);
- }
- return value;
+ function getVoterFromAccount(address account) public view returns (address) {
+ require(isAccount(account));
+ address voter = accounts[account].authorizations.voter;
+ return voter == address(0) ? account : voter;
}
/**
- * @notice Updates the Locked Gold commitment for a given notice period to a new value.
- * @param account The account to update the Locked Gold commitment for.
- * @param value The new value of the Locked Gold commitment.
- * @param noticePeriod The notice period of the Locked Gold commitment.
- */
- function updateLockedCommitment(
- Account storage account,
- uint256 value,
- uint256 noticePeriod
- )
- private
- {
- Commitment storage locked = account.commitments.locked[noticePeriod];
- require(value != locked.value);
- uint256 weight;
- if (locked.value == 0) {
- locked.index = uint128(account.commitments.noticePeriods.length);
- locked.value = uint128(value);
- account.commitments.noticePeriods.push(noticePeriod);
- weight = getCommitmentWeight(value, noticePeriod);
- account.weight = account.weight.add(weight);
- totalWeight = totalWeight.add(weight);
- } else if (value == 0) {
- weight = getCommitmentWeight(locked.value, noticePeriod);
- account.weight = account.weight.sub(weight);
- totalWeight = totalWeight.sub(weight);
- deleteCommitment(locked, account.commitments, CommitmentType.Locked);
- } else {
- uint256 originalWeight = getCommitmentWeight(locked.value, noticePeriod);
- weight = getCommitmentWeight(value, noticePeriod);
-
- uint256 difference;
- if (weight >= originalWeight) {
- difference = weight.sub(originalWeight);
- account.weight = account.weight.add(difference);
- totalWeight = totalWeight.add(difference);
- } else {
- difference = originalWeight.sub(weight);
- account.weight = account.weight.sub(difference);
- totalWeight = totalWeight.sub(difference);
- }
-
- locked.value = uint128(value);
- }
- }
-
- /**
- * @notice Updates the notified commitment for a given availability time to a new value.
- * @param account The account to update the notified commitment for.
- * @param value The new value of the notified commitment.
- * @param availabilityTime The availability time of the notified commitment.
+ * @notice Returns the validator for the specified account.
+ * @param account The address of the account.
+ * @return The address with which the account can register a validator or group.
*/
- function updateNotifiedDeposit(
- Account storage account,
- uint256 value,
- uint256 availabilityTime
- )
- private
- {
- Commitment storage notified = account.commitments.notified[availabilityTime];
- require(value != notified.value);
- if (notified.value == 0) {
- notified.index = uint128(account.commitments.availabilityTimes.length);
- notified.value = uint128(value);
- account.commitments.availabilityTimes.push(availabilityTime);
- account.weight = account.weight.add(notified.value);
- totalWeight = totalWeight.add(notified.value);
- } else if (value == 0) {
- account.weight = account.weight.sub(notified.value);
- totalWeight = totalWeight.sub(notified.value);
- deleteCommitment(notified, account.commitments, CommitmentType.Notified);
- } else {
- uint256 difference;
- if (value >= notified.value) {
- difference = value.sub(notified.value);
- account.weight = account.weight.add(difference);
- totalWeight = totalWeight.add(difference);
- } else {
- difference = uint256(notified.value).sub(value);
- account.weight = account.weight.sub(difference);
- totalWeight = totalWeight.sub(difference);
- }
-
- notified.value = uint128(value);
- }
+ function getValidatorFromAccount(address account) public view returns (address) {
+ require(isAccount(account));
+ address validator = accounts[account].authorizations.validator;
+ return validator == address(0) ? account : validator;
}
/**
- * @notice Deletes a commitment from an account.
- * @param _commitment The commitment to delete.
- * @param commitments The struct containing the account's commitments.
- * @param commitmentType Whether the deleted commitment is locked or notified.
+ * @notice Authorizes voting or validating power of `msg.sender`'s account to another address.
+ * @param current The address to authorize.
+ * @param previous The previous authorized address.
+ * @param v The recovery id of the incoming ECDSA signature.
+ * @param r Output value r of the ECDSA signature.
+ * @param s Output value s of the ECDSA signature.
+ * @dev Fails if the address is already authorized or is an account.
+ * @dev v, r, s constitute `authorize`'s signature on `msg.sender`.
*/
- function deleteCommitment(
- Commitment storage _commitment,
- Commitments storage commitments,
- CommitmentType commitmentType
+ function authorize(
+ address current,
+ address previous,
+ uint8 v,
+ bytes32 r,
+ bytes32 s
)
private
+ nonReentrant
{
- uint256 lastIndex;
- if (commitmentType == CommitmentType.Locked) {
- lastIndex = commitments.noticePeriods.length.sub(1);
- commitments.locked[commitments.noticePeriods[lastIndex]].index = _commitment.index;
- deleteElement(commitments.noticePeriods, _commitment.index, lastIndex);
- } else {
- lastIndex = commitments.availabilityTimes.length.sub(1);
- commitments.notified[commitments.availabilityTimes[lastIndex]].index = _commitment.index;
- deleteElement(commitments.availabilityTimes, _commitment.index, lastIndex);
- }
+ require(isAccount(msg.sender) && isNotAccount(current) && isNotAuthorized(current));
- // Delete commitment info.
- _commitment.index = 0;
- _commitment.value = 0;
- }
+ address signer = Signatures.getSignerOfAddress(msg.sender, v, r, s);
+ require(signer == current);
- /**
- * @notice Deletes an element from a list of uint256s.
- * @param list The list of uint256s.
- * @param index The index of the element to delete.
- * @param lastIndex The index of the last element in the list.
- */
- function deleteElement(uint256[] storage list, uint256 index, uint256 lastIndex) private {
- list[index] = list[lastIndex];
- list[lastIndex] = 0;
- list.length = lastIndex;
+ authorizations[previous] = address(0);
+ authorizations[current] = msg.sender;
}
function isAccount(address account) internal view returns (bool) {
@@ -729,35 +282,13 @@ contract LockedGold is ILockedGold, ReentrancyGuard, Initializable, UsingRegistr
return (!accounts[account].exists);
}
- // Reverts if rewards, voting, or validating rights have been delegated to `account`.
- function isNotDelegate(address account) internal view returns (bool) {
- return (delegations[account] == address(0));
- }
-
- // TODO(asa): Allow users to notify if they would continue to meet the registration
- // requirements.
- function isNotValidating(address account) internal view returns (bool) {
- address validator = getDelegateFromAccountAndRole(account, DelegateRole.Validating);
- IValidators validators = IValidators(registry.getAddressFor(VALIDATORS_REGISTRY_ID));
- return (!validators.isValidating(validator));
+ function isNotAuthorized(address account) internal view returns (bool) {
+ return (authorizations[account] == address(0));
}
- // TODO: consider using Fixidity's roots
- /**
- * @notice Approxmiates the square root of x using the Bablyonian method.
- * @param x The number to take the square root of.
- * @return An approximation of the square root of x.
- * @dev The error can be large for smaller numbers, so we multiply by the square of `precision`.
- */
- function sqrt(uint256 x) private pure returns (FractionUtil.Fraction memory) {
- uint256 precision = 100;
- uint256 px = x.mul(precision.mul(precision));
- uint256 z = px.add(1).div(2);
- uint256 y = px;
- while (z < y) {
- y = z;
- z = px.div(z).add(z).div(2);
- }
- return FractionUtil.Fraction(y, precision);
+ function deletePendingWithdrawal(PendingWithdrawal[] storage list, uint256 index) private {
+ uint256 lastIndex = list.length.sub(1);
+ list[index] = list[lastIndex];
+ list.length = lastIndex;
}
}
diff --git a/packages/protocol/contracts/governance/Validators.sol b/packages/protocol/contracts/governance/Validators.sol
index c029aecb732..efd7525edb2 100644
--- a/packages/protocol/contracts/governance/Validators.sol
+++ b/packages/protocol/contracts/governance/Validators.sol
@@ -18,67 +18,53 @@ import "../common/linkedlists/AddressSortedLinkedList.sol";
*/
contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, UsingLockedGold {
+ // Votes live in the Election SC, votePortion,
+
using FixidityLib for FixidityLib.Fraction;
using AddressLinkedList for LinkedList.List;
using AddressSortedLinkedList for SortedLinkedList.List;
using SafeMath for uint256;
using BytesLib for bytes;
- // Address of the getValidator precompiled contract
- address constant public GET_VALIDATOR_ADDRESS = address(0xfa);
+ address constant PROOF_OF_POSSESSION = address(0xff - 4);
+
+ struct RegistrationRequirements {
+ uint256 group;
+ uint256 validator;
+ }
- // TODO(asa): These strings should be modifiable
struct ValidatorGroup {
- string identifier;
string name;
string url;
+ FixidityLib.Fraction commission;
LinkedList.List members;
}
- // TODO(asa): These strings should be modifiable
struct Validator {
- string identifier;
string name;
string url;
bytes publicKeysData;
address affiliation;
}
- struct LockedGoldCommitment {
- uint256 noticePeriod;
- uint256 value;
- }
-
mapping(address => ValidatorGroup) private groups;
mapping(address => Validator) private validators;
- // TODO(asa): Implement abstaining
- mapping(address => address) public voters;
address[] private _groups;
address[] private _validators;
- SortedLinkedList.List private votes;
- // TODO(asa): Support different requirements for groups vs. validators.
- LockedGoldCommitment private registrationRequirement;
- uint256 public minElectableValidators;
- uint256 public maxElectableValidators;
-
- address constant PROOF_OF_POSSESSION = address(0xff - 4);
+ RegistrationRequirements public registrationRequirements;
+ uint256 public maxGroupSize;
- event MinElectableValidatorsSet(
- uint256 minElectableValidators
- );
-
- event MaxElectableValidatorsSet(
- uint256 maxElectableValidators
+ event MaxGroupSizeSet(
+ uint256 size
);
event RegistrationRequirementSet(
- uint256 value,
- uint256 noticePeriod
+ uint256 group,
+ uint256 validator
);
event ValidatorRegistered(
address indexed validator,
- string identifier,
string name,
string url,
bytes publicKeysData
@@ -100,7 +86,6 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
event ValidatorGroupRegistered(
address indexed group,
- string identifier,
string name,
string url
);
@@ -128,124 +113,70 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
address indexed group
);
- event ValidatorGroupVoteCast(
- address indexed account,
- address indexed group,
- uint256 weight
- );
-
- event ValidatorGroupVoteRevoked(
- address indexed account,
- address indexed group,
- uint256 weight
- );
-
/**
* @notice Initializes critical variables.
* @param registryAddress The address of the registry contract.
- * @param _minElectableValidators The minimum number of validators that can be elected.
- * @param _maxElectableValidators The maximum number of validators that can be elected.
- * @param requirementValue The minimum Locked Gold commitment value to register a group or
- validator.
- * @param requirementNoticePeriod The minimum Locked Gold commitment notice period to register
- * a group or validator.
+ * @param groupRequirement The minimum locked gold needed to register a group.
+ * @param validatorRequirement The minimum locked gold needed to register a validator.
+ * @param size The maximum group size.
* @dev Should be called only once.
*/
function initialize(
address registryAddress,
- uint256 _minElectableValidators,
- uint256 _maxElectableValidators,
- uint256 requirementValue,
- uint256 requirementNoticePeriod
+ uint256 groupRequirement,
+ uint256 validatorRequirement,
+ uint256 _maxGroupSize
)
external
initializer
{
- require(_minElectableValidators > 0 && _maxElectableValidators >= _minElectableValidators);
_transferOwnership(msg.sender);
setRegistry(registryAddress);
- minElectableValidators = _minElectableValidators;
- maxElectableValidators = _maxElectableValidators;
- registrationRequirement.value = requirementValue;
- registrationRequirement.noticePeriod = requirementNoticePeriod;
+ registrationRequirement.group = groupRequirement;
+ registrationRequirement.validator = validatorRequirement;
+ maxGroupSize = _maxGroupSize;
}
/**
- * @notice Updates the minimum number of validators that can be elected.
- * @param _minElectableValidators The minimum number of validators that can be elected.
+ * @notice Updates the maximum number of members a group can have.
+ * @param size The maximum group size.
* @return True upon success.
*/
- function setMinElectableValidators(
- uint256 _minElectableValidators
- )
- external
- onlyOwner
- returns (bool)
- {
- require(
- _minElectableValidators > 0 &&
- _minElectableValidators != minElectableValidators &&
- _minElectableValidators <= maxElectableValidators
- );
- minElectableValidators = _minElectableValidators;
- emit MinElectableValidatorsSet(_minElectableValidators);
- return true;
- }
-
- /**
- * @notice Updates the maximum number of validators that can be elected.
- * @param _maxElectableValidators The maximum number of validators that can be elected.
- * @return True upon success.
- */
- function setMaxElectableValidators(
- uint256 _maxElectableValidators
- )
- external
- onlyOwner
- returns (bool)
- {
- require(
- _maxElectableValidators != maxElectableValidators &&
- _maxElectableValidators >= minElectableValidators
- );
- maxElectableValidators = _maxElectableValidators;
- emit MaxElectableValidatorsSet(_maxElectableValidators);
+ function setMaxGroupSize(uint256 size) external onlyOwner returns (bool) {
+ require(0 < size && size != maxGroupSize);
+ maxGroupSize = size;
+ emit MaxGroupSizeSet(size);
return true;
}
/**
- * @notice Updates the minimum bonding requirements to register a validator group or validator.
- * @param value The minimum Locked Gold commitment value to register a group or validator.
- * @param noticePeriod The minimum Locked Gold commitment notice period to register a group or
- * validator.
+ * @notice Updates the minimum gold requirements to register a validator group or validator.
+ * @param groupRequirement The minimum locked gold needed to register a group.
+ * @param validatorRequirement The minimum locked gold needed to register a validator.
* @return True upon success.
* @dev The new requirement is only enforced for future validator or group registrations.
*/
- function setRegistrationRequirement(
- uint256 value,
- uint256 noticePeriod
+ function setRegistrationRequirements(
+ uint256 groupRequirement,
+ uint256 validatorRequirement
)
external
onlyOwner
returns (bool)
{
require(
- value != registrationRequirement.value ||
- noticePeriod != registrationRequirement.noticePeriod
+ groupRequirement != registrationRequirements.group ||
+ validatorRequirement != registrationRequirements.validator
);
- registrationRequirement.value = value;
- registrationRequirement.noticePeriod = noticePeriod;
- emit RegistrationRequirementSet(value, noticePeriod);
+ registrationRequirements = RegistrationRequirements(groupRequirement, validatorRequirement);
+ emit RegistrationRequirementSet(groupRequirement, validatorRequirement);
return true;
}
/**
* @notice Registers a validator unaffiliated with any validator group.
- * @param identifier An identifier for this validator.
* @param name A name for the validator.
* @param url A URL for the validator.
- * @param noticePeriod The notice period of the Locked Gold commitment that meets the
- * requirements for validator registration.
* @param publicKeysData Comprised of three tightly-packed elements:
* - publicKey - The public key that the validator is using for consensus, should match
* msg.sender. 64 bytes.
@@ -257,18 +188,15 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
* @dev Fails if the account does not have sufficient weight.
*/
function registerValidator(
- string calldata identifier,
string calldata name,
string calldata url,
bytes calldata publicKeysData,
- uint256 noticePeriod
)
external
nonReentrant
returns (bool)
{
require(
- bytes(identifier).length > 0 &&
bytes(name).length > 0 &&
bytes(url).length > 0 &&
// secp256k1 public key + BLS public key + BLS proof of possession
@@ -279,12 +207,13 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
address account = getAccountFromValidator(msg.sender);
require(!isValidator(account) && !isValidatorGroup(account));
- require(meetsRegistrationRequirements(account, noticePeriod));
+ require(meetsRegistrationRequirements(account));
- Validator memory validator = Validator(identifier, name, url, publicKeysData, address(0));
+ Validator memory validator = Validator(name, url, publicKeysData, address(0));
validators[account] = validator;
_validators.push(account);
- emit ValidatorRegistered(account, identifier, name, url, publicKeysData);
+ setAccountMustMaintain(account, requirements.validator, MAX_INT);
+ emit ValidatorRegistered(account, name, url, publicKeysData);
return true;
}
@@ -314,6 +243,7 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
}
delete validators[account];
deleteElement(_validators, account, index);
+ setAccountMustMaintain(account, requirements.validator, now.add(deregisterPeriods.validator));
emit ValidatorDeregistered(account);
return true;
}
@@ -352,35 +282,39 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
/**
* @notice Registers a validator group with no member validators.
- * @param identifier A identifier for this validator group.
* @param name A name for the validator group.
* @param url A URL for the validator group.
- * @param noticePeriod The notice period of the Locked Gold commitment that meets the
- * requirements for validator registration.
* @return True upon success.
* @dev Fails if the account is already a validator or validator group.
* @dev Fails if the account does not have sufficient weight.
*/
function registerValidatorGroup(
- string calldata identifier,
string calldata name,
string calldata url,
- uint256 noticePeriod
+ uint256 commission,
+ address[] calldata members
)
external
nonReentrant
returns (bool)
{
- require(bytes(identifier).length > 0 && bytes(name).length > 0 && bytes(url).length > 0);
+ require(bytes(name).length > 0);
+ require(bytes(url).length > 0);
+ require(isFraction(commission));
+ require(members.length <= maxGroupSize);
address account = getAccountFromValidator(msg.sender);
require(!isValidator(account) && !isValidatorGroup(account));
- require(meetsRegistrationRequirements(account, noticePeriod));
- ValidatorGroup storage group = groups[account];
- group.identifier = identifier;
+ require(meetsRegistrationRequirements(account));
+
+ ValdiatorGroup storage group = groups[account];
group.name = name;
group.url = url;
+ for (uint256 i = 0; i < members.length; i = i.add(1)) {
+ group.addMember(members[i]);
+ }
_groups.push(account);
- emit ValidatorGroupRegistered(account, identifier, name, url);
+ setAccountMustMaintain(account, requirements.group, MAX_INT);
+ emit ValidatorGroupRegistered(account, name, url);
return true;
}
@@ -396,6 +330,7 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
require(isValidatorGroup(account) && groups[account].members.numElements == 0);
delete groups[account];
deleteElement(_groups, account, index);
+ setAccountMustMaintain(account, requirements.group, now.add(deregisterPeriods.group));
emit ValidatorGroupDeregistered(account);
return true;
}
@@ -410,6 +345,7 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
address account = getAccountFromValidator(msg.sender);
require(isValidatorGroup(account) && isValidator(validator));
ValidatorGroup storage group = groups[account];
+ require(group.members.length < maxGroupSize);
require(validators[validator].affiliation == account && !group.members.contains(validator));
group.members.push(validator);
emit ValidatorGroupMemberAdded(account, validator);
@@ -456,135 +392,6 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
return true;
}
- /**
- * @notice Casts a vote for a validator group.
- * @param group The validator group to vote for.
- * @param lesser The group receiving fewer votes than `group`, or 0 if `group` has the
- * fewest votes of any validator group.
- * @param greater The group receiving more votes than `group`, or 0 if `group` has the
- * most votes of any validator group.
- * @return True upon success.
- * @dev Fails if `group` is empty or not a validator group.
- * @dev Fails if the account is frozen.
- */
- function vote(
- address group,
- address lesser,
- address greater
- )
- external
- nonReentrant
- returns (bool)
- {
- // Empty validator groups are not electable.
- require(isValidatorGroup(group) && groups[group].members.numElements > 0);
- address account = getAccountFromVoter(msg.sender);
- require(!isVotingFrozen(account));
- require(voters[account] == address(0));
- uint256 weight = getAccountWeight(account);
- require(weight > 0);
- if (votes.contains(group)) {
- votes.update(
- group,
- votes.getValue(group).add(uint256(weight)),
- lesser,
- greater
- );
- } else {
- votes.insert(
- group,
- weight,
- lesser,
- greater
- );
- }
- voters[account] = group;
- emit ValidatorGroupVoteCast(account, group, weight);
- return true;
- }
-
- /**
- * @notice Revokes an outstanding vote for a validator group.
- * @param lesser The group receiving fewer votes than the group for which the vote was revoked,
- * or 0 if that group has the fewest votes of any validator group.
- * @param greater The group receiving more votes than the group for which the vote was revoked,
- * or 0 if that group has the most votes of any validator group.
- * @return True upon success.
- * @dev Fails if the account has not voted on a validator group.
- */
- function revokeVote(
- address lesser,
- address greater
- )
- external
- nonReentrant
- returns (bool)
- {
- address account = getAccountFromVoter(msg.sender);
- address group = voters[account];
- require(group != address(0));
- uint256 weight = getAccountWeight(account);
- // If the group we had previously voted on removed all its members it is no longer eligible
- // to receive votes and we don't have to worry about removing our vote.
- if (votes.contains(group)) {
- require(weight > 0);
- uint256 newVoteTotal = votes.getValue(group).sub(uint256(weight));
- if (newVoteTotal > 0) {
- votes.update(
- group,
- newVoteTotal,
- lesser,
- greater
- );
- } else {
- // Groups receiving no votes are not electable.
- votes.remove(group);
- }
- }
- voters[account] = address(0);
- emit ValidatorGroupVoteRevoked(account, group, weight);
- return true;
- }
-
- function validatorAddressFromCurrentSet(uint256 index) external view returns (address) {
- address validatorAddress;
- assembly {
- let newCallDataPosition := mload(0x40)
- mstore(newCallDataPosition, index)
- let success := staticcall(
- 5000,
- 0xfa,
- newCallDataPosition,
- 32,
- 0,
- 0
- )
- returndatacopy(add(newCallDataPosition, 64), 0, 32)
- validatorAddress := mload(add(newCallDataPosition, 64))
- }
-
- return validatorAddress;
- }
-
- function numberValidatorsInCurrentSet() external view returns (uint256) {
- uint256 numberValidators;
- assembly {
- let success := staticcall(
- 5000,
- 0xf9,
- 0,
- 0,
- 0,
- 0
- )
- let returnData := mload(0x40)
- returndatacopy(returnData, 0, 32)
- numberValidators := mload(returnData)
- }
-
- return numberValidators;
- }
-
/**
* @notice Returns validator information.
* @param account The account that registered the validator.
@@ -596,7 +403,6 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
external
view
returns (
- string memory identifier,
string memory name,
string memory url,
bytes memory publicKeysData,
@@ -606,7 +412,6 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
require(isValidator(account));
Validator storage validator = validators[account];
return (
- validator.identifier,
validator.name,
validator.url,
validator.publicKeysData,
@@ -628,32 +433,15 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
{
require(isValidatorGroup(account));
ValidatorGroup storage group = groups[account];
- return (group.identifier, group.name, group.url, group.members.getKeys());
- }
-
- /**
- * @notice Returns electable validator group addresses and their vote totals.
- * @return Electable validator group addresses and their vote totals.
- */
- function getValidatorGroupVotes() external view returns (address[] memory, uint256[] memory) {
- return votes.getElements();
- }
-
- /**
- * @notice Returns the number of votes a particular validator group has received.
- * @param group The account that registered the validator group.
- * @return The number of votes a particular validator group has received.
- */
- function getVotesReceived(address group) external view returns (uint256) {
- return votes.getValue(group);
+ return (group.name, group.url, group.members.getKeys());
}
/**
- * @notice Returns the Locked Gold commitment requirements to register a validator or group.
- * @return The minimum value and notice period for the Locked Gold commitment.
+ * @notice Returns the Locked Gold requirements to register a validator or group.
+ * @return The locked gold requirements to register a validator or group.
*/
- function getRegistrationRequirement() external view returns (uint256, uint256) {
- return (registrationRequirement.value, registrationRequirement.noticePeriod);
+ function getRegistrationRequirements() external view returns (uint256, uint256) {
+ return (registrationRequirements.group, registrationRequirement.validator);
}
/**
@@ -672,86 +460,13 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
return _groups;
}
- /**
- * @notice Returns whether a particular account is a registered validator or validator group.
- * @param account The account.
- * @return Whether a particular account is a registered validator or validator group.
- */
- function isValidating(address account) external view returns (bool) {
- return isValidator(account) || isValidatorGroup(account);
- }
-
- /**
- * @notice Returns whether a particular account is voting for a validator group.
- * @param account The account.
- * @return Whether a particular account is voting for a validator group.
- */
- function isVoting(address account) external view returns (bool) {
- return (voters[account] != address(0));
- }
-
- /**
- * @notice 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.
- */
- /* solhint-disable code-complexity */
- function getValidators() external view returns (address[] memory) {
- // Only members of these validator groups are eligible for election.
- uint256 numElectionGroups = maxElectableValidators;
- if (numElectionGroups > votes.list.numElements) {
- numElectionGroups = votes.list.numElements;
- }
- address[] memory electionGroups = votes.list.headN(numElectionGroups);
- // Holds the number of members elected for each of the eligible validator groups.
- uint256[] memory numMembersElected = new uint256[](electionGroups.length);
- uint256 totalNumMembersElected = 0;
- bool memberElectedInRound = true;
- // Assign a number of seats to each validator group.
- while (totalNumMembersElected < maxElectableValidators && memberElectedInRound) {
- memberElectedInRound = false;
- uint256 groupIndex = 0;
- FixidityLib.Fraction memory maxN = FixidityLib.wrap(0);
- for (uint256 i = 0; i < electionGroups.length; i = i.add(1)) {
- bool isWinningestGroupInRound = false;
- (maxN, isWinningestGroupInRound) = dHondt(maxN, electionGroups[i], numMembersElected[i]);
- if (isWinningestGroupInRound) {
- memberElectedInRound = true;
- groupIndex = i;
- }
- }
-
- if (memberElectedInRound) {
- numMembersElected[groupIndex] = numMembersElected[groupIndex].add(1);
- totalNumMembersElected = totalNumMembersElected.add(1);
- }
- }
- require(totalNumMembersElected >= minElectableValidators);
- // Grab the top validators from each group that won seats.
- address[] memory electedValidators = new address[](totalNumMembersElected);
- totalNumMembersElected = 0;
- for (uint256 i = 0; i < electionGroups.length; i = i.add(1)) {
- address[] memory electedGroupMembers = groups[electionGroups[i]].members.headN(
- numMembersElected[i]
- );
- for (uint256 j = 0; j < electedGroupMembers.length; j = j.add(1)) {
- // We use the validating delegate if one is set.
- electedValidators[totalNumMembersElected] = getValidatorFromAccount(electedGroupMembers[j]);
- totalNumMembersElected = totalNumMembersElected.add(1);
- }
- }
- return electedValidators;
- }
- /* solhint-enable code-complexity */
-
/**
* @notice Returns whether a particular account has a registered validator group.
* @param account The account.
* @return Whether a particular address is a registered validator group.
*/
function isValidatorGroup(address account) public view returns (bool) {
- return bytes(groups[account].identifier).length > 0;
+ return bytes(groups[account].name).length > 0;
}
/**
@@ -760,29 +475,16 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
* @return Whether a particular address is a registered validator.
*/
function isValidator(address account) public view returns (bool) {
- return bytes(validators[account].identifier).length > 0;
+ return bytes(validators[account].name).length > 0;
}
/**
* @notice Returns whether an account meets the requirements to register a validator or group.
* @param account The account.
- * @param noticePeriod The notice period of the Locked Gold commitment that meets the
- * requirements.
* @return Whether an account meets the requirements to register a validator or group.
*/
- function meetsRegistrationRequirements(
- address account,
- uint256 noticePeriod
- )
- public
- view
- returns (bool)
- {
- uint256 value = getLockedCommitmentValue(account, noticePeriod);
- return (
- value >= registrationRequirement.value &&
- noticePeriod >= registrationRequirement.noticePeriod
- );
+ function meetsValidatorRegistrationRequirements(address account) public view returns (bool) {
+ // TODO
}
/**
@@ -816,6 +518,9 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
// Empty validator groups are not electable.
if (groups[group].members.numElements == 0) {
if (votes.contains(group)) {
+ // TODO(asa): What needs to happen here????
+ // We need to remove from the linked list but preserve all other info...
+ // And probably add back in if it gets a member...
votes.remove(group);
}
emit ValidatorGroupEmptied(group);
@@ -845,34 +550,4 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
validator.affiliation = address(0);
return true;
}
-
- /**
- * @notice Runs D'Hondt for a validator group.
- * @param maxN The maximum number of votes per elected seat for a group in this round.
- * @param groupAddress The address of the validator group.
- * @param numMembersElected The number of members elected so far for this group.
- * @dev See https://en.wikipedia.org/wiki/D%27Hondt_method#Allocation for more information.
- * @return The new `maxN` and whether or not the group should win a seat in this round thus far.
- */
- function dHondt(
- FixidityLib.Fraction memory maxN,
- address groupAddress,
- uint256 numMembersElected
- )
- private
- view
- returns (FixidityLib.Fraction memory, bool)
- {
- ValidatorGroup storage group = groups[groupAddress];
- // Only consider groups with members left to be elected.
- if (group.members.numElements > numMembersElected) {
- FixidityLib.Fraction memory n = FixidityLib.newFixed(votes.getValue(groupAddress)).divide(
- FixidityLib.newFixed(numMembersElected.add(1))
- );
- if (n.gt(maxN)) {
- return (n, true);
- }
- }
- return (maxN, false);
- }
}
From 0f226c8c34ad617c9538621b06f649b5117b6aac Mon Sep 17 00:00:00 2001
From: Asa Oines
Date: Wed, 18 Sep 2019 20:09:36 -0700
Subject: [PATCH 002/149] Trying to get things to compile
---
.../contracts/common/UsingRegistry.sol | 7 +-
.../contracts/governance/Election.sol | 97 +++++++++----------
.../contracts/governance/Governance.sol | 39 ++++----
.../contracts/governance/LockedGold.sol | 51 +++++-----
.../contracts/governance/UsingLockedGold.sol | 75 +++++---------
.../contracts/governance/Validators.sol | 29 +++++-
6 files changed, 148 insertions(+), 150 deletions(-)
diff --git a/packages/protocol/contracts/common/UsingRegistry.sol b/packages/protocol/contracts/common/UsingRegistry.sol
index 378e43ea5ca..d5a599a5fc3 100644
--- a/packages/protocol/contracts/common/UsingRegistry.sol
+++ b/packages/protocol/contracts/common/UsingRegistry.sol
@@ -15,12 +15,13 @@ contract UsingRegistry is Ownable {
// solhint-disable state-visibility
bytes32 constant ATTESTATIONS_REGISTRY_ID = keccak256(abi.encodePacked("Attestations"));
- bytes32 constant LOCKED_GOLD_REGISTRY_ID = keccak256(abi.encodePacked("LockedGold"));
+ bytes32 constant ELECTION_REGISTRY_ID = keccak256(abi.encodePacked("Election"));
bytes32 constant GAS_CURRENCY_WHITELIST_REGISTRY_ID = keccak256(
abi.encodePacked("GasCurrencyWhitelist")
);
bytes32 constant GOLD_TOKEN_REGISTRY_ID = keccak256(abi.encodePacked("GoldToken"));
bytes32 constant GOVERNANCE_REGISTRY_ID = keccak256(abi.encodePacked("Governance"));
+ bytes32 constant LOCKED_GOLD_REGISTRY_ID = keccak256(abi.encodePacked("LockedGold"));
bytes32 constant RESERVE_REGISTRY_ID = keccak256(abi.encodePacked("Reserve"));
bytes32 constant RANDOM_REGISTRY_ID = keccak256(abi.encodePacked("Random"));
bytes32 constant SORTED_ORACLES_REGISTRY_ID = keccak256(abi.encodePacked("SortedOracles"));
@@ -29,6 +30,10 @@ contract UsingRegistry is Ownable {
IRegistry public registry;
+ modifier onlyRegisteredContract(bytes32 identifierHash, address sender) {
+ require(registry.getAddressForOrDie(identifierHash) == sender);
+ _;
+ }
/**
* @notice Updates the address pointing to a Registry contract.
* @param registryAddress The address of a registry contract for routing to other contracts.
diff --git a/packages/protocol/contracts/governance/Election.sol b/packages/protocol/contracts/governance/Election.sol
index 0ceb736ca54..c86888bee7e 100644
--- a/packages/protocol/contracts/governance/Election.sol
+++ b/packages/protocol/contracts/governance/Election.sol
@@ -1,22 +1,22 @@
pragma solidity ^0.5.3;
+import "openzeppelin-solidity/contracts/math/Math.sol";
import "openzeppelin-solidity/contracts/math/SafeMath.sol";
import "openzeppelin-solidity/contracts/ownership/Ownable.sol";
import "openzeppelin-solidity/contracts/utils/ReentrancyGuard.sol";
-import "solidity-bytes-utils/contracts/BytesLib.sol";
import "./UsingLockedGold.sol";
+import "./UsingValidators.sol";
import "./interfaces/IValidators.sol";
import "../common/Initializable.sol";
import "../common/FixidityLib.sol";
-import "../common/linkedlists/AddressLinkedList.sol";
import "../common/linkedlists/AddressSortedLinkedList.sol";
-contract Election is Ownable, ReentrancyGuard, Initializable, UsingLockedGold {
+contract Election is Ownable, ReentrancyGuard, Initializable, UsingLockedGold, UsingValidators {
- using FixidityLib for FixidityLib.Fraction;
using AddressSortedLinkedList for SortedLinkedList.List;
+ using FixidityLib for FixidityLib.Fraction;
using SafeMath for uint256;
// Pending votes are those for which no following elections have been held.
@@ -154,7 +154,7 @@ contract Election is Ownable, ReentrancyGuard, Initializable, UsingLockedGold {
function setMaxVotesPerAccount(uint256 _maxVotesPerAccount) external onlyOwner returns (bool) {
require(_maxVotesPerAccount != maxVotesPerAccount);
maxVotesPerAccount = _maxVotesPerAccount;
- emit MaxVotesPerAccountSet(_maxVotePerAccount);
+ emit MaxVotesPerAccountSet(_maxVotesPerAccount);
return true;
}
@@ -240,7 +240,7 @@ contract Election is Ownable, ReentrancyGuard, Initializable, UsingLockedGold {
if (getAccountTotalVotesForGroup(group, account) == 0) {
deleteElement(votes.lists[account], group, index);
}
- emit ValidatorGroupVoteRevoked(account, group, weight);
+ emit ValidatorGroupVoteRevoked(account, group, value);
return true;
}
@@ -276,7 +276,7 @@ contract Election is Ownable, ReentrancyGuard, Initializable, UsingLockedGold {
if (getAccountTotalVotesForGroup(group, account) == 0) {
deleteElement(votes.lists[account], group, index);
}
- emit ValidatorGroupVoteRevoked(account, group, weight);
+ emit ValidatorGroupVoteRevoked(account, group, value);
return true;
}
@@ -289,18 +289,18 @@ contract Election is Ownable, ReentrancyGuard, Initializable, UsingLockedGold {
return total;
}
- function getAccountPendingVotesForGroup(address group, address account) external view returns (uint256) {
+ function getAccountPendingVotesForGroup(address group, address account) public view returns (uint256) {
return votes.pending.values[group][account];
}
- function getAccountActiveVotesForGroup(address group, address account) external view returns (uint256) {
+ function getAccountActiveVotesForGroup(address group, address account) public view returns (uint256) {
uint256 numerator = votes.active.numerators[group][account].mul(votes.total.getValue(group));
uint256 denominator = votes.total.getValue(group);
return numerator.div(denominator);
}
- function getAccountTotalVotesForGroup(address group, address account) external view returns (uint256) {
- uint256 pending = getAccountPendingVotesForGroup(group, account)
+ function getAccountTotalVotesForGroup(address group, address account) public view returns (uint256) {
+ uint256 pending = getAccountPendingVotesForGroup(group, account);
uint256 active = getAccountActiveVotesForGroup(group, account);
return pending.add(active);
}
@@ -392,7 +392,7 @@ contract Election is Ownable, ReentrancyGuard, Initializable, UsingLockedGold {
function getActiveVotesDelta(address group, address account, uint256 value) private {
uint256 total = votes.totals.getValue(group);
// Preserve delta * total = value * denominator
- uint256 delta = value.mul(active.denominators[group]).div(total);
+ uint256 delta = value.mul(votes.active.denominators[group]).div(total);
}
/**
@@ -463,33 +463,26 @@ contract Election is Ownable, ReentrancyGuard, Initializable, UsingLockedGold {
* @return The list of elected validators.
* @dev See https://en.wikipedia.org/wiki/D%27Hondt_method#Allocation for more information.
*/
- /* solhint-disable code-complexity */
function electValidators() external view returns (address[] memory) {
// Only members of these validator groups are eligible for election.
uint256 maxNumElectionGroups = Math.min(maxElectableValidators, votes.totals.list.numElements);
uint256 requiredVotes = electabilityThreshold.multiply(FixidityLib.newFixed(totalVotes)).fromFixed();
address[] memory electionGroups = votes.totals.list.headN(maxNumElectionGroups, requiredVotes);
+ uint256[] memory numMembers = getNumGroupMembers(electionGroups);
// Holds the number of members elected for each of the eligible validator groups.
uint256[] memory numMembersElected = new uint256[](electionGroups.length);
uint256 totalNumMembersElected = 0;
- bool memberElectedInRound = true;
// Assign a number of seats to each validator group.
- while (totalNumMembersElected < maxElectableValidators && memberElectedInRound) {
- memberElectedInRound = false;
+ while (totalNumMembersElected < maxElectableValidators) {
uint256 groupIndex = 0;
- FixidityLib.Fraction memory maxN = FixidityLib.wrap(0);
- for (uint256 i = 0; i < electionGroups.length; i = i.add(1)) {
- bool isWinningestGroupInRound = false;
- (maxN, isWinningestGroupInRound) = dHondt(maxN, electionGroups[i], numMembersElected[i]);
- if (isWinningestGroupInRound) {
- memberElectedInRound = true;
- groupIndex = i;
- }
- }
+ bool memberElected = false;
+ (groupIndex, memberElected) = dHondt(electionGroups, numMembers, numMembersElected);
- if (memberElectedInRound) {
+ if (memberElected) {
numMembersElected[groupIndex] = numMembersElected[groupIndex].add(1);
totalNumMembersElected = totalNumMembersElected.add(1);
+ } else {
+ break;
}
}
require(totalNumMembersElected >= minElectableValidators);
@@ -497,46 +490,48 @@ contract Election is Ownable, ReentrancyGuard, Initializable, UsingLockedGold {
address[] memory electedValidators = new address[](totalNumMembersElected);
totalNumMembersElected = 0;
for (uint256 i = 0; i < electionGroups.length; i = i.add(1)) {
- address[] memory electedGroupMembers = groups[electionGroups[i]].members.headN(
+ // We use the validating delegate if one is set.
+ address[] memory electedGroupValidators = getTopValidatorsFromGroup(
+ electionGroups[i],
numMembersElected[i]
);
- for (uint256 j = 0; j < electedGroupMembers.length; j = j.add(1)) {
- // We use the validating delegate if one is set.
- electedValidators[totalNumMembersElected] = getValidatorFromAccount(electedGroupMembers[j]);
+ for (uint256 j = 0; j < electedGroupValidators.length; j = j.add(1)) {
+ electedValidators[totalNumMembersElected] = electedGroupValidators[j];
totalNumMembersElected = totalNumMembersElected.add(1);
}
}
return electedValidators;
}
- /* solhint-enable code-complexity */
/**
- * @notice Runs D'Hondt for a validator group.
- * @param maxN The maximum number of votes per elected seat for a group in this round.
- * @param groupAddress The address of the validator group.
- * @param numMembersElected The number of members elected so far for this group.
+ * @notice Runs a round of the D'Hondt algorithm.
+ * @param electionGroups The addresses of the validator groups in the election.
+ * @param numMembers The number of members in each group.
+ * @param numMembersElected The number of members elected in each group up to this point.
* @dev See https://en.wikipedia.org/wiki/D%27Hondt_method#Allocation for more information.
- * @return The new `maxN` and whether or not the group should win a seat in this round thus far.
+ * @return Whether or not a group elected a member, and the index of the group if so.
*/
- function dHondt(
- FixidityLib.Fraction memory maxN,
- address groupAddress,
- uint256 numMembersElected
- )
+ function dHondt(address[] memory electionGroups, uint256[] memory numMembers, uint256[] memory numMembersElected)
private
view
- returns (FixidityLib.Fraction memory, bool)
+ returns (uint256, bool)
{
- ValidatorGroup storage group = groups[groupAddress];
- // Only consider groups with members left to be elected.
- if (group.members.numElements > numMembersElected) {
- FixidityLib.Fraction memory n = FixidityLib.newFixed(votes.getValue(groupAddress)).divide(
- FixidityLib.newFixed(numMembersElected.add(1))
- );
- if (n.gt(maxN)) {
- return (n, true);
+ bool memberElected = false;
+ uint256 groupIndex = 0;
+ FixidityLib.Fraction memory maxN = FixidityLib.wrap(0);
+ for (uint256 i = 0; i < electionGroups.length; i = i.add(1)) {
+ address group = electionGroups[i];
+ // Only consider groups with members left to be elected.
+ if (numMembers[i] > numMembersElected[i]) {
+ FixidityLib.Fraction memory n = FixidityLib.newFixed(votes.totals.getValue(group)).divide(
+ FixidityLib.newFixed(numMembersElected[i].add(1))
+ );
+ if (n.gt(maxN)) {
+ groupIndex = i;
+ memberElected = true;
+ }
}
}
- return (maxN, false);
+ return (groupIndex, memberElected);
}
}
diff --git a/packages/protocol/contracts/governance/Governance.sol b/packages/protocol/contracts/governance/Governance.sol
index 343b5436405..bb145035db2 100644
--- a/packages/protocol/contracts/governance/Governance.sol
+++ b/packages/protocol/contracts/governance/Governance.sol
@@ -44,14 +44,20 @@ contract Governance is IGovernance, Ownable, Initializable, UsingLockedGold, Ree
Yes
}
+ struct UpvoteRecord {
+ uint256 proposalId;
+ uint256 weight;
+ }
+
struct VoteRecord {
VoteValue value;
uint256 proposalId;
+ uint256 weight;
}
struct Voter {
// Key of the proposal voted for in the proposal queue
- uint256 upvotedProposal;
+ UpvoteRecord upvote;
uint256 mostRecentReferendumProposal;
// Maps a `dequeued` index to a voter's vote record.
mapping(uint256 => VoteRecord) referendumVotes;
@@ -423,7 +429,6 @@ contract Governance is IGovernance, Ownable, Initializable, UsingLockedGold, Ree
returns (bool)
{
address account = getAccountFromVoter(msg.sender);
- require(!isVotingFrozen(account));
// TODO(asa): When upvoting a proposal that will get dequeued, should we let the tx succeed
// and return false?
dequeueProposalsIfReady();
@@ -436,20 +441,20 @@ contract Governance is IGovernance, Ownable, Initializable, UsingLockedGold, Ree
}
Voter storage voter = voters[account];
// We can upvote a proposal in the queue if we're not already upvoting a proposal in the queue.
- uint256 weight = getAccountWeight(account);
+ uint256 weight = getAccountTotalLockedGold(account);
require(
isQueued(proposalId) &&
- (voter.upvotedProposal == 0 || !queue.contains(voter.upvotedProposal)) &&
+ (voter.upvote.proposalId == 0 || !queue.contains(voter.upvote.proposalId)) &&
weight > 0
);
- uint256 upvotes = queue.getValue(proposalId).add(uint256(weight));
+ uint256 upvotes = queue.getValue(proposalId).add(weight);
queue.update(
proposalId,
upvotes,
lesser,
greater
);
- voter.upvotedProposal = proposalId;
+ voter.upvote = UpvoteRecord(proposalId, weight);
emit ProposalUpvoted(proposalId, account, weight);
return true;
}
@@ -474,7 +479,7 @@ contract Governance is IGovernance, Ownable, Initializable, UsingLockedGold, Ree
dequeueProposalsIfReady();
address account = getAccountFromVoter(msg.sender);
Voter storage voter = voters[account];
- uint256 proposalId = voter.upvotedProposal;
+ uint256 proposalId = voter.upvote.proposalId;
Proposal storage proposal = proposals[proposalId];
require(_proposalExists(proposal));
// If acting on an expired proposal, expire the proposal.
@@ -485,18 +490,16 @@ contract Governance is IGovernance, Ownable, Initializable, UsingLockedGold, Ree
queue.remove(proposalId);
emit ProposalExpired(proposalId);
} else {
- uint256 weight = getAccountWeight(account);
- require(weight > 0);
queue.update(
proposalId,
- queue.getValue(proposalId).sub(weight),
+ queue.getValue(proposalId).sub(voter.upvote.weight),
lesser,
greater
);
- emit ProposalUpvoteRevoked(proposalId, account, weight);
+ emit ProposalUpvoteRevoked(proposalId, account, voter.upvote.weight);
}
}
- voter.upvotedProposal = 0;
+ voter.upvote = UpvoteRecord(0, 0);
return true;
}
@@ -541,7 +544,6 @@ contract Governance is IGovernance, Ownable, Initializable, UsingLockedGold, Ree
returns (bool)
{
address account = getAccountFromVoter(msg.sender);
- require(!isVotingFrozen(account));
dequeueProposalsIfReady();
Proposal storage proposal = proposals[proposalId];
require(_proposalExists(proposal) && dequeued[index] == proposalId);
@@ -551,7 +553,7 @@ contract Governance is IGovernance, Ownable, Initializable, UsingLockedGold, Ree
}
ProposalStage stage = _getDequeuedProposalStage(proposal.timestamp);
Voter storage voter = voters[account];
- uint256 weight = getAccountWeight(account);
+ uint256 weight = getAccountTotalLockedGold(account);
require(
proposal.approved &&
stage == ProposalStage.Referendum &&
@@ -562,11 +564,11 @@ contract Governance is IGovernance, Ownable, Initializable, UsingLockedGold, Ree
// If we've already voted on this proposal, subtract the previous vote.
if (voteRecord.proposalId == proposalId) {
if (voteRecord.value == VoteValue.Abstain) {
- proposal.votes.abstain = proposal.votes.abstain.sub(weight);
+ proposal.votes.abstain = proposal.votes.abstain.sub(voteRecord.weight);
} else if (voteRecord.value == VoteValue.Yes) {
- proposal.votes.yes = proposal.votes.yes.sub(weight);
+ proposal.votes.yes = proposal.votes.yes.sub(voteRecord.weight);
} else if (voteRecord.value == VoteValue.No) {
- proposal.votes.no = proposal.votes.no.sub(weight);
+ proposal.votes.no = proposal.votes.no.sub(voteRecord.weight);
}
}
@@ -578,8 +580,7 @@ contract Governance is IGovernance, Ownable, Initializable, UsingLockedGold, Ree
} else if (value == VoteValue.No) {
proposal.votes.no = proposal.votes.no.add(weight);
}
- voteRecord.proposalId = proposalId;
- voteRecord.value = value;
+ voteRecord = VoteRecord(value, proposalId, weight);
if (proposal.timestamp > voter.mostRecentReferendumProposal) {
voter.mostRecentReferendumProposal = proposalId;
}
diff --git a/packages/protocol/contracts/governance/LockedGold.sol b/packages/protocol/contracts/governance/LockedGold.sol
index 91afae461a7..ac67a2cd6e5 100644
--- a/packages/protocol/contracts/governance/LockedGold.sol
+++ b/packages/protocol/contracts/governance/LockedGold.sol
@@ -5,15 +5,11 @@ import "openzeppelin-solidity/contracts/math/SafeMath.sol";
import "openzeppelin-solidity/contracts/ownership/Ownable.sol";
import "./interfaces/ILockedGold.sol";
-import "./interfaces/IGovernance.sol";
-import "./interfaces/IValidators.sol";
import "../common/Initializable.sol";
import "../common/UsingRegistry.sol";
-import "../common/FixidityLib.sol";
import "../common/interfaces/IERC20Token.sol";
import "../common/Signatures.sol";
-import "../common/FractionUtil.sol";
contract LockedGold is ILockedGold, ReentrancyGuard, Initializable, UsingRegistry {
@@ -81,6 +77,7 @@ contract LockedGold is ILockedGold, ReentrancyGuard, Initializable, UsingRegistr
uint8 v,
bytes32 r,
bytes32 s
+ )
external
nonReentrant
{
@@ -95,6 +92,7 @@ contract LockedGold is ILockedGold, ReentrancyGuard, Initializable, UsingRegistr
uint8 v,
bytes32 r,
bytes32 s
+ )
external
nonReentrant
{
@@ -111,19 +109,25 @@ contract LockedGold is ILockedGold, ReentrancyGuard, Initializable, UsingRegistr
function lock(uint256 value) external nonReentrant {
require(isAccount(msg.sender));
require(msg.value == value && value > 0);
- incrementNonvotingAccountBalance(msg.sender, value)
+ _incrementNonvotingAccountBalance(msg.sender, value);
emit GoldLocked(msg.sender, value);
}
- function incrementNonvotingAccountBalance(address account, uint256 value) private {
- Account storage account = accounts[account];
- account.gold.nonvoting = account.gold.nonvoting.add(value);
+ function incrementNonvotingAccountBalance(address account, uint256 value) external onlyRegisteredContract('Election', msg.sender) {
+ _incrementNonvotingAccountBalance(account, value);
+ }
+
+ function decrementNonvotingAccountBalance(address account, uint256 value) external onlyRegisteredContract('Election', msg.sender) {
+ _decrementNonvotingAccountBalance(account, value);
+ }
+
+ function _incrementNonvotingAccountBalance(address account, uint256 value) private {
+ accounts[account].gold.nonvoting = accounts[account].gold.nonvoting.add(value);
totalNonvoting = totalNonvoting.add(value);
}
function decrementNonvotingAccountBalance(address account, uint256 value) private {
- Account storage account = accounts[account];
- account.gold.nonvoting = account.gold.nonvoting.sub(value);
+ accounts[account].gold.nonvoting = accounts[account].gold.nonvoting.sub(value);
totalNonvoting = totalNonvoting.sub(value);
}
@@ -142,24 +146,25 @@ contract LockedGold is ILockedGold, ReentrancyGuard, Initializable, UsingRegistr
emit GoldUnlocked(msg.sender, value, available);
}
- function relock(uint256 value, uint256 index) external nonReentrant {
+ // TODO(asa): Allow partial relock
+ function relock(uint256 index) external nonReentrant {
require(isAccount(msg.sender));
Account storage account = accounts[msg.sender];
- require(index < account.gold.unlocking.length);
- uint256 value = account.gold.unlocking[index].value;
- incrementNonvotingAccountBalance(msg.sender, value);
- deletePendingWithdrawal(account.gold.unlocking, index);
+ require(index < account.balances.pendingWithdrawals.length);
+ uint256 value = account.balances.pendingWithdrawals[index].value;
+ _incrementNonvotingAccountBalance(msg.sender, value);
+ deletePendingWithdrawal(account.balances.pendingWithdrawals, index);
emit GoldLocked(msg.sender, value);
}
- function withdraw(uint256 value, uint256 index) external nonReentrant {
+ function withdraw(uint256 index) external nonReentrant {
require(isAccount(msg.sender));
Account storage account = accounts[msg.sender];
- require(index < account.gold.unlocking.length);
- PendingWithdrawal memory unlocking = account.gold.unlocking[index];
- require(now >= unlocking.available);
- uint256 value = unlocking.value;
- deletePendingWithdrawal(account.gold.unlocking, index);
+ require(index < account.balances.pendingWithdrawals.length);
+ PendingWithdrawal memory pendingWithdrawal = account.balances.pendingWithdrawals[index];
+ require(now >= pendingWithdrawal.available);
+ uint256 value = pendingWithdrawal.value;
+ deletePendingWithdrawal(account.balances.pendingWithdrawals, index);
IERC20Token goldToken = IERC20Token(registry.getAddressFor(GOLD_TOKEN_REGISTRY_ID));
require(goldToken.transfer(msg.sender, value));
emit GoldWithdrawn(msg.sender, value);
@@ -181,7 +186,7 @@ contract LockedGold is ILockedGold, ReentrancyGuard, Initializable, UsingRegistr
// TODO(asa): Dedup
/**
- * @notice Returns the account associated with the `voter` address.
+ * @notice Returns the account associated with `accountOrVoter`.
* @param accountOrVoter The address of the account or authorized voter.
* @dev Fails if the `accountOrVoter` is not an account or authorized voter.
* @return The associated account.
@@ -207,7 +212,7 @@ contract LockedGold is ILockedGold, ReentrancyGuard, Initializable, UsingRegistr
}
/**
- * @notice Returns the account associated with the `validator` address.
+ * @notice Returns the account associated with `accountOrValidator`.
* @param accountOrValidator The address of the account or authorized validator.
* @dev Fails if the `accountOrValidator` is not an account or authorized validator.
* @return The associated account.
diff --git a/packages/protocol/contracts/governance/UsingLockedGold.sol b/packages/protocol/contracts/governance/UsingLockedGold.sol
index 4b6ec028f39..c0570fcd7f7 100644
--- a/packages/protocol/contracts/governance/UsingLockedGold.sol
+++ b/packages/protocol/contracts/governance/UsingLockedGold.sol
@@ -10,26 +10,23 @@ import "../common/UsingRegistry.sol";
*/
contract UsingLockedGold is UsingRegistry {
/**
- * @notice Returns whether or not an account's voting power is frozen.
- * @param account The address of the account.
- * @return Whether or not the account's voting power is frozen.
- * @dev Frozen accounts can retract existing votes but not make future votes.
+ * @notice Returns the account associated with `accountOrVoter`.
+ * @param accountOrVoter The address of the account or authorized voter.
+ * @dev Fails if the `accountOrVoter` is not an account or authorized voter.
+ * @return The associated account.
*/
- function isVotingFrozen(address account) internal view returns (bool) {
- return getLockedGold().isVotingFrozen(account);
+ function getAccountFromVoter(address accountOrVoter) internal view returns (address) {
+ return getLockedGold().getAccountFromVoter(accountOrVoter);
}
/**
- * @notice Returns the account associated with the provided account or voting delegate.
- * @param accountOrDelegate The address of the account or voting delegate.
- * @dev Fails if the `accountOrDelegate` is a non-voting delegate.
+ * @notice Returns the account associated with `accountOrValidator`.
+ * @param accountOrValidator The address of the account or authorized validator.
+ * @dev Fails if the `accountOrValidator` is not an account or authorized validator.
* @return The associated account.
*/
- function getAccountFromVoter(address accountOrDelegate) internal view returns (address) {
- return getLockedGold().getAccountFromDelegateAndRole(
- accountOrDelegate,
- ILockedGold.DelegateRole.Voting
- );
+ function getAccountFromValidator(address accountOrValidator) internal view returns (address) {
+ return getLockedGold().getAccountFromValidator(accountOrValidator);
}
/**
@@ -38,51 +35,23 @@ contract UsingLockedGold is UsingRegistry {
* @return The associated validator address.
*/
function getValidatorFromAccount(address account) internal view returns (address) {
- return getLockedGold().getDelegateFromAccountAndRole(
- account,
- ILockedGold.DelegateRole.Validating
- );
+ return getLockedGold().getValidatorFromAccount(account);
}
- /**
- * @notice Returns the account associated with the provided account or validating delegate.
- * @param accountOrDelegate The address of the account or validating delegate.
- * @dev Fails if the `accountOrDelegate` is a non-validating delegate.
- * @return The associated account.
- */
- function getAccountFromValidator(address accountOrDelegate) internal view returns (address) {
- return getLockedGold().getAccountFromDelegateAndRole(
- accountOrDelegate,
- ILockedGold.DelegateRole.Validating
- );
+ function getTotalLockedGold() internal view returns (uint256) {
+ return getLockedGold().getTotalLockedGold();
}
- /**
- * @notice Returns voting weight for a particular account.
- * @param account The address of the account.
- * @return The voting weight of `account`.
- */
- function getAccountWeight(address account) internal view returns (uint256) {
- return getLockedGold().getAccountWeight(account);
+ function getAccountTotalLockedGold(address account) internal view returns (uint256) {
+ return getLockedGold().getAccountTotalLockedGold(account);
}
- /**
- * @notice Returns the Locked Gold commitment value for particular account and notice period.
- * @param account The address of the account.
- * @param noticePeriod The notice period of the Locked Gold commitment.
- * @return The value of the Locked Gold commitment.
- */
- function getLockedCommitmentValue(
- address account,
- uint256 noticePeriod
- )
- internal
- view
- returns (uint256)
- {
- uint256 value;
- (value,) = getLockedGold().getLockedCommitment(account, noticePeriod);
- return value;
+ function incrementNonvotingAccountBalance(address account, uint256 value) internal returns (bool) {
+ return getLockedGold().incrementNonvotingAccountBalance(account, value);
+ }
+
+ function decrementNonvotingAccountBalance(address account, uint256 value) internal returns (bool) {
+ return getLockedGold().decrementNonvotingAccountBalance(account, value);
}
function getLockedGold() private view returns(ILockedGold) {
diff --git a/packages/protocol/contracts/governance/Validators.sol b/packages/protocol/contracts/governance/Validators.sol
index efd7525edb2..bde57cde9a4 100644
--- a/packages/protocol/contracts/governance/Validators.sol
+++ b/packages/protocol/contracts/governance/Validators.sol
@@ -10,7 +10,6 @@ import "./interfaces/IValidators.sol";
import "../common/Initializable.sol";
import "../common/FixidityLib.sol";
import "../common/linkedlists/AddressLinkedList.sol";
-import "../common/linkedlists/AddressSortedLinkedList.sol";
/**
@@ -22,7 +21,6 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
using FixidityLib for FixidityLib.Fraction;
using AddressLinkedList for LinkedList.List;
- using AddressSortedLinkedList for SortedLinkedList.List;
using SafeMath for uint256;
using BytesLib for bytes;
@@ -190,7 +188,7 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
function registerValidator(
string calldata name,
string calldata url,
- bytes calldata publicKeysData,
+ bytes calldata publicKeysData
)
external
nonReentrant
@@ -436,6 +434,31 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
return (group.name, group.url, group.members.getKeys());
}
+ function getNumGroupMembers(address account) public view returns (uint256) {
+ return groups[account].members.numElements;
+ }
+
+ function getTopValidatorsFromGroup(address account, uint256 n) external view returns (address[]) {
+ address[] memory topAccounts = groups[account].members.list.headN(n);
+ address[] memory topValidators = new address[](n);
+ for (uint256 i = 0; i < n; i = i.add(1)) {
+ topValidators[i] = getValidatorFromAccount(topAccounts[i]);
+ }
+ return topValidators;
+ }
+
+ function getNumGroupMembers(address[] accounts) external view returns (uint256) {
+ uint256[] memory numMembers = new uint256[](accounts.length);
+ for (uint256 i = 0; i < accounts.length; i = i.add(1)) {
+ numMembers[i] = getNumGroupMembers(accounts[i]);
+ }
+ return numMembers;
+ }
+
+ function getNumRegisteredValidators() external view returns (uint256) {
+ return _validators.length;
+ }
+
/**
* @notice Returns the Locked Gold requirements to register a validator or group.
* @return The locked gold requirements to register a validator or group.
From 721b9dafcbb3faab6a841fb7fb1c4b8ae88a9d9d Mon Sep 17 00:00:00 2001
From: Asa Oines
Date: Wed, 18 Sep 2019 20:25:27 -0700
Subject: [PATCH 003/149] More work
---
.../contracts/governance/LockedGold.sol | 27 +++++++++++--------
.../contracts/governance/UsingLockedGold.sol | 2 +-
.../contracts/governance/Validators.sol | 16 +++++------
3 files changed, 25 insertions(+), 20 deletions(-)
diff --git a/packages/protocol/contracts/governance/LockedGold.sol b/packages/protocol/contracts/governance/LockedGold.sol
index ac67a2cd6e5..0db2beabd87 100644
--- a/packages/protocol/contracts/governance/LockedGold.sol
+++ b/packages/protocol/contracts/governance/LockedGold.sol
@@ -5,13 +5,14 @@ import "openzeppelin-solidity/contracts/math/SafeMath.sol";
import "openzeppelin-solidity/contracts/ownership/Ownable.sol";
import "./interfaces/ILockedGold.sol";
+import "./UsingElection.sol";
import "../common/Initializable.sol";
import "../common/UsingRegistry.sol";
import "../common/interfaces/IERC20Token.sol";
import "../common/Signatures.sol";
-contract LockedGold is ILockedGold, ReentrancyGuard, Initializable, UsingRegistry {
+contract LockedGold is ILockedGold, ReentrancyGuard, Initializable, UsingRegistry, UsingElection {
// TODO(asa): How do adjust for updated requirements?
// Have a refreshRequirements function validators and groups can call
@@ -50,11 +51,15 @@ contract LockedGold is ILockedGold, ReentrancyGuard, Initializable, UsingRegistr
mapping(address => Account) public accounts;
// Maps voting and validating keys to the account that provided the authorization.
mapping(address => address) public authorizations;
- uint256 public nonvotingTotal;
+ uint256 public totalNonvoting;
uint256 public unlockingPeriod;
event VoterAuthorized(address indexed account, address voter);
event ValidatorAuthorized(address indexed account, address validator);
+ event GoldLocked(address indexed account, uint256 value);
+ event GoldUnlocked(address indexed account, uint256 value, uint256 available);
+ event GoldWithdrawn(address indexed account, uint256 value);
+ event AccountMustMaintainSet(address indexed account, uint256 value, uint256 timestamp);
function initialize(address registryAddress) external initializer {
_transferOwnership(msg.sender);
@@ -126,7 +131,7 @@ contract LockedGold is ILockedGold, ReentrancyGuard, Initializable, UsingRegistr
totalNonvoting = totalNonvoting.add(value);
}
- function decrementNonvotingAccountBalance(address account, uint256 value) private {
+ function _decrementNonvotingAccountBalance(address account, uint256 value) private {
accounts[account].gold.nonvoting = accounts[account].gold.nonvoting.sub(value);
totalNonvoting = totalNonvoting.sub(value);
}
@@ -140,7 +145,7 @@ contract LockedGold is ILockedGold, ReentrancyGuard, Initializable, UsingRegistr
now >= requirement.timestamp ||
getAccountTotalLockedGold(msg.sender).sub(value) >= requirement.value
);
- decrementNonvotingAccountBalance(msg.sender, value);
+ _decrementNonvotingAccountBalance(msg.sender, value);
uint256 available = now.add(unlockingPeriod);
account.balances.pendingWithdrawals.push(PendingWithdrawal(value, available));
emit GoldUnlocked(msg.sender, value, available);
@@ -192,7 +197,7 @@ contract LockedGold is ILockedGold, ReentrancyGuard, Initializable, UsingRegistr
* @return The associated account.
*/
function getAccountFromVoter(address accountOrVoter) public view returns (address) {
- address authorizingAccount = authorizations[voter];
+ address authorizingAccount = authorizations[accountOrVoter];
if (authorizingAccount != address(0)) {
require(accounts[authorizingAccount].authorizations.voter == accountOrVoter);
return authorizingAccount;
@@ -203,12 +208,12 @@ contract LockedGold is ILockedGold, ReentrancyGuard, Initializable, UsingRegistr
}
function getTotalLockedGold() public view returns (uint256) {
- return nonvotingTotal.add(getTotalVotes());
+ return totalNonvoting.add(getElection().totalVotes());
}
function getAccountTotalLockedGold(address account) public view returns (uint256) {
uint256 total = accounts[account].balances.nonvoting;
- return total.add(getAccountTotalVotes(account));
+ return total.add(getElection().getAccountTotalVotes(account));
}
/**
@@ -218,13 +223,13 @@ contract LockedGold is ILockedGold, ReentrancyGuard, Initializable, UsingRegistr
* @return The associated account.
*/
function getAccountFromValidator(address accountOrValidator) public view returns (address) {
- address authorizingAccount = authorizations[validator];
+ address authorizingAccount = authorizations[accountOrValidator];
if (authorizingAccount != address(0)) {
- require(accounts[authorizingAccount].authorizations.validator == accountOrVoter);
+ require(accounts[authorizingAccount].authorizations.validator == accountOrValidator);
return authorizingAccount;
} else {
- require(isAccount(accountOrVoter));
- return accountOrVoter;
+ require(isAccount(accountOrValidator));
+ return accountOrValidator;
}
}
diff --git a/packages/protocol/contracts/governance/UsingLockedGold.sol b/packages/protocol/contracts/governance/UsingLockedGold.sol
index c0570fcd7f7..7f29f199155 100644
--- a/packages/protocol/contracts/governance/UsingLockedGold.sol
+++ b/packages/protocol/contracts/governance/UsingLockedGold.sol
@@ -54,7 +54,7 @@ contract UsingLockedGold is UsingRegistry {
return getLockedGold().decrementNonvotingAccountBalance(account, value);
}
- function getLockedGold() private view returns(ILockedGold) {
+ function getLockedGold() internal view returns(ILockedGold) {
return ILockedGold(registry.getAddressForOrDie(LOCKED_GOLD_REGISTRY_ID));
}
}
diff --git a/packages/protocol/contracts/governance/Validators.sol b/packages/protocol/contracts/governance/Validators.sol
index bde57cde9a4..2c2efa48dbd 100644
--- a/packages/protocol/contracts/governance/Validators.sol
+++ b/packages/protocol/contracts/governance/Validators.sol
@@ -130,8 +130,8 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
{
_transferOwnership(msg.sender);
setRegistry(registryAddress);
- registrationRequirement.group = groupRequirement;
- registrationRequirement.validator = validatorRequirement;
+ registrationRequirements.group = groupRequirement;
+ registrationRequirements.validator = validatorRequirement;
maxGroupSize = _maxGroupSize;
}
@@ -210,7 +210,7 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
Validator memory validator = Validator(name, url, publicKeysData, address(0));
validators[account] = validator;
_validators.push(account);
- setAccountMustMaintain(account, requirements.validator, MAX_INT);
+ getLockedGold().setAccountMustMaintain(account, registrationRequirements.validator, MAX_INT);
emit ValidatorRegistered(account, name, url, publicKeysData);
return true;
}
@@ -241,7 +241,7 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
}
delete validators[account];
deleteElement(_validators, account, index);
- setAccountMustMaintain(account, requirements.validator, now.add(deregisterPeriods.validator));
+ getLockedGold().setAccountMustMaintain(account, registrationRequirements.validator, now.add(deregisterPeriods.validator));
emit ValidatorDeregistered(account);
return true;
}
@@ -311,7 +311,7 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
group.addMember(members[i]);
}
_groups.push(account);
- setAccountMustMaintain(account, requirements.group, MAX_INT);
+ getLockedGold().setAccountMustMaintain(account, requirements.group, MAX_INT);
emit ValidatorGroupRegistered(account, name, url);
return true;
}
@@ -328,7 +328,7 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
require(isValidatorGroup(account) && groups[account].members.numElements == 0);
delete groups[account];
deleteElement(_groups, account, index);
- setAccountMustMaintain(account, requirements.group, now.add(deregisterPeriods.group));
+ getLockedGold().setAccountMustMaintain(account, requirements.group, now.add(deregisterPeriods.group));
emit ValidatorGroupDeregistered(account);
return true;
}
@@ -438,7 +438,7 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
return groups[account].members.numElements;
}
- function getTopValidatorsFromGroup(address account, uint256 n) external view returns (address[]) {
+ function getTopValidatorsFromGroup(address account, uint256 n) external view returns (address[] memory) {
address[] memory topAccounts = groups[account].members.list.headN(n);
address[] memory topValidators = new address[](n);
for (uint256 i = 0; i < n; i = i.add(1)) {
@@ -447,7 +447,7 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
return topValidators;
}
- function getNumGroupMembers(address[] accounts) external view returns (uint256) {
+ function getNumGroupMembers(address[] calldata accounts) external view returns (uint256) {
uint256[] memory numMembers = new uint256[](accounts.length);
for (uint256 i = 0; i < accounts.length; i = i.add(1)) {
numMembers[i] = getNumGroupMembers(accounts[i]);
From 17a624e0bb80d02944f0870d29a6fa5ef54a64b6 Mon Sep 17 00:00:00 2001
From: Asa Oines
Date: Thu, 19 Sep 2019 16:50:07 -0700
Subject: [PATCH 004/149] More changes
---
.../contracts/common/UsingRegistry.sol | 20 ++-
.../contracts/governance/Election.sol | 165 +++++++++---------
.../contracts/governance/Governance.sol | 14 +-
.../contracts/governance/LockedGold.sol | 9 +-
.../contracts/governance/UsingLockedGold.sol | 60 -------
.../contracts/governance/Validators.sol | 128 +++++++++-----
.../governance/interfaces/ILockedGold.sol | 24 +--
.../governance/interfaces/IValidators.sol | 6 +-
.../governance/test/MockLockedGold.sol | 98 -----------
.../contracts/identity/Attestations.sol | 13 +-
10 files changed, 203 insertions(+), 334 deletions(-)
delete mode 100644 packages/protocol/contracts/governance/UsingLockedGold.sol
diff --git a/packages/protocol/contracts/common/UsingRegistry.sol b/packages/protocol/contracts/common/UsingRegistry.sol
index d5a599a5fc3..5a19dcef961 100644
--- a/packages/protocol/contracts/common/UsingRegistry.sol
+++ b/packages/protocol/contracts/common/UsingRegistry.sol
@@ -4,6 +4,10 @@ import "openzeppelin-solidity/contracts/ownership/Ownable.sol";
import "./interfaces/IRegistry.sol";
+import "../governance/interfaces/IElection.sol";
+import "../governance/interfaces/ILockedGold.sol";
+import "../governance/interfaces/IValidators.sol";
+
// Ideally, UsingRegistry should inherit from Initializable and implement initialize() which calls
// setRegistry(). TypeChain currently has problems resolving overloaded functions, so this is not
// possible right now.
@@ -30,8 +34,8 @@ contract UsingRegistry is Ownable {
IRegistry public registry;
- modifier onlyRegisteredContract(bytes32 identifierHash, address sender) {
- require(registry.getAddressForOrDie(identifierHash) == sender);
+ modifier onlyRegisteredContract(bytes32 identifierHash) {
+ require(registry.getAddressForOrDie(identifierHash) == msg.sender);
_;
}
/**
@@ -42,4 +46,16 @@ contract UsingRegistry is Ownable {
registry = IRegistry(registryAddress);
emit RegistrySet(registryAddress);
}
+
+ function getElection() internal view returns (IElection) {
+ return IElection(registry.getAddressForOrDie(ELECTION_REGISTRY_ID));
+ }
+
+ function getLockedGold() internal view returns(ILockedGold) {
+ return ILockedGold(registry.getAddressForOrDie(LOCKED_GOLD_REGISTRY_ID));
+ }
+
+ function getValidators() internal view returns(IValidators) {
+ return IValidators(registry.getAddressForOrDie(VALIDATORS_REGISTRY_ID));
+ }
}
diff --git a/packages/protocol/contracts/governance/Election.sol b/packages/protocol/contracts/governance/Election.sol
index c86888bee7e..c4a07f2a699 100644
--- a/packages/protocol/contracts/governance/Election.sol
+++ b/packages/protocol/contracts/governance/Election.sol
@@ -5,33 +5,39 @@ import "openzeppelin-solidity/contracts/math/SafeMath.sol";
import "openzeppelin-solidity/contracts/ownership/Ownable.sol";
import "openzeppelin-solidity/contracts/utils/ReentrancyGuard.sol";
-import "./UsingLockedGold.sol";
-import "./UsingValidators.sol";
import "./interfaces/IValidators.sol";
import "../common/Initializable.sol";
import "../common/FixidityLib.sol";
import "../common/linkedlists/AddressSortedLinkedList.sol";
+import "../common/UsingRegistry.sol";
-contract Election is Ownable, ReentrancyGuard, Initializable, UsingLockedGold, UsingValidators {
+contract Election is Ownable, ReentrancyGuard, Initializable, UsingRegistry {
using AddressSortedLinkedList for SortedLinkedList.List;
using FixidityLib for FixidityLib.Fraction;
using SafeMath for uint256;
+ // We need some way of keeping track of the number of active/pending votes per group, so that
+ // we know how to adjust `activeVotes`.
+
// Pending votes are those for which no following elections have been held.
// These votes have yet to contribute to the election of validators and thus do not accrue
// rewards.
struct PendingVotes {
+ // Maps groups to total pending voting balance.
+ mapping(address => uint256) total;
// Maps groups to accounts to pending voting balance.
- mapping(address => mapping(address => uint256)) value;
+ mapping(address => mapping(address => uint256)) balances;
// Maps groups to accounts to timestamp of the account's most recent vote for the group.
- mapping(address => mapping(address => uint256)) timestamp;
+ mapping(address => mapping(address => uint256)) timestamps;
}
// Active votes are those for which at least one following election has been held.
// These votes have contributed to the election of validators and thus accrue rewards.
struct ActiveVotes {
+ // Maps groups to total active voting balance.
+ mapping(address => uint256) total;
// Maps groups to accounts to the numerator of the account's fraction of the group's
// total active votes.
mapping(address => mapping(address => uint256)) numerators;
@@ -39,20 +45,26 @@ contract Election is Ownable, ReentrancyGuard, Initializable, UsingLockedGold, U
mapping(address => uint256) denominators;
}
+
+ struct TotalVotes {
+ // The total number of votes cast.
+ uint256 total;
+ // A list of eligible ValidatorGroups sorted by total votes.
+ SortedLinkedList.List eligible;
+ }
+
struct Votes {
PendingVotes pending;
ActiveVotes active;
- // A sorted list of ValidatorGroups by total votes.
- SortedLinkedList.List totals;
+ TotalVotes total;
// Maps an account to the list of groups it's voting for.
mapping(address => address[]) lists;
}
- Votes public votes;
+ Votes private votes;
uint256 public minElectableValidators;
uint256 public maxElectableValidators;
uint256 public maxVotesPerAccount;
- uint256 public totalVotes;
FixidityLib.Fraction public electabilityThreshold;
event MinElectableValidatorsSet(
@@ -179,8 +191,9 @@ contract Election is Ownable, ReentrancyGuard, Initializable, UsingLockedGold, U
nonReentrant
returns (bool)
{
+ require(votes.total.eligible.contains(group));
require(0 < value && value <= getNumVotesReceivable(group));
- address account = getAccountFromVoter(msg.sender);
+ address account = getLockedGold().getAccountFromVoter(msg.sender);
address[] storage list = votes.lists[account];
require(list.length < maxVotesPerAccount);
for (uint256 i = 0; i < list.length; i = i.add(1)) {
@@ -188,8 +201,8 @@ contract Election is Ownable, ReentrancyGuard, Initializable, UsingLockedGold, U
}
list.push(group);
incrementPendingVotes(group, account, value);
- incrementTotalVotes(group, value);
- decrementNonvotingAccountBalance(account, value);
+ incrementTotalVotes(group, value, lesser, greater);
+ getLockedGold().decrementNonvotingAccountBalance(account, value);
emit ValidatorGroupVoteCast(account, group, value);
return true;
}
@@ -200,12 +213,12 @@ contract Election is Ownable, ReentrancyGuard, Initializable, UsingLockedGold, U
* @return True upon success.
*/
function activate(address group) external nonReentrant returns (bool) {
- address account = getAccountFromVoter(msg.sender);
+ address account = getLockedGold().getAccountFromVoter(msg.sender);
PendingVotes storage pending = votes.pending;
- uint256 pendingValue = pending.values[group][account];
- require(0 < pendingValue);
- decrementPendingVotes(group, account, pendingValue);
- incrementActiveVotes(group, account, pendingValue);
+ uint256 value = pending.balances[group][account];
+ require(0 < value);
+ decrementPendingVotes(group, account, value);
+ incrementActiveVotes(group, account, value);
}
/**
@@ -232,11 +245,11 @@ contract Election is Ownable, ReentrancyGuard, Initializable, UsingLockedGold, U
returns (bool)
{
require(group != address(0));
- address account = getAccountFromVoter(msg.sender);
+ address account = getLockedGold().getAccountFromVoter(msg.sender);
require(0 < value && value <= getAccountPendingVotesForGroup(group, account));
decrementPendingVotes(group, account, value);
decrementTotalVotes(group, value, lesser, greater);
- incrementNonvotingAccountBalance(account, value);
+ getLockedGold().incrementNonvotingAccountBalance(account, value);
if (getAccountTotalVotesForGroup(group, account) == 0) {
deleteElement(votes.lists[account], group, index);
}
@@ -268,11 +281,11 @@ contract Election is Ownable, ReentrancyGuard, Initializable, UsingLockedGold, U
returns (bool)
{
require(group != address(0));
- address account = getAccountFromVoter(msg.sender);
+ address account = getLockedGold().getAccountFromVoter(msg.sender);
require(0 < value && value <= getAccountActiveVotesForGroup(group, account));
decrementActiveVotes(group, account, value);
decrementTotalVotes(group, value, lesser, greater);
- incrementNonvotingAccountBalance(account, value);
+ getLockedGold().incrementNonvotingAccountBalance(account, value);
if (getAccountTotalVotesForGroup(group, account) == 0) {
deleteElement(votes.lists[account], group, index);
}
@@ -290,12 +303,12 @@ contract Election is Ownable, ReentrancyGuard, Initializable, UsingLockedGold, U
}
function getAccountPendingVotesForGroup(address group, address account) public view returns (uint256) {
- return votes.pending.values[group][account];
+ return votes.pending.balances[group][account];
}
function getAccountActiveVotesForGroup(address group, address account) public view returns (uint256) {
- uint256 numerator = votes.active.numerators[group][account].mul(votes.total.getValue(group));
- uint256 denominator = votes.total.getValue(group);
+ uint256 numerator = votes.active.numerators[group][account].mul(votes.active.total[group]);
+ uint256 denominator = votes.active.denominators[group];
return numerator.div(denominator);
}
@@ -322,15 +335,12 @@ contract Election is Ownable, ReentrancyGuard, Initializable, UsingLockedGold, U
)
private
{
- if (votes.contains(group)) {
- votes.totals.update(group, votes.getValue(group).add(value), lesser, greater);
- } else {
- votes.totals.insert(group, value, lesser, greater);
- }
- totalVotes = totalVotes.add(value);
+ require(votes.total.eligible.contains(group));
+ uint256 newVoteTotal = votes.total.eligible.getValue(group).add(value);
+ votes.total.eligible.update(group, newVoteTotal, lesser, greater);
+ votes.total.total = votes.total.total.add(value);
}
-
/**
* @notice Decrements the number of total votes for `group` by `value`.
* @param group The validator group whose vote total should be decremented.
@@ -348,51 +358,60 @@ contract Election is Ownable, ReentrancyGuard, Initializable, UsingLockedGold, U
)
private
{
- if (votes.totals.contains(group)) {
- uint256 newVoteTotal = votes.totals.getValue(group).sub(value);
- if (newVoteTotal > 0) {
- votes.totals.update(group, newVoteTotal, lesser, greater);
- } else {
- // Groups receiving no votes are not electable.
- votes.totals.remove(group);
- }
+ if (votes.total.eligible.contains(group)) {
+ uint256 newVoteTotal = votes.total.eligible.getValue(group).sub(value);
+ votes.total.eligible.update(group, newVoteTotal, lesser, greater);
}
- totalVotes = totalVotes.sub(value);
+ votes.total.total = votes.total.total.add(value);
}
- function incrementActiveVotes(address group, address account, uint256 value) private {
- uint256 delta = getActiveVotesDelta(group, account, value);
- ActiveVotes storage active = votes.active;
- active.denominators[group] = active.denominators[group].add(delta);
- active.numerators[group][account] = active.numerators[group][account].add(delta);
+ function markGroupIneligible(address group) external onlyRegisteredContract('Validators') {
+ votes.total.eligible.remove(group);
}
- function decrementActiveVotes(address group, address account, uint256 value) private {
- uint256 delta = getActiveVotesDelta(group, account, value);
- ActiveVotes storage active = votes.active;
- active.denominators[group] = active.denominators[group].sub(delta);
- active.numerators[group][account] = active.numerators[group][account].sub(delta);
+ function markGroupEligible(address group, address lesser, address greater) external {
+ require(!votes.total.eligible.contains(group));
+ require(getValidators().getNumGroupMembers(group) > 0);
+ uint256 value = votes.pending.total[group].add(votes.active.total[group]);
+ votes.total.eligible.insert(group, value, lesser, greater);
}
function incrementPendingVotes(address group, address account, uint256 value) private {
PendingVotes storage pending = votes.pending;
- pending.values[group][account] = pending.values[group][account].add(value);
+ pending.balances[group][account] = pending.balances[group][account].add(value);
pending.timestamps[group][account] = now;
+ pending.total[group] = pending.total[group].add(value);
}
function decrementPendingVotes(address group, address account, uint256 value) private {
PendingVotes storage pending = votes.pending;
- uint256 newValue = pending.values[group][account].sub(value);
- pending.values[group][account] = newValue;
+ uint256 newValue = pending.balances[group][account].sub(value);
+ pending.balances[group][account] = newValue;
if (newValue == 0) {
pending.timestamps[group][account] = 0;
}
+ pending.total[group] = pending.total[group].sub(value);
}
- function getActiveVotesDelta(address group, address account, uint256 value) private {
- uint256 total = votes.totals.getValue(group);
+ function incrementActiveVotes(address group, address account, uint256 value) private {
+ uint256 delta = getActiveVotesDelta(group, value);
+ ActiveVotes storage active = votes.active;
+ active.numerators[group][account] = active.numerators[group][account].add(delta);
+ active.denominators[group] = active.denominators[group].add(delta);
+ active.total[group] = active.total[group].add(value);
+ }
+
+ function decrementActiveVotes(address group, address account, uint256 value) private {
+ uint256 delta = getActiveVotesDelta(group, value);
+ ActiveVotes storage active = votes.active;
+ active.numerators[group][account] = active.numerators[group][account].sub(delta);
+ active.denominators[group] = active.denominators[group].sub(delta);
+ active.total[group] = active.total[group].sub(value);
+ }
+
+ function getActiveVotesDelta(address group, uint256 value) private returns (uint256) {
// Preserve delta * total = value * denominator
- uint256 delta = value.mul(votes.active.denominators[group]).div(total);
+ return value.mul(votes.active.denominators[group]).div(votes.active.total[group]);
}
/**
@@ -410,8 +429,8 @@ contract Election is Ownable, ReentrancyGuard, Initializable, UsingLockedGold, U
}
function getNumVotesReceivable(address group) public view returns (uint256) {
- uint256 numerator = getNumGroupMembers(group).add(1).mul(getTotalLockedGold());
- uint256 denominator = Math.min(maxElectableValidators, getNumRegisteredValidators());
+ uint256 numerator = getValidators().getNumGroupMembers(group).add(1).mul(votes.total.total);
+ uint256 denominator = Math.min(maxElectableValidators, getValidators().getNumRegisteredValidators());
return numerator.div(denominator);
}
@@ -440,23 +459,6 @@ contract Election is Ownable, ReentrancyGuard, Initializable, UsingLockedGold, U
return numberValidators;
}
- /**
- * @notice Returns electable validator group addresses and their vote totals.
- * @return Electable validator group addresses and their vote totals.
- */
- function getValidatorGroupVotes() external view returns (address[] memory, uint256[] memory) {
- return votes.getElements();
- }
-
- /**
- * @notice Returns the number of votes a particular validator group has received.
- * @param group The account that registered the validator group.
- * @return The number of votes a particular validator group has received.
- */
- function getVotesReceived(address group) external view returns (uint256) {
- return votes.getValue(group);
- }
-
/**
* @notice Returns a list of elected validators with seats allocated to groups via the D'Hondt
* method.
@@ -465,10 +467,11 @@ contract Election is Ownable, ReentrancyGuard, Initializable, UsingLockedGold, U
*/
function electValidators() external view returns (address[] memory) {
// Only members of these validator groups are eligible for election.
- uint256 maxNumElectionGroups = Math.min(maxElectableValidators, votes.totals.list.numElements);
- uint256 requiredVotes = electabilityThreshold.multiply(FixidityLib.newFixed(totalVotes)).fromFixed();
- address[] memory electionGroups = votes.totals.list.headN(maxNumElectionGroups, requiredVotes);
- uint256[] memory numMembers = getNumGroupMembers(electionGroups);
+ uint256 maxNumElectionGroups = Math.min(maxElectableValidators, votes.total.eligible.list.numElements);
+ // uint256 requiredVotes = electabilityThreshold.multiply(FixidityLib.newFixed(votes.total.total)).fromFixed();
+ // TODO(asa): Filter by > requiredVotes
+ address[] memory electionGroups = votes.total.eligible.list.headN(maxNumElectionGroups);
+ uint256[] memory numMembers = getValidators().getNumGroupMembers(electionGroups);
// Holds the number of members elected for each of the eligible validator groups.
uint256[] memory numMembersElected = new uint256[](electionGroups.length);
uint256 totalNumMembersElected = 0;
@@ -491,7 +494,7 @@ contract Election is Ownable, ReentrancyGuard, Initializable, UsingLockedGold, U
totalNumMembersElected = 0;
for (uint256 i = 0; i < electionGroups.length; i = i.add(1)) {
// We use the validating delegate if one is set.
- address[] memory electedGroupValidators = getTopValidatorsFromGroup(
+ address[] memory electedGroupValidators = getValidators().getTopValidatorsFromGroup(
electionGroups[i],
numMembersElected[i]
);
@@ -523,7 +526,7 @@ contract Election is Ownable, ReentrancyGuard, Initializable, UsingLockedGold, U
address group = electionGroups[i];
// Only consider groups with members left to be elected.
if (numMembers[i] > numMembersElected[i]) {
- FixidityLib.Fraction memory n = FixidityLib.newFixed(votes.totals.getValue(group)).divide(
+ FixidityLib.Fraction memory n = FixidityLib.newFixed(votes.total.eligible.getValue(group)).divide(
FixidityLib.newFixed(numMembersElected[i].add(1))
);
if (n.gt(maxN)) {
diff --git a/packages/protocol/contracts/governance/Governance.sol b/packages/protocol/contracts/governance/Governance.sol
index bb145035db2..0f7473ed4cd 100644
--- a/packages/protocol/contracts/governance/Governance.sol
+++ b/packages/protocol/contracts/governance/Governance.sol
@@ -6,19 +6,19 @@ import "openzeppelin-solidity/contracts/math/Math.sol";
import "openzeppelin-solidity/contracts/math/SafeMath.sol";
import "solidity-bytes-utils/contracts/BytesLib.sol";
-import "./UsingLockedGold.sol";
import "./interfaces/IGovernance.sol";
import "../common/Initializable.sol";
import "../common/FixidityLib.sol";
import "../common/FractionUtil.sol";
import "../common/linkedlists/IntegerSortedLinkedList.sol";
+import "../common/UsingRegistry.sol";
// TODO(asa): Hardcode minimum times for queueExpiry, etc.
/**
* @title A contract for making, passing, and executing on-chain governance proposals.
*/
-contract Governance is IGovernance, Ownable, Initializable, UsingLockedGold, ReentrancyGuard {
+contract Governance is IGovernance, Ownable, Initializable, ReentrancyGuard, UsingRegistry {
using FixidityLib for FixidityLib.Fraction;
using FractionUtil for FractionUtil.Fraction;
using SafeMath for uint256;
@@ -428,7 +428,7 @@ contract Governance is IGovernance, Ownable, Initializable, UsingLockedGold, Ree
nonReentrant
returns (bool)
{
- address account = getAccountFromVoter(msg.sender);
+ address account = getLockedGold().getAccountFromVoter(msg.sender);
// TODO(asa): When upvoting a proposal that will get dequeued, should we let the tx succeed
// and return false?
dequeueProposalsIfReady();
@@ -441,7 +441,7 @@ contract Governance is IGovernance, Ownable, Initializable, UsingLockedGold, Ree
}
Voter storage voter = voters[account];
// We can upvote a proposal in the queue if we're not already upvoting a proposal in the queue.
- uint256 weight = getAccountTotalLockedGold(account);
+ uint256 weight = getLockedGold().getAccountTotalLockedGold(account);
require(
isQueued(proposalId) &&
(voter.upvote.proposalId == 0 || !queue.contains(voter.upvote.proposalId)) &&
@@ -477,7 +477,7 @@ contract Governance is IGovernance, Ownable, Initializable, UsingLockedGold, Ree
returns (bool)
{
dequeueProposalsIfReady();
- address account = getAccountFromVoter(msg.sender);
+ address account = getLockedGold().getAccountFromVoter(msg.sender);
Voter storage voter = voters[account];
uint256 proposalId = voter.upvote.proposalId;
Proposal storage proposal = proposals[proposalId];
@@ -543,7 +543,7 @@ contract Governance is IGovernance, Ownable, Initializable, UsingLockedGold, Ree
nonReentrant
returns (bool)
{
- address account = getAccountFromVoter(msg.sender);
+ address account = getLockedGold().getAccountFromVoter(msg.sender);
dequeueProposalsIfReady();
Proposal storage proposal = proposals[proposalId];
require(_proposalExists(proposal) && dequeued[index] == proposalId);
@@ -553,7 +553,7 @@ contract Governance is IGovernance, Ownable, Initializable, UsingLockedGold, Ree
}
ProposalStage stage = _getDequeuedProposalStage(proposal.timestamp);
Voter storage voter = voters[account];
- uint256 weight = getAccountTotalLockedGold(account);
+ uint256 weight = getLockedGold().getAccountTotalLockedGold(account);
require(
proposal.approved &&
stage == ProposalStage.Referendum &&
diff --git a/packages/protocol/contracts/governance/LockedGold.sol b/packages/protocol/contracts/governance/LockedGold.sol
index 0db2beabd87..5dae668a557 100644
--- a/packages/protocol/contracts/governance/LockedGold.sol
+++ b/packages/protocol/contracts/governance/LockedGold.sol
@@ -5,14 +5,13 @@ import "openzeppelin-solidity/contracts/math/SafeMath.sol";
import "openzeppelin-solidity/contracts/ownership/Ownable.sol";
import "./interfaces/ILockedGold.sol";
-import "./UsingElection.sol";
import "../common/Initializable.sol";
-import "../common/UsingRegistry.sol";
import "../common/interfaces/IERC20Token.sol";
import "../common/Signatures.sol";
+import "../common/UsingRegistry.sol";
-contract LockedGold is ILockedGold, ReentrancyGuard, Initializable, UsingRegistry, UsingElection {
+contract LockedGold is ILockedGold, ReentrancyGuard, Initializable, UsingRegistry {
// TODO(asa): How do adjust for updated requirements?
// Have a refreshRequirements function validators and groups can call
@@ -196,7 +195,7 @@ contract LockedGold is ILockedGold, ReentrancyGuard, Initializable, UsingRegistr
* @dev Fails if the `accountOrVoter` is not an account or authorized voter.
* @return The associated account.
*/
- function getAccountFromVoter(address accountOrVoter) public view returns (address) {
+ function getAccountFromVoter(address accountOrVoter) external view returns (address) {
address authorizingAccount = authorizations[accountOrVoter];
if (authorizingAccount != address(0)) {
require(accounts[authorizingAccount].authorizations.voter == accountOrVoter);
@@ -207,7 +206,7 @@ contract LockedGold is ILockedGold, ReentrancyGuard, Initializable, UsingRegistr
}
}
- function getTotalLockedGold() public view returns (uint256) {
+ function getTotalLockedGold() external view returns (uint256) {
return totalNonvoting.add(getElection().totalVotes());
}
diff --git a/packages/protocol/contracts/governance/UsingLockedGold.sol b/packages/protocol/contracts/governance/UsingLockedGold.sol
deleted file mode 100644
index 7f29f199155..00000000000
--- a/packages/protocol/contracts/governance/UsingLockedGold.sol
+++ /dev/null
@@ -1,60 +0,0 @@
-pragma solidity ^0.5.3;
-
-import "./interfaces/ILockedGold.sol";
-import "../common/UsingRegistry.sol";
-
-
-/**
- * @title A contract for calling functions on the LockedGold contract.
- * @dev Any contract calling these functions should guard against reentrancy.
- */
-contract UsingLockedGold is UsingRegistry {
- /**
- * @notice Returns the account associated with `accountOrVoter`.
- * @param accountOrVoter The address of the account or authorized voter.
- * @dev Fails if the `accountOrVoter` is not an account or authorized voter.
- * @return The associated account.
- */
- function getAccountFromVoter(address accountOrVoter) internal view returns (address) {
- return getLockedGold().getAccountFromVoter(accountOrVoter);
- }
-
- /**
- * @notice Returns the account associated with `accountOrValidator`.
- * @param accountOrValidator The address of the account or authorized validator.
- * @dev Fails if the `accountOrValidator` is not an account or authorized validator.
- * @return The associated account.
- */
- function getAccountFromValidator(address accountOrValidator) internal view returns (address) {
- return getLockedGold().getAccountFromValidator(accountOrValidator);
- }
-
- /**
- * @notice Returns the validator address for a particular account.
- * @param account The account.
- * @return The associated validator address.
- */
- function getValidatorFromAccount(address account) internal view returns (address) {
- return getLockedGold().getValidatorFromAccount(account);
- }
-
- function getTotalLockedGold() internal view returns (uint256) {
- return getLockedGold().getTotalLockedGold();
- }
-
- function getAccountTotalLockedGold(address account) internal view returns (uint256) {
- return getLockedGold().getAccountTotalLockedGold(account);
- }
-
- function incrementNonvotingAccountBalance(address account, uint256 value) internal returns (bool) {
- return getLockedGold().incrementNonvotingAccountBalance(account, value);
- }
-
- function decrementNonvotingAccountBalance(address account, uint256 value) internal returns (bool) {
- return getLockedGold().decrementNonvotingAccountBalance(account, value);
- }
-
- function getLockedGold() internal view returns(ILockedGold) {
- return ILockedGold(registry.getAddressForOrDie(LOCKED_GOLD_REGISTRY_ID));
- }
-}
diff --git a/packages/protocol/contracts/governance/Validators.sol b/packages/protocol/contracts/governance/Validators.sol
index 2c2efa48dbd..85bccffa361 100644
--- a/packages/protocol/contracts/governance/Validators.sol
+++ b/packages/protocol/contracts/governance/Validators.sol
@@ -5,19 +5,17 @@ import "openzeppelin-solidity/contracts/ownership/Ownable.sol";
import "openzeppelin-solidity/contracts/utils/ReentrancyGuard.sol";
import "solidity-bytes-utils/contracts/BytesLib.sol";
-import "./UsingLockedGold.sol";
import "./interfaces/IValidators.sol";
import "../common/Initializable.sol";
import "../common/FixidityLib.sol";
import "../common/linkedlists/AddressLinkedList.sol";
+import "../common/UsingRegistry.sol";
/**
* @title A contract for registering and electing Validator Groups and Validators.
*/
-contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, UsingLockedGold {
-
- // Votes live in the Election SC, votePortion,
+contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, UsingRegistry {
using FixidityLib for FixidityLib.Fraction;
using AddressLinkedList for LinkedList.List;
@@ -25,12 +23,18 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
using BytesLib for bytes;
address constant PROOF_OF_POSSESSION = address(0xff - 4);
+ uint256 constant MAX_INT = 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff;
struct RegistrationRequirements {
uint256 group;
uint256 validator;
}
+ struct DeregistrationLockups {
+ uint256 group;
+ uint256 validator;
+ }
+
struct ValidatorGroup {
string name;
string url;
@@ -50,13 +54,19 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
address[] private _groups;
address[] private _validators;
RegistrationRequirements public registrationRequirements;
+ DeregistrationLockups public deregistrationLockups;
uint256 public maxGroupSize;
event MaxGroupSizeSet(
uint256 size
);
- event RegistrationRequirementSet(
+ event RegistrationRequirementsSet(
+ uint256 group,
+ uint256 validator
+ );
+
+ event DeregistrationLockupsSet(
uint256 group,
uint256 validator
);
@@ -107,22 +117,22 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
address indexed validator
);
- event ValidatorGroupEmptied(
- address indexed group
- );
-
/**
* @notice Initializes critical variables.
* @param registryAddress The address of the registry contract.
* @param groupRequirement The minimum locked gold needed to register a group.
* @param validatorRequirement The minimum locked gold needed to register a validator.
- * @param size The maximum group size.
+ * @param groupLockup The duration the above gold remains locked after deregistration.
+ * @param validatorLockup The duration the above gold remains locked after deregistration.
+ * @param _maxGroupSize The maximum group size.
* @dev Should be called only once.
*/
function initialize(
address registryAddress,
uint256 groupRequirement,
uint256 validatorRequirement,
+ uint256 groupLockup,
+ uint256 validatorLockup,
uint256 _maxGroupSize
)
external
@@ -167,7 +177,31 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
validatorRequirement != registrationRequirements.validator
);
registrationRequirements = RegistrationRequirements(groupRequirement, validatorRequirement);
- emit RegistrationRequirementSet(groupRequirement, validatorRequirement);
+ emit RegistrationRequirementsSet(groupRequirement, validatorRequirement);
+ return true;
+ }
+
+ /**
+ * @notice Updates the duration for which gold remains locked after deregistration.
+ * @param groupLockup The duration for groups.
+ * @param validatorLockup The duration for validators.
+ * @return True upon success.
+ * @dev The new requirement is only enforced for future validator or group deregistrations.
+ */
+ function setDeregistrationLockup(
+ uint256 groupLockup,
+ uint256 validatorLockup
+ )
+ external
+ onlyOwner
+ returns (bool)
+ {
+ require(
+ groupLockup != deregistrationLockups.group ||
+ validatorLockup != deregistrationLockups.validator
+ );
+ deregistrationLockups = DeregistrationLockups(groupLockup, validatorLockup);
+ emit DeregistrationLockupsSet(groupLockup, validatorLockup);
return true;
}
@@ -203,9 +237,9 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
bytes memory proofOfPossessionBytes = publicKeysData.slice(64, 48 + 96);
require(checkProofOfPossession(proofOfPossessionBytes));
- address account = getAccountFromValidator(msg.sender);
+ address account = getValidators().getAccountFromValidator(msg.sender);
require(!isValidator(account) && !isValidatorGroup(account));
- require(meetsRegistrationRequirements(account));
+ require(meetsValidatorRegistrationRequirement(account));
Validator memory validator = Validator(name, url, publicKeysData, address(0));
validators[account] = validator;
@@ -226,6 +260,24 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
return success;
}
+ /**
+ * @notice 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.
+ */
+ function meetsValidatorRegistrationRequirement(address account) public returns (bool) {
+ getLockedGold().getAccountTotalLockedGold() >= registrationRequirements.validator;
+ }
+
+ /**
+ * @notice 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.
+ */
+ function meetsValidatorGroupRegistrationRequirement(address account) public returns (bool) {
+ getLockedGold().getAccountTotalLockedGold() >= registrationRequirements.group;
+ }
+
/**
* @notice De-registers a validator, removing it from the group for which it is a member.
* @param index The index of this validator in the list of all validators.
@@ -233,7 +285,7 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
* @dev Fails if the account is not a validator.
*/
function deregisterValidator(uint256 index) external nonReentrant returns (bool) {
- address account = getAccountFromValidator(msg.sender);
+ address account = getValidators().getAccountFromValidator(msg.sender);
require(isValidator(account));
Validator storage validator = validators[account];
if (validator.affiliation != address(0)) {
@@ -241,7 +293,7 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
}
delete validators[account];
deleteElement(_validators, account, index);
- getLockedGold().setAccountMustMaintain(account, registrationRequirements.validator, now.add(deregisterPeriods.validator));
+ getLockedGold().setAccountMustMaintain(account, registrationRequirements.validator, now.add(deregistrationLockups.validator));
emit ValidatorDeregistered(account);
return true;
}
@@ -253,7 +305,7 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
* @dev De-affiliates with the previously affiliated group if present.
*/
function affiliate(address group) external nonReentrant returns (bool) {
- address account = getAccountFromValidator(msg.sender);
+ address account = getValidators().getAccountFromValidator(msg.sender);
require(isValidator(account) && isValidatorGroup(group));
Validator storage validator = validators[account];
if (validator.affiliation != address(0)) {
@@ -270,7 +322,7 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
* @dev Fails if the account is not a validator with non-zero affiliation.
*/
function deaffiliate() external nonReentrant returns (bool) {
- address account = getAccountFromValidator(msg.sender);
+ address account = getValidators().getAccountFromValidator(msg.sender);
require(isValidator(account));
Validator storage validator = validators[account];
require(validator.affiliation != address(0));
@@ -298,20 +350,21 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
{
require(bytes(name).length > 0);
require(bytes(url).length > 0);
- require(isFraction(commission));
+ // TODO(asa)
+ // require(isFraction(commission));
require(members.length <= maxGroupSize);
- address account = getAccountFromValidator(msg.sender);
+ address account = getValidators().getAccountFromValidator(msg.sender);
require(!isValidator(account) && !isValidatorGroup(account));
- require(meetsRegistrationRequirements(account));
+ require(meetsValidatorGroupRegistrationRequirement(account));
- ValdiatorGroup storage group = groups[account];
+ ValidatorGroup storage group = groups[account];
group.name = name;
group.url = url;
for (uint256 i = 0; i < members.length; i = i.add(1)) {
group.addMember(members[i]);
}
_groups.push(account);
- getLockedGold().setAccountMustMaintain(account, requirements.group, MAX_INT);
+ getLockedGold().setAccountMustMaintain(account, registrationRequirements.group, MAX_INT);
emit ValidatorGroupRegistered(account, name, url);
return true;
}
@@ -323,12 +376,12 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
* @dev Fails if the account is not a validator group with no members.
*/
function deregisterValidatorGroup(uint256 index) external nonReentrant returns (bool) {
- address account = getAccountFromValidator(msg.sender);
+ address account = getValidators().getAccountFromValidator(msg.sender);
// Only empty Validator Groups can be deregistered.
require(isValidatorGroup(account) && groups[account].members.numElements == 0);
delete groups[account];
deleteElement(_groups, account, index);
- getLockedGold().setAccountMustMaintain(account, requirements.group, now.add(deregisterPeriods.group));
+ getLockedGold().setAccountMustMaintain(account, registrationRequirements.group, now.add(deregistrationLockups.group));
emit ValidatorGroupDeregistered(account);
return true;
}
@@ -340,7 +393,7 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
* @dev Fails if `validator` has not set their affiliation to this account.
*/
function addMember(address validator) external nonReentrant returns (bool) {
- address account = getAccountFromValidator(msg.sender);
+ address account = getValidators().getAccountFromValidator(msg.sender);
require(isValidatorGroup(account) && isValidator(validator));
ValidatorGroup storage group = groups[account];
require(group.members.length < maxGroupSize);
@@ -357,7 +410,7 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
* @dev Fails if `validator` is not a member of the account's group.
*/
function removeMember(address validator) external nonReentrant returns (bool) {
- address account = getAccountFromValidator(msg.sender);
+ address account = getValidators().getAccountFromValidator(msg.sender);
require(isValidatorGroup(account) && isValidator(validator));
return _removeMember(account, validator);
}
@@ -381,7 +434,7 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
nonReentrant
returns (bool)
{
- address account = getAccountFromValidator(msg.sender);
+ address account = getValidators().getAccountFromValidator(msg.sender);
require(isValidatorGroup(account) && isValidator(validator));
ValidatorGroup storage group = groups[account];
require(group.members.contains(validator));
@@ -442,7 +495,7 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
address[] memory topAccounts = groups[account].members.list.headN(n);
address[] memory topValidators = new address[](n);
for (uint256 i = 0; i < n; i = i.add(1)) {
- topValidators[i] = getValidatorFromAccount(topAccounts[i]);
+ topValidators[i] = getLockedGold().getValidatorFromAccount(topAccounts[i]);
}
return topValidators;
}
@@ -464,7 +517,7 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
* @return The locked gold requirements to register a validator or group.
*/
function getRegistrationRequirements() external view returns (uint256, uint256) {
- return (registrationRequirements.group, registrationRequirement.validator);
+ return (registrationRequirements.group, registrationRequirements.validator);
}
/**
@@ -501,15 +554,6 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
return bytes(validators[account].name).length > 0;
}
- /**
- * @notice Returns whether an account meets the requirements to register a validator or group.
- * @param account The account.
- * @return Whether an account meets the requirements to register a validator or group.
- */
- function meetsValidatorRegistrationRequirements(address account) public view returns (bool) {
- // TODO
- }
-
/**
* @notice Deletes an element from a list of addresses.
* @param list The list of addresses.
@@ -540,13 +584,7 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
// Empty validator groups are not electable.
if (groups[group].members.numElements == 0) {
- if (votes.contains(group)) {
- // TODO(asa): What needs to happen here????
- // We need to remove from the linked list but preserve all other info...
- // And probably add back in if it gets a member...
- votes.remove(group);
- }
- emit ValidatorGroupEmptied(group);
+ getElection().markGroupUnelectable(group);
}
return true;
}
diff --git a/packages/protocol/contracts/governance/interfaces/ILockedGold.sol b/packages/protocol/contracts/governance/interfaces/ILockedGold.sol
index 6b37899dd74..de7d7888736 100644
--- a/packages/protocol/contracts/governance/interfaces/ILockedGold.sol
+++ b/packages/protocol/contracts/governance/interfaces/ILockedGold.sol
@@ -2,26 +2,8 @@ pragma solidity ^0.5.3;
interface ILockedGold {
- enum DelegateRole {Validating, Voting, Rewards}
- enum CommitmentType {Locked, Notified}
function initialize(address, uint256) external;
- function isVotingFrozen(address) external view returns (bool);
- function setCumulativeRewardWeight(uint256) external;
- function setMaxNoticePeriod(uint256) external;
- function redeemRewards() external returns (uint256);
- function freezeVoting() external;
- function unfreezeVoting() external;
- function newCommitment(uint256) external payable returns (uint256);
- function notifyCommitment(uint256, uint256) external returns (uint256);
- function extendCommitment(uint256, uint256) external returns (uint256);
- function withdrawCommitment(uint256) external returns (uint256);
- function increaseNoticePeriod(uint256, uint256, uint256) external returns (uint256);
- function getRewardsLastRedeemed(address) external view returns (uint96);
- function getNoticePeriods(address) external view returns (uint256[] memory);
- function getAvailabilityTimes(address) external view returns (uint256[] memory);
- function getLockedCommitment(address, uint256) external view returns (uint256, uint256);
- function getAccountWeight(address) external view returns (uint256);
- function delegateRole(DelegateRole, address, uint8, bytes32, bytes32) external;
- function getAccountFromDelegateAndRole(address, DelegateRole) external view returns (address);
- function getDelegateFromAccountAndRole(address, DelegateRole) external view returns (address);
+ function getAccountFromVoter(address) external view returns (address);
+ function incrementNonvotingAccountBalance(address, uint256) external;
+ function decrementNonvotingAccountBalance(address, uint256) external;
}
diff --git a/packages/protocol/contracts/governance/interfaces/IValidators.sol b/packages/protocol/contracts/governance/interfaces/IValidators.sol
index adde44d0178..757c0f29c64 100644
--- a/packages/protocol/contracts/governance/interfaces/IValidators.sol
+++ b/packages/protocol/contracts/governance/interfaces/IValidators.sol
@@ -2,7 +2,7 @@ pragma solidity ^0.5.3;
interface IValidators {
- function isVoting(address) external view returns (bool);
- function isValidating(address) external view returns (bool);
- function getValidators() external view returns (address[] memory);
+ function electValidators() external view returns (address[] memory);
+ function getNumGroupMembers(address) external view returns (uint256);
+ function getNumRegisteredValidators() external view returns (uint256);
}
diff --git a/packages/protocol/contracts/governance/test/MockLockedGold.sol b/packages/protocol/contracts/governance/test/MockLockedGold.sol
index 4fa6fee6724..f223c548a43 100644
--- a/packages/protocol/contracts/governance/test/MockLockedGold.sol
+++ b/packages/protocol/contracts/governance/test/MockLockedGold.sol
@@ -7,102 +7,4 @@ import "../interfaces/ILockedGold.sol";
* @title A mock LockedGold for testing.
*/
contract MockLockedGold is ILockedGold {
- mapping(address => mapping(uint256 => uint256)) public locked;
- mapping(address => uint256) public weights;
- mapping(address => bool) public frozen;
- // Maps a delegating address to an account.
- mapping(address => address) public delegations;
- // Maps an account address to their voting delegate.
- mapping(address => address) public voters;
- // Maps an account address to their validating delegate.
- mapping(address => address) public validators;
- // Maps an account address to their rewards delegate.
- mapping(address => address) public rewarders;
-
- function initialize(address, uint256) external {}
- function setCumulativeRewardWeight(uint256) external {}
- function setMaxNoticePeriod(uint256) external {}
- function redeemRewards() external returns (uint256) {}
- function freezeVoting() external {}
- function unfreezeVoting() external {}
- function newCommitment(uint256) external payable returns (uint256) {}
- function notifyCommitment(uint256, uint256) external returns (uint256) {}
- function extendCommitment(uint256, uint256) external returns (uint256) {}
- function withdrawCommitment(uint256) external returns (uint256) {}
- function increaseNoticePeriod(uint256, uint256, uint256) external returns (uint256) {}
- function getRewardsLastRedeemed(address) external view returns (uint96) {}
- function getNoticePeriods(address) external view returns (uint256[] memory) {}
- function getAvailabilityTimes(address) external view returns (uint256[] memory) {}
- function delegateRole(DelegateRole, address, uint8, bytes32, bytes32) external {}
-
- function isVotingFrozen(address account) external view returns (bool) {
- return frozen[account];
- }
-
- function setWeight(address account, uint256 weight) external {
- weights[account] = weight;
- }
-
- function setLockedCommitment(address account, uint256 noticePeriod, uint256 value) external {
- locked[account][noticePeriod] = value;
- }
-
- function setVotingFrozen(address account) external {
- frozen[account] = true;
- }
-
- function delegateVoting(address account, address delegate) external {
- delegations[delegate] = account;
- voters[account] = delegate;
- }
-
- function delegateValidating(address account, address delegate) external {
- delegations[delegate] = account;
- validators[account] = delegate;
- }
-
- function getAccountWeight(address account) external view returns (uint256) {
- return weights[account];
- }
-
- function getAccountFromDelegateAndRole(address delegate, DelegateRole)
- external view returns (address)
- {
- address a = delegations[delegate];
- if (a != address(0)) {
- return a;
- } else {
- return delegate;
- }
- }
-
- function getDelegateFromAccountAndRole(address account, DelegateRole role)
- external view returns (address)
- {
- address a;
- if (role == DelegateRole.Validating) {
- a = validators[account];
- } else if (role == DelegateRole.Voting) {
- a = voters[account];
- } else if (role == DelegateRole.Rewards) {
- a = rewarders[account];
- }
- if (a != address(0)) {
- return a;
- } else {
- return account;
- }
- }
-
- function getLockedCommitment(
- address account,
- uint256 noticePeriod
- )
- external
- view
- returns (uint256, uint256)
- {
- // Always return 0 for the index.
- return (locked[account][noticePeriod], 0);
- }
}
diff --git a/packages/protocol/contracts/identity/Attestations.sol b/packages/protocol/contracts/identity/Attestations.sol
index 9f0a071da67..db5f7dd03d0 100644
--- a/packages/protocol/contracts/identity/Attestations.sol
+++ b/packages/protocol/contracts/identity/Attestations.sol
@@ -565,17 +565,6 @@ contract Attestations is IAttestations, Ownable, Initializable, UsingRegistry, R
return identifiers[identifier].accounts;
}
- /**
- * @notice Returns the current validator set
- * TODO: Should be replaced with a precompile
- */
- function getValidators() public view returns (address[] memory) {
- IValidators validatorContract = IValidators(
- registry.getAddressForOrDie(VALIDATORS_REGISTRY_ID)
- );
- return validatorContract.getValidators();
- }
-
/**
* @notice Helper function for batchGetAttestationStats to calculate the
total number of addresses that have >0 complete attestations for the identifiers
@@ -618,7 +607,7 @@ contract Attestations is IAttestations, Ownable, Initializable, UsingRegistry, R
IRandom random = IRandom(registry.getAddressForOrDie(RANDOM_REGISTRY_ID));
bytes32 seed = random.random();
- address[] memory validators = getValidators();
+ address[] memory validators = getElection().electValidators();
uint256 currentIndex = 0;
address validator;
From 6ce7f7e1a55903235f7a3a956a3e7b091145da99 Mon Sep 17 00:00:00 2001
From: Asa Oines
Date: Fri, 20 Sep 2019 11:06:17 -0700
Subject: [PATCH 005/149] Compiling
---
.../linkedlists/AddressSortedLinkedList.sol | 14 +++++
.../common/linkedlists/SortedLinkedList.sol | 10 +++
.../contracts/common/test/FixidityTest.sol | 22 +++----
.../contracts/governance/Election.sol | 14 +++--
.../contracts/governance/Governance.sol | 28 +++------
.../contracts/governance/LockedGold.sol | 35 ++++++-----
.../contracts/governance/Validators.sol | 62 +++++++++++--------
.../governance/interfaces/IElection.sol | 5 +-
.../governance/interfaces/IGovernance.sol | 3 +-
.../governance/interfaces/ILockedGold.sol | 6 +-
.../governance/interfaces/IValidators.sol | 5 +-
11 files changed, 118 insertions(+), 86 deletions(-)
diff --git a/packages/protocol/contracts/common/linkedlists/AddressSortedLinkedList.sol b/packages/protocol/contracts/common/linkedlists/AddressSortedLinkedList.sol
index 3949b873124..528d5500ece 100644
--- a/packages/protocol/contracts/common/linkedlists/AddressSortedLinkedList.sol
+++ b/packages/protocol/contracts/common/linkedlists/AddressSortedLinkedList.sol
@@ -105,4 +105,18 @@ library AddressSortedLinkedList {
}
return (keys, values);
}
+
+ /**
+ * @notice Returns the N greatest elements of the list.
+ * @param n The number of elements to return.
+ * @return The keys of the greatest elements.
+ */
+ function headN(SortedLinkedList.List storage list, uint256 n) public view returns (address[] memory) {
+ bytes32[] memory byteKeys = list.headN(n);
+ address[] memory keys = new address[](n);
+ for (uint256 i = 0; i < n; i++) {
+ keys[i] = toAddress(byteKeys[i]);
+ }
+ return keys;
+ }
}
diff --git a/packages/protocol/contracts/common/linkedlists/SortedLinkedList.sol b/packages/protocol/contracts/common/linkedlists/SortedLinkedList.sol
index 80a057324dc..176d499749c 100644
--- a/packages/protocol/contracts/common/linkedlists/SortedLinkedList.sol
+++ b/packages/protocol/contracts/common/linkedlists/SortedLinkedList.sol
@@ -144,6 +144,16 @@ library SortedLinkedList {
return list.list.getKeys();
}
+ /**
+ * @notice Returns first N greatest elements of the list.
+ * @param n The number of elements to return.
+ * @return The keys of the first n elements.
+ */
+ function headN(List storage list, uint256 n) public view returns (bytes32[] memory) {
+ return list.list.headN(n);
+ }
+
+
// TODO(asa): Gas optimizations by passing in elements to isValueBetween
/**
* @notice Returns the keys of the elements greaterKey than and less than the provided value.
diff --git a/packages/protocol/contracts/common/test/FixidityTest.sol b/packages/protocol/contracts/common/test/FixidityTest.sol
index 8ce11ca87fb..452b5da0c9a 100644
--- a/packages/protocol/contracts/common/test/FixidityTest.sol
+++ b/packages/protocol/contracts/common/test/FixidityTest.sol
@@ -6,47 +6,47 @@ import "../FixidityLib.sol";
contract FixidityTest {
using FixidityLib for FixidityLib.Fraction;
- function newFixed(uint256 a) external view returns (uint256) {
+ function newFixed(uint256 a) external pure returns (uint256) {
return FixidityLib.newFixed(a).unwrap();
}
- function newFixedFraction(uint256 a, uint256 b) external view returns (uint256) {
+ function newFixedFraction(uint256 a, uint256 b) external pure returns (uint256) {
return FixidityLib.newFixedFraction(a, b).unwrap();
}
- function add(uint256 a, uint256 b) external view returns (uint256) {
+ function add(uint256 a, uint256 b) external pure returns (uint256) {
return FixidityLib.wrap(a).add(FixidityLib.wrap(b)).unwrap();
}
- function subtract(uint256 a, uint256 b) external view returns (uint256) {
+ function subtract(uint256 a, uint256 b) external pure returns (uint256) {
return FixidityLib.wrap(a).subtract(FixidityLib.wrap(b)).unwrap();
}
- function multiply(uint256 a, uint256 b) external view returns (uint256) {
+ function multiply(uint256 a, uint256 b) external pure returns (uint256) {
return FixidityLib.wrap(a).multiply(FixidityLib.wrap(b)).unwrap();
}
- function reciprocal(uint256 a) external view returns (uint256) {
+ function reciprocal(uint256 a) external pure returns (uint256) {
return FixidityLib.wrap(a).reciprocal().unwrap();
}
- function divide(uint256 a, uint256 b) external view returns (uint256) {
+ function divide(uint256 a, uint256 b) external pure returns (uint256) {
return FixidityLib.wrap(a).divide(FixidityLib.wrap(b)).unwrap();
}
- function gt(uint256 a, uint256 b) external view returns (bool) {
+ function gt(uint256 a, uint256 b) external pure returns (bool) {
return FixidityLib.wrap(a).gt(FixidityLib.wrap(b));
}
- function gte(uint256 a, uint256 b) external view returns (bool) {
+ function gte(uint256 a, uint256 b) external pure returns (bool) {
return FixidityLib.wrap(a).gte(FixidityLib.wrap(b));
}
- function lt(uint256 a, uint256 b) external view returns (bool) {
+ function lt(uint256 a, uint256 b) external pure returns (bool) {
return FixidityLib.wrap(a).lt(FixidityLib.wrap(b));
}
- function lte(uint256 a, uint256 b) external view returns (bool) {
+ function lte(uint256 a, uint256 b) external pure returns (bool) {
return FixidityLib.wrap(a).lte(FixidityLib.wrap(b));
}
}
diff --git a/packages/protocol/contracts/governance/Election.sol b/packages/protocol/contracts/governance/Election.sol
index c4a07f2a699..294a6ffebea 100644
--- a/packages/protocol/contracts/governance/Election.sol
+++ b/packages/protocol/contracts/governance/Election.sol
@@ -371,7 +371,7 @@ contract Election is Ownable, ReentrancyGuard, Initializable, UsingRegistry {
function markGroupEligible(address group, address lesser, address greater) external {
require(!votes.total.eligible.contains(group));
- require(getValidators().getNumGroupMembers(group) > 0);
+ require(getValidators().getGroupNumMembers(group) > 0);
uint256 value = votes.pending.total[group].add(votes.active.total[group]);
votes.total.eligible.insert(group, value, lesser, greater);
}
@@ -409,7 +409,7 @@ contract Election is Ownable, ReentrancyGuard, Initializable, UsingRegistry {
active.total[group] = active.total[group].sub(value);
}
- function getActiveVotesDelta(address group, uint256 value) private returns (uint256) {
+ function getActiveVotesDelta(address group, uint256 value) private view returns (uint256) {
// Preserve delta * total = value * denominator
return value.mul(votes.active.denominators[group]).div(votes.active.total[group]);
}
@@ -429,11 +429,15 @@ contract Election is Ownable, ReentrancyGuard, Initializable, UsingRegistry {
}
function getNumVotesReceivable(address group) public view returns (uint256) {
- uint256 numerator = getValidators().getNumGroupMembers(group).add(1).mul(votes.total.total);
+ uint256 numerator = getValidators().getGroupNumMembers(group).add(1).mul(getLockedGold().getTotalLockedGold());
uint256 denominator = Math.min(maxElectableValidators, getValidators().getNumRegisteredValidators());
return numerator.div(denominator);
}
+ function getTotalVotes() external view returns (uint256) {
+ return votes.total.total;
+ }
+
function validatorAddressFromCurrentSet(uint256 index) external view returns (address) {
address validatorAddress;
assembly {
@@ -470,8 +474,8 @@ contract Election is Ownable, ReentrancyGuard, Initializable, UsingRegistry {
uint256 maxNumElectionGroups = Math.min(maxElectableValidators, votes.total.eligible.list.numElements);
// uint256 requiredVotes = electabilityThreshold.multiply(FixidityLib.newFixed(votes.total.total)).fromFixed();
// TODO(asa): Filter by > requiredVotes
- address[] memory electionGroups = votes.total.eligible.list.headN(maxNumElectionGroups);
- uint256[] memory numMembers = getValidators().getNumGroupMembers(electionGroups);
+ address[] memory electionGroups = votes.total.eligible.headN(maxNumElectionGroups);
+ uint256[] memory numMembers = getValidators().getGroupsNumMembers(electionGroups);
// Holds the number of members elected for each of the eligible validator groups.
uint256[] memory numMembersElected = new uint256[](electionGroups.length);
uint256 totalNumMembersElected = 0;
diff --git a/packages/protocol/contracts/governance/Governance.sol b/packages/protocol/contracts/governance/Governance.sol
index 0f7473ed4cd..256a10b3d5c 100644
--- a/packages/protocol/contracts/governance/Governance.sol
+++ b/packages/protocol/contracts/governance/Governance.sol
@@ -109,7 +109,7 @@ contract Governance is IGovernance, Ownable, Initializable, ReentrancyGuard, Usi
mapping(address => uint256) public refundedDeposits;
mapping(address => ContractConstitution) private constitution;
mapping(uint256 => Proposal) private proposals;
- mapping(address => Voter) public voters;
+ mapping(address => Voter) private voters;
SortedLinkedList.List private queue;
uint256[] public dequeued;
uint256[] public emptyIndices;
@@ -580,7 +580,7 @@ contract Governance is IGovernance, Ownable, Initializable, ReentrancyGuard, Usi
} else if (value == VoteValue.No) {
proposal.votes.no = proposal.votes.no.add(weight);
}
- voteRecord = VoteRecord(value, proposalId, weight);
+ voter.referendumVotes[index] = VoteRecord(value, proposalId, weight);
if (proposal.timestamp > voter.mostRecentReferendumProposal) {
voter.mostRecentReferendumProposal = proposalId;
}
@@ -784,12 +784,13 @@ contract Governance is IGovernance, Ownable, Initializable, ReentrancyGuard, Usi
}
/**
- * @notice Returns the ID of the proposal upvoted by `account`.
+ * @notice Returns the ID of the proposal upvoted by `account` and the weight of that upvote.
* @param account The address of the account.
- * @return The ID of the proposal upvoted by `account`.
+ * @return The ID of the proposal upvoted by `account` and the weight of that upvote.
*/
- function getUpvotedProposal(address account) external view returns (uint256) {
- return voters[account].upvotedProposal;
+ function getUpvoteRecord(address account) external view returns (uint256, uint256) {
+ UpvoteRecord memory upvoteRecord = voters[account].upvote;
+ return (upvoteRecord.proposalId, upvoteRecord.weight);
}
/**
@@ -801,21 +802,6 @@ contract Governance is IGovernance, Ownable, Initializable, ReentrancyGuard, Usi
return voters[account].mostRecentReferendumProposal;
}
- /**
- * @notice Returns whether or not a particular account is voting on proposals.
- * @param account The address of the account.
- * @return Whether or not the account is voting on proposals.
- */
- function isVoting(address account) external view returns (bool) {
- Voter storage voter = voters[account];
- bool isVotingQueue = voter.upvotedProposal != 0 && isQueued(voter.upvotedProposal);
- Proposal storage proposal = proposals[voter.mostRecentReferendumProposal];
- bool isVotingReferendum = (
- _getDequeuedProposalStage(proposal.timestamp) == ProposalStage.Referendum
- );
- return isVotingQueue || isVotingReferendum;
- }
-
/**
* @notice Removes the proposals with the most upvotes from the queue, moving them to the
* approval stage.
diff --git a/packages/protocol/contracts/governance/LockedGold.sol b/packages/protocol/contracts/governance/LockedGold.sol
index 5dae668a557..3f7fab5d9a2 100644
--- a/packages/protocol/contracts/governance/LockedGold.sol
+++ b/packages/protocol/contracts/governance/LockedGold.sol
@@ -13,6 +13,8 @@ import "../common/UsingRegistry.sol";
contract LockedGold is ILockedGold, ReentrancyGuard, Initializable, UsingRegistry {
+ using SafeMath for uint256;
+
// TODO(asa): How do adjust for updated requirements?
// Have a refreshRequirements function validators and groups can call
struct MustMaintain {
@@ -47,7 +49,7 @@ contract LockedGold is ILockedGold, ReentrancyGuard, Initializable, UsingRegistr
Balances balances;
}
- mapping(address => Account) public accounts;
+ mapping(address => Account) private accounts;
// Maps voting and validating keys to the account that provided the authorization.
mapping(address => address) public authorizations;
uint256 public totalNonvoting;
@@ -60,9 +62,10 @@ contract LockedGold is ILockedGold, ReentrancyGuard, Initializable, UsingRegistr
event GoldWithdrawn(address indexed account, uint256 value);
event AccountMustMaintainSet(address indexed account, uint256 value, uint256 timestamp);
- function initialize(address registryAddress) external initializer {
+ function initialize(address registryAddress, uint256 _unlockingPeriod) external initializer {
_transferOwnership(msg.sender);
setRegistry(registryAddress);
+ unlockingPeriod = _unlockingPeriod;
}
/**
@@ -110,28 +113,28 @@ contract LockedGold is ILockedGold, ReentrancyGuard, Initializable, UsingRegistr
* @notice Locks gold to be used for voting.
* @param value The amount of gold to be locked.
*/
- function lock(uint256 value) external nonReentrant {
+ function lock(uint256 value) external payable nonReentrant {
require(isAccount(msg.sender));
require(msg.value == value && value > 0);
_incrementNonvotingAccountBalance(msg.sender, value);
emit GoldLocked(msg.sender, value);
}
- function incrementNonvotingAccountBalance(address account, uint256 value) external onlyRegisteredContract('Election', msg.sender) {
+ function incrementNonvotingAccountBalance(address account, uint256 value) external onlyRegisteredContract('Election') {
_incrementNonvotingAccountBalance(account, value);
}
- function decrementNonvotingAccountBalance(address account, uint256 value) external onlyRegisteredContract('Election', msg.sender) {
+ function decrementNonvotingAccountBalance(address account, uint256 value) external onlyRegisteredContract('Election') {
_decrementNonvotingAccountBalance(account, value);
}
function _incrementNonvotingAccountBalance(address account, uint256 value) private {
- accounts[account].gold.nonvoting = accounts[account].gold.nonvoting.add(value);
+ accounts[account].balances.nonvoting = accounts[account].balances.nonvoting.add(value);
totalNonvoting = totalNonvoting.add(value);
}
function _decrementNonvotingAccountBalance(address account, uint256 value) private {
- accounts[account].gold.nonvoting = accounts[account].gold.nonvoting.sub(value);
+ accounts[account].balances.nonvoting = accounts[account].balances.nonvoting.sub(value);
totalNonvoting = totalNonvoting.sub(value);
}
@@ -139,7 +142,7 @@ contract LockedGold is ILockedGold, ReentrancyGuard, Initializable, UsingRegistr
function unlock(uint256 value) external nonReentrant {
require(isAccount(msg.sender));
Account storage account = accounts[msg.sender];
- MustMaintain memory requirement = account.requirement;
+ MustMaintain memory requirement = account.balances.requirements;
require(
now >= requirement.timestamp ||
getAccountTotalLockedGold(msg.sender).sub(value) >= requirement.value
@@ -166,7 +169,7 @@ contract LockedGold is ILockedGold, ReentrancyGuard, Initializable, UsingRegistr
Account storage account = accounts[msg.sender];
require(index < account.balances.pendingWithdrawals.length);
PendingWithdrawal memory pendingWithdrawal = account.balances.pendingWithdrawals[index];
- require(now >= pendingWithdrawal.available);
+ require(now >= pendingWithdrawal.timestamp);
uint256 value = pendingWithdrawal.value;
deletePendingWithdrawal(account.balances.pendingWithdrawals, index);
IERC20Token goldToken = IERC20Token(registry.getAddressFor(GOLD_TOKEN_REGISTRY_ID));
@@ -180,11 +183,11 @@ contract LockedGold is ILockedGold, ReentrancyGuard, Initializable, UsingRegistr
uint256 timestamp
)
public
- onlyRegisteredContract('Election', msg.sender)
+ onlyRegisteredContract('Election')
nonReentrant
returns (bool)
{
- accounts[account].requirement = MustMaintain(value, timestamp);
+ accounts[account].balances.requirements = MustMaintain(value, timestamp);
emit AccountMustMaintainSet(account, value, timestamp);
}
@@ -198,7 +201,7 @@ contract LockedGold is ILockedGold, ReentrancyGuard, Initializable, UsingRegistr
function getAccountFromVoter(address accountOrVoter) external view returns (address) {
address authorizingAccount = authorizations[accountOrVoter];
if (authorizingAccount != address(0)) {
- require(accounts[authorizingAccount].authorizations.voter == accountOrVoter);
+ require(accounts[authorizingAccount].authorizations.voting == accountOrVoter);
return authorizingAccount;
} else {
require(isAccount(accountOrVoter));
@@ -207,7 +210,7 @@ contract LockedGold is ILockedGold, ReentrancyGuard, Initializable, UsingRegistr
}
function getTotalLockedGold() external view returns (uint256) {
- return totalNonvoting.add(getElection().totalVotes());
+ return totalNonvoting.add(getElection().getTotalVotes());
}
function getAccountTotalLockedGold(address account) public view returns (uint256) {
@@ -224,7 +227,7 @@ contract LockedGold is ILockedGold, ReentrancyGuard, Initializable, UsingRegistr
function getAccountFromValidator(address accountOrValidator) public view returns (address) {
address authorizingAccount = authorizations[accountOrValidator];
if (authorizingAccount != address(0)) {
- require(accounts[authorizingAccount].authorizations.validator == accountOrValidator);
+ require(accounts[authorizingAccount].authorizations.validating == accountOrValidator);
return authorizingAccount;
} else {
require(isAccount(accountOrValidator));
@@ -239,7 +242,7 @@ contract LockedGold is ILockedGold, ReentrancyGuard, Initializable, UsingRegistr
*/
function getVoterFromAccount(address account) public view returns (address) {
require(isAccount(account));
- address voter = accounts[account].authorizations.voter;
+ address voter = accounts[account].authorizations.voting;
return voter == address(0) ? account : voter;
}
@@ -250,7 +253,7 @@ contract LockedGold is ILockedGold, ReentrancyGuard, Initializable, UsingRegistr
*/
function getValidatorFromAccount(address account) public view returns (address) {
require(isAccount(account));
- address validator = accounts[account].authorizations.validator;
+ address validator = accounts[account].authorizations.validating;
return validator == address(0) ? account : validator;
}
diff --git a/packages/protocol/contracts/governance/Validators.sol b/packages/protocol/contracts/governance/Validators.sol
index 85bccffa361..4f3555cea6f 100644
--- a/packages/protocol/contracts/governance/Validators.sol
+++ b/packages/protocol/contracts/governance/Validators.sol
@@ -140,8 +140,8 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
{
_transferOwnership(msg.sender);
setRegistry(registryAddress);
- registrationRequirements.group = groupRequirement;
- registrationRequirements.validator = validatorRequirement;
+ registrationRequirements = RegistrationRequirements(groupRequirement, validatorRequirement);
+ deregistrationLockups = DeregistrationLockups(groupLockup, validatorLockup);
maxGroupSize = _maxGroupSize;
}
@@ -237,7 +237,7 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
bytes memory proofOfPossessionBytes = publicKeysData.slice(64, 48 + 96);
require(checkProofOfPossession(proofOfPossessionBytes));
- address account = getValidators().getAccountFromValidator(msg.sender);
+ address account = getLockedGold().getAccountFromValidator(msg.sender);
require(!isValidator(account) && !isValidatorGroup(account));
require(meetsValidatorRegistrationRequirement(account));
@@ -265,8 +265,8 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
* @param account The account.
* @return Whether an account meets the requirements to register a validator.
*/
- function meetsValidatorRegistrationRequirement(address account) public returns (bool) {
- getLockedGold().getAccountTotalLockedGold() >= registrationRequirements.validator;
+ function meetsValidatorRegistrationRequirement(address account) public view returns (bool) {
+ getLockedGold().getAccountTotalLockedGold(account) >= registrationRequirements.validator;
}
/**
@@ -274,8 +274,8 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
* @param account The account.
* @return Whether an account meets the requirements to register a group.
*/
- function meetsValidatorGroupRegistrationRequirement(address account) public returns (bool) {
- getLockedGold().getAccountTotalLockedGold() >= registrationRequirements.group;
+ function meetsValidatorGroupRegistrationRequirement(address account) public view returns (bool) {
+ getLockedGold().getAccountTotalLockedGold(account) >= registrationRequirements.group;
}
/**
@@ -285,7 +285,7 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
* @dev Fails if the account is not a validator.
*/
function deregisterValidator(uint256 index) external nonReentrant returns (bool) {
- address account = getValidators().getAccountFromValidator(msg.sender);
+ address account = getLockedGold().getAccountFromValidator(msg.sender);
require(isValidator(account));
Validator storage validator = validators[account];
if (validator.affiliation != address(0)) {
@@ -305,7 +305,7 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
* @dev De-affiliates with the previously affiliated group if present.
*/
function affiliate(address group) external nonReentrant returns (bool) {
- address account = getValidators().getAccountFromValidator(msg.sender);
+ address account = getLockedGold().getAccountFromValidator(msg.sender);
require(isValidator(account) && isValidatorGroup(group));
Validator storage validator = validators[account];
if (validator.affiliation != address(0)) {
@@ -322,7 +322,7 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
* @dev Fails if the account is not a validator with non-zero affiliation.
*/
function deaffiliate() external nonReentrant returns (bool) {
- address account = getValidators().getAccountFromValidator(msg.sender);
+ address account = getLockedGold().getAccountFromValidator(msg.sender);
require(isValidator(account));
Validator storage validator = validators[account];
require(validator.affiliation != address(0));
@@ -353,15 +353,16 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
// TODO(asa)
// require(isFraction(commission));
require(members.length <= maxGroupSize);
- address account = getValidators().getAccountFromValidator(msg.sender);
+ address account = getLockedGold().getAccountFromValidator(msg.sender);
require(!isValidator(account) && !isValidatorGroup(account));
require(meetsValidatorGroupRegistrationRequirement(account));
ValidatorGroup storage group = groups[account];
group.name = name;
group.url = url;
+ group.commission = FixidityLib.wrap(commission);
for (uint256 i = 0; i < members.length; i = i.add(1)) {
- group.addMember(members[i]);
+ _addMember(account, members[i]);
}
_groups.push(account);
getLockedGold().setAccountMustMaintain(account, registrationRequirements.group, MAX_INT);
@@ -376,7 +377,7 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
* @dev Fails if the account is not a validator group with no members.
*/
function deregisterValidatorGroup(uint256 index) external nonReentrant returns (bool) {
- address account = getValidators().getAccountFromValidator(msg.sender);
+ address account = getLockedGold().getAccountFromValidator(msg.sender);
// Only empty Validator Groups can be deregistered.
require(isValidatorGroup(account) && groups[account].members.numElements == 0);
delete groups[account];
@@ -393,16 +394,23 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
* @dev Fails if `validator` has not set their affiliation to this account.
*/
function addMember(address validator) external nonReentrant returns (bool) {
- address account = getValidators().getAccountFromValidator(msg.sender);
+ address account = getLockedGold().getAccountFromValidator(msg.sender);
require(isValidatorGroup(account) && isValidator(validator));
- ValidatorGroup storage group = groups[account];
- require(group.members.length < maxGroupSize);
- require(validators[validator].affiliation == account && !group.members.contains(validator));
- group.members.push(validator);
- emit ValidatorGroupMemberAdded(account, validator);
+ return _addMember(account, validator);
+ }
+
+ function _addMember(address group, address validator) private returns (bool) {
+ ValidatorGroup storage _group = groups[group];
+ require(_group.members.numElements < maxGroupSize);
+ require(validators[validator].affiliation == group && !_group.members.contains(validator));
+ _group.members.push(validator);
+ emit ValidatorGroupMemberAdded(group, validator);
return true;
}
+ /**
+ * @notice De-affiliates a validator, removing it from the group for which it is a member.
+
/**
* @notice Removes a member from a validator group.
* @param validator The validator to remove from the group
@@ -410,7 +418,7 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
* @dev Fails if `validator` is not a member of the account's group.
*/
function removeMember(address validator) external nonReentrant returns (bool) {
- address account = getValidators().getAccountFromValidator(msg.sender);
+ address account = getLockedGold().getAccountFromValidator(msg.sender);
require(isValidatorGroup(account) && isValidator(validator));
return _removeMember(account, validator);
}
@@ -434,7 +442,7 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
nonReentrant
returns (bool)
{
- address account = getValidators().getAccountFromValidator(msg.sender);
+ address account = getLockedGold().getAccountFromValidator(msg.sender);
require(isValidatorGroup(account) && isValidator(validator));
ValidatorGroup storage group = groups[account];
require(group.members.contains(validator));
@@ -480,19 +488,19 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
)
external
view
- returns (string memory, string memory, string memory, address[] memory)
+ returns (string memory, string memory, address[] memory)
{
require(isValidatorGroup(account));
ValidatorGroup storage group = groups[account];
return (group.name, group.url, group.members.getKeys());
}
- function getNumGroupMembers(address account) public view returns (uint256) {
+ function getGroupNumMembers(address account) public view returns (uint256) {
return groups[account].members.numElements;
}
function getTopValidatorsFromGroup(address account, uint256 n) external view returns (address[] memory) {
- address[] memory topAccounts = groups[account].members.list.headN(n);
+ address[] memory topAccounts = groups[account].members.headN(n);
address[] memory topValidators = new address[](n);
for (uint256 i = 0; i < n; i = i.add(1)) {
topValidators[i] = getLockedGold().getValidatorFromAccount(topAccounts[i]);
@@ -500,10 +508,10 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
return topValidators;
}
- function getNumGroupMembers(address[] calldata accounts) external view returns (uint256) {
+ function getGroupsNumMembers(address[] calldata accounts) external view returns (uint256[] memory) {
uint256[] memory numMembers = new uint256[](accounts.length);
for (uint256 i = 0; i < accounts.length; i = i.add(1)) {
- numMembers[i] = getNumGroupMembers(accounts[i]);
+ numMembers[i] = getGroupNumMembers(accounts[i]);
}
return numMembers;
}
@@ -584,7 +592,7 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
// Empty validator groups are not electable.
if (groups[group].members.numElements == 0) {
- getElection().markGroupUnelectable(group);
+ getElection().markGroupIneligible(group);
}
return true;
}
diff --git a/packages/protocol/contracts/governance/interfaces/IElection.sol b/packages/protocol/contracts/governance/interfaces/IElection.sol
index dbb105d708b..c6935b95abb 100644
--- a/packages/protocol/contracts/governance/interfaces/IElection.sol
+++ b/packages/protocol/contracts/governance/interfaces/IElection.sol
@@ -2,5 +2,8 @@ pragma solidity ^0.5.3;
interface IElection {
- function isVoting(address) external view returns(bool);
+ function getTotalVotes() external view returns (uint256);
+ function getAccountTotalVotes(address account) external view returns (uint256);
+ function markGroupIneligible(address) external;
+ function electValidators() external view returns (address[] memory);
}
diff --git a/packages/protocol/contracts/governance/interfaces/IGovernance.sol b/packages/protocol/contracts/governance/interfaces/IGovernance.sol
index ee0a5317b06..2209c4ebb6e 100644
--- a/packages/protocol/contracts/governance/interfaces/IGovernance.sol
+++ b/packages/protocol/contracts/governance/interfaces/IGovernance.sol
@@ -44,9 +44,8 @@ interface IGovernance {
function getUpvotes(uint256) external view returns (uint256);
function getQueue() external view returns (uint256[] memory, uint256[] memory);
function getDequeue() external view returns (uint256[] memory);
- function getUpvotedProposal(address) external view returns (uint256);
+ function getUpvoteRecord(address) external view returns (uint256, uint256);
function getMostRecentReferendumProposal(address) external view returns (uint256);
- function isVoting(address) external view returns (bool);
function isQueued(uint256) external view returns (bool);
function isProposalPassing(uint256) external view returns (bool);
}
diff --git a/packages/protocol/contracts/governance/interfaces/ILockedGold.sol b/packages/protocol/contracts/governance/interfaces/ILockedGold.sol
index de7d7888736..1f961197356 100644
--- a/packages/protocol/contracts/governance/interfaces/ILockedGold.sol
+++ b/packages/protocol/contracts/governance/interfaces/ILockedGold.sol
@@ -2,8 +2,12 @@ pragma solidity ^0.5.3;
interface ILockedGold {
- function initialize(address, uint256) external;
function getAccountFromVoter(address) external view returns (address);
+ function getAccountFromValidator(address) external view returns (address);
+ function getValidatorFromAccount(address) external view returns (address);
function incrementNonvotingAccountBalance(address, uint256) external;
function decrementNonvotingAccountBalance(address, uint256) external;
+ function getAccountTotalLockedGold(address) external view returns (uint256);
+ function getTotalLockedGold() external view returns (uint256);
+ function setAccountMustMaintain(address, uint256, uint256) external returns (bool);
}
diff --git a/packages/protocol/contracts/governance/interfaces/IValidators.sol b/packages/protocol/contracts/governance/interfaces/IValidators.sol
index 757c0f29c64..5bc937ee60f 100644
--- a/packages/protocol/contracts/governance/interfaces/IValidators.sol
+++ b/packages/protocol/contracts/governance/interfaces/IValidators.sol
@@ -2,7 +2,8 @@ pragma solidity ^0.5.3;
interface IValidators {
- function electValidators() external view returns (address[] memory);
- function getNumGroupMembers(address) external view returns (uint256);
+ function getGroupNumMembers(address) external view returns (uint256);
+ function getGroupsNumMembers(address[] calldata) external view returns (uint256[] memory);
function getNumRegisteredValidators() external view returns (uint256);
+ function getTopValidatorsFromGroup(address, uint256) external view returns (address[] memory);
}
From c19b8b60d35e759484ffb1f399f996cfcbe46325 Mon Sep 17 00:00:00 2001
From: Asa Oines
Date: Fri, 20 Sep 2019 20:04:58 -0700
Subject: [PATCH 006/149] Updated validators test
---
.../contracts/common/UsingRegistry.sol | 5 +
.../contracts/governance/LockedGold.sol | 32 +-
.../contracts/governance/Validators.sol | 9 +-
.../governance/test/MockElection.sol | 15 +
.../governance/test/MockLockedGold.sol | 29 +
.../protocol/contracts/stability/Exchange.sol | 9 +-
.../protocol/contracts/stability/Reserve.sol | 4 +-
.../test/governance/bondeddeposits.ts | 1067 -------------
packages/protocol/test/governance/election.ts | 1315 +++++++++++++++++
.../protocol/test/governance/lockedgold.ts | 469 ++++++
.../protocol/test/governance/validators.ts | 922 ++++--------
11 files changed, 2153 insertions(+), 1723 deletions(-)
create mode 100644 packages/protocol/contracts/governance/test/MockElection.sol
delete mode 100644 packages/protocol/test/governance/bondeddeposits.ts
create mode 100644 packages/protocol/test/governance/election.ts
create mode 100644 packages/protocol/test/governance/lockedgold.ts
diff --git a/packages/protocol/contracts/common/UsingRegistry.sol b/packages/protocol/contracts/common/UsingRegistry.sol
index 5a19dcef961..2e4ced1e5d3 100644
--- a/packages/protocol/contracts/common/UsingRegistry.sol
+++ b/packages/protocol/contracts/common/UsingRegistry.sol
@@ -2,6 +2,7 @@ pragma solidity ^0.5.3;
import "openzeppelin-solidity/contracts/ownership/Ownable.sol";
+import "./interfaces/IERC20Token.sol";
import "./interfaces/IRegistry.sol";
import "../governance/interfaces/IElection.sol";
@@ -51,6 +52,10 @@ contract UsingRegistry is Ownable {
return IElection(registry.getAddressForOrDie(ELECTION_REGISTRY_ID));
}
+ function getGoldToken() internal view returns(IERC20Token) {
+ return IERC20Token(registry.getAddressForOrDie(GOLD_TOKEN_REGISTRY_ID));
+ }
+
function getLockedGold() internal view returns(ILockedGold) {
return ILockedGold(registry.getAddressForOrDie(LOCKED_GOLD_REGISTRY_ID));
}
diff --git a/packages/protocol/contracts/governance/LockedGold.sol b/packages/protocol/contracts/governance/LockedGold.sol
index 3f7fab5d9a2..d860949a348 100644
--- a/packages/protocol/contracts/governance/LockedGold.sol
+++ b/packages/protocol/contracts/governance/LockedGold.sol
@@ -7,7 +7,6 @@ import "openzeppelin-solidity/contracts/ownership/Ownable.sol";
import "./interfaces/ILockedGold.sol";
import "../common/Initializable.sol";
-import "../common/interfaces/IERC20Token.sol";
import "../common/Signatures.sol";
import "../common/UsingRegistry.sol";
@@ -51,7 +50,7 @@ contract LockedGold is ILockedGold, ReentrancyGuard, Initializable, UsingRegistr
mapping(address => Account) private accounts;
// Maps voting and validating keys to the account that provided the authorization.
- mapping(address => address) public authorizations;
+ mapping(address => address) public authorizedBy;
uint256 public totalNonvoting;
uint256 public unlockingPeriod;
@@ -113,11 +112,11 @@ contract LockedGold is ILockedGold, ReentrancyGuard, Initializable, UsingRegistr
* @notice Locks gold to be used for voting.
* @param value The amount of gold to be locked.
*/
- function lock(uint256 value) external payable nonReentrant {
+ function lock() external payable nonReentrant {
require(isAccount(msg.sender));
- require(msg.value == value && value > 0);
- _incrementNonvotingAccountBalance(msg.sender, value);
- emit GoldLocked(msg.sender, value);
+ require(msg.value > 0);
+ _incrementNonvotingAccountBalance(msg.sender, msg.value);
+ emit GoldLocked(msg.sender, msg.value);
}
function incrementNonvotingAccountBalance(address account, uint256 value) external onlyRegisteredContract('Election') {
@@ -172,8 +171,7 @@ contract LockedGold is ILockedGold, ReentrancyGuard, Initializable, UsingRegistr
require(now >= pendingWithdrawal.timestamp);
uint256 value = pendingWithdrawal.value;
deletePendingWithdrawal(account.balances.pendingWithdrawals, index);
- IERC20Token goldToken = IERC20Token(registry.getAddressFor(GOLD_TOKEN_REGISTRY_ID));
- require(goldToken.transfer(msg.sender, value));
+ require(getGoldToken().transfer(msg.sender, value));
emit GoldWithdrawn(msg.sender, value);
}
@@ -199,7 +197,7 @@ contract LockedGold is ILockedGold, ReentrancyGuard, Initializable, UsingRegistr
* @return The associated account.
*/
function getAccountFromVoter(address accountOrVoter) external view returns (address) {
- address authorizingAccount = authorizations[accountOrVoter];
+ address authorizingAccount = authorizedBy[accountOrVoter];
if (authorizingAccount != address(0)) {
require(accounts[authorizingAccount].authorizations.voting == accountOrVoter);
return authorizingAccount;
@@ -213,11 +211,19 @@ contract LockedGold is ILockedGold, ReentrancyGuard, Initializable, UsingRegistr
return totalNonvoting.add(getElection().getTotalVotes());
}
+ function getNonvotingLockedGold() external view returns (uint256) {
+ return totalNonvoting;
+ }
+
function getAccountTotalLockedGold(address account) public view returns (uint256) {
uint256 total = accounts[account].balances.nonvoting;
return total.add(getElection().getAccountTotalVotes(account));
}
+ function getAccountNonvotingLockedGold(address account) external view returns (uint256) {
+ return accounts[account].balances.nonvoting;
+ }
+
/**
* @notice Returns the account associated with `accountOrValidator`.
* @param accountOrValidator The address of the account or authorized validator.
@@ -225,7 +231,7 @@ contract LockedGold is ILockedGold, ReentrancyGuard, Initializable, UsingRegistr
* @return The associated account.
*/
function getAccountFromValidator(address accountOrValidator) public view returns (address) {
- address authorizingAccount = authorizations[accountOrValidator];
+ address authorizingAccount = authorizedBy[accountOrValidator];
if (authorizingAccount != address(0)) {
require(accounts[authorizingAccount].authorizations.validating == accountOrValidator);
return authorizingAccount;
@@ -282,8 +288,8 @@ contract LockedGold is ILockedGold, ReentrancyGuard, Initializable, UsingRegistr
address signer = Signatures.getSignerOfAddress(msg.sender, v, r, s);
require(signer == current);
- authorizations[previous] = address(0);
- authorizations[current] = msg.sender;
+ authorizedBy[previous] = address(0);
+ authorizedBy[current] = msg.sender;
}
function isAccount(address account) internal view returns (bool) {
@@ -295,7 +301,7 @@ contract LockedGold is ILockedGold, ReentrancyGuard, Initializable, UsingRegistr
}
function isNotAuthorized(address account) internal view returns (bool) {
- return (authorizations[account] == address(0));
+ return (authorizedBy[account] == address(0));
}
function deletePendingWithdrawal(PendingWithdrawal[] storage list, uint256 index) private {
diff --git a/packages/protocol/contracts/governance/Validators.sol b/packages/protocol/contracts/governance/Validators.sol
index 4f3555cea6f..ea344f95c82 100644
--- a/packages/protocol/contracts/governance/Validators.sol
+++ b/packages/protocol/contracts/governance/Validators.sol
@@ -121,7 +121,7 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
* @notice Initializes critical variables.
* @param registryAddress The address of the registry contract.
* @param groupRequirement The minimum locked gold needed to register a group.
- * @param validatorRequirement The minimum locked gold needed to register a validator.
+ * @param validatorRequirement The minimum locked gold needed to register a validator.
* @param groupLockup The duration the above gold remains locked after deregistration.
* @param validatorLockup The duration the above gold remains locked after deregistration.
* @param _maxGroupSize The maximum group size.
@@ -160,7 +160,7 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
/**
* @notice Updates the minimum gold requirements to register a validator group or validator.
* @param groupRequirement The minimum locked gold needed to register a group.
- * @param validatorRequirement The minimum locked gold needed to register a validator.
+ * @param validatorRequirement The minimum locked gold needed to register a validator.
* @return True upon success.
* @dev The new requirement is only enforced for future validator or group registrations.
*/
@@ -342,7 +342,6 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
string calldata name,
string calldata url,
uint256 commission,
- address[] calldata members
)
external
nonReentrant
@@ -352,7 +351,6 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
require(bytes(url).length > 0);
// TODO(asa)
// require(isFraction(commission));
- require(members.length <= maxGroupSize);
address account = getLockedGold().getAccountFromValidator(msg.sender);
require(!isValidator(account) && !isValidatorGroup(account));
require(meetsValidatorGroupRegistrationRequirement(account));
@@ -361,9 +359,6 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
group.name = name;
group.url = url;
group.commission = FixidityLib.wrap(commission);
- for (uint256 i = 0; i < members.length; i = i.add(1)) {
- _addMember(account, members[i]);
- }
_groups.push(account);
getLockedGold().setAccountMustMaintain(account, registrationRequirements.group, MAX_INT);
emit ValidatorGroupRegistered(account, name, url);
diff --git a/packages/protocol/contracts/governance/test/MockElection.sol b/packages/protocol/contracts/governance/test/MockElection.sol
new file mode 100644
index 00000000000..e0e85344631
--- /dev/null
+++ b/packages/protocol/contracts/governance/test/MockElection.sol
@@ -0,0 +1,15 @@
+pragma solidity ^0.5.3;
+
+import "../interfaces/IElection.sol";
+
+/**
+ * @title Holds a list of addresses of validators
+ */
+contract MockElection is IElection {
+
+ mapping(address => bool) public isIneligible;
+
+ function markGroupIneligible(address account) external {
+ isIneligible[account] = true;
+ }
+}
diff --git a/packages/protocol/contracts/governance/test/MockLockedGold.sol b/packages/protocol/contracts/governance/test/MockLockedGold.sol
index f223c548a43..5d757728b8d 100644
--- a/packages/protocol/contracts/governance/test/MockLockedGold.sol
+++ b/packages/protocol/contracts/governance/test/MockLockedGold.sol
@@ -7,4 +7,33 @@ import "../interfaces/ILockedGold.sol";
* @title A mock LockedGold for testing.
*/
contract MockLockedGold is ILockedGold {
+ struct MustMaintain {
+ uint256 value;
+ uint256 timestamp;
+ }
+
+ mapping(address => uint256) public totalLockedGold;
+ mapping(address => MustMaintain) public mustMaintain;
+
+
+ function getAccountFromValidator(address accountOrValidator) external view returns (address) {
+ return accountOrValidator;
+ }
+
+ function setAccountMustMaintain(address account, uint256 value, uint256 timestamp) external {
+ mustMaintain[account] = MustMaintain(value, timestamp);
+ }
+
+ function getAccountMustMaintain(address account) external view returns (uint256, uint256) {
+ MustMaintain storage mustMaintain = mustMaintain[account];
+ return (mustMaintain.value, mustMaintain.timestamp);
+ }
+
+ function setAccountTotalLockedGold(address account, uint256 value) external {
+ totalLockedGold[account] = value;
+ }
+
+ function getAccountTotalLockedGold(address account) external view returns (uint256) {
+ return totalLockedGold[account];
+ }
}
diff --git a/packages/protocol/contracts/stability/Exchange.sol b/packages/protocol/contracts/stability/Exchange.sol
index 12ce4d5020d..03466f99474 100644
--- a/packages/protocol/contracts/stability/Exchange.sol
+++ b/packages/protocol/contracts/stability/Exchange.sol
@@ -11,7 +11,6 @@ import "../common/FractionUtil.sol";
import "../common/Initializable.sol";
import "../common/FixidityLib.sol";
import "../common/UsingRegistry.sol";
-import "../common/interfaces/IERC20Token.sol";
/**
@@ -138,7 +137,7 @@ contract Exchange is IExchange, Initializable, Ownable, UsingRegistry, Reentranc
goldBucket = goldBucket.add(sellAmount);
stableBucket = stableBucket.sub(buyAmount);
require(
- gold().transferFrom(msg.sender, address(reserve), sellAmount),
+ getGoldToken().transferFrom(msg.sender, address(reserve), sellAmount),
"Transfer of sell token failed"
);
require(IStableToken(stable).mint(msg.sender, buyAmount), "Mint of stable token failed");
@@ -332,7 +331,7 @@ contract Exchange is IExchange, Initializable, Ownable, UsingRegistry, Reentranc
}
function getUpdatedGoldBucket() private view returns (uint256) {
- uint256 reserveGoldBalance = gold().balanceOf(registry.getAddressForOrDie(RESERVE_REGISTRY_ID));
+ uint256 reserveGoldBalance = getGoldToken().balanceOf(registry.getAddressForOrDie(RESERVE_REGISTRY_ID));
return reserveFraction.multiply(FixidityLib.newFixed(reserveGoldBalance)).fromFixed();
}
@@ -388,8 +387,4 @@ contract Exchange is IExchange, Initializable, Ownable, UsingRegistry, Reentranc
ISortedOracles(registry.getAddressForOrDie(SORTED_ORACLES_REGISTRY_ID)).medianRate(stable);
return FractionUtil.Fraction(rateNumerator, rateDenominator);
}
-
- function gold() private view returns (IERC20Token) {
- return IERC20Token(registry.getAddressForOrDie(GOLD_TOKEN_REGISTRY_ID));
- }
}
diff --git a/packages/protocol/contracts/stability/Reserve.sol b/packages/protocol/contracts/stability/Reserve.sol
index 7321d53d8d6..c1abf178b0d 100644
--- a/packages/protocol/contracts/stability/Reserve.sol
+++ b/packages/protocol/contracts/stability/Reserve.sol
@@ -10,7 +10,6 @@ import "./interfaces/IStableToken.sol";
import "../common/Initializable.sol";
import "../common/UsingRegistry.sol";
-import "../common/interfaces/IERC20Token.sol";
/**
@@ -154,8 +153,7 @@ contract Reserve is IReserve, Ownable, Initializable, UsingRegistry, ReentrancyG
returns (bool)
{
require(isSpender[msg.sender], "sender not allowed to transfer Reserve funds");
- IERC20Token goldToken = IERC20Token(registry.getAddressForOrDie(GOLD_TOKEN_REGISTRY_ID));
- require(goldToken.transfer(to, value), "transfer of gold token failed");
+ require(getGoldToken().transfer(to, value), "transfer of gold token failed");
return true;
}
diff --git a/packages/protocol/test/governance/bondeddeposits.ts b/packages/protocol/test/governance/bondeddeposits.ts
deleted file mode 100644
index 32e9fabf007..00000000000
--- a/packages/protocol/test/governance/bondeddeposits.ts
+++ /dev/null
@@ -1,1067 +0,0 @@
-import { CeloContractName } from '@celo/protocol/lib/registry-utils'
-import {
- assertEqualBN,
- assertLogMatches,
- assertRevert,
- NULL_ADDRESS,
- timeTravel,
-} from '@celo/protocol/lib/test-utils'
-import BigNumber from 'bignumber.js'
-import {
- LockedGoldContract,
- LockedGoldInstance,
- MockGoldTokenContract,
- MockGoldTokenInstance,
- MockGovernanceContract,
- MockGovernanceInstance,
- MockValidatorsContract,
- MockValidatorsInstance,
- RegistryContract,
- RegistryInstance,
-} from 'types'
-
-const LockedGold: LockedGoldContract = artifacts.require('LockedGold')
-const Registry: RegistryContract = artifacts.require('Registry')
-const MockGoldToken: MockGoldTokenContract = artifacts.require('MockGoldToken')
-const MockGovernance: MockGovernanceContract = artifacts.require('MockGovernance')
-const MockValidators: MockValidatorsContract = artifacts.require('MockValidators')
-
-// @ts-ignore
-// TODO(mcortesi): Use BN
-LockedGold.numberFormat = 'BigNumber'
-
-const HOUR = 60 * 60
-const DAY = 24 * HOUR
-const YEAR = 365 * DAY
-
-// TODO(asa): Test reward redemption
-contract('LockedGold', (accounts: string[]) => {
- let account = accounts[0]
- const nonOwner = accounts[1]
- const maxNoticePeriod = 2 * YEAR
- let mockGoldToken: MockGoldTokenInstance
- let mockGovernance: MockGovernanceInstance
- let mockValidators: MockValidatorsInstance
- let lockedGold: LockedGoldInstance
- let registry: RegistryInstance
-
- const getParsedSignatureOfAddress = async (address: string, signer: string) => {
- // @ts-ignore
- const hash = web3.utils.soliditySha3({ type: 'address', value: address })
- const signature = (await web3.eth.sign(hash, signer)).slice(2)
- return {
- r: `0x${signature.slice(0, 64)}`,
- s: `0x${signature.slice(64, 128)}`,
- v: web3.utils.hexToNumber(signature.slice(128, 130)) + 27,
- }
- }
-
- enum roles {
- validating,
- voting,
- rewards,
- }
- const forEachRole = (tests: (arg0: roles) => void) =>
- Object.keys(roles)
- .slice(3)
- .map((role) => describe(`when dealing with ${role} role`, () => tests(roles[role])))
-
- beforeEach(async () => {
- lockedGold = await LockedGold.new()
- mockGoldToken = await MockGoldToken.new()
- mockGovernance = await MockGovernance.new()
- mockValidators = await MockValidators.new()
- registry = await Registry.new()
- await registry.setAddressFor(CeloContractName.GoldToken, mockGoldToken.address)
- await registry.setAddressFor(CeloContractName.Governance, mockGovernance.address)
- await registry.setAddressFor(CeloContractName.Validators, mockValidators.address)
- await lockedGold.initialize(registry.address, maxNoticePeriod)
- await lockedGold.createAccount()
- })
-
- describe('#initialize()', () => {
- it('should set the owner', async () => {
- const owner: string = await lockedGold.owner()
- assert.equal(owner, account)
- })
-
- it('should set the maxNoticePeriod', async () => {
- const actual = await lockedGold.maxNoticePeriod()
- assert.equal(actual.toNumber(), maxNoticePeriod)
- })
-
- it('should set the registry address', async () => {
- const registryAddress: string = await lockedGold.registry()
- assert.equal(registryAddress, registry.address)
- })
-
- it('should revert if already initialized', async () => {
- await assertRevert(lockedGold.initialize(registry.address, maxNoticePeriod))
- })
- })
-
- describe('#setRegistry()', () => {
- const anAddress: string = accounts[2]
-
- it('should set the registry when called by the owner', async () => {
- await lockedGold.setRegistry(anAddress)
- assert.equal(await lockedGold.registry(), anAddress)
- })
-
- it('should revert when not called by the owner', async () => {
- await assertRevert(lockedGold.setRegistry(anAddress, { from: nonOwner }))
- })
- })
-
- describe('#setMaxNoticePeriod()', () => {
- it('should set maxNoticePeriod when called by the owner', async () => {
- await lockedGold.setMaxNoticePeriod(1)
- assert.equal((await lockedGold.maxNoticePeriod()).toNumber(), 1)
- })
-
- it('should emit a MaxNoticePeriodSet event', async () => {
- const resp = await lockedGold.setMaxNoticePeriod(1)
- assert.equal(resp.logs.length, 1)
- const log = resp.logs[0]
- assertLogMatches(log, 'MaxNoticePeriodSet', {
- maxNoticePeriod: new BigNumber(1),
- })
- })
-
- it('should revert when not called by the owner', async () => {
- await assertRevert(lockedGold.setMaxNoticePeriod(1, { from: nonOwner }))
- })
- })
-
- describe('#delegateRole()', () => {
- const delegate = accounts[1]
- let sig
-
- beforeEach(async () => {
- sig = await getParsedSignatureOfAddress(account, delegate)
- })
-
- forEachRole((role) => {
- it('should set the role delegate', async () => {
- await lockedGold.delegateRole(role, delegate, sig.v, sig.r, sig.s)
- assert.equal(await lockedGold.delegations(delegate), account)
- assert.equal(await lockedGold.getDelegateFromAccountAndRole(account, role), delegate)
- assert.equal(await lockedGold.getAccountFromDelegateAndRole(delegate, role), account)
- })
-
- it('should emit a RoleDelegated event', async () => {
- const resp = await lockedGold.delegateRole(role, delegate, sig.v, sig.r, sig.s)
- assert.equal(resp.logs.length, 1)
- const log = resp.logs[0]
- assertLogMatches(log, 'RoleDelegated', {
- role,
- account,
- delegate,
- })
- })
-
- it('should revert if the delegate is an account', async () => {
- await lockedGold.createAccount({ from: delegate })
- await assertRevert(lockedGold.delegateRole(role, delegate, sig.v, sig.r, sig.s))
- })
-
- it('should revert if the address is already being delegated to', async () => {
- const otherAccount = accounts[2]
- const otherSig = await getParsedSignatureOfAddress(otherAccount, delegate)
- await lockedGold.createAccount({ from: otherAccount })
- await lockedGold.delegateRole(role, delegate, otherSig.v, otherSig.r, otherSig.s, {
- from: otherAccount,
- })
- await assertRevert(lockedGold.delegateRole(role, delegate, sig.v, sig.r, sig.s))
- })
-
- it('should revert if the signature is incorrect', async () => {
- const nonDelegate = accounts[3]
- const incorrectSig = await getParsedSignatureOfAddress(account, nonDelegate)
- await assertRevert(
- lockedGold.delegateRole(role, delegate, incorrectSig.v, incorrectSig.r, incorrectSig.s)
- )
- })
-
- describe('when a previous delegation has been made', async () => {
- const newDelegate = accounts[2]
- let newSig
- beforeEach(async () => {
- await lockedGold.delegateRole(role, delegate, sig.v, sig.r, sig.s)
- newSig = await getParsedSignatureOfAddress(account, newDelegate)
- })
-
- it('should set the new delegate', async () => {
- await lockedGold.delegateRole(role, newDelegate, newSig.v, newSig.r, newSig.s)
- assert.equal(await lockedGold.delegations(newDelegate), account)
- assert.equal(await lockedGold.getDelegateFromAccountAndRole(account, role), newDelegate)
- assert.equal(await lockedGold.getAccountFromDelegateAndRole(newDelegate, role), account)
- })
-
- it('should reset the previous delegate', async () => {
- await lockedGold.delegateRole(role, newDelegate, newSig.v, newSig.r, newSig.s)
- assert.equal(await lockedGold.delegations(delegate), NULL_ADDRESS)
- })
- })
- })
- })
-
- describe('#freezeVoting()', () => {
- it('should set the account voting to frozen', async () => {
- await lockedGold.freezeVoting()
- assert.isTrue(await lockedGold.isVotingFrozen(account))
- })
-
- it('should emit a VotingFrozen event', async () => {
- const resp = await lockedGold.freezeVoting()
- assert.equal(resp.logs.length, 1)
- const log = resp.logs[0]
- assertLogMatches(log, 'VotingFrozen', {
- account,
- })
- })
-
- it('should revert if the account voting is already frozen', async () => {
- await lockedGold.freezeVoting()
- await assertRevert(lockedGold.freezeVoting())
- })
- })
-
- describe('#unfreezeVoting()', () => {
- beforeEach(async () => {
- await lockedGold.freezeVoting()
- })
-
- it('should set the account voting to unfrozen', async () => {
- await lockedGold.unfreezeVoting()
- assert.isFalse(await lockedGold.isVotingFrozen(account))
- })
-
- it('should emit a VotingUnfrozen event', async () => {
- const resp = await lockedGold.unfreezeVoting()
- assert.equal(resp.logs.length, 1)
- const log = resp.logs[0]
- assertLogMatches(log, 'VotingUnfrozen', {
- account,
- })
- })
-
- it('should revert if the account voting is already unfrozen', async () => {
- await lockedGold.unfreezeVoting()
- await assertRevert(lockedGold.unfreezeVoting())
- })
- })
-
- describe('#newCommitment()', () => {
- const noticePeriod = 1 * DAY + 1 * HOUR
- const value = 1000
- const expectedWeight = 1033
-
- it('should add a Locked Gold commitment', async () => {
- // @ts-ignore: TODO(mcortesi) fix typings for TransactionDetails
- await lockedGold.newCommitment(noticePeriod, { value })
- const noticePeriods = await lockedGold.getNoticePeriods(account)
- assert.equal(noticePeriods.length, 1)
- assert.equal(noticePeriods[0].toNumber(), noticePeriod)
- const [lockedValue, index] = await lockedGold.getLockedCommitment(account, noticePeriod)
- assert.equal(lockedValue.toNumber(), value)
- assert.equal(index.toNumber(), 0)
- })
-
- it('should update the account weight', async () => {
- // @ts-ignore: TODO(mcortesi) fix typings for TransactionDetails
- await lockedGold.newCommitment(noticePeriod, { value })
- const weight = await lockedGold.getAccountWeight(account)
- assert.equal(weight.toNumber(), expectedWeight)
- })
-
- it('should update the total weight', async () => {
- // @ts-ignore: TODO(mcortesi) fix typings for TransactionDetails
- await lockedGold.newCommitment(noticePeriod, { value })
- const totalWeight = await lockedGold.totalWeight()
- assert.equal(totalWeight.toNumber(), expectedWeight)
- })
-
- it('should emit a NewCommitment event', async () => {
- // @ts-ignore: TODO(mcortesi) fix typings for TransactionDetails
- const resp = await lockedGold.newCommitment(noticePeriod, { value })
- assert.equal(resp.logs.length, 1)
- const log = resp.logs[0]
- assertLogMatches(log, 'NewCommitment', {
- account,
- value: new BigNumber(value),
- noticePeriod: new BigNumber(noticePeriod),
- })
- })
-
- it('should revert when the specified notice period is too large', async () => {
- // @ts-ignore: TODO(mcortesi) fix typings for TransactionDetails
- await assertRevert(lockedGold.newCommitment(maxNoticePeriod + 1, { value }))
- })
-
- it('should revert when the specified value is 0', async () => {
- await assertRevert(lockedGold.newCommitment(noticePeriod))
- })
-
- it('should revert when the account does not exist', async () => {
- await assertRevert(lockedGold.newCommitment(noticePeriod, { value, from: accounts[1] }))
- })
-
- it('should revert if the caller is voting', async () => {
- await mockGovernance.setVoting(account)
- // @ts-ignore: TODO(mcortesi) fix typings for TransactionDetails
- await assertRevert(lockedGold.newCommitment(noticePeriod, { value }))
- })
- })
-
- describe('#notifyCommitment()', () => {
- const noticePeriod = 60 * 60 * 24 // 1 day
- const value = 1000
- beforeEach(async () => {
- // @ts-ignore: TODO(mcortesi) fix typings for TransactionDetails
- await lockedGold.newCommitment(noticePeriod, { value })
- })
-
- it('should add a notified deposit', async () => {
- await lockedGold.notifyCommitment(value, noticePeriod)
- const availabilityTime = new BigNumber(noticePeriod).plus(
- (await web3.eth.getBlock('latest')).timestamp
- )
- const availabilityTimes = await lockedGold.getAvailabilityTimes(account)
- assert.equal(availabilityTimes.length, 1)
- assert.equal(availabilityTimes[0].toNumber(), availabilityTime.toNumber())
-
- const [notifiedValue, index] = await lockedGold.getNotifiedCommitment(
- account,
- availabilityTime
- )
- assert.equal(notifiedValue.toNumber(), value)
- assert.equal(index.toNumber(), 0)
- })
-
- it('should remove the Locked Gold commitment', async () => {
- await lockedGold.notifyCommitment(value, noticePeriod)
- const noticePeriods = await lockedGold.getNoticePeriods(account)
- assert.equal(noticePeriods.length, 0)
- const [lockedValue, index] = await lockedGold.getLockedCommitment(account, noticePeriod)
- assert.equal(lockedValue.toNumber(), 0)
- assert.equal(index.toNumber(), 0)
- })
-
- it('should update the account weight', async () => {
- await lockedGold.notifyCommitment(value, noticePeriod)
- const weight = await lockedGold.getAccountWeight(account)
- assert.equal(weight.toNumber(), value)
- })
-
- it('should update the total weight', async () => {
- await lockedGold.notifyCommitment(value, noticePeriod)
- const totalWeight = await lockedGold.totalWeight()
- assert.equal(totalWeight.toNumber(), value)
- })
-
- it('should emit a CommitmentNotified event', async () => {
- const resp = await lockedGold.notifyCommitment(value, noticePeriod)
- assert.equal(resp.logs.length, 1)
- const log = resp.logs[0]
- assertLogMatches(log, 'CommitmentNotified', {
- account,
- value: new BigNumber(value),
- noticePeriod: new BigNumber(noticePeriod),
- availabilityTime: new BigNumber(noticePeriod).plus(
- (await web3.eth.getBlock('latest')).timestamp
- ),
- })
- })
-
- it('should revert when the value of the Locked Gold commitment is 0', async () => {
- await assertRevert(lockedGold.notifyCommitment(1, noticePeriod + 1))
- })
-
- it('should revert when value is greater than the value of the Locked Gold commitment', async () => {
- await assertRevert(lockedGold.notifyCommitment(value + 1, noticePeriod))
- })
-
- it('should revert when the value is 0', async () => {
- await assertRevert(lockedGold.notifyCommitment(0, noticePeriod))
- })
-
- it('should revert if the account is validating', async () => {
- await mockValidators.setValidating(account)
- await assertRevert(lockedGold.notifyCommitment(value, noticePeriod))
- })
-
- it('should revert if the caller is voting', async () => {
- await mockGovernance.setVoting(account)
- await assertRevert(lockedGold.notifyCommitment(value, noticePeriod))
- })
- })
-
- describe('#extendCommitment()', () => {
- const value = 1000
- const expectedWeight = 1033
- let availabilityTime: BigNumber
-
- beforeEach(async () => {
- // Set an initial notice period of just over one day, so that when we rebond, we're
- // guaranteed that the new notice period is at least one day.
- const noticePeriod = 1 * DAY + 1 * HOUR
- // @ts-ignore: TODO(mcortesi) fix typings for TransactionDetails
- await lockedGold.newCommitment(noticePeriod, { value })
- await lockedGold.notifyCommitment(value, noticePeriod)
- availabilityTime = new BigNumber(noticePeriod).plus(
- (await web3.eth.getBlock('latest')).timestamp
- )
- })
-
- it('should add a Locked Gold commitment', async () => {
- await lockedGold.extendCommitment(value, availabilityTime)
- const noticePeriods = await lockedGold.getNoticePeriods(account)
- assert.equal(noticePeriods.length, 1)
- const noticePeriod = availabilityTime
- .minus((await web3.eth.getBlock('latest')).timestamp)
- .toNumber()
- assert.equal(noticePeriods[0].toNumber(), noticePeriod)
- const [lockedValue, index] = await lockedGold.getLockedCommitment(account, noticePeriod)
- assert.equal(lockedValue.toNumber(), value)
- assert.equal(index.toNumber(), 0)
- })
-
- it('should remove a notified deposit', async () => {
- await lockedGold.extendCommitment(value, availabilityTime)
- const availabilityTimes = await lockedGold.getAvailabilityTimes(account)
- assert.equal(availabilityTimes.length, 0)
- const [notifiedValue, index] = await lockedGold.getNotifiedCommitment(
- account,
- availabilityTime
- )
- assert.equal(notifiedValue.toNumber(), 0)
- assert.equal(index.toNumber(), 0)
- })
-
- it('should update the account weight', async () => {
- await lockedGold.extendCommitment(value, availabilityTime)
- const weight = await lockedGold.getAccountWeight(account)
- assert.equal(weight.toNumber(), expectedWeight)
- })
-
- it('should update the total weight', async () => {
- await lockedGold.extendCommitment(value, availabilityTime)
- const totalWeight = await lockedGold.totalWeight()
- assert.equal(totalWeight.toNumber(), expectedWeight)
- })
-
- it('should emit a CommitmentExtended event', async () => {
- const resp = await lockedGold.extendCommitment(value, availabilityTime)
- const noticePeriod = availabilityTime.minus((await web3.eth.getBlock('latest')).timestamp)
- assert.equal(resp.logs.length, 1)
- const log = resp.logs[0]
- assertLogMatches(log, 'CommitmentExtended', {
- account,
- value: new BigNumber(value),
- noticePeriod,
- availabilityTime,
- })
- })
-
- it('should revert when the notified deposit is withdrawable', async () => {
- await timeTravel(
- availabilityTime
- .minus((await web3.eth.getBlock('latest')).timestamp)
- .plus(1)
- .toNumber(),
- web3
- )
- await assertRevert(lockedGold.extendCommitment(value, availabilityTime))
- })
-
- it('should revert when the value of the notified deposit is 0', async () => {
- await assertRevert(lockedGold.extendCommitment(value, availabilityTime.plus(1)))
- })
-
- it('should revert when the value is 0', async () => {
- await assertRevert(lockedGold.extendCommitment(0, availabilityTime))
- })
-
- it('should revert if the caller is voting', async () => {
- await mockGovernance.setVoting(account)
- await assertRevert(lockedGold.extendCommitment(value, availabilityTime))
- })
- })
-
- describe('#withdrawCommitment()', () => {
- const noticePeriod = 1 * DAY
- const value = 1000
- let availabilityTime: BigNumber
-
- beforeEach(async () => {
- // @ts-ignore: TODO(mcortesi) fix typings for TransactionDetails
- await lockedGold.newCommitment(noticePeriod, { value })
- await lockedGold.notifyCommitment(value, noticePeriod)
- availabilityTime = new BigNumber(noticePeriod).plus(
- (await web3.eth.getBlock('latest')).timestamp
- )
- })
-
- it('should remove the notified deposit', async () => {
- await timeTravel(noticePeriod, web3)
- await lockedGold.withdrawCommitment(availabilityTime)
-
- const availabilityTimes = await lockedGold.getAvailabilityTimes(account)
- assert.equal(availabilityTimes.length, 0)
- })
-
- it('should update the account weight', async () => {
- await timeTravel(noticePeriod, web3)
- await lockedGold.withdrawCommitment(availabilityTime)
-
- const weight = await lockedGold.getAccountWeight(account)
- assert.equal(weight.toNumber(), 0)
- })
-
- it('should update the total weight', async () => {
- await timeTravel(noticePeriod, web3)
- await lockedGold.withdrawCommitment(availabilityTime)
-
- const totalWeight = await lockedGold.totalWeight()
- assert.equal(totalWeight.toNumber(), 0)
- })
-
- it('should emit a Withdrawal event', async () => {
- await timeTravel(noticePeriod, web3)
- const resp = await lockedGold.withdrawCommitment(availabilityTime)
- assert.equal(resp.logs.length, 1)
- const log = resp.logs[0]
- assertLogMatches(log, 'Withdrawal', {
- account,
- value: new BigNumber(value),
- })
- })
-
- it('should revert if the account is validating', async () => {
- await mockValidators.setValidating(account)
- await assertRevert(lockedGold.withdrawCommitment(availabilityTime))
- })
-
- it('should revert when the notice period has not passed', async () => {
- await assertRevert(lockedGold.withdrawCommitment(availabilityTime))
- })
-
- it('should revert when the value of the notified deposit is 0', async () => {
- await timeTravel(noticePeriod, web3)
- await assertRevert(lockedGold.withdrawCommitment(availabilityTime.plus(1)))
- })
-
- it('should revert if the caller is voting', async () => {
- await timeTravel(noticePeriod, web3)
- await mockGovernance.setVoting(account)
- await assertRevert(lockedGold.withdrawCommitment(availabilityTime))
- })
- })
-
- describe('#increaseNoticePeriod()', () => {
- const noticePeriod = 1 * DAY
- const value = 1000
- const increase = noticePeriod
- const expectedWeight = 1047
-
- beforeEach(async () => {
- // @ts-ignore: TODO(mcortesi) fix typings for TransactionDetails
- await lockedGold.newCommitment(noticePeriod, { value })
- })
-
- it('should update the Locked Gold commitment', async () => {
- await lockedGold.increaseNoticePeriod(value, noticePeriod, increase)
- const noticePeriods = await lockedGold.getNoticePeriods(account)
- assert.equal(noticePeriods.length, 1)
- assert.equal(noticePeriods[0].toNumber(), noticePeriod + increase)
- const [lockedValue, index] = await lockedGold.getLockedCommitment(
- account,
- noticePeriod + increase
- )
- assert.equal(lockedValue.toNumber(), value)
- assert.equal(index.toNumber(), 0)
- })
-
- it('should update the account weight', async () => {
- await lockedGold.increaseNoticePeriod(value, noticePeriod, increase)
- const weight = await lockedGold.getAccountWeight(account)
- assert.equal(weight.toNumber(), expectedWeight)
- })
-
- it('should update the total weight', async () => {
- await lockedGold.increaseNoticePeriod(value, noticePeriod, increase)
- const totalWeight = await lockedGold.totalWeight()
- assert.equal(totalWeight.toNumber(), expectedWeight)
- })
-
- it('should emit a NoticePeriodIncreased event', async () => {
- const resp = await lockedGold.increaseNoticePeriod(value, noticePeriod, increase)
- assert.equal(resp.logs.length, 1)
- const log = resp.logs[0]
- assertLogMatches(log, 'NoticePeriodIncreased', {
- account,
- value: new BigNumber(value),
- noticePeriod: new BigNumber(noticePeriod),
- increase: new BigNumber(increase),
- })
- })
-
- it('should revert if the value is 0', async () => {
- await assertRevert(lockedGold.increaseNoticePeriod(0, noticePeriod, increase))
- })
-
- it('should revert if the increase is 0', async () => {
- await assertRevert(lockedGold.increaseNoticePeriod(value, noticePeriod, 0))
- })
-
- it('should revert if the Locked Gold commitment is smaller than the value', async () => {
- await assertRevert(lockedGold.increaseNoticePeriod(value, noticePeriod + 1, increase))
- })
-
- it('should revert if the caller is voting', async () => {
- await mockGovernance.setVoting(account)
- await assertRevert(lockedGold.increaseNoticePeriod(value, noticePeriod, increase))
- })
- })
-
- describe('#getAccountFromDelegateAndRole()', () => {
- forEachRole((role) => {
- describe('when the account is not delegating', () => {
- it('should return the account when passed the account', async () => {
- assert.equal(await lockedGold.getAccountFromDelegateAndRole(account, role), account)
- })
-
- it('should revert when passed a delegate that is not the role delegate', async () => {
- const delegate = accounts[2]
- const diffRole = (role + 1) % 3
- const sig = await getParsedSignatureOfAddress(account, delegate)
- await lockedGold.delegateRole(role, delegate, sig.v, sig.r, sig.s)
- await assertRevert(lockedGold.getAccountFromDelegateAndRole(delegate, diffRole))
- })
- })
-
- describe('when the account is delegating', () => {
- const delegate = accounts[1]
-
- beforeEach(async () => {
- const sig = await getParsedSignatureOfAddress(account, delegate)
- await lockedGold.delegateRole(role, delegate, sig.v, sig.r, sig.s)
- })
-
- it('should return the account when passed the delegate', async () => {
- assert.equal(await lockedGold.getAccountFromDelegateAndRole(delegate, role), account)
- })
-
- it('should return the account when passed the account', async () => {
- assert.equal(await lockedGold.getAccountFromDelegateAndRole(account, role), account)
- })
-
- it('should revert when passed a delegate that is not the role delegate', async () => {
- const delegate = accounts[2]
- const diffRole = (role + 1) % 3
- const sig = await getParsedSignatureOfAddress(account, delegate)
- await lockedGold.delegateRole(role, delegate, sig.v, sig.r, sig.s)
- await assertRevert(lockedGold.getAccountFromDelegateAndRole(delegate, diffRole))
- })
- })
- })
- })
-
- describe('#getDelegateFromAccountAndRole()', () => {
- forEachRole((role) => {
- describe('when the account is not delegating', () => {
- it('should return the account when passed the account', async () => {
- assert.equal(await lockedGold.getDelegateFromAccountAndRole(account, role), account)
- })
- })
-
- describe('when the account is delegating', () => {
- const delegate = accounts[1]
-
- beforeEach(async () => {
- const sig = await getParsedSignatureOfAddress(account, delegate)
- await lockedGold.delegateRole(role, delegate, sig.v, sig.r, sig.s)
- })
-
- it('should return the account when passed undelegated role', async () => {
- const role2 = (role + 1) % 3
- assert.equal(await lockedGold.getDelegateFromAccountAndRole(account, role2), account)
- })
-
- it('should return the delegate when passed the delegated role', async () => {
- assert.equal(await lockedGold.getDelegateFromAccountAndRole(account, role), delegate)
- })
- })
- })
- })
-
- describe('#isVoting()', () => {
- describe('when the account is not delegating', () => {
- it('should return false if the account is not voting in governance or validator elections', async () => {
- assert.isFalse(await lockedGold.isVoting(account))
- })
-
- it('should return true if the account is voting in governance', async () => {
- await mockGovernance.setVoting(account)
- assert.isTrue(await lockedGold.isVoting(account))
- })
-
- it('should return true if the account is voting in validator elections', async () => {
- await mockValidators.setVoting(account)
- assert.isTrue(await lockedGold.isVoting(account))
- })
-
- it('should return true if the account is voting in governance and validator elections', async () => {
- await mockGovernance.setVoting(account)
- await mockValidators.setVoting(account)
- assert.isTrue(await lockedGold.isVoting(account))
- })
- })
-
- describe('when the account is delegating', () => {
- const delegate = accounts[1]
-
- beforeEach(async () => {
- const sig = await getParsedSignatureOfAddress(account, delegate)
- await lockedGold.delegateRole(roles.voting, delegate, sig.v, sig.r, sig.s)
- })
-
- it('should return false if the delegate is not voting in governance or validator elections', async () => {
- assert.isFalse(await lockedGold.isVoting(account))
- })
-
- it('should return true if the delegate is voting in governance', async () => {
- await mockGovernance.setVoting(delegate)
- assert.isTrue(await lockedGold.isVoting(account))
- })
-
- it('should return true if the delegate is voting in validator elections', async () => {
- await mockValidators.setVoting(delegate)
- assert.isTrue(await lockedGold.isVoting(account))
- })
-
- it('should return true if the delegate is voting in governance and validator elections', async () => {
- await mockGovernance.setVoting(delegate)
- await mockValidators.setVoting(delegate)
- assert.isTrue(await lockedGold.isVoting(account))
- })
- })
- })
-
- describe('#getCommitmentWeight()', () => {
- const value = new BigNumber(521000)
- const oneDay = new BigNumber(DAY)
- it('should return the commitment value when notice period is zero', async () => {
- const noticePeriod = new BigNumber(0)
- assertEqualBN(await lockedGold.getCommitmentWeight(value, noticePeriod), value)
- })
-
- it('should return the commitment value when notice period is less than one day', async () => {
- const noticePeriod = oneDay.minus(1)
- assertEqualBN(await lockedGold.getCommitmentWeight(value, noticePeriod), value)
- })
-
- it('should return the commitment value times 1.0333 when notice period is one day', async () => {
- const noticePeriod = oneDay
- assertEqualBN(
- await lockedGold.getCommitmentWeight(value, noticePeriod),
- value.times(1.0333).integerValue(BigNumber.ROUND_DOWN)
- )
- })
-
- it('should return the commitment value times 1.047 when notice period is two days', async () => {
- const noticePeriod = oneDay.times(2)
- assertEqualBN(
- await lockedGold.getCommitmentWeight(value, noticePeriod),
- value.times(1.047).integerValue(BigNumber.ROUND_DOWN)
- )
- })
-
- it('should return the commitment value times 1.1823 when notice period is 30 days', async () => {
- const noticePeriod = oneDay.times(30)
- assertEqualBN(
- await lockedGold.getCommitmentWeight(value, noticePeriod),
- value.times(1.1823).integerValue(BigNumber.ROUND_DOWN)
- )
- })
-
- it('should return the commitment value times 2.103 when notice period is 3 years', async () => {
- const noticePeriod = oneDay.times(365).times(3)
- assertEqualBN(
- await lockedGold.getCommitmentWeight(value, noticePeriod),
- value.times(2.103).integerValue(BigNumber.ROUND_DOWN)
- )
- })
- })
-
- describe('when there are multiple commitments, notifies, rebondings, notice period increases, and withdrawals', () => {
- beforeEach(async () => {
- for (const accountToCreate of accounts) {
- // Account for `account` has already been created.
- if (accountToCreate !== account) {
- await lockedGold.createAccount({ from: accountToCreate })
- }
- }
- })
-
- enum ActionType {
- Deposit = 'Deposit',
- Notify = 'Notify',
- Increase = 'Increase',
- Rebond = 'Rebond',
- Withdraw = 'Withdraw',
- }
-
- const initializeState = (numAccounts: number) => {
- const locked: Map> = new Map()
- const notified: Map> = new Map()
- const noticePeriods: Map> = new Map()
- const availabilityTimes: Map> = new Map()
- const selectedAccounts = accounts.slice(0, numAccounts)
- for (const acc of selectedAccounts) {
- // Map keys, set elements appear not to be able to be BigNumbers, so we use strings instead.
- locked.set(acc, new Map())
- notified.set(acc, new Map())
- noticePeriods.set(acc, new Set([]))
- availabilityTimes.set(acc, new Set([]))
- }
-
- return { locked, notified, noticePeriods, availabilityTimes, selectedAccounts }
- }
-
- const rndElement = (elems: A[]) => {
- return elems[
- Math.floor(
- BigNumber.random()
- .times(elems.length)
- .toNumber()
- )
- ]
- }
- const rndSetElement = (s: Set) => rndElement(Array.from(s))
-
- const getOrElse = (map: Map, key: B, defaultValue: A) =>
- map.has(key) ? map.get(key) : defaultValue
-
- const executeActionsAndAssertState = async (numActions: number, numAccounts: number) => {
- const {
- selectedAccounts,
- locked,
- notified,
- noticePeriods,
- availabilityTimes,
- } = initializeState(numAccounts)
-
- for (let i = 0; i < numActions; i++) {
- const blockTime = 5
- await timeTravel(blockTime, web3)
- account = rndElement(selectedAccounts)
-
- const accountLockedGold = locked.get(account)
- const accountNotifiedCommitments = notified.get(account)
- const accountNoticePeriods = noticePeriods.get(account)
- const accountAvailabilityTimes = availabilityTimes.get(account)
-
- const getWithdrawableAvailabilityTimes = async (): Promise> => {
- const nextTimestamp = new BigNumber((await web3.eth.getBlock('latest')).timestamp)
- const items: string[] = Array.from(accountAvailabilityTimes)
- return new Set(items.filter((x: string) => nextTimestamp.gt(x)))
- }
-
- const getRebondableAvailabilityTimes = async (): Promise> => {
- const nextTimestamp = new BigNumber((await web3.eth.getBlock('latest')).timestamp).plus(
- blockTime
- )
- const items: string[] = Array.from(accountAvailabilityTimes)
- // Subtract one to cover edge case where block time is 6 seconds.
- return new Set(items.filter((x: string) => nextTimestamp.plus(1).lt(x)))
- }
-
- // Select random action type.
- const actionTypeOptions = [ActionType.Deposit]
- if (accountNoticePeriods.size > 0) {
- actionTypeOptions.push(ActionType.Notify)
- actionTypeOptions.push(ActionType.Increase)
- }
- const rebondableAvailabilityTimes = await getRebondableAvailabilityTimes()
- if (rebondableAvailabilityTimes.size > 0) {
- // Push twice to increase likelihood
- actionTypeOptions.push(ActionType.Rebond)
- actionTypeOptions.push(ActionType.Rebond)
- }
- const withdrawableAvailabilityTimes = await getWithdrawableAvailabilityTimes()
- if (withdrawableAvailabilityTimes.size > 0) {
- // Push twice to increase likelihood
- actionTypeOptions.push(ActionType.Withdraw)
- actionTypeOptions.push(ActionType.Withdraw)
- }
- const actionType = rndElement(actionTypeOptions)
-
- const getLockedCommitmentValue = (noticePeriod: string) =>
- getOrElse(accountLockedGold, noticePeriod, new BigNumber(0))
- const getNotifiedCommitmentValue = (availabilityTime: string) =>
- getOrElse(accountNotifiedCommitments, availabilityTime, new BigNumber(0))
-
- const randomSometimesMaximumValue = (maximum: BigNumber) => {
- assert.isFalse(maximum.eq(0))
- const random = BigNumber.random().toNumber()
- if (random < 0.5) {
- return maximum
- } else {
- return BigNumber.max(
- BigNumber.random()
- .times(maximum)
- .integerValue(),
- 1
- )
- }
- }
-
- // Perform random action and update test implementation state.
- if (actionType === ActionType.Deposit) {
- const value = new BigNumber(web3.utils.randomHex(2)).toNumber()
- // Notice period of at most 10 blocks.
- const noticePeriod = BigNumber.random()
- .times(10)
- .times(blockTime)
- .integerValue()
- .valueOf()
- await lockedGold.newCommitment(noticePeriod, { value, from: account })
- accountNoticePeriods.add(noticePeriod)
- accountLockedGold.set(noticePeriod, getLockedCommitmentValue(noticePeriod).plus(value))
- } else if (actionType === ActionType.Notify || actionType === ActionType.Increase) {
- const noticePeriod = rndSetElement(accountNoticePeriods)
- const lockedDepositValue = getLockedCommitmentValue(noticePeriod)
- const value = randomSometimesMaximumValue(lockedDepositValue)
-
- if (value.eq(lockedDepositValue)) {
- accountLockedGold.delete(noticePeriod)
- accountNoticePeriods.delete(noticePeriod)
- } else {
- accountLockedGold.set(noticePeriod, lockedDepositValue.minus(value))
- }
-
- if (actionType === ActionType.Notify) {
- await lockedGold.notifyCommitment(value, noticePeriod, { from: account })
- const availabilityTime = new BigNumber(noticePeriod)
- .plus((await web3.eth.getBlock('latest')).timestamp)
- .valueOf()
- accountAvailabilityTimes.add(availabilityTime)
- accountNotifiedCommitments.set(
- availabilityTime,
- getNotifiedCommitmentValue(availabilityTime).plus(value)
- )
- } else {
- // Notice period increase of at most 10 blocks.
- const increase = BigNumber.random()
- .times(10)
- .times(blockTime)
- .integerValue()
- .plus(1)
- await lockedGold.increaseNoticePeriod(value, noticePeriod, increase, {
- from: account,
- })
- const increasedNoticePeriod = increase.plus(noticePeriod).valueOf()
- accountNoticePeriods.add(increasedNoticePeriod)
- accountLockedGold.set(
- increasedNoticePeriod,
- getLockedCommitmentValue(increasedNoticePeriod).plus(value)
- )
- }
- } else if (actionType === ActionType.Rebond) {
- const availabilityTime = rndSetElement(rebondableAvailabilityTimes)
- const notifiedDepositValue = getNotifiedCommitmentValue(availabilityTime)
- const value = randomSometimesMaximumValue(notifiedDepositValue)
- await lockedGold.extendCommitment(value, availabilityTime, { from: account })
-
- if (value.eq(notifiedDepositValue)) {
- accountNotifiedCommitments.delete(availabilityTime)
- accountAvailabilityTimes.delete(availabilityTime)
- } else {
- accountNotifiedCommitments.set(availabilityTime, notifiedDepositValue.minus(value))
- }
- const noticePeriod = new BigNumber(availabilityTime)
- .minus((await web3.eth.getBlock('latest')).timestamp)
- .valueOf()
- accountLockedGold.set(noticePeriod, getLockedCommitmentValue(noticePeriod).plus(value))
- accountNoticePeriods.add(noticePeriod)
- } else if (actionType === ActionType.Withdraw) {
- const availabilityTime = rndSetElement(withdrawableAvailabilityTimes)
- await lockedGold.withdrawCommitment(availabilityTime, { from: account })
- accountAvailabilityTimes.delete(availabilityTime)
- accountNotifiedCommitments.delete(availabilityTime)
- } else {
- assert.isTrue(false)
- }
-
- // Sanity check our test implementation.
- selectedAccounts.forEach((acc) => {
- if (locked.get(acc).size > 0) {
- assert.hasAllKeys(
- noticePeriods.get(acc),
- Array.from(locked.get(acc).keys()),
- `notice periods don\'t match for account: ${acc}`
- )
- }
- if (notified.get(acc).size > 0) {
- assert.hasAllKeys(
- availabilityTimes.get(acc),
- Array.from(notified.get(acc).keys()),
- `availability times don\'t match for account: ${acc}`
- )
- }
- })
-
- // Test the contract state matches our test implementation.
- let expectedTotalWeight = new BigNumber(0)
- for (const acc of selectedAccounts) {
- let expectedAccountWeight = new BigNumber(0)
- const actualNoticePeriods = await lockedGold.getNoticePeriods(acc)
-
- assert.lengthOf(actualNoticePeriods, noticePeriods.get(acc).size)
- for (let k = 0; k < actualNoticePeriods.length; k++) {
- const noticePeriod = actualNoticePeriods[k]
- assert.isTrue(noticePeriods.get(acc).has(noticePeriod.valueOf()))
- const [actualValue, actualIndex] = await lockedGold.getLockedCommitment(
- acc,
- noticePeriod
- )
- assertEqualBN(actualIndex, k)
- const expectedValue = locked.get(acc).get(noticePeriod.valueOf())
- assertEqualBN(actualValue, expectedValue)
- assertEqualBN(actualNoticePeriods[actualIndex.toNumber()], noticePeriod)
- expectedAccountWeight = expectedAccountWeight.plus(
- await lockedGold.getCommitmentWeight(expectedValue, noticePeriod)
- )
- }
-
- const actualAvailabilityTimes = await lockedGold.getAvailabilityTimes(acc)
-
- assert.equal(actualAvailabilityTimes.length, availabilityTimes.get(acc).size)
- for (let k = 0; k < actualAvailabilityTimes.length; k++) {
- const availabilityTime = actualAvailabilityTimes[k]
- assert.isTrue(availabilityTimes.get(acc).has(availabilityTime.valueOf()))
- const [actualValue, actualIndex] = await lockedGold.getNotifiedCommitment(
- acc,
- availabilityTime
- )
- assertEqualBN(actualIndex, k)
- const expectedValue = notified.get(acc).get(availabilityTime.valueOf())
- assertEqualBN(actualValue, expectedValue)
- assertEqualBN(actualAvailabilityTimes[actualIndex.toNumber()], availabilityTime)
- expectedAccountWeight = expectedAccountWeight.plus(expectedValue)
- }
- assertEqualBN(await lockedGold.getAccountWeight(acc), expectedAccountWeight)
- expectedTotalWeight = expectedTotalWeight.plus(expectedAccountWeight)
- }
- }
- }
-
- it.skip('should match a simple typescript implementation', async () => {
- const numActions = 100
- const numAccounts = 2
- await executeActionsAndAssertState(numActions, numAccounts)
- })
- })
-})
diff --git a/packages/protocol/test/governance/election.ts b/packages/protocol/test/governance/election.ts
new file mode 100644
index 00000000000..af5e83f4e28
--- /dev/null
+++ b/packages/protocol/test/governance/election.ts
@@ -0,0 +1,1315 @@
+import { CeloContractName } from '@celo/protocol/lib/registry-utils'
+import { fixed1, toFixed, fromFixed, multiply } from '@celo/utils/lib/fixidity'
+import {
+ assertContainSubset,
+ assertEqualBN,
+ assertRevert,
+ NULL_ADDRESS,
+} from '@celo/protocol/lib/test-utils'
+import BigNumber from 'bignumber.js'
+import {
+ MockLockedGoldContract,
+ MockLockedGoldInstance,
+ RegistryContract,
+ RegistryInstance,
+ ValidatorsContract,
+ ValidatorsInstance,
+} from 'types'
+
+const Validators: ValidatorsContract = artifacts.require('Validators')
+const MockLockedGold: MockLockedGoldContract = artifacts.require('MockLockedGold')
+const Registry: RegistryContract = artifacts.require('Registry')
+
+// @ts-ignore
+// TODO(mcortesi): Use BN
+Validators.numberFormat = 'BigNumber'
+
+const parseValidatorParams = (validatorParams: any) => {
+ return {
+ name: validatorParams[1],
+ url: validatorParams[2],
+ publicKeysData: validatorParams[3],
+ affiliation: validatorParams[4],
+ }
+}
+
+const parseValidatorGroupParams = (groupParams: any) => {
+ return {
+ name: groupParams[1],
+ url: groupParams[2],
+ members: groupParams[3],
+ }
+}
+
+const HOUR = 60 * 60
+const DAY = 24 * HOUR
+const YEAR = 365 * DAY
+
+contract('Validators', (accounts: string[]) => {
+ let validators: ValidatorsInstance
+ let registry: RegistryInstance
+ let mockLockedGold: MockLockedGoldInstance
+ // A random 64 byte hex string.
+ const publicKey =
+ 'ea0733ad275e2b9e05541341a97ee82678c58932464fad26164657a111a7e37a9fa0300266fb90e2135a1f1512350cb4e985488a88809b14e3cbe415e76e82b2'
+ const blsPublicKey =
+ '4d23d8cd06f30b1fa7cf368e2f5399ab04bb6846c682f493a98a607d3dfb7e53a712bb79b475c57b0ac2785460f91301'
+ const blsPoP =
+ '9d3e1d8f49f6b0d8e9a03d80ca07b1d24cf1cc0557bdcc04f5e17a46e35d02d0d411d956dbd5d2d2464eebd7b74ae30005d223780d785d2abc5644fac7ac29fb0e302bdc80c81a5d45018b68b1045068a4b3a4861c93037685fd0d252d740501'
+
+ const publicKeysData = '0x' + publicKey + blsPublicKey + blsPoP
+
+ const nonOwner = accounts[1]
+ const registrationRequirements = { group: new BigNumber(1000), validator: new BigNumber(100) }
+ const deregistrationLockups = {
+ group: new BigNumber(100 * DAY),
+ validator: new BigNumber(60 * DAY),
+ }
+ const maxGroupSize = 5
+ const name = 'test-name'
+ const url = 'test-url'
+ const commission = toFixed(1 / 100)
+ beforeEach(async () => {
+ validators = await Validators.new()
+ mockLockedGold = await MockLockedGold.new()
+ registry = await Registry.new()
+ await registry.setAddressFor(CeloContractName.LockedGold, mockLockedGold.address)
+ await validators.initialize(
+ registry.address,
+ registrationRequirements.group,
+ registrationRequirements.validator,
+ deregistrationLockups.group,
+ deregistrationLockups.validator,
+ maxGroupSize
+ )
+ })
+
+ const registerValidator = async (validator: string) => {
+ await mockLockedGold.setAccountTotalLockedGold(validator, registrationRequirements.validator)
+ await validators.registerValidator(
+ name,
+ url,
+ // @ts-ignore bytes type
+ publicKeysData,
+ { from: validator }
+ )
+ }
+
+ const registerValidatorGroup = async (group: string) => {
+ await mockLockedGold.setAccountTotalLockedGold(group, registrationRequirements.group)
+ await validators.registerValidatorGroup(name, url, commission, { from: group })
+ }
+
+ const registerValidatorGroupWithMembers = async (group: string, members: string[]) => {
+ await registerValidatorGroup(group)
+ for (const validator of members) {
+ await registerValidator(validator)
+ await validators.affiliate(group, { from: validator })
+ await validators.addMember(validator, { from: group })
+ }
+ }
+
+ describe('#initialize()', () => {
+ it('should have set the owner', async () => {
+ const owner: string = await validators.owner()
+ assert.equal(owner, accounts[0])
+ })
+
+ it('should have set the registration requirements', async () => {
+ const [group, validator] = await validators.getRegistrationRequirement()
+ assertEqualBN(group, registrationRequirements.group)
+ assertEqualBN(validator, registrationRequirements.validator)
+ })
+
+ it('should have set the deregistration lockups', async () => {
+ const [group, validator] = await validators.getDeregistrationLockups()
+ assertEqualBN(group, deregistrationLockups.group)
+ assertEqualBN(validator, deregistrationLockups.validator)
+ })
+
+ it('should not be callable again', async () => {
+ await assertRevert(
+ validators.initialize(
+ registry.address,
+ registrationRequirements.group,
+ registrationRequirements.validator,
+ deregistrationLockups.group,
+ deregistrationLockups.validator,
+ maxGroupSize
+ )
+ )
+ })
+ })
+
+ describe('#setMinElectableValidators', () => {
+ const newMinElectableValidators = minElectableValidators.plus(1)
+ it('should set the minimum deposit', async () => {
+ await validators.setMinElectableValidators(newMinElectableValidators)
+ assertEqualBN(await validators.minElectableValidators(), newMinElectableValidators)
+ })
+
+ it('should emit the MinElectableValidatorsSet event', async () => {
+ const resp = await validators.setMinElectableValidators(newMinElectableValidators)
+ assert.equal(resp.logs.length, 1)
+ const log = resp.logs[0]
+ assertContainSubset(log, {
+ event: 'MinElectableValidatorsSet',
+ args: {
+ minElectableValidators: new BigNumber(newMinElectableValidators),
+ },
+ })
+ })
+
+ it('should revert when the minElectableValidators is zero', async () => {
+ await assertRevert(validators.setMinElectableValidators(0))
+ })
+
+ it('should revert when the minElectableValidators is greater than maxElectableValidators', async () => {
+ await assertRevert(validators.setMinElectableValidators(maxElectableValidators.plus(1)))
+ })
+
+ it('should revert when the minElectableValidators is unchanged', async () => {
+ await assertRevert(validators.setMinElectableValidators(minElectableValidators))
+ })
+
+ it('should revert when called by anyone other than the owner', async () => {
+ await assertRevert(
+ validators.setMinElectableValidators(newMinElectableValidators, { from: nonOwner })
+ )
+ })
+ })
+
+ describe('#setMaxElectableValidators', () => {
+ const newMaxElectableValidators = maxElectableValidators.plus(1)
+ it('should set the minimum deposit', async () => {
+ await validators.setMaxElectableValidators(newMaxElectableValidators)
+ assertEqualBN(await validators.maxElectableValidators(), newMaxElectableValidators)
+ })
+
+ it('should emit the MaxElectableValidatorsSet event', async () => {
+ const resp = await validators.setMaxElectableValidators(newMaxElectableValidators)
+ assert.equal(resp.logs.length, 1)
+ const log = resp.logs[0]
+ assertContainSubset(log, {
+ event: 'MaxElectableValidatorsSet',
+ args: {
+ maxElectableValidators: new BigNumber(newMaxElectableValidators),
+ },
+ })
+ })
+
+ it('should revert when the maxElectableValidators is less than minElectableValidators', async () => {
+ await assertRevert(validators.setMaxElectableValidators(minElectableValidators.minus(1)))
+ })
+
+ it('should revert when the maxElectableValidators is unchanged', async () => {
+ await assertRevert(validators.setMaxElectableValidators(maxElectableValidators))
+ })
+
+ it('should revert when called by anyone other than the owner', async () => {
+ await assertRevert(
+ validators.setMaxElectableValidators(newMaxElectableValidators, { from: nonOwner })
+ )
+ })
+ })
+
+ describe('#setRegistrationRequirement', () => {
+ const newValue = registrationRequirement.value.plus(1)
+ const newNoticePeriod = registrationRequirement.noticePeriod.plus(1)
+
+ it('should set the value and notice period', async () => {
+ await validators.setRegistrationRequirement(newValue, newNoticePeriod)
+ const [value, noticePeriod] = await validators.getRegistrationRequirement()
+ assertEqualBN(value, newValue)
+ assertEqualBN(noticePeriod, newNoticePeriod)
+ })
+
+ it('should emit the RegistrationRequirementSet event', async () => {
+ const resp = await validators.setRegistrationRequirement(newValue, newNoticePeriod)
+ assert.equal(resp.logs.length, 1)
+ const log = resp.logs[0]
+ assertContainSubset(log, {
+ event: 'RegistrationRequirementSet',
+ args: {
+ value: new BigNumber(newValue),
+ noticePeriod: new BigNumber(newNoticePeriod),
+ },
+ })
+ })
+
+ it('should revert when the requirement is unchanged', async () => {
+ await assertRevert(
+ validators.setRegistrationRequirement(
+ registrationRequirement.value,
+ registrationRequirement.noticePeriod
+ )
+ )
+ })
+
+ it('should revert when called by anyone other than the owner', async () => {
+ await assertRevert(
+ validators.setRegistrationRequirement(newValue, newNoticePeriod, { from: nonOwner })
+ )
+ })
+ })
+
+ describe('#registerValidator', () => {
+ const validator = accounts[0]
+ beforeEach(async () => {
+ await mockLockedGold.setLockedCommitment(
+ validator,
+ registrationRequirement.noticePeriod,
+ registrationRequirement.value
+ )
+ })
+
+ it('should mark the account as a validator', async () => {
+ await validators.registerValidator(
+ name,
+ url,
+ // @ts-ignore bytes type
+ publicKeysData,
+ registrationRequirement.noticePeriod
+ )
+ assert.isTrue(await validators.isValidator(validator))
+ })
+
+ it('should add the account to the list of validators', async () => {
+ await validators.registerValidator(
+ name,
+ url,
+ // @ts-ignore bytes type
+ publicKeysData,
+ registrationRequirement.noticePeriod
+ )
+ assert.deepEqual(await validators.getRegisteredValidators(), [validator])
+ })
+
+ it('should set the validator name, url, and public key', async () => {
+ await validators.registerValidator(
+ name,
+ url,
+ // @ts-ignore bytes type
+ publicKeysData,
+ registrationRequirement.noticePeriod
+ )
+ const parsedValidator = parseValidatorParams(await validators.getValidator(validator))
+ assert.equal(parsedValidator.name, name)
+ assert.equal(parsedValidator.url, url)
+ assert.equal(parsedValidator.publicKeysData, publicKeysData)
+ })
+
+ it('should emit the ValidatorRegistered event', async () => {
+ const resp = await validators.registerValidator(
+ name,
+ url,
+ // @ts-ignore bytes type
+ publicKeysData,
+ registrationRequirement.noticePeriod
+ )
+ assert.equal(resp.logs.length, 1)
+ const log = resp.logs[0]
+ assertContainSubset(log, {
+ event: 'ValidatorRegistered',
+ args: {
+ validator,
+ name,
+ url,
+ publicKeysData,
+ },
+ })
+ })
+
+ describe('when the account is already a registered validator', () => {
+ beforeEach(async () => {
+ await validators.registerValidator(
+ name,
+ url,
+ // @ts-ignore bytes type
+ publicKeysData,
+ registrationRequirement.noticePeriod
+ )
+ })
+
+ it('should revert', async () => {
+ await assertRevert(
+ validators.registerValidator(
+ name,
+ url,
+ // @ts-ignore bytes type
+ publicKeysData,
+ registrationRequirement.noticePeriod
+ )
+ )
+ })
+ })
+
+ describe('when the account is already a registered validator group', () => {
+ beforeEach(async () => {
+ await validators.registerValidatorGroup(name, url, registrationRequirement.noticePeriod)
+ })
+
+ it('should revert', async () => {
+ await assertRevert(
+ validators.registerValidator(
+ name,
+ url,
+ // @ts-ignore bytes type
+ publicKeysData,
+ registrationRequirement.noticePeriod
+ )
+ )
+ })
+ })
+
+ describe('when the account does not meet the registration requirements', () => {
+ beforeEach(async () => {
+ await mockLockedGold.setLockedCommitment(
+ validator,
+ registrationRequirement.noticePeriod,
+ registrationRequirement.value.minus(1)
+ )
+ })
+
+ it('should revert', async () => {
+ await assertRevert(
+ validators.registerValidator(
+ name,
+ url,
+ // @ts-ignore bytes type
+ publicKeysData,
+ registrationRequirement.noticePeriod
+ )
+ )
+ })
+ })
+ })
+
+ describe('#deregisterValidator', () => {
+ const validator = accounts[0]
+ const index = 0
+ beforeEach(async () => {
+ await registerValidator(validator)
+ })
+
+ it('should mark the account as not a validator', async () => {
+ await validators.deregisterValidator(index)
+ assert.isFalse(await validators.isValidator(validator))
+ })
+
+ it('should remove the account from the list of validators', async () => {
+ await validators.deregisterValidator(index)
+ assert.deepEqual(await validators.getRegisteredValidators(), [])
+ })
+
+ it('should emit the ValidatorDeregistered event', async () => {
+ const resp = await validators.deregisterValidator(index)
+ assert.equal(resp.logs.length, 1)
+ const log = resp.logs[0]
+ assertContainSubset(log, {
+ event: 'ValidatorDeregistered',
+ args: {
+ validator,
+ },
+ })
+ })
+
+ describe('when the validator is affiliated with a validator group', () => {
+ const group = accounts[1]
+ beforeEach(async () => {
+ await registerValidatorGroup(group)
+ await validators.affiliate(group)
+ })
+
+ it('should emit the ValidatorDeafilliated event', async () => {
+ const resp = await validators.deregisterValidator(index)
+ assert.equal(resp.logs.length, 2)
+ const log = resp.logs[0]
+ assertContainSubset(log, {
+ event: 'ValidatorDeaffiliated',
+ args: {
+ validator,
+ group,
+ },
+ })
+ })
+
+ describe('when the validator is a member of that group', () => {
+ beforeEach(async () => {
+ await validators.addMember(validator, { from: group })
+ })
+
+ it('should remove the validator from the group membership list', async () => {
+ await validators.deregisterValidator(index)
+ const parsedGroup = parseValidatorGroupParams(await validators.getValidatorGroup(group))
+ assert.deepEqual(parsedGroup.members, [])
+ })
+
+ it('should emit the ValidatorGroupMemberRemoved event', async () => {
+ const resp = await validators.deregisterValidator(index)
+ assert.equal(resp.logs.length, 4)
+ const log = resp.logs[0]
+ assertContainSubset(log, {
+ event: 'ValidatorGroupMemberRemoved',
+ args: {
+ validator,
+ group,
+ },
+ })
+ })
+
+ describe('when the validator is the only member of that group', () => {
+ it('should emit the ValidatorGroupEmptied event', async () => {
+ const resp = await validators.deregisterValidator(index)
+ assert.equal(resp.logs.length, 4)
+ const log = resp.logs[1]
+ assertContainSubset(log, {
+ event: 'ValidatorGroupEmptied',
+ args: {
+ group,
+ },
+ })
+ })
+
+ describe('when that group has received votes', () => {
+ beforeEach(async () => {
+ const voter = accounts[2]
+ const weight = 10
+ await mockLockedGold.setWeight(voter, weight)
+ await validators.vote(group, NULL_ADDRESS, NULL_ADDRESS, { from: voter })
+ })
+
+ it('should remove the group from the list of electable groups with votes', async () => {
+ await validators.deregisterValidator(index)
+ const [groups] = await validators.getValidatorGroupVotes()
+ assert.deepEqual(groups, [])
+ })
+ })
+ })
+ })
+ })
+
+ it('should revert when the account is not a registered validator', async () => {
+ await assertRevert(validators.deregisterValidator(index, { from: accounts[2] }))
+ })
+
+ it('should revert when the wrong index is provided', async () => {
+ await assertRevert(validators.deregisterValidator(index + 1))
+ })
+ })
+
+ describe('#affiliate', () => {
+ const validator = accounts[0]
+ const group = accounts[1]
+ beforeEach(async () => {
+ await registerValidator(validator)
+ await registerValidatorGroup(group)
+ })
+
+ it('should set the affiliate', async () => {
+ await validators.affiliate(group)
+ const parsedValidator = parseValidatorParams(await validators.getValidator(validator))
+ assert.equal(parsedValidator.affiliation, group)
+ })
+
+ it('should emit the ValidatorAffiliated event', async () => {
+ const resp = await validators.affiliate(group)
+ assert.equal(resp.logs.length, 1)
+ const log = resp.logs[0]
+ assertContainSubset(log, {
+ event: 'ValidatorAffiliated',
+ args: {
+ validator,
+ group,
+ },
+ })
+ })
+
+ describe('when the validator is already affiliated with a validator group', () => {
+ const otherGroup = accounts[2]
+ beforeEach(async () => {
+ await validators.affiliate(group)
+ await registerValidatorGroup(otherGroup)
+ })
+
+ it('should set the affiliate', async () => {
+ await validators.affiliate(otherGroup)
+ const parsedValidator = parseValidatorParams(await validators.getValidator(validator))
+ assert.equal(parsedValidator.affiliation, otherGroup)
+ })
+
+ it('should emit the ValidatorDeafilliated event', async () => {
+ const resp = await validators.affiliate(otherGroup)
+ assert.equal(resp.logs.length, 2)
+ const log = resp.logs[0]
+ assertContainSubset(log, {
+ event: 'ValidatorDeaffiliated',
+ args: {
+ validator,
+ group,
+ },
+ })
+ })
+
+ it('should emit the ValidatorAffiliated event', async () => {
+ const resp = await validators.affiliate(otherGroup)
+ assert.equal(resp.logs.length, 2)
+ const log = resp.logs[1]
+ assertContainSubset(log, {
+ event: 'ValidatorAffiliated',
+ args: {
+ validator,
+ group: otherGroup,
+ },
+ })
+ })
+
+ describe('when the validator is a member of that group', () => {
+ beforeEach(async () => {
+ await validators.addMember(validator, { from: group })
+ })
+
+ it('should remove the validator from the group membership list', async () => {
+ await validators.affiliate(otherGroup)
+ const parsedGroup = parseValidatorGroupParams(await validators.getValidatorGroup(group))
+ assert.deepEqual(parsedGroup.members, [])
+ })
+
+ it('should emit the ValidatorGroupMemberRemoved event', async () => {
+ const resp = await validators.affiliate(otherGroup)
+ assert.equal(resp.logs.length, 4)
+ const log = resp.logs[0]
+ assertContainSubset(log, {
+ event: 'ValidatorGroupMemberRemoved',
+ args: {
+ validator,
+ group,
+ },
+ })
+ })
+
+ describe('when the validator is the only member of that group', () => {
+ it('should emit the ValidatorGroupEmptied event', async () => {
+ const resp = await validators.affiliate(otherGroup)
+ assert.equal(resp.logs.length, 4)
+ const log = resp.logs[1]
+ assertContainSubset(log, {
+ event: 'ValidatorGroupEmptied',
+ args: {
+ group,
+ },
+ })
+ })
+
+ describe('when that group has received votes', () => {
+ beforeEach(async () => {
+ const voter = accounts[2]
+ const weight = 10
+ await mockLockedGold.setWeight(voter, weight)
+ await validators.vote(group, NULL_ADDRESS, NULL_ADDRESS, { from: voter })
+ })
+
+ it('should remove the group from the list of electable groups with votes', async () => {
+ await validators.affiliate(otherGroup)
+ const [groups] = await validators.getValidatorGroupVotes()
+ assert.deepEqual(groups, [])
+ })
+ })
+ })
+ })
+ })
+
+ it('should revert when the account is not a registered validator', async () => {
+ await assertRevert(validators.affiliate(group, { from: accounts[2] }))
+ })
+
+ it('should revert when the group is not a registered validator group', async () => {
+ await assertRevert(validators.affiliate(accounts[2]))
+ })
+ })
+
+ describe('#deaffiliate', () => {
+ const validator = accounts[0]
+ const group = accounts[1]
+ beforeEach(async () => {
+ await registerValidator(validator)
+ await registerValidatorGroup(group)
+ await validators.affiliate(group)
+ })
+
+ it('should clear the affiliate', async () => {
+ await validators.deaffiliate()
+ const parsedValidator = parseValidatorParams(await validators.getValidator(validator))
+ assert.equal(parsedValidator.affiliation, NULL_ADDRESS)
+ })
+
+ it('should emit the ValidatorDeaffiliated event', async () => {
+ const resp = await validators.deaffiliate()
+ assert.equal(resp.logs.length, 1)
+ const log = resp.logs[0]
+ assertContainSubset(log, {
+ event: 'ValidatorDeaffiliated',
+ args: {
+ validator,
+ group,
+ },
+ })
+ })
+
+ describe('when the validator is a member of the affiliated group', () => {
+ beforeEach(async () => {
+ await validators.addMember(validator, { from: group })
+ })
+
+ it('should remove the validator from the group membership list', async () => {
+ await validators.deaffiliate()
+ const parsedGroup = parseValidatorGroupParams(await validators.getValidatorGroup(group))
+ assert.deepEqual(parsedGroup.members, [])
+ })
+
+ it('should emit the ValidatorGroupMemberRemoved event', async () => {
+ const resp = await validators.deaffiliate()
+ assert.equal(resp.logs.length, 3)
+ const log = resp.logs[0]
+ assertContainSubset(log, {
+ event: 'ValidatorGroupMemberRemoved',
+ args: {
+ validator,
+ group,
+ },
+ })
+ })
+
+ describe('when the validator is the only member of that group', () => {
+ it('should emit the ValidatorGroupEmptied event', async () => {
+ const resp = await validators.deaffiliate()
+ assert.equal(resp.logs.length, 3)
+ const log = resp.logs[1]
+ assertContainSubset(log, {
+ event: 'ValidatorGroupEmptied',
+ args: {
+ group,
+ },
+ })
+ })
+
+ describe('when that group has received votes', () => {
+ beforeEach(async () => {
+ const voter = accounts[2]
+ const weight = 10
+ await mockLockedGold.setWeight(voter, weight)
+ await validators.vote(group, NULL_ADDRESS, NULL_ADDRESS, { from: voter })
+ })
+
+ it('should remove the group from the list of electable groups with votes', async () => {
+ await validators.deaffiliate()
+ const [groups] = await validators.getValidatorGroupVotes()
+ assert.deepEqual(groups, [])
+ })
+ })
+ })
+ })
+
+ it('should revert when the account is not a registered validator', async () => {
+ await assertRevert(validators.deaffiliate({ from: accounts[2] }))
+ })
+
+ it('should revert when the validator is not affiliated with a validator group', async () => {
+ await validators.deaffiliate()
+ await assertRevert(validators.deaffiliate())
+ })
+ })
+
+ describe('#registerValidatorGroup', () => {
+ const group = accounts[0]
+ beforeEach(async () => {
+ await mockLockedGold.setLockedCommitment(
+ group,
+ registrationRequirement.noticePeriod,
+ registrationRequirement.value
+ )
+ })
+
+ it('should mark the account as a validator group', async () => {
+ await validators.registerValidatorGroup(name, url, registrationRequirement.noticePeriod)
+ assert.isTrue(await validators.isValidatorGroup(group))
+ })
+
+ it('should add the account to the list of validator groups', async () => {
+ await validators.registerValidatorGroup(name, url, registrationRequirement.noticePeriod)
+ assert.deepEqual(await validators.getRegisteredValidatorGroups(), [group])
+ })
+
+ it('should set the validator group name, and url', async () => {
+ await validators.registerValidatorGroup(name, url, registrationRequirement.noticePeriod)
+ const parsedGroup = parseValidatorGroupParams(await validators.getValidatorGroup(group))
+ assert.equal(parsedGroup.name, name)
+ assert.equal(parsedGroup.url, url)
+ })
+
+ it('should emit the ValidatorGroupRegistered event', async () => {
+ const resp = await validators.registerValidatorGroup(
+ name,
+ url,
+ registrationRequirement.noticePeriod
+ )
+ assert.equal(resp.logs.length, 1)
+ const log = resp.logs[0]
+ assertContainSubset(log, {
+ event: 'ValidatorGroupRegistered',
+ args: {
+ group,
+ name,
+ url,
+ },
+ })
+ })
+
+ describe('when the account is already a registered validator', () => {
+ beforeEach(async () => {
+ await registerValidator(group)
+ })
+
+ it('should revert', async () => {
+ await assertRevert(
+ validators.registerValidatorGroup(name, url, registrationRequirement.noticePeriod)
+ )
+ })
+ })
+
+ describe('when the account is already a registered validator group', () => {
+ beforeEach(async () => {
+ await validators.registerValidatorGroup(name, url, registrationRequirement.noticePeriod)
+ })
+
+ it('should revert', async () => {
+ await assertRevert(
+ validators.registerValidatorGroup(name, url, registrationRequirement.noticePeriod)
+ )
+ })
+ })
+
+ describe('when the account does not meet the registration requirements', () => {
+ beforeEach(async () => {
+ await mockLockedGold.setLockedCommitment(
+ group,
+ registrationRequirement.noticePeriod,
+ registrationRequirement.value.minus(1)
+ )
+ })
+
+ it('should revert', async () => {
+ await assertRevert(
+ validators.registerValidatorGroup(name, url, registrationRequirement.noticePeriod)
+ )
+ })
+ })
+ })
+
+ describe('#deregisterValidatorGroup', () => {
+ const index = 0
+ const group = accounts[0]
+ beforeEach(async () => {
+ await registerValidatorGroup(group)
+ })
+
+ it('should mark the account as not a validator group', async () => {
+ await validators.deregisterValidatorGroup(index)
+ assert.isFalse(await validators.isValidatorGroup(group))
+ })
+
+ it('should remove the account from the list of validator groups', async () => {
+ await validators.deregisterValidatorGroup(index)
+ assert.deepEqual(await validators.getRegisteredValidatorGroups(), [])
+ })
+
+ it('should emit the ValidatorGroupDeregistered event', async () => {
+ const resp = await validators.deregisterValidatorGroup(index)
+ assert.equal(resp.logs.length, 1)
+ const log = resp.logs[0]
+ assertContainSubset(log, {
+ event: 'ValidatorGroupDeregistered',
+ args: {
+ group,
+ },
+ })
+ })
+
+ it('should revert when the account is not a registered validator group', async () => {
+ await assertRevert(validators.deregisterValidatorGroup(index, { from: accounts[2] }))
+ })
+
+ it('should revert when the wrong index is provided', async () => {
+ await assertRevert(validators.deregisterValidatorGroup(index + 1))
+ })
+
+ describe('when the validator group is not empty', () => {
+ const validator = accounts[1]
+ beforeEach(async () => {
+ await registerValidator(validator)
+ await validators.affiliate(group, { from: validator })
+ await validators.addMember(validator)
+ })
+
+ it('should revert', async () => {
+ await assertRevert(validators.deregisterValidatorGroup(index))
+ })
+ })
+ })
+
+ describe('#addMember', () => {
+ const group = accounts[0]
+ const validator = accounts[1]
+ beforeEach(async () => {
+ await registerValidator(validator)
+ await registerValidatorGroup(group)
+ await validators.affiliate(group, { from: validator })
+ })
+
+ it('should add the member to the list of members', async () => {
+ await validators.addMember(validator)
+ const parsedGroup = parseValidatorGroupParams(await validators.getValidatorGroup(group))
+ assert.deepEqual(parsedGroup.members, [validator])
+ })
+
+ it('should emit the ValidatorGroupMemberAdded event', async () => {
+ const resp = await validators.addMember(validator)
+ assert.equal(resp.logs.length, 1)
+ const log = resp.logs[0]
+ assertContainSubset(log, {
+ event: 'ValidatorGroupMemberAdded',
+ args: {
+ group,
+ validator,
+ },
+ })
+ })
+
+ it('should revert when the account is not a registered validator group', async () => {
+ await assertRevert(validators.addMember(validator, { from: accounts[2] }))
+ })
+
+ it('should revert when the member is not a registered validator', async () => {
+ await assertRevert(validators.addMember(accounts[2]))
+ })
+
+ describe('when the validator has not affiliated themselves with the group', () => {
+ beforeEach(async () => {
+ await validators.deaffiliate({ from: validator })
+ })
+
+ it('should revert', async () => {
+ await assertRevert(validators.addMember(validator))
+ })
+ })
+
+ describe('when the validator is already a member of the group', () => {
+ beforeEach(async () => {
+ await validators.addMember(validator)
+ })
+
+ it('should revert', async () => {
+ await assertRevert(validators.addMember(validator))
+ })
+ })
+ })
+
+ describe('#removeMember', () => {
+ const group = accounts[0]
+ const validator = accounts[1]
+ beforeEach(async () => {
+ await registerValidatorGroupWithMembers(group, [validator])
+ })
+
+ it('should remove the member from the list of members', async () => {
+ await validators.removeMember(validator)
+ const parsedGroup = parseValidatorGroupParams(await validators.getValidatorGroup(group))
+ assert.deepEqual(parsedGroup.members, [])
+ })
+
+ it('should emit the ValidatorGroupMemberRemoved event', async () => {
+ const resp = await validators.removeMember(validator)
+ assert.equal(resp.logs.length, 2)
+ const log = resp.logs[0]
+ assertContainSubset(log, {
+ event: 'ValidatorGroupMemberRemoved',
+ args: {
+ group,
+ validator,
+ },
+ })
+ })
+
+ describe('when the validator is the only member of the group', () => {
+ it('should emit the ValidatorGroupEmptied event', async () => {
+ const resp = await validators.removeMember(validator)
+ assert.equal(resp.logs.length, 2)
+ const log = resp.logs[1]
+ assertContainSubset(log, {
+ event: 'ValidatorGroupEmptied',
+ args: {
+ group,
+ },
+ })
+ })
+
+ describe('when the group has received votes', () => {
+ beforeEach(async () => {
+ const voter = accounts[2]
+ const weight = 10
+ await mockLockedGold.setWeight(voter, weight)
+ await validators.vote(group, NULL_ADDRESS, NULL_ADDRESS, { from: voter })
+ })
+
+ it('should remove the group from the list of electable groups with votes', async () => {
+ await validators.removeMember(validator)
+ const [groups] = await validators.getValidatorGroupVotes()
+ assert.deepEqual(groups, [])
+ })
+ })
+ })
+
+ it('should revert when the account is not a registered validator group', async () => {
+ await assertRevert(validators.removeMember(validator, { from: accounts[2] }))
+ })
+
+ it('should revert when the member is not a registered validator', async () => {
+ await assertRevert(validators.removeMember(accounts[2]))
+ })
+
+ describe('when the validator is not a member of the validator group', () => {
+ beforeEach(async () => {
+ await validators.deaffiliate({ from: validator })
+ })
+
+ it('should revert', async () => {
+ await assertRevert(validators.removeMember(validator))
+ })
+ })
+ })
+
+ describe('#reorderMember', () => {
+ const group = accounts[0]
+ const validator1 = accounts[1]
+ const validator2 = accounts[2]
+ beforeEach(async () => {
+ await registerValidatorGroupWithMembers(group, [validator1, validator2])
+ })
+
+ it('should reorder the list of group members', async () => {
+ await validators.reorderMember(validator2, validator1, NULL_ADDRESS)
+ const parsedGroup = parseValidatorGroupParams(await validators.getValidatorGroup(group))
+ assert.deepEqual(parsedGroup.members, [validator2, validator1])
+ })
+
+ it('should emit the ValidatorGroupMemberReordered event', async () => {
+ const resp = await validators.reorderMember(validator2, validator1, NULL_ADDRESS)
+ assert.equal(resp.logs.length, 1)
+ const log = resp.logs[0]
+ assertContainSubset(log, {
+ event: 'ValidatorGroupMemberReordered',
+ args: {
+ group,
+ validator: validator2,
+ },
+ })
+ })
+
+ it('should revert when the account is not a registered validator group', async () => {
+ await assertRevert(
+ validators.reorderMember(validator2, validator1, NULL_ADDRESS, { from: accounts[2] })
+ )
+ })
+
+ it('should revert when the member is not a registered validator', async () => {
+ await assertRevert(validators.reorderMember(accounts[3], validator1, NULL_ADDRESS))
+ })
+
+ describe('when the validator is not a member of the validator group', () => {
+ beforeEach(async () => {
+ await validators.deaffiliate({ from: validator2 })
+ })
+
+ it('should revert', async () => {
+ await assertRevert(validators.reorderMember(validator2, validator1, NULL_ADDRESS))
+ })
+ })
+ })
+
+ describe('#vote', () => {
+ const weight = new BigNumber(5)
+ const voter = accounts[0]
+ const validator = accounts[1]
+ const group = accounts[2]
+ beforeEach(async () => {
+ await registerValidatorGroupWithMembers(group, [validator])
+ await mockLockedGold.setWeight(voter, weight)
+ })
+
+ it("should set the voter's vote", async () => {
+ await validators.vote(group, NULL_ADDRESS, NULL_ADDRESS)
+ assert.isTrue(await validators.isVoting(voter))
+ assert.equal(await validators.voters(voter), group)
+ })
+
+ it('should add the group to the list of those receiving votes', async () => {
+ await validators.vote(group, NULL_ADDRESS, NULL_ADDRESS)
+ const [groups] = await validators.getValidatorGroupVotes()
+ assert.deepEqual(groups, [group])
+ })
+
+ it("should increment the validator group's vote total", async () => {
+ await validators.vote(group, NULL_ADDRESS, NULL_ADDRESS)
+ assertEqualBN(await validators.getVotesReceived(group), weight)
+ })
+
+ it('should emit the ValidatorGroupVoteCast event', async () => {
+ const resp = await validators.vote(group, NULL_ADDRESS, NULL_ADDRESS)
+ assert.equal(resp.logs.length, 1)
+ const log = resp.logs[0]
+ assertContainSubset(log, {
+ event: 'ValidatorGroupVoteCast',
+ args: {
+ account: voter,
+ group,
+ weight: new BigNumber(weight),
+ },
+ })
+ })
+
+ describe('when the group had not previously received votes', () => {
+ it('should add the group to the list of electable groups with votes', async () => {
+ await validators.vote(group, NULL_ADDRESS, NULL_ADDRESS)
+ const [groups] = await validators.getValidatorGroupVotes()
+ assert.deepEqual(groups, [group])
+ })
+ })
+
+ it('should revert when the group is not a registered validator group', async () => {
+ await assertRevert(validators.vote(accounts[3], NULL_ADDRESS, NULL_ADDRESS))
+ })
+
+ describe('when the group is empty', () => {
+ beforeEach(async () => {
+ await validators.removeMember(validator, { from: group })
+ })
+
+ it('should revert', async () => {
+ await assertRevert(validators.vote(group, NULL_ADDRESS, NULL_ADDRESS))
+ })
+ })
+
+ describe('when the account voting is frozen', () => {
+ beforeEach(async () => {
+ await mockLockedGold.setVotingFrozen(voter)
+ })
+
+ it('should revert', async () => {
+ await assertRevert(validators.vote(group, NULL_ADDRESS, NULL_ADDRESS))
+ })
+ })
+
+ describe('when the account has no weight', () => {
+ beforeEach(async () => {
+ await mockLockedGold.setWeight(voter, NULL_ADDRESS)
+ })
+
+ it('should revert', async () => {
+ await assertRevert(validators.vote(group, NULL_ADDRESS, NULL_ADDRESS))
+ })
+ })
+ describe('when the account has an outstanding vote', () => {
+ beforeEach(async () => {
+ await validators.vote(group, NULL_ADDRESS, NULL_ADDRESS)
+ })
+
+ it('should revert', async () => {
+ await assertRevert(validators.vote(group, NULL_ADDRESS, NULL_ADDRESS))
+ })
+ })
+ })
+
+ describe('#revokeVote', () => {
+ const weight = 5
+ const voter = accounts[0]
+ const validator = accounts[1]
+ const group = accounts[2]
+ beforeEach(async () => {
+ await registerValidatorGroupWithMembers(group, [validator])
+ await mockLockedGold.setWeight(voter, weight)
+ await validators.vote(group, NULL_ADDRESS, NULL_ADDRESS)
+ })
+
+ it("should clear the voter's vote", async () => {
+ await validators.revokeVote(NULL_ADDRESS, NULL_ADDRESS)
+ assert.isFalse(await validators.isVoting(voter))
+ assert.equal(await validators.voters(voter), NULL_ADDRESS)
+ })
+
+ it("should decrement the validator group's vote total", async () => {
+ await validators.revokeVote(NULL_ADDRESS, NULL_ADDRESS)
+ const [groups, votes] = await validators.getValidatorGroupVotes()
+ assert.deepEqual(groups, [])
+ assert.deepEqual(votes, [])
+ })
+
+ it('should emit the ValidatorGroupVoteRevoked event', async () => {
+ const resp = await validators.revokeVote(NULL_ADDRESS, NULL_ADDRESS)
+ assert.equal(resp.logs.length, 1)
+ const log = resp.logs[0]
+ assertContainSubset(log, {
+ event: 'ValidatorGroupVoteRevoked',
+ args: {
+ account: voter,
+ group,
+ weight: new BigNumber(weight),
+ },
+ })
+ })
+
+ describe('when the group had not received other votes', () => {
+ it('should remove the group from the list of electable groups with votes', async () => {
+ await validators.revokeVote(NULL_ADDRESS, NULL_ADDRESS)
+ const [groups] = await validators.getValidatorGroupVotes()
+ assert.deepEqual(groups, [])
+ })
+ })
+
+ describe('when the account does not have an outstanding vote', () => {
+ beforeEach(async () => {
+ await validators.revokeVote(NULL_ADDRESS, NULL_ADDRESS)
+ })
+
+ it('should revert', async () => {
+ await assertRevert(validators.revokeVote(NULL_ADDRESS, NULL_ADDRESS))
+ })
+ })
+ })
+
+ describe('#getValidators', () => {
+ const group1 = accounts[0]
+ const group2 = accounts[1]
+ const group3 = accounts[2]
+ const validator1 = accounts[3]
+ const validator2 = accounts[4]
+ const validator3 = accounts[5]
+ const validator4 = accounts[6]
+ const validator5 = accounts[7]
+ const validator6 = accounts[8]
+ const validator7 = accounts[9]
+
+ // If voterN votes for groupN:
+ // group1 gets 20 votes per member
+ // group2 gets 25 votes per member
+ // group3 gets 30 votes per member
+ // The ordering of the returned validators should be from group with most votes to group,
+ // with fewest votes, and within each group, members are elected from first to last.
+ const voter1 = { address: accounts[0], weight: 80 }
+ const voter2 = { address: accounts[1], weight: 50 }
+ const voter3 = { address: accounts[2], weight: 30 }
+ const assertAddressesEqual = (actual: string[], expected: string[]) => {
+ assert.deepEqual(actual.map((x) => x.toLowerCase()), expected.map((x) => x.toLowerCase()))
+ }
+
+ beforeEach(async () => {
+ await registerValidatorGroupWithMembers(group1, [
+ validator1,
+ validator2,
+ validator3,
+ validator4,
+ ])
+ await registerValidatorGroupWithMembers(group2, [validator5, validator6])
+ await registerValidatorGroupWithMembers(group3, [validator7])
+
+ for (const voter of [voter1, voter2, voter3]) {
+ await mockLockedGold.setWeight(voter.address, voter.weight)
+ }
+ })
+
+ describe('when a single group has >= minElectableValidators as members and received votes', () => {
+ beforeEach(async () => {
+ await validators.vote(group1, NULL_ADDRESS, NULL_ADDRESS, { from: voter1.address })
+ })
+
+ it("should return that group's member list", async () => {
+ assertAddressesEqual(await validators.getValidators(), [
+ validator1,
+ validator2,
+ validator3,
+ validator4,
+ ])
+ })
+ })
+
+ describe("when > maxElectableValidators members's groups receive votes", () => {
+ beforeEach(async () => {
+ await validators.vote(group1, NULL_ADDRESS, NULL_ADDRESS, { from: voter1.address })
+ await validators.vote(group2, NULL_ADDRESS, group1, { from: voter2.address })
+ await validators.vote(group3, NULL_ADDRESS, group2, { from: voter3.address })
+ })
+
+ it('should return maxElectableValidators elected validators', async () => {
+ assertAddressesEqual(await validators.getValidators(), [
+ validator1,
+ validator2,
+ validator3,
+ validator5,
+ validator6,
+ validator7,
+ ])
+ })
+ })
+
+ describe('when a group receives enough votes for > n seats but only has n members', () => {
+ beforeEach(async () => {
+ await mockLockedGold.setWeight(voter3.address, 1000)
+ await validators.vote(group3, NULL_ADDRESS, NULL_ADDRESS, { from: voter3.address })
+ await validators.vote(group1, NULL_ADDRESS, group3, { from: voter1.address })
+ await validators.vote(group2, NULL_ADDRESS, group1, { from: voter2.address })
+ })
+
+ it('should elect only n members from that group', async () => {
+ assertAddressesEqual(await validators.getValidators(), [
+ validator7,
+ validator1,
+ validator2,
+ validator3,
+ validator5,
+ validator6,
+ ])
+ })
+ })
+
+ describe('when an account has delegated validating to another address', () => {
+ const validatingDelegate = '0x47e172f6cfb6c7d01c1574fa3e2be7cc73269d95'
+ beforeEach(async () => {
+ await mockLockedGold.delegateValidating(validator3, validatingDelegate)
+ await validators.vote(group1, NULL_ADDRESS, NULL_ADDRESS, { from: voter1.address })
+ await validators.vote(group2, NULL_ADDRESS, group1, { from: voter2.address })
+ await validators.vote(group3, NULL_ADDRESS, group2, { from: voter3.address })
+ })
+
+ it('should return the validating delegate in place of the account', async () => {
+ assertAddressesEqual(await validators.getValidators(), [
+ validator1,
+ validator2,
+ validatingDelegate,
+ validator5,
+ validator6,
+ validator7,
+ ])
+ })
+ })
+
+ describe('when there are not enough electable validators', () => {
+ beforeEach(async () => {
+ await validators.vote(group2, NULL_ADDRESS, NULL_ADDRESS, { from: voter2.address })
+ await validators.vote(group3, NULL_ADDRESS, group2, { from: voter3.address })
+ })
+
+ it('should revert', async () => {
+ await assertRevert(validators.getValidators())
+ })
+ })
+ })
+})
diff --git a/packages/protocol/test/governance/lockedgold.ts b/packages/protocol/test/governance/lockedgold.ts
new file mode 100644
index 00000000000..9a29a8f0464
--- /dev/null
+++ b/packages/protocol/test/governance/lockedgold.ts
@@ -0,0 +1,469 @@
+import { CeloContractName } from '@celo/protocol/lib/registry-utils'
+import {
+ assertEqualBN,
+ assertLogMatches,
+ assertRevert,
+ NULL_ADDRESS,
+ timeTravel,
+} from '@celo/protocol/lib/test-utils'
+import BigNumber from 'bignumber.js'
+import {
+ LockedGoldContract,
+ LockedGoldInstance,
+ MockGoldTokenContract,
+ MockGoldTokenInstance,
+ MockGovernanceContract,
+ MockGovernanceInstance,
+ MockValidatorsContract,
+ MockValidatorsInstance,
+ RegistryContract,
+ RegistryInstance,
+} from 'types'
+
+const LockedGold: LockedGoldContract = artifacts.require('LockedGold')
+const Registry: RegistryContract = artifacts.require('Registry')
+const MockGoldToken: MockGoldTokenContract = artifacts.require('MockGoldToken')
+const MockGovernance: MockGovernanceContract = artifacts.require('MockGovernance')
+const MockValidators: MockValidatorsContract = artifacts.require('MockValidators')
+
+// @ts-ignore
+// TODO(mcortesi): Use BN
+LockedGold.numberFormat = 'BigNumber'
+
+const HOUR = 60 * 60
+const DAY = 24 * HOUR
+const YEAR = 365 * DAY
+
+contract('LockedGold', (accounts: string[]) => {
+ let account = accounts[0]
+ const nonOwner = accounts[1]
+ const unlockingPeriod = 3 * DAY
+ let mockGoldToken: MockGoldTokenInstance
+ let mockGovernance: MockGovernanceInstance
+ let mockValidators: MockValidatorsInstance
+ let lockedGold: LockedGoldInstance
+ let registry: RegistryInstance
+
+ const getParsedSignatureOfAddress = async (address: string, signer: string) => {
+ // @ts-ignore
+ const hash = web3.utils.soliditySha3({ type: 'address', value: address })
+ const signature = (await web3.eth.sign(hash, signer)).slice(2)
+ return {
+ r: `0x${signature.slice(0, 64)}`,
+ s: `0x${signature.slice(64, 128)}`,
+ v: web3.utils.hexToNumber(signature.slice(128, 130)) + 27,
+ }
+ }
+
+ beforeEach(async () => {
+ lockedGold = await LockedGold.new()
+ mockGoldToken = await MockGoldToken.new()
+ mockGovernance = await MockGovernance.new()
+ mockValidators = await MockValidators.new()
+ registry = await Registry.new()
+ await registry.setAddressFor(CeloContractName.GoldToken, mockGoldToken.address)
+ await registry.setAddressFor(CeloContractName.Governance, mockGovernance.address)
+ await registry.setAddressFor(CeloContractName.Validators, mockValidators.address)
+ await lockedGold.initialize(registry.address, unlockingPeriod)
+ await lockedGold.createAccount()
+ })
+
+ describe('#initialize()', () => {
+ it('should set the owner', async () => {
+ const owner: string = await lockedGold.owner()
+ assert.equal(owner, account)
+ })
+
+ it('should set the registry address', async () => {
+ const registryAddress: string = await lockedGold.registry()
+ assert.equal(registryAddress, registry.address)
+ })
+
+ it('should set the unlocking period', async () => {
+ const period: string = await lockedGold.unlockingPeriod()
+ assert.equal(unlockingPeriod, period)
+ })
+
+ it('should revert if already initialized', async () => {
+ await assertRevert(lockedGold.initialize(registry.address, unlockingPeriod))
+ })
+ })
+
+ describe('#setRegistry()', () => {
+ const anAddress: string = accounts[2]
+
+ it('should set the registry when called by the owner', async () => {
+ await lockedGold.setRegistry(anAddress)
+ assert.equal(await lockedGold.registry(), anAddress)
+ })
+
+ it('should revert when not called by the owner', async () => {
+ await assertRevert(lockedGold.setRegistry(anAddress, { from: nonOwner }))
+ })
+ })
+
+ const authorizationTests = [
+ {
+ name: 'Voter',
+ fn: lockedGold.authorizeVoter,
+ getFromAccount: lockedGold.getVoterFromAccount,
+ getAccount: lockedGold.getAccountFromVoter,
+ },
+ {
+ name: 'Validator',
+ fn: lockedGold.authorizeValidator,
+ getFromAccount: lockedGold.getValidatorFromAccount,
+ getAccount: lockedGold.getAccountFromValidator,
+ },
+ ]
+ for (const test in authorizationTests) {
+ describe(`#authorize${test.name}()`, () => {
+ const authorized = accounts[1]
+ let sig
+
+ beforeEach(async () => {
+ sig = await getParsedSignatureOfAddress(account, authorized)
+ })
+
+ it(`should set the authorized ${test.name}`, async () => {
+ await test.fn(authorized, sig.v, sig.r, sig.s)
+ assert.equal(await lockedGold.authorizedBy(authorized), account)
+ assert.equal(await test.getFromAccount(account), authorized)
+ assert.equal(await test.getAccount(authorized), account)
+ })
+
+ it(`should emit a ${test.name}Authorized event`, async () => {
+ const resp = await test.fn(authorized, sig.v, sig.r, sig.s)
+ assert.equal(resp.logs.length, 1)
+ const log = resp.logs[0]
+ assertLogMatches(log, `${test.name}Authorized`, {
+ account,
+ authorized,
+ })
+ })
+
+ it(`should revert if the ${test.name} is an account`, async () => {
+ await lockedGold.createAccount({ from: authorized })
+ await assertRevert(test.fn(authorized, sig.v, sig.r, sig.s))
+ })
+
+ it(`should revert if the ${test.name} is already authorized`, async () => {
+ const otherAccount = accounts[2]
+ const otherSig = await getParsedSignatureOfAddress(otherAccount, authorized)
+ await lockedGold.createAccount({ from: otherAccount })
+ await test.fn(authorized, otherSig.v, otherSig.r, otherSig.s, {
+ from: otherAccount,
+ })
+ await assertRevert(test.fn(authorized, sig.v, sig.r, sig.s))
+ })
+
+ it('should revert if the signature is incorrect', async () => {
+ const nonVoter = accounts[3]
+ const incorrectSig = await getParsedSignatureOfAddress(account, nonVoter)
+ await assertRevert(test.fn(authorized, incorrectSig.v, incorrectSig.r, incorrectSig.s))
+ })
+
+ describe('when a previous authorization has been made', async () => {
+ const newAuthorized = accounts[2]
+ let newSig
+ beforeEach(async () => {
+ await test.fn(authorized, sig.v, sig.r, sig.s)
+ newSig = await getParsedSignatureOfAddress(account, newAuthorized)
+ await test.fn(newAuthorized, newSig.v, newSig.r, newSig.s)
+ })
+
+ it(`should set the new authorized ${test.name}`, async () => {
+ assert.equal(await lockedGold.authorizedBy(newAuthorized), account)
+ assert.equal(await test.getFromAccount(account), newAuthorized)
+ assert.equal(await test.getAccount(newAuthorized), account)
+ })
+
+ it('should reset the previous authorization', async () => {
+ assert.equal(await lockedGold.authorizedBy(authorized), NULL_ADDRESS)
+ })
+ })
+ })
+
+ describe(`#getAccountFrom${test.name}()`, () => {
+ describe(`when the account has not authorized a ${test.name}`, () => {
+ it('should return the account when passed the account', async () => {
+ assert.equal(await test.getAccount(account), account)
+ })
+
+ it('should revert when passed an address that is not an account', async () => {
+ await assertRevert(test.getAccount(accounts[1]))
+ })
+ })
+
+ describe(`when the account has authorized a ${test.name}`, () => {
+ const authorized = accounts[1]
+ before(async () => {
+ const sig = await getParsedSignatureOfAddress(account, voter)
+ await test.fn(authorized, sig.v, sig.r, sig.s)
+ })
+
+ it('should return the account when passed the account', async () => {
+ assert.equal(await test.getAccount(account), account)
+ })
+
+ it(`should return the account when passed the ${test.name}`, async () => {
+ assert.equal(await test.getAccount(authorized), account)
+ })
+ })
+ })
+
+ describe(`#get${test.name}FromAccount()`, () => {
+ describe(`when the account has not authorized a ${test.name}`, () => {
+ it('should return the account when passed the account', async () => {
+ assert.equal(await test.getFromAccount(account), account)
+ })
+
+ it('should revert when not passed an account', async () => {
+ await assertRevert(test.getFromAccount(account), account)
+ })
+ })
+
+ describe(`when the account has authorized a ${test.name}`, () => {
+ const authorized = accounts[1]
+
+ before(async () => {
+ const sig = await getParsedSignatureOfAddress(account, authorized)
+ await test.fn(authorized, sig.v, sig.r, sig.s)
+ })
+
+ it(`should return the ${test.name} when passed the account`, async () => {
+ assert.equal(await test.getFromAccount(account), authorized)
+ })
+ })
+ })
+ }
+
+ describe('#lock()', () => {
+ const value = 1000
+
+ it("should increase the account's nonvoting locked gold balance", async () => {
+ // @ts-ignore: TODO(mcortesi) fix typings for TransactionDetails
+ await lockedGold.lock({ value })
+ assert.equal(await lockedGold.getAccountNonvotingLockedGold(account), value)
+ })
+
+ it("should increase the account's total locked gold balance", async () => {
+ // @ts-ignore: TODO(mcortesi) fix typings for TransactionDetails
+ await lockedGold.lock({ value })
+ assert.equal(await lockedGold.getAccountTotalLockedGold(account), value)
+ })
+
+ it('should increase the nonvoting locked gold balance', async () => {
+ // @ts-ignore: TODO(mcortesi) fix typings for TransactionDetails
+ await lockedGold.lock({ value })
+ assert.equal(await lockedGold.getNonvotingLockedGold(), value)
+ })
+
+ it('should increase the total locked gold balance', async () => {
+ // @ts-ignore: TODO(mcortesi) fix typings for TransactionDetails
+ await lockedGold.lock({ value })
+ assert.equal(await lockedGold.getTotalLockedGold(), value)
+ })
+
+ it('should emit a GoldLocked event', async () => {
+ // @ts-ignore: TODO(mcortesi) fix typings for TransactionDetails
+ const resp = await lockedGold.lock({ value })
+ assert.equal(resp.logs.length, 1)
+ const log = resp.logs[0]
+ assertLogMatches(log, 'GoldLocked', {
+ account,
+ value: new BigNumber(value),
+ })
+ })
+
+ it('should revert when the specified value is 0', async () => {
+ await assertRevert(lockedGold.lock({ value: 0 }))
+ })
+
+ it('should revert when the account does not exist', async () => {
+ await assertRevert(lockedGold.lock({ value, from: accounts[1] }))
+ })
+ })
+
+ describe('#unlock()', () => {
+ const value = 1000
+ let availabilityTime: BigNumber
+ let resp: any
+ describe('when there are no balance requirements', () => {
+ before(async () => {
+ // @ts-ignore: TODO(mcortesi) fix typings for TransactionDetails
+ await lockedGold.lock({ value })
+ resp = await lockedGold.unlock(value)
+ availabilityTime = new BigNumber(unlockingPeriod).plus(
+ (await web3.eth.getBlock('latest')).timestamp
+ )
+ })
+
+ it('should add a pending withdrawal', async () => {
+ const pendingWithdrawals = await lockedGold.getPendingWithdrawals(account)
+ assert.equal(pendingWithdrawals.length, 1)
+ assert.equal(pendingWithdrawals[0], value)
+ assert.equal(pendingWithdrawals[1], availabilityTime)
+ })
+
+ it("should decrease the account's nonvoting locked gold balance", async () => {
+ assert.equal(await lockedGold.getAccountNonvotingLockedGold(account), 0)
+ })
+
+ it("should decrease the account's total locked gold balance", async () => {
+ assert.equal(await lockedGold.getAccountTotalLockedGold(account), 0)
+ })
+
+ it('should decrease the nonvoting locked gold balance', async () => {
+ assert.equal(await lockedGold.getNonvotingLockedGold(), 0)
+ })
+
+ it('should decrease the total locked gold balance', async () => {
+ assert.equal(await lockedGold.getTotalLockedGold(), 0)
+ })
+
+ it('should emit a GoldUnlocked event', async () => {
+ assert.equal(resp.logs.length, 1)
+ const log = resp.logs[0]
+ assertLogMatches(log, 'GoldUnlocked', {
+ account,
+ value: new BigNumber(value),
+ available,
+ })
+ })
+ })
+
+ describe('when there are balance requirements', () => {
+ let mustMaintain: any
+ before(async () => {
+ // @ts-ignore: TODO(mcortesi) fix typings for TransactionDetails
+ await lockedGold.lock({ value })
+ // Allow ourselves to call `setAccountMustMaintain()`
+ await registry.setAddressFor('Election', account)
+ const timestamp = (await web3.eth.getBlock('latest')).timestamp
+ const mustMaintain = { value: 100, timestamp: timestamp + DAY }
+ await lockedGold.setAccountMustMaintain(account, mustMaintain.value, mustMaintain.timestamp)
+ })
+
+ describe('when unlocking would yield a locked gold balance less than the required value', () => {
+ describe('when the the current time is earlier than the requirement time', () => {
+ it('should revert', async () => {
+ await assertRevert(lockedGold.unlock(value))
+ })
+ })
+
+ describe('when the the current time is later than the requirement time', () => {
+ it('should succeed', async () => {
+ await timeTravel(web3, DAY)
+ await lockedGold.unlock(value)
+ })
+ })
+ })
+
+ describe('when unlocking would yield a locked gold balance equal to the required value', () => {
+ it('should succeed', async () => {
+ await lockedGold.unlock(value - mustMaintain.value)
+ })
+ })
+ })
+ })
+
+ describe('#relock()', () => {
+ const value = 1000
+ const index = 0
+ describe('when a pending withdrawal exists', () => {
+ before(async () => {
+ // @ts-ignore: TODO(mcortesi) fix typings for TransactionDetails
+ await lockedGold.lock({ value })
+ await lockedGold.unlock(value)
+ resp = await lockedGold.relock(index)
+ })
+
+ it("should increase the account's nonvoting locked gold balance", async () => {
+ assert.equal(await lockedGold.getAccountNonvotingLockedGold(account), value)
+ })
+
+ it("should increase the account's total locked gold balance", async () => {
+ assert.equal(await lockedGold.getAccountTotalLockedGold(account), value)
+ })
+
+ it('should increase the nonvoting locked gold balance', async () => {
+ assert.equal(await lockedGold.getNonvotingLockedGold(), value)
+ })
+
+ it('should increase the total locked gold balance', async () => {
+ assert.equal(await lockedGold.getTotalLockedGold(), value)
+ })
+
+ it('should emit a GoldLocked event', async () => {
+ assert.equal(resp.logs.length, 1)
+ const log = resp.logs[0]
+ assertLogMatches(log, 'GoldLocked', {
+ account,
+ value: new BigNumber(value),
+ })
+ })
+
+ it('should remove the pending withdrawal', async () => {
+ const pendingWithdrawals = await lockedGold.getPendingWithdrawals(account)
+ assert.equal(pendingWithdrawals.length, 0)
+ })
+ })
+
+ describe('when a pending withdrawal does not exist', () => {
+ it('should revert', async () => {
+ await assertRevert(lockedGold.relock(index))
+ })
+ })
+ })
+
+ describe('#withdraw()', () => {
+ const value = 1000
+ const index = 0
+ let availabilityTime: BigNumber
+ let resp: any
+ describe('when a pending withdrawal exists', () => {
+ before(async () => {
+ // @ts-ignore: TODO(mcortesi) fix typings for TransactionDetails
+ await lockedGold.lock({ value })
+ resp = await lockedGold.unlock(value)
+ availabilityTime = new BigNumber(unlockingPeriod).plus(
+ (await web3.eth.getBlock('latest')).timestamp
+ )
+ })
+
+ describe('when it is after the availablity time', () => {
+ before(async () => {
+ await timeTravel(web3, unlockingPeriod)
+ resp = await lockedGold.withdraw(index)
+ })
+
+ it('should remove the pending withdrawal', async () => {
+ const pendingWithdrawals = await lockedGold.getPendingWithdrawals(account)
+ assert.equal(pendingWithdrawals.length, 0)
+ })
+
+ it('should emit a GoldWithdrawn event', async () => {
+ assert.equal(resp.logs.length, 1)
+ const log = resp.logs[0]
+ assertLogMatches(log, 'GoldWithdrawn', {
+ account,
+ value: new BigNumber(value),
+ })
+ })
+ })
+
+ describe('when it is before the availablity time', () => {
+ it('should revert', async () => {
+ await assertRevert(lockedGold.withdraw(index))
+ })
+ })
+ })
+
+ describe('when a pending withdrawal does not exist', () => {
+ it('should revert', async () => {
+ await assertRevert(lockedGold.withdraw(index))
+ })
+ })
+ })
+})
diff --git a/packages/protocol/test/governance/validators.ts b/packages/protocol/test/governance/validators.ts
index ea8b413d8a9..a0f3be9f4fc 100644
--- a/packages/protocol/test/governance/validators.ts
+++ b/packages/protocol/test/governance/validators.ts
@@ -1,4 +1,5 @@
import { CeloContractName } from '@celo/protocol/lib/registry-utils'
+import { fixed1, toFixed, fromFixed, multiply } from '@celo/utils/lib/fixidity'
import {
assertContainSubset,
assertEqualBN,
@@ -25,7 +26,6 @@ Validators.numberFormat = 'BigNumber'
const parseValidatorParams = (validatorParams: any) => {
return {
- identifier: validatorParams[0],
name: validatorParams[1],
url: validatorParams[2],
publicKeysData: validatorParams[3],
@@ -35,13 +35,17 @@ const parseValidatorParams = (validatorParams: any) => {
const parseValidatorGroupParams = (groupParams: any) => {
return {
- identifier: groupParams[0],
name: groupParams[1],
url: groupParams[2],
members: groupParams[3],
}
}
+const HOUR = 60 * 60
+const DAY = 24 * HOUR
+const YEAR = 365 * DAY
+const MAX_UINT256 = new BigInt(2).pow(256).minus(1)
+
contract('Validators', (accounts: string[]) => {
let validators: ValidatorsInstance
let registry: RegistryInstance
@@ -57,12 +61,15 @@ contract('Validators', (accounts: string[]) => {
const publicKeysData = '0x' + publicKey + blsPublicKey + blsPoP
const nonOwner = accounts[1]
- const minElectableValidators = new BigNumber(4)
- const maxElectableValidators = new BigNumber(6)
- const registrationRequirement = { value: new BigNumber(100), noticePeriod: new BigNumber(60) }
- const identifier = 'test-identifier'
+ const registrationRequirements = { group: new BigNumber(1000), validator: new BigNumber(100) }
+ const deregistrationLockups = {
+ group: new BigNumber(100 * DAY),
+ validator: new BigNumber(60 * DAY),
+ }
+ const maxGroupSize = 5
const name = 'test-name'
const url = 'test-url'
+ const commission = toFixed(1 / 100)
beforeEach(async () => {
validators = await Validators.new()
mockLockedGold = await MockLockedGold.new()
@@ -70,43 +77,28 @@ contract('Validators', (accounts: string[]) => {
await registry.setAddressFor(CeloContractName.LockedGold, mockLockedGold.address)
await validators.initialize(
registry.address,
- minElectableValidators,
- maxElectableValidators,
- registrationRequirement.value,
- registrationRequirement.noticePeriod
+ registrationRequirements.group,
+ registrationRequirements.validator,
+ deregistrationLockups.group,
+ deregistrationLockups.validator,
+ maxGroupSize
)
})
const registerValidator = async (validator: string) => {
- await mockLockedGold.setLockedCommitment(
- validator,
- registrationRequirement.noticePeriod,
- registrationRequirement.value
- )
+ await mockLockedGold.setAccountTotalLockedGold(validator, registrationRequirements.validator)
await validators.registerValidator(
- identifier,
name,
url,
// @ts-ignore bytes type
publicKeysData,
- registrationRequirement.noticePeriod,
{ from: validator }
)
}
const registerValidatorGroup = async (group: string) => {
- await mockLockedGold.setLockedCommitment(
- group,
- registrationRequirement.noticePeriod,
- registrationRequirement.value
- )
- await validators.registerValidatorGroup(
- identifier,
- name,
- url,
- registrationRequirement.noticePeriod,
- { from: group }
- )
+ await mockLockedGold.setAccountTotalLockedGold(group, registrationRequirements.group)
+ await validators.registerValidatorGroup(name, url, commission, { from: group })
}
const registerValidatorGroupWithMembers = async (group: string, members: string[]) => {
@@ -124,241 +116,256 @@ contract('Validators', (accounts: string[]) => {
assert.equal(owner, accounts[0])
})
- it('should have set minElectableValidators', async () => {
- const actualMinElectableValidators = await validators.minElectableValidators()
- assertEqualBN(actualMinElectableValidators, minElectableValidators)
- })
-
- it('should have set maxElectableValidators', async () => {
- const actualMaxElectableValidators = await validators.maxElectableValidators()
- assertEqualBN(actualMaxElectableValidators, maxElectableValidators)
+ it('should have set the registration requirements', async () => {
+ const [group, validator] = await validators.getRegistrationRequirement()
+ assertEqualBN(group, registrationRequirements.group)
+ assertEqualBN(validator, registrationRequirements.validator)
})
- it('should have set the registration requirements', async () => {
- const [value, noticePeriod] = await validators.getRegistrationRequirement()
- assertEqualBN(value, registrationRequirement.value)
- assertEqualBN(noticePeriod, registrationRequirement.noticePeriod)
+ it('should have set the deregistration lockups', async () => {
+ const [group, validator] = await validators.getDeregistrationLockups()
+ assertEqualBN(group, deregistrationLockups.group)
+ assertEqualBN(validator, deregistrationLockups.validator)
})
it('should not be callable again', async () => {
await assertRevert(
validators.initialize(
registry.address,
- minElectableValidators,
- maxElectableValidators,
- registrationRequirement.value,
- registrationRequirement.noticePeriod
+ registrationRequirements.group,
+ registrationRequirements.validator,
+ deregistrationLockups.group,
+ deregistrationLockups.validator,
+ maxGroupSize
)
)
})
})
- describe('#setMinElectableValidators', () => {
- const newMinElectableValidators = minElectableValidators.plus(1)
- it('should set the minimum deposit', async () => {
- await validators.setMinElectableValidators(newMinElectableValidators)
- assertEqualBN(await validators.minElectableValidators(), newMinElectableValidators)
- })
-
- it('should emit the MinElectableValidatorsSet event', async () => {
- const resp = await validators.setMinElectableValidators(newMinElectableValidators)
- assert.equal(resp.logs.length, 1)
- const log = resp.logs[0]
- assertContainSubset(log, {
- event: 'MinElectableValidatorsSet',
- args: {
- minElectableValidators: new BigNumber(newMinElectableValidators),
- },
- })
- })
+ describe('#setRegistrationRequirements()', () => {
+ describe('when the requirements are different', () => {
+ describe('when called by the owner', () => {
+ let resp: any
+ const newRequirements = {
+ group: registrationRequirements.group.plus(1),
+ validator: registrationRequirements.validator.plus(1),
+ }
+
+ before(async () => {
+ resp = await validators.setRegistrationRequirements(
+ newRequirements.group,
+ newRequirements.validator
+ )
+ })
- it('should revert when the minElectableValidators is zero', async () => {
- await assertRevert(validators.setMinElectableValidators(0))
- })
+ it('should set the group and validator requirements', async () => {
+ const [group, validator] = await validators.getRegistrationRequirements()
+ assertEqualBN(group, newRequirements.group)
+ assertEqualBN(validator, newRequirements.validator)
+ })
- it('should revert when the minElectableValidators is greater than maxElectableValidators', async () => {
- await assertRevert(validators.setMinElectableValidators(maxElectableValidators.plus(1)))
- })
+ it('should emit the RegistrationRequirementsSet event', async () => {
+ assert.equal(resp.logs.length, 1)
+ const log = resp.logs[0]
+ assertContainSubset(log, {
+ event: 'RegistrationRequirementsSet',
+ args: {
+ group: new BigNumber(newRequirements.group),
+ validator: new BigNumber(newRequirements.validator),
+ },
+ })
+ })
+ })
- it('should revert when the minElectableValidators is unchanged', async () => {
- await assertRevert(validators.setMinElectableValidators(minElectableValidators))
+ describe('when the requirements are the same', () => {
+ it('should revert', async () => {
+ await assertRevert(
+ validators.setRegistrationRequirements(
+ registrationRequirements.group,
+ registrationRequirements.validator
+ )
+ )
+ })
+ })
})
- it('should revert when called by anyone other than the owner', async () => {
- await assertRevert(
- validators.setMinElectableValidators(newMinElectableValidators, { from: nonOwner })
- )
+ describe('when called by a non-owner', () => {
+ it('should revert', async () => {
+ await assertRevert(
+ validators.setRegistrationRequirements(newRequirements.group, newRequirements.validator, {
+ from: nonOwner,
+ })
+ )
+ })
})
})
- describe('#setMaxElectableValidators', () => {
- const newMaxElectableValidators = maxElectableValidators.plus(1)
- it('should set the minimum deposit', async () => {
- await validators.setMaxElectableValidators(newMaxElectableValidators)
- assertEqualBN(await validators.maxElectableValidators(), newMaxElectableValidators)
- })
+ describe('#setDeregistrationLockups()', () => {
+ describe('when the requirements are different', () => {
+ describe('when called by the owner', () => {
+ let resp: any
+ const newLockups = {
+ group: deregistrationLockups.group.plus(1),
+ validator: deregistrationLockups.validator.plus(1),
+ }
+
+ before(async () => {
+ resp = await validators.setDeregistrationLockups(newLockups.group, newLockups.validator)
+ })
- it('should emit the MaxElectableValidatorsSet event', async () => {
- const resp = await validators.setMaxElectableValidators(newMaxElectableValidators)
- assert.equal(resp.logs.length, 1)
- const log = resp.logs[0]
- assertContainSubset(log, {
- event: 'MaxElectableValidatorsSet',
- args: {
- maxElectableValidators: new BigNumber(newMaxElectableValidators),
- },
- })
- })
+ it('should set the group and validator requirements', async () => {
+ const [group, validator] = await validators.getDeregistrationLockups()
+ assertEqualBN(group, newLockups.group)
+ assertEqualBN(validator, newLockups.validator)
+ })
- it('should revert when the maxElectableValidators is less than minElectableValidators', async () => {
- await assertRevert(validators.setMaxElectableValidators(minElectableValidators.minus(1)))
- })
+ it('should emit the DeregistrationLockupsSet event', async () => {
+ assert.equal(resp.logs.length, 1)
+ const log = resp.logs[0]
+ assertContainSubset(log, {
+ event: 'DeregistrationLockupsSet',
+ args: {
+ group: new BigNumber(newLockups.group),
+ validator: new BigNumber(newLockups.validator),
+ },
+ })
+ })
+ })
- it('should revert when the maxElectableValidators is unchanged', async () => {
- await assertRevert(validators.setMaxElectableValidators(maxElectableValidators))
+ describe('when the requirements are the same', () => {
+ it('should revert', async () => {
+ await assertRevert(
+ validators.setDeregistrationLockups(
+ deregistrationLockups.group,
+ deregistrationLockups.validator
+ )
+ )
+ })
+ })
})
- it('should revert when called by anyone other than the owner', async () => {
- await assertRevert(
- validators.setMaxElectableValidators(newMaxElectableValidators, { from: nonOwner })
- )
+ describe('when called by a non-owner', () => {
+ it('should revert', async () => {
+ await assertRevert(
+ validators.setDeregistrationLockups(newLockups.group, newLockups.validator, {
+ from: nonOwner,
+ })
+ )
+ })
})
})
- describe('#setRegistrationRequirement', () => {
- const newValue = registrationRequirement.value.plus(1)
- const newNoticePeriod = registrationRequirement.noticePeriod.plus(1)
+ describe('#setMaxGroupSize()', () => {
+ describe('when the size is different', () => {
+ describe('when called by the owner', () => {
+ let resp: any
+ const newSize = maxGroupSize.plus(1)
- it('should set the value and notice period', async () => {
- await validators.setRegistrationRequirement(newValue, newNoticePeriod)
- const [value, noticePeriod] = await validators.getRegistrationRequirement()
- assertEqualBN(value, newValue)
- assertEqualBN(noticePeriod, newNoticePeriod)
- })
+ before(async () => {
+ resp = await validators.setMaxGroupSize(newSize)
+ })
- it('should emit the RegistrationRequirementSet event', async () => {
- const resp = await validators.setRegistrationRequirement(newValue, newNoticePeriod)
- assert.equal(resp.logs.length, 1)
- const log = resp.logs[0]
- assertContainSubset(log, {
- event: 'RegistrationRequirementSet',
- args: {
- value: new BigNumber(newValue),
- noticePeriod: new BigNumber(newNoticePeriod),
- },
+ it('should set the max group size', async () => {
+ const size = await validators.getMaxGroupSize()
+ assertEqualBN(size, newSize)
+ })
+
+ it('should emit the MaxGroupSizeSet event', async () => {
+ assert.equal(resp.logs.length, 1)
+ const log = resp.logs[0]
+ assertContainSubset(log, {
+ event: 'MaxGroupSizeSet',
+ args: {
+ maxGroupSize: new BigNumber(newSize),
+ },
+ })
+ })
})
- })
- it('should revert when the requirement is unchanged', async () => {
- await assertRevert(
- validators.setRegistrationRequirement(
- registrationRequirement.value,
- registrationRequirement.noticePeriod
- )
- )
+ describe('when the size is the same', () => {
+ it('should revert', async () => {
+ await assertRevert(validators.setMaxGroupSize(maxGroupSize))
+ })
+ })
})
- it('should revert when called by anyone other than the owner', async () => {
- await assertRevert(
- validators.setRegistrationRequirement(newValue, newNoticePeriod, { from: nonOwner })
- )
+ describe('when called by a non-owner', () => {
+ it('should revert', async () => {
+ await assertRevert(validators.setMaxGroupSize(maxGroupSize, { from: nonOwner }))
+ })
})
})
describe('#registerValidator', () => {
const validator = accounts[0]
- beforeEach(async () => {
- await mockLockedGold.setLockedCommitment(
- validator,
- registrationRequirement.noticePeriod,
- registrationRequirement.value
- )
- })
+ let resp: any
+ describe('when the account is not a registered validator', () => {
+ before(async () => {
+ await mockLockedGold.setAccountTotalLockedGold(
+ validator,
+ registrationRequirements.validator
+ )
+ resp = await validators.registerValidator(
+ name,
+ url,
+ // @ts-ignore bytes type
+ publicKeysData
+ )
+ })
- it('should mark the account as a validator', async () => {
- await validators.registerValidator(
- identifier,
- name,
- url,
- // @ts-ignore bytes type
- publicKeysData,
- registrationRequirement.noticePeriod
- )
- assert.isTrue(await validators.isValidator(validator))
- })
+ it('should mark the account as a validator', async () => {
+ assert.isTrue(await validators.isValidator(validator))
+ })
- it('should add the account to the list of validators', async () => {
- await validators.registerValidator(
- identifier,
- name,
- url,
- // @ts-ignore bytes type
- publicKeysData,
- registrationRequirement.noticePeriod
- )
- assert.deepEqual(await validators.getRegisteredValidators(), [validator])
- })
+ it('should add the account to the list of validators', async () => {
+ assert.deepEqual(await validators.getRegisteredValidators(), [validator])
+ })
- it('should set the validator identifier, name, url, and public key', async () => {
- await validators.registerValidator(
- identifier,
- name,
- url,
- // @ts-ignore bytes type
- publicKeysData,
- registrationRequirement.noticePeriod
- )
- const parsedValidator = parseValidatorParams(await validators.getValidator(validator))
- assert.equal(parsedValidator.identifier, identifier)
- assert.equal(parsedValidator.name, name)
- assert.equal(parsedValidator.url, url)
- assert.equal(parsedValidator.publicKeysData, publicKeysData)
- })
+ it('should set the validator name, url, and public key', async () => {
+ const parsedValidator = parseValidatorParams(await validators.getValidator(validator))
+ assert.equal(parsedValidator.name, name)
+ assert.equal(parsedValidator.url, url)
+ assert.equal(parsedValidator.publicKeysData, publicKeysData)
+ })
- it('should emit the ValidatorRegistered event', async () => {
- const resp = await validators.registerValidator(
- identifier,
- name,
- url,
- // @ts-ignore bytes type
- publicKeysData,
- registrationRequirement.noticePeriod
- )
- assert.equal(resp.logs.length, 1)
- const log = resp.logs[0]
- assertContainSubset(log, {
- event: 'ValidatorRegistered',
- args: {
- validator,
- identifier,
- name,
- url,
- publicKeysData,
- },
+ it('should set account balance requirements on locked gold', async () => {
+ const [value, timestamp] = await mockLockedGold.getAccountMustMaintain(validator)
+ assert.equal(value, registrationRequirements.validator)
+ assert.equal(timestamp, MAX_UINT256)
+ })
+
+ it('should emit the ValidatorRegistered event', async () => {
+ assert.equal(resp.logs.length, 1)
+ const log = resp.logs[0]
+ assertContainSubset(log, {
+ event: 'ValidatorRegistered',
+ args: {
+ validator,
+ name,
+ url,
+ publicKeysData,
+ },
+ })
})
})
describe('when the account is already a registered validator', () => {
- beforeEach(async () => {
+ before(async () => {
await validators.registerValidator(
- identifier,
name,
url,
// @ts-ignore bytes type
- publicKeysData,
- registrationRequirement.noticePeriod
+ publicKeysData
)
})
it('should revert', async () => {
await assertRevert(
validators.registerValidator(
- identifier,
name,
url,
// @ts-ignore bytes type
- publicKeysData,
- registrationRequirement.noticePeriod
+ publicKeysData
)
)
})
@@ -366,23 +373,16 @@ contract('Validators', (accounts: string[]) => {
describe('when the account is already a registered validator group', () => {
beforeEach(async () => {
- await validators.registerValidatorGroup(
- identifier,
- name,
- url,
- registrationRequirement.noticePeriod
- )
+ await validators.registerValidatorGroup(name, url, commission)
})
it('should revert', async () => {
await assertRevert(
validators.registerValidator(
- identifier,
name,
url,
// @ts-ignore bytes type
- publicKeysData,
- registrationRequirement.noticePeriod
+ publicKeysData
)
)
})
@@ -390,22 +390,19 @@ contract('Validators', (accounts: string[]) => {
describe('when the account does not meet the registration requirements', () => {
beforeEach(async () => {
- await mockLockedGold.setLockedCommitment(
+ await mockLockedGold.setAccountTotalLockedGold(
validator,
- registrationRequirement.noticePeriod,
- registrationRequirement.value.minus(1)
+ registrationRequirements.validator.minus(1)
)
})
it('should revert', async () => {
await assertRevert(
validators.registerValidator(
- identifier,
name,
url,
// @ts-ignore bytes type
- publicKeysData,
- registrationRequirement.noticePeriod
+ publicKeysData
)
)
})
@@ -415,29 +412,37 @@ contract('Validators', (accounts: string[]) => {
describe('#deregisterValidator', () => {
const validator = accounts[0]
const index = 0
- beforeEach(async () => {
- await registerValidator(validator)
- })
+ let resp: any
+ describe('when the account is not a registered validator', () => {
+ before(async () => {
+ await registerValidator(validator)
+ resp = await validators.deregisterValidator(index)
+ })
- it('should mark the account as not a validator', async () => {
- await validators.deregisterValidator(index)
- assert.isFalse(await validators.isValidator(validator))
- })
+ it('should mark the account as not a validator', async () => {
+ assert.isFalse(await validators.isValidator(validator))
+ })
- it('should remove the account from the list of validators', async () => {
- await validators.deregisterValidator(index)
- assert.deepEqual(await validators.getRegisteredValidators(), [])
- })
+ it('should remove the account from the list of validators', async () => {
+ assert.deepEqual(await validators.getRegisteredValidators(), [])
+ })
- it('should emit the ValidatorDeregistered event', async () => {
- const resp = await validators.deregisterValidator(index)
- assert.equal(resp.logs.length, 1)
- const log = resp.logs[0]
- assertContainSubset(log, {
- event: 'ValidatorDeregistered',
- args: {
- validator,
- },
+ it('should set account balance requirements on locked gold', async () => {
+ const latestTimestamp = (await web3.eth.getBlock('latest')).timestamp
+ const [value, timestamp] = await mockLockedGold.getAccountMustMaintain(validator)
+ assert.equal(value, registrationRequirements.validator)
+ assert.equal(timestamp, new BigNumber(timestamp).plus(deregistrationLockups.validator))
+ })
+
+ it('should emit the ValidatorDeregistered event', async () => {
+ assert.equal(resp.logs.length, 1)
+ const log = resp.logs[0]
+ assertContainSubset(log, {
+ event: 'ValidatorDeregistered',
+ args: {
+ validator,
+ },
+ })
})
})
@@ -749,64 +754,44 @@ contract('Validators', (accounts: string[]) => {
describe('#registerValidatorGroup', () => {
const group = accounts[0]
- beforeEach(async () => {
- await mockLockedGold.setLockedCommitment(
- group,
- registrationRequirement.noticePeriod,
- registrationRequirement.value
- )
- })
+ let resp: any
+ describe('when the account is not a registered validator group', () => {
+ before(async () => {
+ await mockLockedGold.setAccountTotalLockedGold(group, registrationRequirements.group)
+ resp = await validators.registerValidatorGroup(name, url, commission)
+ resp = await validators.registerValidator(
+ name,
+ url,
+ // @ts-ignore bytes type
+ publicKeysData
+ )
+ })
- it('should mark the account as a validator group', async () => {
- await validators.registerValidatorGroup(
- identifier,
- name,
- url,
- registrationRequirement.noticePeriod
- )
- assert.isTrue(await validators.isValidatorGroup(group))
- })
+ it('should mark the account as a validator group', async () => {
+ assert.isTrue(await validators.isValidatorGroup(group))
+ })
- it('should add the account to the list of validator groups', async () => {
- await validators.registerValidatorGroup(
- identifier,
- name,
- url,
- registrationRequirement.noticePeriod
- )
- assert.deepEqual(await validators.getRegisteredValidatorGroups(), [group])
- })
+ it('should add the account to the list of validator groups', async () => {
+ assert.deepEqual(await validators.getRegisteredValidatorGroups(), [group])
+ })
- it('should set the validator group identifier, name, and url', async () => {
- await validators.registerValidatorGroup(
- identifier,
- name,
- url,
- registrationRequirement.noticePeriod
- )
- const parsedGroup = parseValidatorGroupParams(await validators.getValidatorGroup(group))
- assert.equal(parsedGroup.identifier, identifier)
- assert.equal(parsedGroup.name, name)
- assert.equal(parsedGroup.url, url)
- })
+ it('should set the validator group name and url', async () => {
+ const parsedGroup = parseValidatorGroupParams(await validators.getValidatorGroup(group))
+ assert.equal(parsedGroup.name, name)
+ assert.equal(parsedGroup.url, url)
+ })
- it('should emit the ValidatorGroupRegistered event', async () => {
- const resp = await validators.registerValidatorGroup(
- identifier,
- name,
- url,
- registrationRequirement.noticePeriod
- )
- assert.equal(resp.logs.length, 1)
- const log = resp.logs[0]
- assertContainSubset(log, {
- event: 'ValidatorGroupRegistered',
- args: {
- group,
- identifier,
- name,
- url,
- },
+ it('should emit the ValidatorGroupRegistered event', async () => {
+ assert.equal(resp.logs.length, 1)
+ const log = resp.logs[0]
+ assertContainSubset(log, {
+ event: 'ValidatorGroupRegistered',
+ args: {
+ group,
+ name,
+ url,
+ },
+ })
})
})
@@ -817,56 +802,31 @@ contract('Validators', (accounts: string[]) => {
it('should revert', async () => {
await assertRevert(
- validators.registerValidatorGroup(
- identifier,
- name,
- url,
- registrationRequirement.noticePeriod
- )
+ validators.registerValidatorGroup(name, url, registrationRequirement.noticePeriod)
)
})
})
describe('when the account is already a registered validator group', () => {
beforeEach(async () => {
- await validators.registerValidatorGroup(
- identifier,
- name,
- url,
- registrationRequirement.noticePeriod
- )
+ await validators.registerValidatorGroup(name, url, commission)
})
it('should revert', async () => {
- await assertRevert(
- validators.registerValidatorGroup(
- identifier,
- name,
- url,
- registrationRequirement.noticePeriod
- )
- )
+ await assertRevert(validators.registerValidatorGroup(name, url, commission))
})
})
describe('when the account does not meet the registration requirements', () => {
beforeEach(async () => {
- await mockLockedGold.setLockedCommitment(
- group,
- registrationRequirement.noticePeriod,
- registrationRequirement.value.minus(1)
+ await mockLockedGold.setAccountTotalLockedGold(
+ validator,
+ registrationRequirements.group.minus(1)
)
})
it('should revert', async () => {
- await assertRevert(
- validators.registerValidatorGroup(
- identifier,
- name,
- url,
- registrationRequirement.noticePeriod
- )
- )
+ await assertRevert(validators.registerValidatorGroup(name, url, commission))
})
})
})
@@ -874,6 +834,7 @@ contract('Validators', (accounts: string[]) => {
describe('#deregisterValidatorGroup', () => {
const index = 0
const group = accounts[0]
+ let resp: any
beforeEach(async () => {
await registerValidatorGroup(group)
})
@@ -888,6 +849,14 @@ contract('Validators', (accounts: string[]) => {
assert.deepEqual(await validators.getRegisteredValidatorGroups(), [])
})
+ it('should set account balance requirements on locked gold', async () => {
+ await validators.deregisterValidatorGroup(index)
+ const latestTimestamp = (await web3.eth.getBlock('latest')).timestamp
+ const [value, timestamp] = await mockLockedGold.getAccountMustMaintain(group)
+ assert.equal(value, registrationRequirements.group)
+ assert.equal(timestamp, new BigNumber(timestamp).plus(deregistrationLockups.group))
+ })
+
it('should emit the ValidatorGroupDeregistered event', async () => {
const resp = await validators.deregisterValidatorGroup(index)
assert.equal(resp.logs.length, 1)
@@ -1006,31 +975,9 @@ contract('Validators', (accounts: string[]) => {
})
describe('when the validator is the only member of the group', () => {
- it('should emit the ValidatorGroupEmptied event', async () => {
- const resp = await validators.removeMember(validator)
- assert.equal(resp.logs.length, 2)
- const log = resp.logs[1]
- assertContainSubset(log, {
- event: 'ValidatorGroupEmptied',
- args: {
- group,
- },
- })
- })
-
- describe('when the group has received votes', () => {
- beforeEach(async () => {
- const voter = accounts[2]
- const weight = 10
- await mockLockedGold.setWeight(voter, weight)
- await validators.vote(group, NULL_ADDRESS, NULL_ADDRESS, { from: voter })
- })
-
- it('should remove the group from the list of electable groups with votes', async () => {
- await validators.removeMember(validator)
- const [groups] = await validators.getValidatorGroupVotes()
- assert.deepEqual(groups, [])
- })
+ it('should mark the group ineligible', async () => {
+ await validators.removeMember(validator)
+ assert.isTrue(await mockElection.isIneligible(group))
})
})
@@ -1100,281 +1047,4 @@ contract('Validators', (accounts: string[]) => {
})
})
})
-
- describe('#vote', () => {
- const weight = new BigNumber(5)
- const voter = accounts[0]
- const validator = accounts[1]
- const group = accounts[2]
- beforeEach(async () => {
- await registerValidatorGroupWithMembers(group, [validator])
- await mockLockedGold.setWeight(voter, weight)
- })
-
- it("should set the voter's vote", async () => {
- await validators.vote(group, NULL_ADDRESS, NULL_ADDRESS)
- assert.isTrue(await validators.isVoting(voter))
- assert.equal(await validators.voters(voter), group)
- })
-
- it('should add the group to the list of those receiving votes', async () => {
- await validators.vote(group, NULL_ADDRESS, NULL_ADDRESS)
- const [groups] = await validators.getValidatorGroupVotes()
- assert.deepEqual(groups, [group])
- })
-
- it("should increment the validator group's vote total", async () => {
- await validators.vote(group, NULL_ADDRESS, NULL_ADDRESS)
- assertEqualBN(await validators.getVotesReceived(group), weight)
- })
-
- it('should emit the ValidatorGroupVoteCast event', async () => {
- const resp = await validators.vote(group, NULL_ADDRESS, NULL_ADDRESS)
- assert.equal(resp.logs.length, 1)
- const log = resp.logs[0]
- assertContainSubset(log, {
- event: 'ValidatorGroupVoteCast',
- args: {
- account: voter,
- group,
- weight: new BigNumber(weight),
- },
- })
- })
-
- describe('when the group had not previously received votes', () => {
- it('should add the group to the list of electable groups with votes', async () => {
- await validators.vote(group, NULL_ADDRESS, NULL_ADDRESS)
- const [groups] = await validators.getValidatorGroupVotes()
- assert.deepEqual(groups, [group])
- })
- })
-
- it('should revert when the group is not a registered validator group', async () => {
- await assertRevert(validators.vote(accounts[3], NULL_ADDRESS, NULL_ADDRESS))
- })
-
- describe('when the group is empty', () => {
- beforeEach(async () => {
- await validators.removeMember(validator, { from: group })
- })
-
- it('should revert', async () => {
- await assertRevert(validators.vote(group, NULL_ADDRESS, NULL_ADDRESS))
- })
- })
-
- describe('when the account voting is frozen', () => {
- beforeEach(async () => {
- await mockLockedGold.setVotingFrozen(voter)
- })
-
- it('should revert', async () => {
- await assertRevert(validators.vote(group, NULL_ADDRESS, NULL_ADDRESS))
- })
- })
-
- describe('when the account has no weight', () => {
- beforeEach(async () => {
- await mockLockedGold.setWeight(voter, NULL_ADDRESS)
- })
-
- it('should revert', async () => {
- await assertRevert(validators.vote(group, NULL_ADDRESS, NULL_ADDRESS))
- })
- })
- describe('when the account has an outstanding vote', () => {
- beforeEach(async () => {
- await validators.vote(group, NULL_ADDRESS, NULL_ADDRESS)
- })
-
- it('should revert', async () => {
- await assertRevert(validators.vote(group, NULL_ADDRESS, NULL_ADDRESS))
- })
- })
- })
-
- describe('#revokeVote', () => {
- const weight = 5
- const voter = accounts[0]
- const validator = accounts[1]
- const group = accounts[2]
- beforeEach(async () => {
- await registerValidatorGroupWithMembers(group, [validator])
- await mockLockedGold.setWeight(voter, weight)
- await validators.vote(group, NULL_ADDRESS, NULL_ADDRESS)
- })
-
- it("should clear the voter's vote", async () => {
- await validators.revokeVote(NULL_ADDRESS, NULL_ADDRESS)
- assert.isFalse(await validators.isVoting(voter))
- assert.equal(await validators.voters(voter), NULL_ADDRESS)
- })
-
- it("should decrement the validator group's vote total", async () => {
- await validators.revokeVote(NULL_ADDRESS, NULL_ADDRESS)
- const [groups, votes] = await validators.getValidatorGroupVotes()
- assert.deepEqual(groups, [])
- assert.deepEqual(votes, [])
- })
-
- it('should emit the ValidatorGroupVoteRevoked event', async () => {
- const resp = await validators.revokeVote(NULL_ADDRESS, NULL_ADDRESS)
- assert.equal(resp.logs.length, 1)
- const log = resp.logs[0]
- assertContainSubset(log, {
- event: 'ValidatorGroupVoteRevoked',
- args: {
- account: voter,
- group,
- weight: new BigNumber(weight),
- },
- })
- })
-
- describe('when the group had not received other votes', () => {
- it('should remove the group from the list of electable groups with votes', async () => {
- await validators.revokeVote(NULL_ADDRESS, NULL_ADDRESS)
- const [groups] = await validators.getValidatorGroupVotes()
- assert.deepEqual(groups, [])
- })
- })
-
- describe('when the account does not have an outstanding vote', () => {
- beforeEach(async () => {
- await validators.revokeVote(NULL_ADDRESS, NULL_ADDRESS)
- })
-
- it('should revert', async () => {
- await assertRevert(validators.revokeVote(NULL_ADDRESS, NULL_ADDRESS))
- })
- })
- })
-
- describe('#getValidators', () => {
- const group1 = accounts[0]
- const group2 = accounts[1]
- const group3 = accounts[2]
- const validator1 = accounts[3]
- const validator2 = accounts[4]
- const validator3 = accounts[5]
- const validator4 = accounts[6]
- const validator5 = accounts[7]
- const validator6 = accounts[8]
- const validator7 = accounts[9]
-
- // If voterN votes for groupN:
- // group1 gets 20 votes per member
- // group2 gets 25 votes per member
- // group3 gets 30 votes per member
- // The ordering of the returned validators should be from group with most votes to group,
- // with fewest votes, and within each group, members are elected from first to last.
- const voter1 = { address: accounts[0], weight: 80 }
- const voter2 = { address: accounts[1], weight: 50 }
- const voter3 = { address: accounts[2], weight: 30 }
- const assertAddressesEqual = (actual: string[], expected: string[]) => {
- assert.deepEqual(actual.map((x) => x.toLowerCase()), expected.map((x) => x.toLowerCase()))
- }
-
- beforeEach(async () => {
- await registerValidatorGroupWithMembers(group1, [
- validator1,
- validator2,
- validator3,
- validator4,
- ])
- await registerValidatorGroupWithMembers(group2, [validator5, validator6])
- await registerValidatorGroupWithMembers(group3, [validator7])
-
- for (const voter of [voter1, voter2, voter3]) {
- await mockLockedGold.setWeight(voter.address, voter.weight)
- }
- })
-
- describe('when a single group has >= minElectableValidators as members and received votes', () => {
- beforeEach(async () => {
- await validators.vote(group1, NULL_ADDRESS, NULL_ADDRESS, { from: voter1.address })
- })
-
- it("should return that group's member list", async () => {
- assertAddressesEqual(await validators.getValidators(), [
- validator1,
- validator2,
- validator3,
- validator4,
- ])
- })
- })
-
- describe("when > maxElectableValidators members's groups receive votes", () => {
- beforeEach(async () => {
- await validators.vote(group1, NULL_ADDRESS, NULL_ADDRESS, { from: voter1.address })
- await validators.vote(group2, NULL_ADDRESS, group1, { from: voter2.address })
- await validators.vote(group3, NULL_ADDRESS, group2, { from: voter3.address })
- })
-
- it('should return maxElectableValidators elected validators', async () => {
- assertAddressesEqual(await validators.getValidators(), [
- validator1,
- validator2,
- validator3,
- validator5,
- validator6,
- validator7,
- ])
- })
- })
-
- describe('when a group receives enough votes for > n seats but only has n members', () => {
- beforeEach(async () => {
- await mockLockedGold.setWeight(voter3.address, 1000)
- await validators.vote(group3, NULL_ADDRESS, NULL_ADDRESS, { from: voter3.address })
- await validators.vote(group1, NULL_ADDRESS, group3, { from: voter1.address })
- await validators.vote(group2, NULL_ADDRESS, group1, { from: voter2.address })
- })
-
- it('should elect only n members from that group', async () => {
- assertAddressesEqual(await validators.getValidators(), [
- validator7,
- validator1,
- validator2,
- validator3,
- validator5,
- validator6,
- ])
- })
- })
-
- describe('when an account has delegated validating to another address', () => {
- const validatingDelegate = '0x47e172f6cfb6c7d01c1574fa3e2be7cc73269d95'
- beforeEach(async () => {
- await mockLockedGold.delegateValidating(validator3, validatingDelegate)
- await validators.vote(group1, NULL_ADDRESS, NULL_ADDRESS, { from: voter1.address })
- await validators.vote(group2, NULL_ADDRESS, group1, { from: voter2.address })
- await validators.vote(group3, NULL_ADDRESS, group2, { from: voter3.address })
- })
-
- it('should return the validating delegate in place of the account', async () => {
- assertAddressesEqual(await validators.getValidators(), [
- validator1,
- validator2,
- validatingDelegate,
- validator5,
- validator6,
- validator7,
- ])
- })
- })
-
- describe('when there are not enough electable validators', () => {
- beforeEach(async () => {
- await validators.vote(group2, NULL_ADDRESS, NULL_ADDRESS, { from: voter2.address })
- await validators.vote(group3, NULL_ADDRESS, group2, { from: voter3.address })
- })
-
- it('should revert', async () => {
- await assertRevert(validators.getValidators())
- })
- })
- })
})
From a6921d3769a4bae80c44f6194fd2504ad37ea21b Mon Sep 17 00:00:00 2001
From: Asa Oines
Date: Mon, 23 Sep 2019 19:54:04 -0700
Subject: [PATCH 007/149] LockedGold tests passing
---
.../protocol/contracts/common/Signatures.sol | 2 +-
.../contracts/governance/Election.sol | 2 +-
.../contracts/governance/LockedGold.sol | 31 +-
.../contracts/governance/Validators.sol | 2 +-
.../governance/interfaces/IElection.sol | 2 +-
.../governance/proxies/ElectionProxy.sol | 8 +
.../governance/test/MockElection.sol | 13 +
.../governance/test/MockLockedGold.sol | 8 +-
packages/protocol/lib/registry-utils.ts | 2 +
packages/protocol/migrations/10_lockedgold.ts | 2 +-
packages/protocol/migrations/11_validators.ts | 9 +-
packages/protocol/migrations/12_election.ts | 20 +
.../migrations/{12_random.ts => 13_random.ts} | 0
...{13_attestations.ts => 14_attestations.ts} | 0
.../migrations/{14_escrow.ts => 15_escrow.ts} | 0
.../{15_governance.ts => 16_governance.ts} | 1 +
...t_validators.ts => 17_elect_validators.ts} | 26 +-
packages/protocol/migrationsConfig.js | 22 +-
packages/protocol/test/common/integration.ts | 12 +-
packages/protocol/test/governance/election.ts | 1315 -----------------
.../protocol/test/governance/governance.ts | 63 +-
.../protocol/test/governance/lockedgold.ts | 310 ++--
22 files changed, 302 insertions(+), 1548 deletions(-)
create mode 100644 packages/protocol/contracts/governance/proxies/ElectionProxy.sol
create mode 100644 packages/protocol/migrations/12_election.ts
rename packages/protocol/migrations/{12_random.ts => 13_random.ts} (100%)
rename packages/protocol/migrations/{13_attestations.ts => 14_attestations.ts} (100%)
rename packages/protocol/migrations/{14_escrow.ts => 15_escrow.ts} (100%)
rename packages/protocol/migrations/{15_governance.ts => 16_governance.ts} (99%)
rename packages/protocol/migrations/{16_elect_validators.ts => 17_elect_validators.ts} (89%)
delete mode 100644 packages/protocol/test/governance/election.ts
diff --git a/packages/protocol/contracts/common/Signatures.sol b/packages/protocol/contracts/common/Signatures.sol
index 613fcb0369e..381702f0989 100644
--- a/packages/protocol/contracts/common/Signatures.sol
+++ b/packages/protocol/contracts/common/Signatures.sol
@@ -24,4 +24,4 @@ library Signatures {
bytes32 prefixedHash = keccak256(abi.encodePacked(prefix, hash));
return ecrecover(prefixedHash, v, r, s);
}
-}
\ No newline at end of file
+}
diff --git a/packages/protocol/contracts/governance/Election.sol b/packages/protocol/contracts/governance/Election.sol
index 294a6ffebea..1c151426dcf 100644
--- a/packages/protocol/contracts/governance/Election.sol
+++ b/packages/protocol/contracts/governance/Election.sol
@@ -365,7 +365,7 @@ contract Election is Ownable, ReentrancyGuard, Initializable, UsingRegistry {
votes.total.total = votes.total.total.add(value);
}
- function markGroupIneligible(address group) external onlyRegisteredContract('Validators') {
+ function markGroupIneligible(address group) external onlyRegisteredContract(VALIDATORS_REGISTRY_ID) {
votes.total.eligible.remove(group);
}
diff --git a/packages/protocol/contracts/governance/LockedGold.sol b/packages/protocol/contracts/governance/LockedGold.sol
index d860949a348..697e5eba8b5 100644
--- a/packages/protocol/contracts/governance/LockedGold.sol
+++ b/packages/protocol/contracts/governance/LockedGold.sol
@@ -110,7 +110,6 @@ contract LockedGold is ILockedGold, ReentrancyGuard, Initializable, UsingRegistr
/**
* @notice Locks gold to be used for voting.
- * @param value The amount of gold to be locked.
*/
function lock() external payable nonReentrant {
require(isAccount(msg.sender));
@@ -119,11 +118,11 @@ contract LockedGold is ILockedGold, ReentrancyGuard, Initializable, UsingRegistr
emit GoldLocked(msg.sender, msg.value);
}
- function incrementNonvotingAccountBalance(address account, uint256 value) external onlyRegisteredContract('Election') {
+ function incrementNonvotingAccountBalance(address account, uint256 value) external onlyRegisteredContract(ELECTION_REGISTRY_ID) {
_incrementNonvotingAccountBalance(account, value);
}
- function decrementNonvotingAccountBalance(address account, uint256 value) external onlyRegisteredContract('Election') {
+ function decrementNonvotingAccountBalance(address account, uint256 value) external onlyRegisteredContract(ELECTION_REGISTRY_ID) {
_decrementNonvotingAccountBalance(account, value);
}
@@ -139,12 +138,12 @@ contract LockedGold is ILockedGold, ReentrancyGuard, Initializable, UsingRegistr
// TODO: Can't unlock if voting in governance.
function unlock(uint256 value) external nonReentrant {
- require(isAccount(msg.sender));
+ require(isAccount(msg.sender), "not account");
Account storage account = accounts[msg.sender];
MustMaintain memory requirement = account.balances.requirements;
require(
now >= requirement.timestamp ||
- getAccountTotalLockedGold(msg.sender).sub(value) >= requirement.value
+ getAccountTotalLockedGold(msg.sender).sub(value) >= requirement.value, "didn't meet mustmaintain requirements"
);
_decrementNonvotingAccountBalance(msg.sender, value);
uint256 available = now.add(unlockingPeriod);
@@ -181,7 +180,7 @@ contract LockedGold is ILockedGold, ReentrancyGuard, Initializable, UsingRegistr
uint256 timestamp
)
public
- onlyRegisteredContract('Election')
+ onlyRegisteredContract(ELECTION_REGISTRY_ID)
nonReentrant
returns (bool)
{
@@ -199,7 +198,7 @@ contract LockedGold is ILockedGold, ReentrancyGuard, Initializable, UsingRegistr
function getAccountFromVoter(address accountOrVoter) external view returns (address) {
address authorizingAccount = authorizedBy[accountOrVoter];
if (authorizingAccount != address(0)) {
- require(accounts[authorizingAccount].authorizations.voting == accountOrVoter);
+ require(accounts[authorizingAccount].authorizations.voting == accountOrVoter, 'failed first check');
return authorizingAccount;
} else {
require(isAccount(accountOrVoter));
@@ -263,6 +262,19 @@ contract LockedGold is ILockedGold, ReentrancyGuard, Initializable, UsingRegistr
return validator == address(0) ? account : validator;
}
+ function getPendingWithdrawals(address account) public view returns (uint256[] memory, uint256[] memory) {
+ require(isAccount(account));
+ uint256 length = accounts[account].balances.pendingWithdrawals.length;
+ uint256[] memory values = new uint256[](length);
+ uint256[] memory timestamps = new uint256[](length);
+ for (uint256 i = 0; i < length; i++) {
+ PendingWithdrawal memory pendingWithdrawal = accounts[account].balances.pendingWithdrawals[i];
+ values[i] = pendingWithdrawal.value;
+ timestamps[i] = pendingWithdrawal.timestamp;
+ }
+ return (values, timestamps);
+ }
+
/**
* @notice Authorizes voting or validating power of `msg.sender`'s account to another address.
* @param current The address to authorize.
@@ -281,12 +293,11 @@ contract LockedGold is ILockedGold, ReentrancyGuard, Initializable, UsingRegistr
bytes32 s
)
private
- nonReentrant
{
- require(isAccount(msg.sender) && isNotAccount(current) && isNotAuthorized(current));
+ require(isAccount(msg.sender) && isNotAccount(current) && isNotAuthorized(current), "Accounts");
address signer = Signatures.getSignerOfAddress(msg.sender, v, r, s);
- require(signer == current);
+ require(signer == current, "Signature");
authorizedBy[previous] = address(0);
authorizedBy[current] = msg.sender;
diff --git a/packages/protocol/contracts/governance/Validators.sol b/packages/protocol/contracts/governance/Validators.sol
index ea344f95c82..74b462efa66 100644
--- a/packages/protocol/contracts/governance/Validators.sol
+++ b/packages/protocol/contracts/governance/Validators.sol
@@ -341,7 +341,7 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
function registerValidatorGroup(
string calldata name,
string calldata url,
- uint256 commission,
+ uint256 commission
)
external
nonReentrant
diff --git a/packages/protocol/contracts/governance/interfaces/IElection.sol b/packages/protocol/contracts/governance/interfaces/IElection.sol
index c6935b95abb..03c68fca96c 100644
--- a/packages/protocol/contracts/governance/interfaces/IElection.sol
+++ b/packages/protocol/contracts/governance/interfaces/IElection.sol
@@ -3,7 +3,7 @@ pragma solidity ^0.5.3;
interface IElection {
function getTotalVotes() external view returns (uint256);
- function getAccountTotalVotes(address account) external view returns (uint256);
+ function getAccountTotalVotes(address) external view returns (uint256);
function markGroupIneligible(address) external;
function electValidators() external view returns (address[] memory);
}
diff --git a/packages/protocol/contracts/governance/proxies/ElectionProxy.sol b/packages/protocol/contracts/governance/proxies/ElectionProxy.sol
new file mode 100644
index 00000000000..f8f99107a2e
--- /dev/null
+++ b/packages/protocol/contracts/governance/proxies/ElectionProxy.sol
@@ -0,0 +1,8 @@
+pragma solidity ^0.5.3;
+
+import "../../common/Proxy.sol";
+
+
+/* solhint-disable no-empty-blocks */
+contract ElectionProxy is Proxy {
+}
diff --git a/packages/protocol/contracts/governance/test/MockElection.sol b/packages/protocol/contracts/governance/test/MockElection.sol
index e0e85344631..6458ad20f5c 100644
--- a/packages/protocol/contracts/governance/test/MockElection.sol
+++ b/packages/protocol/contracts/governance/test/MockElection.sol
@@ -12,4 +12,17 @@ contract MockElection is IElection {
function markGroupIneligible(address account) external {
isIneligible[account] = true;
}
+
+ function getTotalVotes() external view returns (uint256) {
+ return 0;
+ }
+
+ function getAccountTotalVotes(address) external view returns (uint256) {
+ return 0;
+ }
+
+ function electValidators() external view returns (address[] memory) {
+ address[] memory r = new address[](0);
+ return r;
+ }
}
diff --git a/packages/protocol/contracts/governance/test/MockLockedGold.sol b/packages/protocol/contracts/governance/test/MockLockedGold.sol
index 5d757728b8d..984960ed72d 100644
--- a/packages/protocol/contracts/governance/test/MockLockedGold.sol
+++ b/packages/protocol/contracts/governance/test/MockLockedGold.sol
@@ -13,6 +13,7 @@ contract MockLockedGold is ILockedGold {
}
mapping(address => uint256) public totalLockedGold;
+ // TODO(asa): Rename to minimumBalance
mapping(address => MustMaintain) public mustMaintain;
@@ -20,13 +21,14 @@ contract MockLockedGold is ILockedGold {
return accountOrValidator;
}
- function setAccountMustMaintain(address account, uint256 value, uint256 timestamp) external {
+ function setAccountMustMaintain(address account, uint256 value, uint256 timestamp) external returns (bool) {
mustMaintain[account] = MustMaintain(value, timestamp);
+ return true;
}
function getAccountMustMaintain(address account) external view returns (uint256, uint256) {
- MustMaintain storage mustMaintain = mustMaintain[account];
- return (mustMaintain.value, mustMaintain.timestamp);
+ MustMaintain storage m = mustMaintain[account];
+ return (m.value, m.timestamp);
}
function setAccountTotalLockedGold(address account, uint256 value) external {
diff --git a/packages/protocol/lib/registry-utils.ts b/packages/protocol/lib/registry-utils.ts
index b6f225e63b8..f8176c373bc 100644
--- a/packages/protocol/lib/registry-utils.ts
+++ b/packages/protocol/lib/registry-utils.ts
@@ -1,6 +1,7 @@
export enum CeloContractName {
Attestations = 'Attestations',
LockedGold = 'LockedGold',
+ Election = 'Election',
Escrow = 'Escrow',
Exchange = 'Exchange',
GasCurrencyWhitelist = 'GasCurrencyWhitelist',
@@ -24,6 +25,7 @@ export const usesRegistry = [
// TODO(amy): Find another way to create this list
export const hasEntryInRegistry: string[] = [
CeloContractName.Attestations,
+ CeloContractName.Election,
CeloContractName.Escrow,
CeloContractName.Exchange,
CeloContractName.GoldToken,
diff --git a/packages/protocol/migrations/10_lockedgold.ts b/packages/protocol/migrations/10_lockedgold.ts
index 48c601b92bd..5eb603e7fb0 100644
--- a/packages/protocol/migrations/10_lockedgold.ts
+++ b/packages/protocol/migrations/10_lockedgold.ts
@@ -7,5 +7,5 @@ module.exports = deploymentForCoreContract(
web3,
artifacts,
CeloContractName.LockedGold,
- async () => [config.registry.predeployedProxyAddress, config.lockedGold.maxNoticePeriod]
+ async () => [config.registry.predeployedProxyAddress, config.lockedGold.unlockingPeriod]
)
diff --git a/packages/protocol/migrations/11_validators.ts b/packages/protocol/migrations/11_validators.ts
index 6a23bd2c6e8..4a76fac9fcd 100644
--- a/packages/protocol/migrations/11_validators.ts
+++ b/packages/protocol/migrations/11_validators.ts
@@ -6,10 +6,11 @@ import { ValidatorsInstance } from 'types'
const initializeArgs = async (): Promise => {
return [
config.registry.predeployedProxyAddress,
- config.validators.minElectableValidators,
- config.validators.maxElectableValidators,
- config.validators.minLockedGoldValue,
- config.validators.minLockedGoldNoticePeriod,
+ config.validators.registrationRequirements.group,
+ config.validators.registrationRequirements.validator,
+ config.validators.deregistrationLockups.group,
+ config.validators.deregistrationLockups.validator,
+ config.validators.maxGroupSize,
]
}
diff --git a/packages/protocol/migrations/12_election.ts b/packages/protocol/migrations/12_election.ts
new file mode 100644
index 00000000000..b29d2c157d3
--- /dev/null
+++ b/packages/protocol/migrations/12_election.ts
@@ -0,0 +1,20 @@
+import { CeloContractName } from '@celo/protocol/lib/registry-utils'
+import { deploymentForCoreContract } from '@celo/protocol/lib/web3-utils'
+import { config } from '@celo/protocol/migrationsConfig'
+import { ElectionInstance } from 'types'
+
+const initializeArgs = async (): Promise => {
+ return [
+ config.registry.predeployedProxyAddress,
+ config.election.minElectableValidators,
+ config.election.maxElectableValidators,
+ config.election.maxVotesPerAccount,
+ ]
+}
+
+module.exports = deploymentForCoreContract(
+ web3,
+ artifacts,
+ CeloContractName.Election,
+ initializeArgs
+)
diff --git a/packages/protocol/migrations/12_random.ts b/packages/protocol/migrations/13_random.ts
similarity index 100%
rename from packages/protocol/migrations/12_random.ts
rename to packages/protocol/migrations/13_random.ts
diff --git a/packages/protocol/migrations/13_attestations.ts b/packages/protocol/migrations/14_attestations.ts
similarity index 100%
rename from packages/protocol/migrations/13_attestations.ts
rename to packages/protocol/migrations/14_attestations.ts
diff --git a/packages/protocol/migrations/14_escrow.ts b/packages/protocol/migrations/15_escrow.ts
similarity index 100%
rename from packages/protocol/migrations/14_escrow.ts
rename to packages/protocol/migrations/15_escrow.ts
diff --git a/packages/protocol/migrations/15_governance.ts b/packages/protocol/migrations/16_governance.ts
similarity index 99%
rename from packages/protocol/migrations/15_governance.ts
rename to packages/protocol/migrations/16_governance.ts
index ab5ce88394b..3f6d73a5a70 100644
--- a/packages/protocol/migrations/15_governance.ts
+++ b/packages/protocol/migrations/16_governance.ts
@@ -49,6 +49,7 @@ module.exports = deploymentForCoreContract(
const proxyAndImplementationOwnedByGovernance = [
'Attestations',
'LockedGold',
+ 'Election',
'Escrow',
'Exchange',
'GasCurrencyWhitelist',
diff --git a/packages/protocol/migrations/16_elect_validators.ts b/packages/protocol/migrations/17_elect_validators.ts
similarity index 89%
rename from packages/protocol/migrations/16_elect_validators.ts
rename to packages/protocol/migrations/17_elect_validators.ts
index db17ed8b1f9..800e2f14c16 100644
--- a/packages/protocol/migrations/16_elect_validators.ts
+++ b/packages/protocol/migrations/17_elect_validators.ts
@@ -11,7 +11,7 @@ import {
import { config } from '@celo/protocol/migrationsConfig'
import { BigNumber } from 'bignumber.js'
import * as bls12377js from 'bls12377js'
-import { LockedGoldInstance, ValidatorsInstance } from 'types'
+import { ElectionInstance, LockedGoldInstance, ValidatorsInstance } from 'types'
const Web3 = require('web3')
@@ -27,13 +27,11 @@ async function makeMinimumDeposit(lockedGold: LockedGoldInstance, privateKey: st
})
// @ts-ignore
- const bondTx = lockedGold.contract.methods.newCommitment(
- config.validators.minLockedGoldNoticePeriod
- )
+ const lockTx = lockedGold.contract.methods.lock()
- await sendTransactionWithPrivateKey(web3, bondTx, privateKey, {
+ await sendTransactionWithPrivateKey(web3, lockTx, privateKey, {
to: lockedGold.address,
- value: config.validators.minLockedGoldValue,
+ value: config.validators.registrationRequirements.validator,
})
}
@@ -131,6 +129,11 @@ module.exports = async (_deployer: any) => {
artifacts
)
+ const election: ElectionInstance = await getDeployedProxiedContract(
+ 'Election',
+ artifacts
+ )
+
const valKeys: string[] = config.validators.validatorKeys
if (valKeys.length === 0) {
@@ -168,11 +171,12 @@ module.exports = async (_deployer: any) => {
console.info(' Voting for Validator Group ...')
// Make another deposit so our vote has more weight.
const minLockedGoldVotePerValidator = 10000
- await lockedGold.newCommitment(0, {
+ const value = new BigNumber(valKeys.length)
+ .times(minLockedGoldVotePerValidator)
+ .times(web3.utils.toWei(1))
+ await lockedGold.lock({
// @ts-ignore
- value: new BigNumber(valKeys.length)
- .times(minLockedGoldVotePerValidator)
- .times(config.validators.minLockedGoldValue),
+ value,
})
- await validators.vote(account.address, NULL_ADDRESS, NULL_ADDRESS)
+ await election.vote(account.address, value, NULL_ADDRESS, NULL_ADDRESS)
}
diff --git a/packages/protocol/migrationsConfig.js b/packages/protocol/migrationsConfig.js
index 06f8a0d7f53..ccaffc073d1 100644
--- a/packages/protocol/migrationsConfig.js
+++ b/packages/protocol/migrationsConfig.js
@@ -12,11 +12,16 @@ const DefaultConfig = {
attestationRequestFeeInDollars: 0.05,
},
lockedGold: {
- maxNoticePeriod: 60 * 60 * 24 * 365 * 3, // 3 years
+ unlockingPeriod: 60 * 60 * 24 * 3, // 3 days
},
oracles: {
reportExpiry: 60 * 60, // 1 hour
},
+ election: {
+ minElectableValidators: '10',
+ maxElectableValidators: '100',
+ maxVotesPerAccount: 3,
+ },
exchange: {
spread: 5 / 1000,
reserveFraction: 1,
@@ -57,10 +62,15 @@ const DefaultConfig = {
initialAccounts: [],
},
validators: {
- minElectableValidators: '10',
- maxElectableValidators: '100',
- minLockedGoldValue: '1000000000000000000', // 1 gold
- minLockedGoldNoticePeriod: 60 * 24 * 60 * 60, // 60 days
+ registrationRequirements: {
+ group: '1000000000000000000', // 1 gold
+ validator: '1000000000000000000', // 1 gold
+ },
+ deregistrationLockups: {
+ group: 60 * 24 * 60 * 60, // 60 days
+ validator: 60 * 24 * 60 * 60, // 60 days
+ },
+ maxGroupSize: 10,
validatorKeys: [],
// We register a single validator group during the migration.
@@ -78,7 +88,7 @@ const linkedLibraries = {
],
SortedLinkedListWithMedian: ['AddressSortedLinkedListWithMedian'],
AddressLinkedList: ['Validators'],
- AddressSortedLinkedList: ['Validators'],
+ AddressSortedLinkedList: ['Election'],
IntegerSortedLinkedList: ['Governance', 'IntegerSortedLinkedListTest'],
AddressSortedLinkedListWithMedian: ['SortedOracles', 'AddressSortedLinkedListWithMedianTest'],
Signatures: ['LockedGold', 'Escrow'],
diff --git a/packages/protocol/test/common/integration.ts b/packages/protocol/test/common/integration.ts
index e607edddce7..8707e32e15d 100644
--- a/packages/protocol/test/common/integration.ts
+++ b/packages/protocol/test/common/integration.ts
@@ -26,7 +26,7 @@ contract('Integration: Governance', (accounts: string[]) => {
let governance: GovernanceInstance
let registry: RegistryInstance
let proposalTransactions: any
- let weight: BigNumber
+ let value: BigNumber
before(async () => {
lockedGold = await getDeployedProxiedContract('LockedGold', artifacts)
@@ -34,11 +34,9 @@ contract('Integration: Governance', (accounts: string[]) => {
registry = await getDeployedProxiedContract('Registry', artifacts)
// Set up a LockedGold account with which we can vote.
await lockedGold.createAccount()
- const noticePeriod = 60 * 60 * 24 // 1 day
- const value = new BigNumber('1000000000000000000')
+ value = new BigNumber('1000000000000000000')
// @ts-ignore
- await lockedGold.newCommitment(noticePeriod, { value })
- weight = await lockedGold.getAccountWeight(accounts[0])
+ await lockedGold.lock({ value })
proposalTransactions = [
{
value: 0,
@@ -89,7 +87,7 @@ contract('Integration: Governance', (accounts: string[]) => {
})
it('should increase the number of upvotes for the proposal', async () => {
- assertEqualBN(await governance.getUpvotes(proposalId), weight)
+ assertEqualBN(await governance.getUpvotes(proposalId), value)
})
})
@@ -112,7 +110,7 @@ contract('Integration: Governance', (accounts: string[]) => {
it('should increment the vote totals', async () => {
const [yes, ,] = await governance.getVoteTotals(proposalId)
- assertEqualBN(yes, weight)
+ assertEqualBN(yes, value)
})
})
diff --git a/packages/protocol/test/governance/election.ts b/packages/protocol/test/governance/election.ts
deleted file mode 100644
index af5e83f4e28..00000000000
--- a/packages/protocol/test/governance/election.ts
+++ /dev/null
@@ -1,1315 +0,0 @@
-import { CeloContractName } from '@celo/protocol/lib/registry-utils'
-import { fixed1, toFixed, fromFixed, multiply } from '@celo/utils/lib/fixidity'
-import {
- assertContainSubset,
- assertEqualBN,
- assertRevert,
- NULL_ADDRESS,
-} from '@celo/protocol/lib/test-utils'
-import BigNumber from 'bignumber.js'
-import {
- MockLockedGoldContract,
- MockLockedGoldInstance,
- RegistryContract,
- RegistryInstance,
- ValidatorsContract,
- ValidatorsInstance,
-} from 'types'
-
-const Validators: ValidatorsContract = artifacts.require('Validators')
-const MockLockedGold: MockLockedGoldContract = artifacts.require('MockLockedGold')
-const Registry: RegistryContract = artifacts.require('Registry')
-
-// @ts-ignore
-// TODO(mcortesi): Use BN
-Validators.numberFormat = 'BigNumber'
-
-const parseValidatorParams = (validatorParams: any) => {
- return {
- name: validatorParams[1],
- url: validatorParams[2],
- publicKeysData: validatorParams[3],
- affiliation: validatorParams[4],
- }
-}
-
-const parseValidatorGroupParams = (groupParams: any) => {
- return {
- name: groupParams[1],
- url: groupParams[2],
- members: groupParams[3],
- }
-}
-
-const HOUR = 60 * 60
-const DAY = 24 * HOUR
-const YEAR = 365 * DAY
-
-contract('Validators', (accounts: string[]) => {
- let validators: ValidatorsInstance
- let registry: RegistryInstance
- let mockLockedGold: MockLockedGoldInstance
- // A random 64 byte hex string.
- const publicKey =
- 'ea0733ad275e2b9e05541341a97ee82678c58932464fad26164657a111a7e37a9fa0300266fb90e2135a1f1512350cb4e985488a88809b14e3cbe415e76e82b2'
- const blsPublicKey =
- '4d23d8cd06f30b1fa7cf368e2f5399ab04bb6846c682f493a98a607d3dfb7e53a712bb79b475c57b0ac2785460f91301'
- const blsPoP =
- '9d3e1d8f49f6b0d8e9a03d80ca07b1d24cf1cc0557bdcc04f5e17a46e35d02d0d411d956dbd5d2d2464eebd7b74ae30005d223780d785d2abc5644fac7ac29fb0e302bdc80c81a5d45018b68b1045068a4b3a4861c93037685fd0d252d740501'
-
- const publicKeysData = '0x' + publicKey + blsPublicKey + blsPoP
-
- const nonOwner = accounts[1]
- const registrationRequirements = { group: new BigNumber(1000), validator: new BigNumber(100) }
- const deregistrationLockups = {
- group: new BigNumber(100 * DAY),
- validator: new BigNumber(60 * DAY),
- }
- const maxGroupSize = 5
- const name = 'test-name'
- const url = 'test-url'
- const commission = toFixed(1 / 100)
- beforeEach(async () => {
- validators = await Validators.new()
- mockLockedGold = await MockLockedGold.new()
- registry = await Registry.new()
- await registry.setAddressFor(CeloContractName.LockedGold, mockLockedGold.address)
- await validators.initialize(
- registry.address,
- registrationRequirements.group,
- registrationRequirements.validator,
- deregistrationLockups.group,
- deregistrationLockups.validator,
- maxGroupSize
- )
- })
-
- const registerValidator = async (validator: string) => {
- await mockLockedGold.setAccountTotalLockedGold(validator, registrationRequirements.validator)
- await validators.registerValidator(
- name,
- url,
- // @ts-ignore bytes type
- publicKeysData,
- { from: validator }
- )
- }
-
- const registerValidatorGroup = async (group: string) => {
- await mockLockedGold.setAccountTotalLockedGold(group, registrationRequirements.group)
- await validators.registerValidatorGroup(name, url, commission, { from: group })
- }
-
- const registerValidatorGroupWithMembers = async (group: string, members: string[]) => {
- await registerValidatorGroup(group)
- for (const validator of members) {
- await registerValidator(validator)
- await validators.affiliate(group, { from: validator })
- await validators.addMember(validator, { from: group })
- }
- }
-
- describe('#initialize()', () => {
- it('should have set the owner', async () => {
- const owner: string = await validators.owner()
- assert.equal(owner, accounts[0])
- })
-
- it('should have set the registration requirements', async () => {
- const [group, validator] = await validators.getRegistrationRequirement()
- assertEqualBN(group, registrationRequirements.group)
- assertEqualBN(validator, registrationRequirements.validator)
- })
-
- it('should have set the deregistration lockups', async () => {
- const [group, validator] = await validators.getDeregistrationLockups()
- assertEqualBN(group, deregistrationLockups.group)
- assertEqualBN(validator, deregistrationLockups.validator)
- })
-
- it('should not be callable again', async () => {
- await assertRevert(
- validators.initialize(
- registry.address,
- registrationRequirements.group,
- registrationRequirements.validator,
- deregistrationLockups.group,
- deregistrationLockups.validator,
- maxGroupSize
- )
- )
- })
- })
-
- describe('#setMinElectableValidators', () => {
- const newMinElectableValidators = minElectableValidators.plus(1)
- it('should set the minimum deposit', async () => {
- await validators.setMinElectableValidators(newMinElectableValidators)
- assertEqualBN(await validators.minElectableValidators(), newMinElectableValidators)
- })
-
- it('should emit the MinElectableValidatorsSet event', async () => {
- const resp = await validators.setMinElectableValidators(newMinElectableValidators)
- assert.equal(resp.logs.length, 1)
- const log = resp.logs[0]
- assertContainSubset(log, {
- event: 'MinElectableValidatorsSet',
- args: {
- minElectableValidators: new BigNumber(newMinElectableValidators),
- },
- })
- })
-
- it('should revert when the minElectableValidators is zero', async () => {
- await assertRevert(validators.setMinElectableValidators(0))
- })
-
- it('should revert when the minElectableValidators is greater than maxElectableValidators', async () => {
- await assertRevert(validators.setMinElectableValidators(maxElectableValidators.plus(1)))
- })
-
- it('should revert when the minElectableValidators is unchanged', async () => {
- await assertRevert(validators.setMinElectableValidators(minElectableValidators))
- })
-
- it('should revert when called by anyone other than the owner', async () => {
- await assertRevert(
- validators.setMinElectableValidators(newMinElectableValidators, { from: nonOwner })
- )
- })
- })
-
- describe('#setMaxElectableValidators', () => {
- const newMaxElectableValidators = maxElectableValidators.plus(1)
- it('should set the minimum deposit', async () => {
- await validators.setMaxElectableValidators(newMaxElectableValidators)
- assertEqualBN(await validators.maxElectableValidators(), newMaxElectableValidators)
- })
-
- it('should emit the MaxElectableValidatorsSet event', async () => {
- const resp = await validators.setMaxElectableValidators(newMaxElectableValidators)
- assert.equal(resp.logs.length, 1)
- const log = resp.logs[0]
- assertContainSubset(log, {
- event: 'MaxElectableValidatorsSet',
- args: {
- maxElectableValidators: new BigNumber(newMaxElectableValidators),
- },
- })
- })
-
- it('should revert when the maxElectableValidators is less than minElectableValidators', async () => {
- await assertRevert(validators.setMaxElectableValidators(minElectableValidators.minus(1)))
- })
-
- it('should revert when the maxElectableValidators is unchanged', async () => {
- await assertRevert(validators.setMaxElectableValidators(maxElectableValidators))
- })
-
- it('should revert when called by anyone other than the owner', async () => {
- await assertRevert(
- validators.setMaxElectableValidators(newMaxElectableValidators, { from: nonOwner })
- )
- })
- })
-
- describe('#setRegistrationRequirement', () => {
- const newValue = registrationRequirement.value.plus(1)
- const newNoticePeriod = registrationRequirement.noticePeriod.plus(1)
-
- it('should set the value and notice period', async () => {
- await validators.setRegistrationRequirement(newValue, newNoticePeriod)
- const [value, noticePeriod] = await validators.getRegistrationRequirement()
- assertEqualBN(value, newValue)
- assertEqualBN(noticePeriod, newNoticePeriod)
- })
-
- it('should emit the RegistrationRequirementSet event', async () => {
- const resp = await validators.setRegistrationRequirement(newValue, newNoticePeriod)
- assert.equal(resp.logs.length, 1)
- const log = resp.logs[0]
- assertContainSubset(log, {
- event: 'RegistrationRequirementSet',
- args: {
- value: new BigNumber(newValue),
- noticePeriod: new BigNumber(newNoticePeriod),
- },
- })
- })
-
- it('should revert when the requirement is unchanged', async () => {
- await assertRevert(
- validators.setRegistrationRequirement(
- registrationRequirement.value,
- registrationRequirement.noticePeriod
- )
- )
- })
-
- it('should revert when called by anyone other than the owner', async () => {
- await assertRevert(
- validators.setRegistrationRequirement(newValue, newNoticePeriod, { from: nonOwner })
- )
- })
- })
-
- describe('#registerValidator', () => {
- const validator = accounts[0]
- beforeEach(async () => {
- await mockLockedGold.setLockedCommitment(
- validator,
- registrationRequirement.noticePeriod,
- registrationRequirement.value
- )
- })
-
- it('should mark the account as a validator', async () => {
- await validators.registerValidator(
- name,
- url,
- // @ts-ignore bytes type
- publicKeysData,
- registrationRequirement.noticePeriod
- )
- assert.isTrue(await validators.isValidator(validator))
- })
-
- it('should add the account to the list of validators', async () => {
- await validators.registerValidator(
- name,
- url,
- // @ts-ignore bytes type
- publicKeysData,
- registrationRequirement.noticePeriod
- )
- assert.deepEqual(await validators.getRegisteredValidators(), [validator])
- })
-
- it('should set the validator name, url, and public key', async () => {
- await validators.registerValidator(
- name,
- url,
- // @ts-ignore bytes type
- publicKeysData,
- registrationRequirement.noticePeriod
- )
- const parsedValidator = parseValidatorParams(await validators.getValidator(validator))
- assert.equal(parsedValidator.name, name)
- assert.equal(parsedValidator.url, url)
- assert.equal(parsedValidator.publicKeysData, publicKeysData)
- })
-
- it('should emit the ValidatorRegistered event', async () => {
- const resp = await validators.registerValidator(
- name,
- url,
- // @ts-ignore bytes type
- publicKeysData,
- registrationRequirement.noticePeriod
- )
- assert.equal(resp.logs.length, 1)
- const log = resp.logs[0]
- assertContainSubset(log, {
- event: 'ValidatorRegistered',
- args: {
- validator,
- name,
- url,
- publicKeysData,
- },
- })
- })
-
- describe('when the account is already a registered validator', () => {
- beforeEach(async () => {
- await validators.registerValidator(
- name,
- url,
- // @ts-ignore bytes type
- publicKeysData,
- registrationRequirement.noticePeriod
- )
- })
-
- it('should revert', async () => {
- await assertRevert(
- validators.registerValidator(
- name,
- url,
- // @ts-ignore bytes type
- publicKeysData,
- registrationRequirement.noticePeriod
- )
- )
- })
- })
-
- describe('when the account is already a registered validator group', () => {
- beforeEach(async () => {
- await validators.registerValidatorGroup(name, url, registrationRequirement.noticePeriod)
- })
-
- it('should revert', async () => {
- await assertRevert(
- validators.registerValidator(
- name,
- url,
- // @ts-ignore bytes type
- publicKeysData,
- registrationRequirement.noticePeriod
- )
- )
- })
- })
-
- describe('when the account does not meet the registration requirements', () => {
- beforeEach(async () => {
- await mockLockedGold.setLockedCommitment(
- validator,
- registrationRequirement.noticePeriod,
- registrationRequirement.value.minus(1)
- )
- })
-
- it('should revert', async () => {
- await assertRevert(
- validators.registerValidator(
- name,
- url,
- // @ts-ignore bytes type
- publicKeysData,
- registrationRequirement.noticePeriod
- )
- )
- })
- })
- })
-
- describe('#deregisterValidator', () => {
- const validator = accounts[0]
- const index = 0
- beforeEach(async () => {
- await registerValidator(validator)
- })
-
- it('should mark the account as not a validator', async () => {
- await validators.deregisterValidator(index)
- assert.isFalse(await validators.isValidator(validator))
- })
-
- it('should remove the account from the list of validators', async () => {
- await validators.deregisterValidator(index)
- assert.deepEqual(await validators.getRegisteredValidators(), [])
- })
-
- it('should emit the ValidatorDeregistered event', async () => {
- const resp = await validators.deregisterValidator(index)
- assert.equal(resp.logs.length, 1)
- const log = resp.logs[0]
- assertContainSubset(log, {
- event: 'ValidatorDeregistered',
- args: {
- validator,
- },
- })
- })
-
- describe('when the validator is affiliated with a validator group', () => {
- const group = accounts[1]
- beforeEach(async () => {
- await registerValidatorGroup(group)
- await validators.affiliate(group)
- })
-
- it('should emit the ValidatorDeafilliated event', async () => {
- const resp = await validators.deregisterValidator(index)
- assert.equal(resp.logs.length, 2)
- const log = resp.logs[0]
- assertContainSubset(log, {
- event: 'ValidatorDeaffiliated',
- args: {
- validator,
- group,
- },
- })
- })
-
- describe('when the validator is a member of that group', () => {
- beforeEach(async () => {
- await validators.addMember(validator, { from: group })
- })
-
- it('should remove the validator from the group membership list', async () => {
- await validators.deregisterValidator(index)
- const parsedGroup = parseValidatorGroupParams(await validators.getValidatorGroup(group))
- assert.deepEqual(parsedGroup.members, [])
- })
-
- it('should emit the ValidatorGroupMemberRemoved event', async () => {
- const resp = await validators.deregisterValidator(index)
- assert.equal(resp.logs.length, 4)
- const log = resp.logs[0]
- assertContainSubset(log, {
- event: 'ValidatorGroupMemberRemoved',
- args: {
- validator,
- group,
- },
- })
- })
-
- describe('when the validator is the only member of that group', () => {
- it('should emit the ValidatorGroupEmptied event', async () => {
- const resp = await validators.deregisterValidator(index)
- assert.equal(resp.logs.length, 4)
- const log = resp.logs[1]
- assertContainSubset(log, {
- event: 'ValidatorGroupEmptied',
- args: {
- group,
- },
- })
- })
-
- describe('when that group has received votes', () => {
- beforeEach(async () => {
- const voter = accounts[2]
- const weight = 10
- await mockLockedGold.setWeight(voter, weight)
- await validators.vote(group, NULL_ADDRESS, NULL_ADDRESS, { from: voter })
- })
-
- it('should remove the group from the list of electable groups with votes', async () => {
- await validators.deregisterValidator(index)
- const [groups] = await validators.getValidatorGroupVotes()
- assert.deepEqual(groups, [])
- })
- })
- })
- })
- })
-
- it('should revert when the account is not a registered validator', async () => {
- await assertRevert(validators.deregisterValidator(index, { from: accounts[2] }))
- })
-
- it('should revert when the wrong index is provided', async () => {
- await assertRevert(validators.deregisterValidator(index + 1))
- })
- })
-
- describe('#affiliate', () => {
- const validator = accounts[0]
- const group = accounts[1]
- beforeEach(async () => {
- await registerValidator(validator)
- await registerValidatorGroup(group)
- })
-
- it('should set the affiliate', async () => {
- await validators.affiliate(group)
- const parsedValidator = parseValidatorParams(await validators.getValidator(validator))
- assert.equal(parsedValidator.affiliation, group)
- })
-
- it('should emit the ValidatorAffiliated event', async () => {
- const resp = await validators.affiliate(group)
- assert.equal(resp.logs.length, 1)
- const log = resp.logs[0]
- assertContainSubset(log, {
- event: 'ValidatorAffiliated',
- args: {
- validator,
- group,
- },
- })
- })
-
- describe('when the validator is already affiliated with a validator group', () => {
- const otherGroup = accounts[2]
- beforeEach(async () => {
- await validators.affiliate(group)
- await registerValidatorGroup(otherGroup)
- })
-
- it('should set the affiliate', async () => {
- await validators.affiliate(otherGroup)
- const parsedValidator = parseValidatorParams(await validators.getValidator(validator))
- assert.equal(parsedValidator.affiliation, otherGroup)
- })
-
- it('should emit the ValidatorDeafilliated event', async () => {
- const resp = await validators.affiliate(otherGroup)
- assert.equal(resp.logs.length, 2)
- const log = resp.logs[0]
- assertContainSubset(log, {
- event: 'ValidatorDeaffiliated',
- args: {
- validator,
- group,
- },
- })
- })
-
- it('should emit the ValidatorAffiliated event', async () => {
- const resp = await validators.affiliate(otherGroup)
- assert.equal(resp.logs.length, 2)
- const log = resp.logs[1]
- assertContainSubset(log, {
- event: 'ValidatorAffiliated',
- args: {
- validator,
- group: otherGroup,
- },
- })
- })
-
- describe('when the validator is a member of that group', () => {
- beforeEach(async () => {
- await validators.addMember(validator, { from: group })
- })
-
- it('should remove the validator from the group membership list', async () => {
- await validators.affiliate(otherGroup)
- const parsedGroup = parseValidatorGroupParams(await validators.getValidatorGroup(group))
- assert.deepEqual(parsedGroup.members, [])
- })
-
- it('should emit the ValidatorGroupMemberRemoved event', async () => {
- const resp = await validators.affiliate(otherGroup)
- assert.equal(resp.logs.length, 4)
- const log = resp.logs[0]
- assertContainSubset(log, {
- event: 'ValidatorGroupMemberRemoved',
- args: {
- validator,
- group,
- },
- })
- })
-
- describe('when the validator is the only member of that group', () => {
- it('should emit the ValidatorGroupEmptied event', async () => {
- const resp = await validators.affiliate(otherGroup)
- assert.equal(resp.logs.length, 4)
- const log = resp.logs[1]
- assertContainSubset(log, {
- event: 'ValidatorGroupEmptied',
- args: {
- group,
- },
- })
- })
-
- describe('when that group has received votes', () => {
- beforeEach(async () => {
- const voter = accounts[2]
- const weight = 10
- await mockLockedGold.setWeight(voter, weight)
- await validators.vote(group, NULL_ADDRESS, NULL_ADDRESS, { from: voter })
- })
-
- it('should remove the group from the list of electable groups with votes', async () => {
- await validators.affiliate(otherGroup)
- const [groups] = await validators.getValidatorGroupVotes()
- assert.deepEqual(groups, [])
- })
- })
- })
- })
- })
-
- it('should revert when the account is not a registered validator', async () => {
- await assertRevert(validators.affiliate(group, { from: accounts[2] }))
- })
-
- it('should revert when the group is not a registered validator group', async () => {
- await assertRevert(validators.affiliate(accounts[2]))
- })
- })
-
- describe('#deaffiliate', () => {
- const validator = accounts[0]
- const group = accounts[1]
- beforeEach(async () => {
- await registerValidator(validator)
- await registerValidatorGroup(group)
- await validators.affiliate(group)
- })
-
- it('should clear the affiliate', async () => {
- await validators.deaffiliate()
- const parsedValidator = parseValidatorParams(await validators.getValidator(validator))
- assert.equal(parsedValidator.affiliation, NULL_ADDRESS)
- })
-
- it('should emit the ValidatorDeaffiliated event', async () => {
- const resp = await validators.deaffiliate()
- assert.equal(resp.logs.length, 1)
- const log = resp.logs[0]
- assertContainSubset(log, {
- event: 'ValidatorDeaffiliated',
- args: {
- validator,
- group,
- },
- })
- })
-
- describe('when the validator is a member of the affiliated group', () => {
- beforeEach(async () => {
- await validators.addMember(validator, { from: group })
- })
-
- it('should remove the validator from the group membership list', async () => {
- await validators.deaffiliate()
- const parsedGroup = parseValidatorGroupParams(await validators.getValidatorGroup(group))
- assert.deepEqual(parsedGroup.members, [])
- })
-
- it('should emit the ValidatorGroupMemberRemoved event', async () => {
- const resp = await validators.deaffiliate()
- assert.equal(resp.logs.length, 3)
- const log = resp.logs[0]
- assertContainSubset(log, {
- event: 'ValidatorGroupMemberRemoved',
- args: {
- validator,
- group,
- },
- })
- })
-
- describe('when the validator is the only member of that group', () => {
- it('should emit the ValidatorGroupEmptied event', async () => {
- const resp = await validators.deaffiliate()
- assert.equal(resp.logs.length, 3)
- const log = resp.logs[1]
- assertContainSubset(log, {
- event: 'ValidatorGroupEmptied',
- args: {
- group,
- },
- })
- })
-
- describe('when that group has received votes', () => {
- beforeEach(async () => {
- const voter = accounts[2]
- const weight = 10
- await mockLockedGold.setWeight(voter, weight)
- await validators.vote(group, NULL_ADDRESS, NULL_ADDRESS, { from: voter })
- })
-
- it('should remove the group from the list of electable groups with votes', async () => {
- await validators.deaffiliate()
- const [groups] = await validators.getValidatorGroupVotes()
- assert.deepEqual(groups, [])
- })
- })
- })
- })
-
- it('should revert when the account is not a registered validator', async () => {
- await assertRevert(validators.deaffiliate({ from: accounts[2] }))
- })
-
- it('should revert when the validator is not affiliated with a validator group', async () => {
- await validators.deaffiliate()
- await assertRevert(validators.deaffiliate())
- })
- })
-
- describe('#registerValidatorGroup', () => {
- const group = accounts[0]
- beforeEach(async () => {
- await mockLockedGold.setLockedCommitment(
- group,
- registrationRequirement.noticePeriod,
- registrationRequirement.value
- )
- })
-
- it('should mark the account as a validator group', async () => {
- await validators.registerValidatorGroup(name, url, registrationRequirement.noticePeriod)
- assert.isTrue(await validators.isValidatorGroup(group))
- })
-
- it('should add the account to the list of validator groups', async () => {
- await validators.registerValidatorGroup(name, url, registrationRequirement.noticePeriod)
- assert.deepEqual(await validators.getRegisteredValidatorGroups(), [group])
- })
-
- it('should set the validator group name, and url', async () => {
- await validators.registerValidatorGroup(name, url, registrationRequirement.noticePeriod)
- const parsedGroup = parseValidatorGroupParams(await validators.getValidatorGroup(group))
- assert.equal(parsedGroup.name, name)
- assert.equal(parsedGroup.url, url)
- })
-
- it('should emit the ValidatorGroupRegistered event', async () => {
- const resp = await validators.registerValidatorGroup(
- name,
- url,
- registrationRequirement.noticePeriod
- )
- assert.equal(resp.logs.length, 1)
- const log = resp.logs[0]
- assertContainSubset(log, {
- event: 'ValidatorGroupRegistered',
- args: {
- group,
- name,
- url,
- },
- })
- })
-
- describe('when the account is already a registered validator', () => {
- beforeEach(async () => {
- await registerValidator(group)
- })
-
- it('should revert', async () => {
- await assertRevert(
- validators.registerValidatorGroup(name, url, registrationRequirement.noticePeriod)
- )
- })
- })
-
- describe('when the account is already a registered validator group', () => {
- beforeEach(async () => {
- await validators.registerValidatorGroup(name, url, registrationRequirement.noticePeriod)
- })
-
- it('should revert', async () => {
- await assertRevert(
- validators.registerValidatorGroup(name, url, registrationRequirement.noticePeriod)
- )
- })
- })
-
- describe('when the account does not meet the registration requirements', () => {
- beforeEach(async () => {
- await mockLockedGold.setLockedCommitment(
- group,
- registrationRequirement.noticePeriod,
- registrationRequirement.value.minus(1)
- )
- })
-
- it('should revert', async () => {
- await assertRevert(
- validators.registerValidatorGroup(name, url, registrationRequirement.noticePeriod)
- )
- })
- })
- })
-
- describe('#deregisterValidatorGroup', () => {
- const index = 0
- const group = accounts[0]
- beforeEach(async () => {
- await registerValidatorGroup(group)
- })
-
- it('should mark the account as not a validator group', async () => {
- await validators.deregisterValidatorGroup(index)
- assert.isFalse(await validators.isValidatorGroup(group))
- })
-
- it('should remove the account from the list of validator groups', async () => {
- await validators.deregisterValidatorGroup(index)
- assert.deepEqual(await validators.getRegisteredValidatorGroups(), [])
- })
-
- it('should emit the ValidatorGroupDeregistered event', async () => {
- const resp = await validators.deregisterValidatorGroup(index)
- assert.equal(resp.logs.length, 1)
- const log = resp.logs[0]
- assertContainSubset(log, {
- event: 'ValidatorGroupDeregistered',
- args: {
- group,
- },
- })
- })
-
- it('should revert when the account is not a registered validator group', async () => {
- await assertRevert(validators.deregisterValidatorGroup(index, { from: accounts[2] }))
- })
-
- it('should revert when the wrong index is provided', async () => {
- await assertRevert(validators.deregisterValidatorGroup(index + 1))
- })
-
- describe('when the validator group is not empty', () => {
- const validator = accounts[1]
- beforeEach(async () => {
- await registerValidator(validator)
- await validators.affiliate(group, { from: validator })
- await validators.addMember(validator)
- })
-
- it('should revert', async () => {
- await assertRevert(validators.deregisterValidatorGroup(index))
- })
- })
- })
-
- describe('#addMember', () => {
- const group = accounts[0]
- const validator = accounts[1]
- beforeEach(async () => {
- await registerValidator(validator)
- await registerValidatorGroup(group)
- await validators.affiliate(group, { from: validator })
- })
-
- it('should add the member to the list of members', async () => {
- await validators.addMember(validator)
- const parsedGroup = parseValidatorGroupParams(await validators.getValidatorGroup(group))
- assert.deepEqual(parsedGroup.members, [validator])
- })
-
- it('should emit the ValidatorGroupMemberAdded event', async () => {
- const resp = await validators.addMember(validator)
- assert.equal(resp.logs.length, 1)
- const log = resp.logs[0]
- assertContainSubset(log, {
- event: 'ValidatorGroupMemberAdded',
- args: {
- group,
- validator,
- },
- })
- })
-
- it('should revert when the account is not a registered validator group', async () => {
- await assertRevert(validators.addMember(validator, { from: accounts[2] }))
- })
-
- it('should revert when the member is not a registered validator', async () => {
- await assertRevert(validators.addMember(accounts[2]))
- })
-
- describe('when the validator has not affiliated themselves with the group', () => {
- beforeEach(async () => {
- await validators.deaffiliate({ from: validator })
- })
-
- it('should revert', async () => {
- await assertRevert(validators.addMember(validator))
- })
- })
-
- describe('when the validator is already a member of the group', () => {
- beforeEach(async () => {
- await validators.addMember(validator)
- })
-
- it('should revert', async () => {
- await assertRevert(validators.addMember(validator))
- })
- })
- })
-
- describe('#removeMember', () => {
- const group = accounts[0]
- const validator = accounts[1]
- beforeEach(async () => {
- await registerValidatorGroupWithMembers(group, [validator])
- })
-
- it('should remove the member from the list of members', async () => {
- await validators.removeMember(validator)
- const parsedGroup = parseValidatorGroupParams(await validators.getValidatorGroup(group))
- assert.deepEqual(parsedGroup.members, [])
- })
-
- it('should emit the ValidatorGroupMemberRemoved event', async () => {
- const resp = await validators.removeMember(validator)
- assert.equal(resp.logs.length, 2)
- const log = resp.logs[0]
- assertContainSubset(log, {
- event: 'ValidatorGroupMemberRemoved',
- args: {
- group,
- validator,
- },
- })
- })
-
- describe('when the validator is the only member of the group', () => {
- it('should emit the ValidatorGroupEmptied event', async () => {
- const resp = await validators.removeMember(validator)
- assert.equal(resp.logs.length, 2)
- const log = resp.logs[1]
- assertContainSubset(log, {
- event: 'ValidatorGroupEmptied',
- args: {
- group,
- },
- })
- })
-
- describe('when the group has received votes', () => {
- beforeEach(async () => {
- const voter = accounts[2]
- const weight = 10
- await mockLockedGold.setWeight(voter, weight)
- await validators.vote(group, NULL_ADDRESS, NULL_ADDRESS, { from: voter })
- })
-
- it('should remove the group from the list of electable groups with votes', async () => {
- await validators.removeMember(validator)
- const [groups] = await validators.getValidatorGroupVotes()
- assert.deepEqual(groups, [])
- })
- })
- })
-
- it('should revert when the account is not a registered validator group', async () => {
- await assertRevert(validators.removeMember(validator, { from: accounts[2] }))
- })
-
- it('should revert when the member is not a registered validator', async () => {
- await assertRevert(validators.removeMember(accounts[2]))
- })
-
- describe('when the validator is not a member of the validator group', () => {
- beforeEach(async () => {
- await validators.deaffiliate({ from: validator })
- })
-
- it('should revert', async () => {
- await assertRevert(validators.removeMember(validator))
- })
- })
- })
-
- describe('#reorderMember', () => {
- const group = accounts[0]
- const validator1 = accounts[1]
- const validator2 = accounts[2]
- beforeEach(async () => {
- await registerValidatorGroupWithMembers(group, [validator1, validator2])
- })
-
- it('should reorder the list of group members', async () => {
- await validators.reorderMember(validator2, validator1, NULL_ADDRESS)
- const parsedGroup = parseValidatorGroupParams(await validators.getValidatorGroup(group))
- assert.deepEqual(parsedGroup.members, [validator2, validator1])
- })
-
- it('should emit the ValidatorGroupMemberReordered event', async () => {
- const resp = await validators.reorderMember(validator2, validator1, NULL_ADDRESS)
- assert.equal(resp.logs.length, 1)
- const log = resp.logs[0]
- assertContainSubset(log, {
- event: 'ValidatorGroupMemberReordered',
- args: {
- group,
- validator: validator2,
- },
- })
- })
-
- it('should revert when the account is not a registered validator group', async () => {
- await assertRevert(
- validators.reorderMember(validator2, validator1, NULL_ADDRESS, { from: accounts[2] })
- )
- })
-
- it('should revert when the member is not a registered validator', async () => {
- await assertRevert(validators.reorderMember(accounts[3], validator1, NULL_ADDRESS))
- })
-
- describe('when the validator is not a member of the validator group', () => {
- beforeEach(async () => {
- await validators.deaffiliate({ from: validator2 })
- })
-
- it('should revert', async () => {
- await assertRevert(validators.reorderMember(validator2, validator1, NULL_ADDRESS))
- })
- })
- })
-
- describe('#vote', () => {
- const weight = new BigNumber(5)
- const voter = accounts[0]
- const validator = accounts[1]
- const group = accounts[2]
- beforeEach(async () => {
- await registerValidatorGroupWithMembers(group, [validator])
- await mockLockedGold.setWeight(voter, weight)
- })
-
- it("should set the voter's vote", async () => {
- await validators.vote(group, NULL_ADDRESS, NULL_ADDRESS)
- assert.isTrue(await validators.isVoting(voter))
- assert.equal(await validators.voters(voter), group)
- })
-
- it('should add the group to the list of those receiving votes', async () => {
- await validators.vote(group, NULL_ADDRESS, NULL_ADDRESS)
- const [groups] = await validators.getValidatorGroupVotes()
- assert.deepEqual(groups, [group])
- })
-
- it("should increment the validator group's vote total", async () => {
- await validators.vote(group, NULL_ADDRESS, NULL_ADDRESS)
- assertEqualBN(await validators.getVotesReceived(group), weight)
- })
-
- it('should emit the ValidatorGroupVoteCast event', async () => {
- const resp = await validators.vote(group, NULL_ADDRESS, NULL_ADDRESS)
- assert.equal(resp.logs.length, 1)
- const log = resp.logs[0]
- assertContainSubset(log, {
- event: 'ValidatorGroupVoteCast',
- args: {
- account: voter,
- group,
- weight: new BigNumber(weight),
- },
- })
- })
-
- describe('when the group had not previously received votes', () => {
- it('should add the group to the list of electable groups with votes', async () => {
- await validators.vote(group, NULL_ADDRESS, NULL_ADDRESS)
- const [groups] = await validators.getValidatorGroupVotes()
- assert.deepEqual(groups, [group])
- })
- })
-
- it('should revert when the group is not a registered validator group', async () => {
- await assertRevert(validators.vote(accounts[3], NULL_ADDRESS, NULL_ADDRESS))
- })
-
- describe('when the group is empty', () => {
- beforeEach(async () => {
- await validators.removeMember(validator, { from: group })
- })
-
- it('should revert', async () => {
- await assertRevert(validators.vote(group, NULL_ADDRESS, NULL_ADDRESS))
- })
- })
-
- describe('when the account voting is frozen', () => {
- beforeEach(async () => {
- await mockLockedGold.setVotingFrozen(voter)
- })
-
- it('should revert', async () => {
- await assertRevert(validators.vote(group, NULL_ADDRESS, NULL_ADDRESS))
- })
- })
-
- describe('when the account has no weight', () => {
- beforeEach(async () => {
- await mockLockedGold.setWeight(voter, NULL_ADDRESS)
- })
-
- it('should revert', async () => {
- await assertRevert(validators.vote(group, NULL_ADDRESS, NULL_ADDRESS))
- })
- })
- describe('when the account has an outstanding vote', () => {
- beforeEach(async () => {
- await validators.vote(group, NULL_ADDRESS, NULL_ADDRESS)
- })
-
- it('should revert', async () => {
- await assertRevert(validators.vote(group, NULL_ADDRESS, NULL_ADDRESS))
- })
- })
- })
-
- describe('#revokeVote', () => {
- const weight = 5
- const voter = accounts[0]
- const validator = accounts[1]
- const group = accounts[2]
- beforeEach(async () => {
- await registerValidatorGroupWithMembers(group, [validator])
- await mockLockedGold.setWeight(voter, weight)
- await validators.vote(group, NULL_ADDRESS, NULL_ADDRESS)
- })
-
- it("should clear the voter's vote", async () => {
- await validators.revokeVote(NULL_ADDRESS, NULL_ADDRESS)
- assert.isFalse(await validators.isVoting(voter))
- assert.equal(await validators.voters(voter), NULL_ADDRESS)
- })
-
- it("should decrement the validator group's vote total", async () => {
- await validators.revokeVote(NULL_ADDRESS, NULL_ADDRESS)
- const [groups, votes] = await validators.getValidatorGroupVotes()
- assert.deepEqual(groups, [])
- assert.deepEqual(votes, [])
- })
-
- it('should emit the ValidatorGroupVoteRevoked event', async () => {
- const resp = await validators.revokeVote(NULL_ADDRESS, NULL_ADDRESS)
- assert.equal(resp.logs.length, 1)
- const log = resp.logs[0]
- assertContainSubset(log, {
- event: 'ValidatorGroupVoteRevoked',
- args: {
- account: voter,
- group,
- weight: new BigNumber(weight),
- },
- })
- })
-
- describe('when the group had not received other votes', () => {
- it('should remove the group from the list of electable groups with votes', async () => {
- await validators.revokeVote(NULL_ADDRESS, NULL_ADDRESS)
- const [groups] = await validators.getValidatorGroupVotes()
- assert.deepEqual(groups, [])
- })
- })
-
- describe('when the account does not have an outstanding vote', () => {
- beforeEach(async () => {
- await validators.revokeVote(NULL_ADDRESS, NULL_ADDRESS)
- })
-
- it('should revert', async () => {
- await assertRevert(validators.revokeVote(NULL_ADDRESS, NULL_ADDRESS))
- })
- })
- })
-
- describe('#getValidators', () => {
- const group1 = accounts[0]
- const group2 = accounts[1]
- const group3 = accounts[2]
- const validator1 = accounts[3]
- const validator2 = accounts[4]
- const validator3 = accounts[5]
- const validator4 = accounts[6]
- const validator5 = accounts[7]
- const validator6 = accounts[8]
- const validator7 = accounts[9]
-
- // If voterN votes for groupN:
- // group1 gets 20 votes per member
- // group2 gets 25 votes per member
- // group3 gets 30 votes per member
- // The ordering of the returned validators should be from group with most votes to group,
- // with fewest votes, and within each group, members are elected from first to last.
- const voter1 = { address: accounts[0], weight: 80 }
- const voter2 = { address: accounts[1], weight: 50 }
- const voter3 = { address: accounts[2], weight: 30 }
- const assertAddressesEqual = (actual: string[], expected: string[]) => {
- assert.deepEqual(actual.map((x) => x.toLowerCase()), expected.map((x) => x.toLowerCase()))
- }
-
- beforeEach(async () => {
- await registerValidatorGroupWithMembers(group1, [
- validator1,
- validator2,
- validator3,
- validator4,
- ])
- await registerValidatorGroupWithMembers(group2, [validator5, validator6])
- await registerValidatorGroupWithMembers(group3, [validator7])
-
- for (const voter of [voter1, voter2, voter3]) {
- await mockLockedGold.setWeight(voter.address, voter.weight)
- }
- })
-
- describe('when a single group has >= minElectableValidators as members and received votes', () => {
- beforeEach(async () => {
- await validators.vote(group1, NULL_ADDRESS, NULL_ADDRESS, { from: voter1.address })
- })
-
- it("should return that group's member list", async () => {
- assertAddressesEqual(await validators.getValidators(), [
- validator1,
- validator2,
- validator3,
- validator4,
- ])
- })
- })
-
- describe("when > maxElectableValidators members's groups receive votes", () => {
- beforeEach(async () => {
- await validators.vote(group1, NULL_ADDRESS, NULL_ADDRESS, { from: voter1.address })
- await validators.vote(group2, NULL_ADDRESS, group1, { from: voter2.address })
- await validators.vote(group3, NULL_ADDRESS, group2, { from: voter3.address })
- })
-
- it('should return maxElectableValidators elected validators', async () => {
- assertAddressesEqual(await validators.getValidators(), [
- validator1,
- validator2,
- validator3,
- validator5,
- validator6,
- validator7,
- ])
- })
- })
-
- describe('when a group receives enough votes for > n seats but only has n members', () => {
- beforeEach(async () => {
- await mockLockedGold.setWeight(voter3.address, 1000)
- await validators.vote(group3, NULL_ADDRESS, NULL_ADDRESS, { from: voter3.address })
- await validators.vote(group1, NULL_ADDRESS, group3, { from: voter1.address })
- await validators.vote(group2, NULL_ADDRESS, group1, { from: voter2.address })
- })
-
- it('should elect only n members from that group', async () => {
- assertAddressesEqual(await validators.getValidators(), [
- validator7,
- validator1,
- validator2,
- validator3,
- validator5,
- validator6,
- ])
- })
- })
-
- describe('when an account has delegated validating to another address', () => {
- const validatingDelegate = '0x47e172f6cfb6c7d01c1574fa3e2be7cc73269d95'
- beforeEach(async () => {
- await mockLockedGold.delegateValidating(validator3, validatingDelegate)
- await validators.vote(group1, NULL_ADDRESS, NULL_ADDRESS, { from: voter1.address })
- await validators.vote(group2, NULL_ADDRESS, group1, { from: voter2.address })
- await validators.vote(group3, NULL_ADDRESS, group2, { from: voter3.address })
- })
-
- it('should return the validating delegate in place of the account', async () => {
- assertAddressesEqual(await validators.getValidators(), [
- validator1,
- validator2,
- validatingDelegate,
- validator5,
- validator6,
- validator7,
- ])
- })
- })
-
- describe('when there are not enough electable validators', () => {
- beforeEach(async () => {
- await validators.vote(group2, NULL_ADDRESS, NULL_ADDRESS, { from: voter2.address })
- await validators.vote(group3, NULL_ADDRESS, group2, { from: voter3.address })
- })
-
- it('should revert', async () => {
- await assertRevert(validators.getValidators())
- })
- })
- })
-})
diff --git a/packages/protocol/test/governance/governance.ts b/packages/protocol/test/governance/governance.ts
index 3ed9c299682..302a9276af0 100644
--- a/packages/protocol/test/governance/governance.ts
+++ b/packages/protocol/test/governance/governance.ts
@@ -794,7 +794,7 @@ contract('Governance', (accounts: string[]) => {
const weight = new BigNumber(10)
const proposalId = new BigNumber(1)
beforeEach(async () => {
- await mockLockedGold.setWeight(account, weight)
+ await mockLockedGold.setAccountTotalLockedGold(account, weight)
await governance.propose(
[transactionSuccess1.value],
[transactionSuccess1.destination],
@@ -812,7 +812,9 @@ contract('Governance', (accounts: string[]) => {
it('should mark the account as having upvoted the proposal', async () => {
await governance.upvote(proposalId, 0, 0)
- assertEqualBN(await governance.getUpvotedProposal(account), proposalId)
+ const [recordId, recordWeight] = await governance.getUpvoteRecord(account)
+ assertEqualBN(recordId, proposalId)
+ assertEqualBN(recordWeight, weight)
})
it('should return true', async () => {
@@ -834,13 +836,8 @@ contract('Governance', (accounts: string[]) => {
})
})
- it('should revert when the account is frozen', async () => {
- await mockLockedGold.setVotingFrozen(account)
- await assertRevert(governance.upvote(proposalId, 0, 0))
- })
-
it('should revert when the account weight is 0', async () => {
- await mockLockedGold.setWeight(account, 0)
+ await mockLockedGold.setAccountTotalLockedGold(account, 0)
await assertRevert(governance.upvote(proposalId, 0, 0))
})
@@ -883,7 +880,7 @@ contract('Governance', (accounts: string[]) => {
{ value: minDeposit }
)
const otherAccount = accounts[1]
- await mockLockedGold.setWeight(otherAccount, weight)
+ await mockLockedGold.setAccountTotalLockedGold(otherAccount, weight)
await governance.upvote(otherProposalId, proposalId, 0, { from: otherAccount })
await timeTravel(queueExpiry, web3)
})
@@ -948,7 +945,7 @@ contract('Governance', (accounts: string[]) => {
const weight = new BigNumber(10)
const proposalId = new BigNumber(1)
beforeEach(async () => {
- await mockLockedGold.setWeight(account, weight)
+ await mockLockedGold.setAccountTotalLockedGold(account, weight)
await governance.propose(
[transactionSuccess1.value],
[transactionSuccess1.destination],
@@ -972,12 +969,9 @@ contract('Governance', (accounts: string[]) => {
it('should mark the account as not having upvoted a proposal', async () => {
await governance.revokeUpvote(0, 0)
- assertEqualBN(await governance.getUpvotedProposal(account), 0)
- })
-
- it('should succeed when the account is frozen', async () => {
- await mockLockedGold.setVotingFrozen(account)
- await governance.revokeUpvote(0, 0)
+ const [recordId, recordWeight] = await governance.getUpvoteRecord(account)
+ assertEqualBN(recordId, 0)
+ assertEqualBN(recordWeight, 0)
})
it('should emit the ProposalUpvoteRevoked event', async () => {
@@ -1000,7 +994,7 @@ contract('Governance', (accounts: string[]) => {
})
it('should revert when the account weight is 0', async () => {
- await mockLockedGold.setWeight(account, 0)
+ await mockLockedGold.setAccountTotalLockedGold(account, 0)
await assertRevert(governance.revokeUpvote(0, 0))
})
@@ -1019,7 +1013,9 @@ contract('Governance', (accounts: string[]) => {
it('should mark the account as not having upvoted a proposal', async () => {
await governance.revokeUpvote(0, 0)
- assertEqualBN(await governance.getUpvotedProposal(account), 0)
+ const [recordId, recordWeight] = await governance.getUpvoteRecord(account)
+ assertEqualBN(recordId, 0)
+ assertEqualBN(recordWeight, 0)
})
it('should emit the ProposalExpired event', async () => {
@@ -1049,7 +1045,9 @@ contract('Governance', (accounts: string[]) => {
it('should mark the account as not having upvoted a proposal', async () => {
await governance.revokeUpvote(0, 0)
- assertEqualBN(await governance.getUpvotedProposal(account), 0)
+ const [recordId, recordWeight] = await governance.getUpvoteRecord(account)
+ assertEqualBN(recordId, 0)
+ assertEqualBN(recordWeight, 0)
})
})
})
@@ -1229,7 +1227,7 @@ contract('Governance', (accounts: string[]) => {
await timeTravel(dequeueFrequency, web3)
await governance.approve(proposalId, index)
await timeTravel(approvalStageDuration, web3)
- await mockLockedGold.setWeight(account, weight)
+ await mockLockedGold.setAccountTotalLockedGold(account, weight)
})
it('should return true', async () => {
@@ -1273,13 +1271,8 @@ contract('Governance', (accounts: string[]) => {
})
})
- it('should revert when the account is frozen', async () => {
- await mockLockedGold.setVotingFrozen(account)
- await assertRevert(governance.vote(proposalId, index, value))
- })
-
it('should revert when the account weight is 0', async () => {
- await mockLockedGold.setWeight(account, 0)
+ await mockLockedGold.setAccountTotalLockedGold(account, 0)
await assertRevert(governance.vote(proposalId, index, value))
})
@@ -1393,7 +1386,7 @@ contract('Governance', (accounts: string[]) => {
await timeTravel(dequeueFrequency, web3)
await governance.approve(proposalId, index)
await timeTravel(approvalStageDuration, web3)
- await mockLockedGold.setWeight(account, weight)
+ await mockLockedGold.setAccountTotalLockedGold(account, weight)
await governance.vote(proposalId, index, value)
await timeTravel(referendumStageDuration, web3)
})
@@ -1439,7 +1432,7 @@ contract('Governance', (accounts: string[]) => {
await timeTravel(dequeueFrequency, web3)
await governance.approve(proposalId, index)
await timeTravel(approvalStageDuration, web3)
- await mockLockedGold.setWeight(account, weight)
+ await mockLockedGold.setAccountTotalLockedGold(account, weight)
await governance.vote(proposalId, index, value)
await timeTravel(referendumStageDuration, web3)
})
@@ -1465,7 +1458,7 @@ contract('Governance', (accounts: string[]) => {
await timeTravel(dequeueFrequency, web3)
await governance.approve(proposalId, index)
await timeTravel(approvalStageDuration, web3)
- await mockLockedGold.setWeight(account, weight)
+ await mockLockedGold.setAccountTotalLockedGold(account, weight)
await governance.vote(proposalId, index, value)
await timeTravel(referendumStageDuration, web3)
})
@@ -1514,7 +1507,7 @@ contract('Governance', (accounts: string[]) => {
await timeTravel(dequeueFrequency, web3)
await governance.approve(proposalId, index)
await timeTravel(approvalStageDuration, web3)
- await mockLockedGold.setWeight(account, weight)
+ await mockLockedGold.setAccountTotalLockedGold(account, weight)
await governance.vote(proposalId, index, value)
await timeTravel(referendumStageDuration, web3)
})
@@ -1538,7 +1531,7 @@ contract('Governance', (accounts: string[]) => {
await timeTravel(dequeueFrequency, web3)
await governance.approve(proposalId, index)
await timeTravel(approvalStageDuration, web3)
- await mockLockedGold.setWeight(account, weight)
+ await mockLockedGold.setAccountTotalLockedGold(account, weight)
await governance.vote(proposalId, index, value)
await timeTravel(referendumStageDuration, web3)
})
@@ -1563,7 +1556,7 @@ contract('Governance', (accounts: string[]) => {
await timeTravel(dequeueFrequency, web3)
await governance.approve(proposalId, index)
await timeTravel(approvalStageDuration, web3)
- await mockLockedGold.setWeight(account, weight)
+ await mockLockedGold.setAccountTotalLockedGold(account, weight)
await governance.vote(proposalId, index, value)
await timeTravel(referendumStageDuration, web3)
await timeTravel(executionStageDuration, web3)
@@ -1586,6 +1579,7 @@ contract('Governance', (accounts: string[]) => {
})
})
+ /*
describe('#isVoting()', () => {
describe('when the account has never acted on a proposal', () => {
it('should return false', async () => {
@@ -1597,7 +1591,7 @@ contract('Governance', (accounts: string[]) => {
const weight = 10
const proposalId = 1
beforeEach(async () => {
- await mockLockedGold.setWeight(account, weight)
+ await mockLockedGold.setAccountTotalLockedGold(account, weight)
await governance.propose(
[transactionSuccess1.value],
[transactionSuccess1.destination],
@@ -1651,7 +1645,7 @@ contract('Governance', (accounts: string[]) => {
await timeTravel(dequeueFrequency, web3)
await governance.approve(proposalId, index)
await timeTravel(approvalStageDuration, web3)
- await mockLockedGold.setWeight(account, weight)
+ await mockLockedGold.setAccountTotalLockedGold(account, weight)
await governance.vote(proposalId, index, value)
})
@@ -1670,4 +1664,5 @@ contract('Governance', (accounts: string[]) => {
})
})
})
+ */
})
diff --git a/packages/protocol/test/governance/lockedgold.ts b/packages/protocol/test/governance/lockedgold.ts
index 9a29a8f0464..08544005254 100644
--- a/packages/protocol/test/governance/lockedgold.ts
+++ b/packages/protocol/test/governance/lockedgold.ts
@@ -12,10 +12,8 @@ import {
LockedGoldInstance,
MockGoldTokenContract,
MockGoldTokenInstance,
- MockGovernanceContract,
- MockGovernanceInstance,
- MockValidatorsContract,
- MockValidatorsInstance,
+ MockElectionContract,
+ MockElectionInstance,
RegistryContract,
RegistryInstance,
} from 'types'
@@ -23,8 +21,7 @@ import {
const LockedGold: LockedGoldContract = artifacts.require('LockedGold')
const Registry: RegistryContract = artifacts.require('Registry')
const MockGoldToken: MockGoldTokenContract = artifacts.require('MockGoldToken')
-const MockGovernance: MockGovernanceContract = artifacts.require('MockGovernance')
-const MockValidators: MockValidatorsContract = artifacts.require('MockValidators')
+const MockElection: MockElectionContract = artifacts.require('MockElection')
// @ts-ignore
// TODO(mcortesi): Use BN
@@ -32,17 +29,19 @@ LockedGold.numberFormat = 'BigNumber'
const HOUR = 60 * 60
const DAY = 24 * HOUR
-const YEAR = 365 * DAY
+let authorizationTests = { voter: {}, validator: {} }
contract('LockedGold', (accounts: string[]) => {
let account = accounts[0]
const nonOwner = accounts[1]
const unlockingPeriod = 3 * DAY
- let mockGoldToken: MockGoldTokenInstance
- let mockGovernance: MockGovernanceInstance
- let mockValidators: MockValidatorsInstance
let lockedGold: LockedGoldInstance
let registry: RegistryInstance
+ let mockElection: MockElectionInstance
+
+ const capitalize = (s: string) => {
+ return s.charAt(0).toUpperCase() + s.slice(1)
+ }
const getParsedSignatureOfAddress = async (address: string, signer: string) => {
// @ts-ignore
@@ -56,16 +55,25 @@ contract('LockedGold', (accounts: string[]) => {
}
beforeEach(async () => {
+ const mockGoldToken: MockGoldTokenInstance = await MockGoldToken.new()
+ mockElection = await MockElection.new()
lockedGold = await LockedGold.new()
- mockGoldToken = await MockGoldToken.new()
- mockGovernance = await MockGovernance.new()
- mockValidators = await MockValidators.new()
registry = await Registry.new()
await registry.setAddressFor(CeloContractName.GoldToken, mockGoldToken.address)
- await registry.setAddressFor(CeloContractName.Governance, mockGovernance.address)
- await registry.setAddressFor(CeloContractName.Validators, mockValidators.address)
+ await registry.setAddressFor(CeloContractName.Election, mockElection.address)
await lockedGold.initialize(registry.address, unlockingPeriod)
await lockedGold.createAccount()
+
+ authorizationTests.voter = {
+ fn: lockedGold.authorizeVoter,
+ getAuthorizedFromAccount: lockedGold.getVoterFromAccount,
+ getAccountFromAuthorized: lockedGold.getAccountFromVoter,
+ }
+ authorizationTests.validator = {
+ fn: lockedGold.authorizeValidator,
+ getAuthorizedFromAccount: lockedGold.getValidatorFromAccount,
+ getAccountFromAuthorized: lockedGold.getAccountFromValidator,
+ }
})
describe('#initialize()', () => {
@@ -80,8 +88,8 @@ contract('LockedGold', (accounts: string[]) => {
})
it('should set the unlocking period', async () => {
- const period: string = await lockedGold.unlockingPeriod()
- assert.equal(unlockingPeriod, period)
+ const period = await lockedGold.unlockingPeriod()
+ assertEqualBN(unlockingPeriod, period)
})
it('should revert if already initialized', async () => {
@@ -102,141 +110,135 @@ contract('LockedGold', (accounts: string[]) => {
})
})
- const authorizationTests = [
- {
- name: 'Voter',
- fn: lockedGold.authorizeVoter,
- getFromAccount: lockedGold.getVoterFromAccount,
- getAccount: lockedGold.getAccountFromVoter,
- },
- {
- name: 'Validator',
- fn: lockedGold.authorizeValidator,
- getFromAccount: lockedGold.getValidatorFromAccount,
- getAccount: lockedGold.getAccountFromValidator,
- },
- ]
- for (const test in authorizationTests) {
- describe(`#authorize${test.name}()`, () => {
- const authorized = accounts[1]
- let sig
-
+ Object.keys(authorizationTests).forEach((key) => {
+ describe('authorization tests:', () => {
+ let authorizationTest: any
beforeEach(async () => {
- sig = await getParsedSignatureOfAddress(account, authorized)
+ authorizationTest = authorizationTests[key]
})
- it(`should set the authorized ${test.name}`, async () => {
- await test.fn(authorized, sig.v, sig.r, sig.s)
- assert.equal(await lockedGold.authorizedBy(authorized), account)
- assert.equal(await test.getFromAccount(account), authorized)
- assert.equal(await test.getAccount(authorized), account)
- })
+ describe(`#authorize${capitalize(key)}()`, () => {
+ const authorized = accounts[1]
+ let sig
- it(`should emit a ${test.name}Authorized event`, async () => {
- const resp = await test.fn(authorized, sig.v, sig.r, sig.s)
- assert.equal(resp.logs.length, 1)
- const log = resp.logs[0]
- assertLogMatches(log, `${test.name}Authorized`, {
- account,
- authorized,
+ beforeEach(async () => {
+ sig = await getParsedSignatureOfAddress(account, authorized)
})
- })
-
- it(`should revert if the ${test.name} is an account`, async () => {
- await lockedGold.createAccount({ from: authorized })
- await assertRevert(test.fn(authorized, sig.v, sig.r, sig.s))
- })
- it(`should revert if the ${test.name} is already authorized`, async () => {
- const otherAccount = accounts[2]
- const otherSig = await getParsedSignatureOfAddress(otherAccount, authorized)
- await lockedGold.createAccount({ from: otherAccount })
- await test.fn(authorized, otherSig.v, otherSig.r, otherSig.s, {
- from: otherAccount,
+ it(`should set the authorized ${key}`, async () => {
+ await authorizationTest.fn(authorized, sig.v, sig.r, sig.s)
+ assert.equal(await lockedGold.authorizedBy(authorized), account)
+ assert.equal(await authorizationTest.getAuthorizedFromAccount(account), authorized)
+ assert.equal(await authorizationTest.getAccountFromAuthorized(authorized), account)
})
- await assertRevert(test.fn(authorized, sig.v, sig.r, sig.s))
- })
- it('should revert if the signature is incorrect', async () => {
- const nonVoter = accounts[3]
- const incorrectSig = await getParsedSignatureOfAddress(account, nonVoter)
- await assertRevert(test.fn(authorized, incorrectSig.v, incorrectSig.r, incorrectSig.s))
- })
-
- describe('when a previous authorization has been made', async () => {
- const newAuthorized = accounts[2]
- let newSig
- beforeEach(async () => {
- await test.fn(authorized, sig.v, sig.r, sig.s)
- newSig = await getParsedSignatureOfAddress(account, newAuthorized)
- await test.fn(newAuthorized, newSig.v, newSig.r, newSig.s)
+ it(`should emit a ${capitalize(key)}Authorized event`, async () => {
+ const resp = await authorizationTest.fn(authorized, sig.v, sig.r, sig.s)
+ assert.equal(resp.logs.length, 1)
+ const log = resp.logs[0]
+ const expected = { account }
+ expected[key] = authorized
+ assertLogMatches(log, `${capitalize(key)}Authorized`, expected)
})
- it(`should set the new authorized ${test.name}`, async () => {
- assert.equal(await lockedGold.authorizedBy(newAuthorized), account)
- assert.equal(await test.getFromAccount(account), newAuthorized)
- assert.equal(await test.getAccount(newAuthorized), account)
+ it(`should revert if the ${key} is an account`, async () => {
+ await lockedGold.createAccount({ from: authorized })
+ await assertRevert(authorizationTest.fn(authorized, sig.v, sig.r, sig.s))
})
- it('should reset the previous authorization', async () => {
- assert.equal(await lockedGold.authorizedBy(authorized), NULL_ADDRESS)
+ it(`should revert if the ${key} is already authorized`, async () => {
+ const otherAccount = accounts[2]
+ const otherSig = await getParsedSignatureOfAddress(otherAccount, authorized)
+ await lockedGold.createAccount({ from: otherAccount })
+ await authorizationTest.fn(authorized, otherSig.v, otherSig.r, otherSig.s, {
+ from: otherAccount,
+ })
+ await assertRevert(authorizationTest.fn(authorized, sig.v, sig.r, sig.s))
})
- })
- })
- describe(`#getAccountFrom${test.name}()`, () => {
- describe(`when the account has not authorized a ${test.name}`, () => {
- it('should return the account when passed the account', async () => {
- assert.equal(await test.getAccount(account), account)
+ it('should revert if the signature is incorrect', async () => {
+ const nonVoter = accounts[3]
+ const incorrectSig = await getParsedSignatureOfAddress(account, nonVoter)
+ await assertRevert(
+ authorizationTest.fn(authorized, incorrectSig.v, incorrectSig.r, incorrectSig.s)
+ )
})
- it('should revert when passed an address that is not an account', async () => {
- await assertRevert(test.getAccount(accounts[1]))
+ describe('when a previous authorization has been made', async () => {
+ const newAuthorized = accounts[2]
+ let newSig
+ beforeEach(async () => {
+ await authorizationTest.fn(authorized, sig.v, sig.r, sig.s)
+ newSig = await getParsedSignatureOfAddress(account, newAuthorized)
+ await authorizationTest.fn(newAuthorized, newSig.v, newSig.r, newSig.s)
+ })
+
+ it(`should set the new authorized ${key}`, async () => {
+ assert.equal(await lockedGold.authorizedBy(newAuthorized), account)
+ assert.equal(await authorizationTest.getAuthorizedFromAccount(account), newAuthorized)
+ assert.equal(await authorizationTest.getAccountFromAuthorized(newAuthorized), account)
+ })
+
+ it('should reset the previous authorization', async () => {
+ assert.equal(await lockedGold.authorizedBy(authorized), NULL_ADDRESS)
+ })
})
})
- describe(`when the account has authorized a ${test.name}`, () => {
- const authorized = accounts[1]
- before(async () => {
- const sig = await getParsedSignatureOfAddress(account, voter)
- await test.fn(authorized, sig.v, sig.r, sig.s)
- })
+ describe(`#getAccountFrom${capitalize(key)}()`, () => {
+ describe(`when the account has not authorized a ${key}`, () => {
+ it('should return the account when passed the account', async () => {
+ assert.equal(await authorizationTest.getAccountFromAuthorized(account), account)
+ })
- it('should return the account when passed the account', async () => {
- assert.equal(await test.getAccount(account), account)
+ it('should revert when passed an address that is not an account', async () => {
+ await assertRevert(authorizationTest.getAccountFromAuthorized(accounts[1]))
+ })
})
- it(`should return the account when passed the ${test.name}`, async () => {
- assert.equal(await test.getAccount(authorized), account)
- })
- })
- })
+ describe(`when the account has authorized a ${key}`, () => {
+ const authorized = accounts[1]
+ beforeEach(async () => {
+ const sig = await getParsedSignatureOfAddress(account, authorized)
+ await authorizationTest.fn(authorized, sig.v, sig.r, sig.s)
+ })
- describe(`#get${test.name}FromAccount()`, () => {
- describe(`when the account has not authorized a ${test.name}`, () => {
- it('should return the account when passed the account', async () => {
- assert.equal(await test.getFromAccount(account), account)
- })
+ it('should return the account when passed the account', async () => {
+ assert.equal(await authorizationTest.getAccountFromAuthorized(account), account)
+ })
- it('should revert when not passed an account', async () => {
- await assertRevert(test.getFromAccount(account), account)
+ it(`should return the account when passed the ${key}`, async () => {
+ assert.equal(await authorizationTest.getAccountFromAuthorized(authorized), account)
+ })
})
})
- describe(`when the account has authorized a ${test.name}`, () => {
- const authorized = accounts[1]
+ describe(`#get${capitalize(key)}FromAccount()`, () => {
+ describe(`when the account has not authorized a ${key}`, () => {
+ it('should return the account when passed the account', async () => {
+ assert.equal(await authorizationTest.getAuthorizedFromAccount(account), account)
+ })
- before(async () => {
- const sig = await getParsedSignatureOfAddress(account, authorized)
- await test.fn(authorized, sig.v, sig.r, sig.s)
+ it('should revert when not passed an account', async () => {
+ await assertRevert(authorizationTest.getAuthorizedFromAccount(accounts[1]), account)
+ })
})
- it(`should return the ${test.name} when passed the account`, async () => {
- assert.equal(await test.getFromAccount(account), authorized)
+ describe(`when the account has authorized a ${key}`, () => {
+ const authorized = accounts[1]
+
+ beforeEach(async () => {
+ const sig = await getParsedSignatureOfAddress(account, authorized)
+ await authorizationTest.fn(authorized, sig.v, sig.r, sig.s)
+ })
+
+ it(`should return the ${key} when passed the account`, async () => {
+ assert.equal(await authorizationTest.getAuthorizedFromAccount(account), authorized)
+ })
})
})
})
- }
+ })
describe('#lock()', () => {
const value = 1000
@@ -244,25 +246,25 @@ contract('LockedGold', (accounts: string[]) => {
it("should increase the account's nonvoting locked gold balance", async () => {
// @ts-ignore: TODO(mcortesi) fix typings for TransactionDetails
await lockedGold.lock({ value })
- assert.equal(await lockedGold.getAccountNonvotingLockedGold(account), value)
+ assertEqualBN(await lockedGold.getAccountNonvotingLockedGold(account), value)
})
it("should increase the account's total locked gold balance", async () => {
// @ts-ignore: TODO(mcortesi) fix typings for TransactionDetails
await lockedGold.lock({ value })
- assert.equal(await lockedGold.getAccountTotalLockedGold(account), value)
+ assertEqualBN(await lockedGold.getAccountTotalLockedGold(account), value)
})
it('should increase the nonvoting locked gold balance', async () => {
// @ts-ignore: TODO(mcortesi) fix typings for TransactionDetails
await lockedGold.lock({ value })
- assert.equal(await lockedGold.getNonvotingLockedGold(), value)
+ assertEqualBN(await lockedGold.getNonvotingLockedGold(), value)
})
it('should increase the total locked gold balance', async () => {
// @ts-ignore: TODO(mcortesi) fix typings for TransactionDetails
await lockedGold.lock({ value })
- assert.equal(await lockedGold.getTotalLockedGold(), value)
+ assertEqualBN(await lockedGold.getTotalLockedGold(), value)
})
it('should emit a GoldLocked event', async () => {
@@ -277,6 +279,7 @@ contract('LockedGold', (accounts: string[]) => {
})
it('should revert when the specified value is 0', async () => {
+ // @ts-ignore: TODO(mcortesi) fix typings for TransactionDetails
await assertRevert(lockedGold.lock({ value: 0 }))
})
@@ -290,7 +293,7 @@ contract('LockedGold', (accounts: string[]) => {
let availabilityTime: BigNumber
let resp: any
describe('when there are no balance requirements', () => {
- before(async () => {
+ beforeEach(async () => {
// @ts-ignore: TODO(mcortesi) fix typings for TransactionDetails
await lockedGold.lock({ value })
resp = await lockedGold.unlock(value)
@@ -300,26 +303,27 @@ contract('LockedGold', (accounts: string[]) => {
})
it('should add a pending withdrawal', async () => {
- const pendingWithdrawals = await lockedGold.getPendingWithdrawals(account)
- assert.equal(pendingWithdrawals.length, 1)
- assert.equal(pendingWithdrawals[0], value)
- assert.equal(pendingWithdrawals[1], availabilityTime)
+ const [values, timestamps] = await lockedGold.getPendingWithdrawals(account)
+ assert.equal(values.length, 1)
+ assert.equal(timestamps.length, 1)
+ assertEqualBN(values[0], value)
+ assertEqualBN(timestamps[0], availabilityTime)
})
it("should decrease the account's nonvoting locked gold balance", async () => {
- assert.equal(await lockedGold.getAccountNonvotingLockedGold(account), 0)
+ assertEqualBN(await lockedGold.getAccountNonvotingLockedGold(account), 0)
})
it("should decrease the account's total locked gold balance", async () => {
- assert.equal(await lockedGold.getAccountTotalLockedGold(account), 0)
+ assertEqualBN(await lockedGold.getAccountTotalLockedGold(account), 0)
})
it('should decrease the nonvoting locked gold balance', async () => {
- assert.equal(await lockedGold.getNonvotingLockedGold(), 0)
+ assertEqualBN(await lockedGold.getNonvotingLockedGold(), 0)
})
it('should decrease the total locked gold balance', async () => {
- assert.equal(await lockedGold.getTotalLockedGold(), 0)
+ assertEqualBN(await lockedGold.getTotalLockedGold(), 0)
})
it('should emit a GoldUnlocked event', async () => {
@@ -328,21 +332,22 @@ contract('LockedGold', (accounts: string[]) => {
assertLogMatches(log, 'GoldUnlocked', {
account,
value: new BigNumber(value),
- available,
+ available: availabilityTime,
})
})
})
describe('when there are balance requirements', () => {
let mustMaintain: any
- before(async () => {
+ beforeEach(async () => {
// @ts-ignore: TODO(mcortesi) fix typings for TransactionDetails
await lockedGold.lock({ value })
// Allow ourselves to call `setAccountMustMaintain()`
- await registry.setAddressFor('Election', account)
+ await registry.setAddressFor(CeloContractName.Election, account)
const timestamp = (await web3.eth.getBlock('latest')).timestamp
- const mustMaintain = { value: 100, timestamp: timestamp + DAY }
+ mustMaintain = { value: 100, timestamp: timestamp + DAY }
await lockedGold.setAccountMustMaintain(account, mustMaintain.value, mustMaintain.timestamp)
+ await registry.setAddressFor(CeloContractName.Election, mockElection.address)
})
describe('when unlocking would yield a locked gold balance less than the required value', () => {
@@ -354,7 +359,7 @@ contract('LockedGold', (accounts: string[]) => {
describe('when the the current time is later than the requirement time', () => {
it('should succeed', async () => {
- await timeTravel(web3, DAY)
+ await timeTravel(DAY, web3)
await lockedGold.unlock(value)
})
})
@@ -371,8 +376,9 @@ contract('LockedGold', (accounts: string[]) => {
describe('#relock()', () => {
const value = 1000
const index = 0
+ let resp: any
describe('when a pending withdrawal exists', () => {
- before(async () => {
+ beforeEach(async () => {
// @ts-ignore: TODO(mcortesi) fix typings for TransactionDetails
await lockedGold.lock({ value })
await lockedGold.unlock(value)
@@ -380,19 +386,19 @@ contract('LockedGold', (accounts: string[]) => {
})
it("should increase the account's nonvoting locked gold balance", async () => {
- assert.equal(await lockedGold.getAccountNonvotingLockedGold(account), value)
+ assertEqualBN(await lockedGold.getAccountNonvotingLockedGold(account), value)
})
it("should increase the account's total locked gold balance", async () => {
- assert.equal(await lockedGold.getAccountTotalLockedGold(account), value)
+ assertEqualBN(await lockedGold.getAccountTotalLockedGold(account), value)
})
it('should increase the nonvoting locked gold balance', async () => {
- assert.equal(await lockedGold.getNonvotingLockedGold(), value)
+ assertEqualBN(await lockedGold.getNonvotingLockedGold(), value)
})
it('should increase the total locked gold balance', async () => {
- assert.equal(await lockedGold.getTotalLockedGold(), value)
+ assertEqualBN(await lockedGold.getTotalLockedGold(), value)
})
it('should emit a GoldLocked event', async () => {
@@ -405,8 +411,9 @@ contract('LockedGold', (accounts: string[]) => {
})
it('should remove the pending withdrawal', async () => {
- const pendingWithdrawals = await lockedGold.getPendingWithdrawals(account)
- assert.equal(pendingWithdrawals.length, 0)
+ const [values, timestamps] = await lockedGold.getPendingWithdrawals(account)
+ assert.equal(values.length, 0)
+ assert.equal(timestamps.length, 0)
})
})
@@ -420,27 +427,24 @@ contract('LockedGold', (accounts: string[]) => {
describe('#withdraw()', () => {
const value = 1000
const index = 0
- let availabilityTime: BigNumber
let resp: any
describe('when a pending withdrawal exists', () => {
- before(async () => {
+ beforeEach(async () => {
// @ts-ignore: TODO(mcortesi) fix typings for TransactionDetails
await lockedGold.lock({ value })
resp = await lockedGold.unlock(value)
- availabilityTime = new BigNumber(unlockingPeriod).plus(
- (await web3.eth.getBlock('latest')).timestamp
- )
})
describe('when it is after the availablity time', () => {
- before(async () => {
- await timeTravel(web3, unlockingPeriod)
+ beforeEach(async () => {
+ await timeTravel(unlockingPeriod, web3)
resp = await lockedGold.withdraw(index)
})
it('should remove the pending withdrawal', async () => {
- const pendingWithdrawals = await lockedGold.getPendingWithdrawals(account)
- assert.equal(pendingWithdrawals.length, 0)
+ const [values, timestamps] = await lockedGold.getPendingWithdrawals(account)
+ assert.equal(values.length, 0)
+ assert.equal(timestamps.length, 0)
})
it('should emit a GoldWithdrawn event', async () => {
From 871d06d87f279600b92df9d5f57fe48984591d31 Mon Sep 17 00:00:00 2001
From: Asa Oines
Date: Tue, 24 Sep 2019 12:12:00 -0700
Subject: [PATCH 008/149] Validators tests passing
---
.../contracts/governance/Validators.sol | 20 +-
.../governance/test/MockLockedGold.sol | 15 ++
.../protocol/test/governance/validators.ts | 245 +++++++-----------
3 files changed, 123 insertions(+), 157 deletions(-)
diff --git a/packages/protocol/contracts/governance/Validators.sol b/packages/protocol/contracts/governance/Validators.sol
index 74b462efa66..b1c8e8fff93 100644
--- a/packages/protocol/contracts/governance/Validators.sol
+++ b/packages/protocol/contracts/governance/Validators.sol
@@ -157,6 +157,10 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
return true;
}
+ function getMaxGroupSize() external view returns (uint256) {
+ return maxGroupSize;
+ }
+
/**
* @notice Updates the minimum gold requirements to register a validator group or validator.
* @param groupRequirement The minimum locked gold needed to register a group.
@@ -188,7 +192,7 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
* @return True upon success.
* @dev The new requirement is only enforced for future validator or group deregistrations.
*/
- function setDeregistrationLockup(
+ function setDeregistrationLockups(
uint256 groupLockup,
uint256 validatorLockup
)
@@ -239,7 +243,7 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
address account = getLockedGold().getAccountFromValidator(msg.sender);
require(!isValidator(account) && !isValidatorGroup(account));
- require(meetsValidatorRegistrationRequirement(account));
+ require(meetsValidatorRegistrationRequirement(account), "4");
Validator memory validator = Validator(name, url, publicKeysData, address(0));
validators[account] = validator;
@@ -266,7 +270,7 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
* @return Whether an account meets the requirements to register a validator.
*/
function meetsValidatorRegistrationRequirement(address account) public view returns (bool) {
- getLockedGold().getAccountTotalLockedGold(account) >= registrationRequirements.validator;
+ return getLockedGold().getAccountTotalLockedGold(account) >= registrationRequirements.validator;
}
/**
@@ -275,7 +279,7 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
* @return Whether an account meets the requirements to register a group.
*/
function meetsValidatorGroupRegistrationRequirement(address account) public view returns (bool) {
- getLockedGold().getAccountTotalLockedGold(account) >= registrationRequirements.group;
+ return getLockedGold().getAccountTotalLockedGold(account) >= registrationRequirements.group;
}
/**
@@ -306,7 +310,7 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
*/
function affiliate(address group) external nonReentrant returns (bool) {
address account = getLockedGold().getAccountFromValidator(msg.sender);
- require(isValidator(account) && isValidatorGroup(group));
+ require(isValidator(account) && isValidatorGroup(group), "blah");
Validator storage validator = validators[account];
if (validator.affiliation != address(0)) {
_deaffiliate(validator, account);
@@ -523,6 +527,10 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
return (registrationRequirements.group, registrationRequirements.validator);
}
+ function getDeregistrationLockups() external view returns (uint256, uint256) {
+ return (deregistrationLockups.group, deregistrationLockups.validator);
+ }
+
/**
* @notice Returns the list of registered validator accounts.
* @return The list of registered validator accounts.
@@ -581,7 +589,7 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
*/
function _removeMember(address group, address validator) private returns (bool) {
ValidatorGroup storage _group = groups[group];
- require(validators[validator].affiliation == group && _group.members.contains(validator));
+ require(validators[validator].affiliation == group && _group.members.contains(validator), "boogie");
_group.members.remove(validator);
emit ValidatorGroupMemberRemoved(group, validator);
diff --git a/packages/protocol/contracts/governance/test/MockLockedGold.sol b/packages/protocol/contracts/governance/test/MockLockedGold.sol
index 984960ed72d..3fbb7f2a2d4 100644
--- a/packages/protocol/contracts/governance/test/MockLockedGold.sol
+++ b/packages/protocol/contracts/governance/test/MockLockedGold.sol
@@ -21,6 +21,17 @@ contract MockLockedGold is ILockedGold {
return accountOrValidator;
}
+ function getAccountFromVoter(address accountOrVoter) external view returns (address) {
+ return accountOrVoter;
+ }
+
+ function getValidatorFromAccount(address account) external view returns (address) {
+ return account;
+ }
+
+ function incrementNonvotingAccountBalance(address, uint256) external {}
+ function decrementNonvotingAccountBalance(address, uint256) external {}
+
function setAccountMustMaintain(address account, uint256 value, uint256 timestamp) external returns (bool) {
mustMaintain[account] = MustMaintain(value, timestamp);
return true;
@@ -38,4 +49,8 @@ contract MockLockedGold is ILockedGold {
function getAccountTotalLockedGold(address account) external view returns (uint256) {
return totalLockedGold[account];
}
+
+ function getTotalLockedGold() external view returns (uint256) {
+ return 0;
+ }
}
diff --git a/packages/protocol/test/governance/validators.ts b/packages/protocol/test/governance/validators.ts
index a0f3be9f4fc..e6845eb11ce 100644
--- a/packages/protocol/test/governance/validators.ts
+++ b/packages/protocol/test/governance/validators.ts
@@ -1,5 +1,5 @@
import { CeloContractName } from '@celo/protocol/lib/registry-utils'
-import { fixed1, toFixed, fromFixed, multiply } from '@celo/utils/lib/fixidity'
+import { toFixed } from '@celo/utils/lib/fixidity'
import {
assertContainSubset,
assertEqualBN,
@@ -10,6 +10,8 @@ import BigNumber from 'bignumber.js'
import {
MockLockedGoldContract,
MockLockedGoldInstance,
+ MockElectionContract,
+ MockElectionInstance,
RegistryContract,
RegistryInstance,
ValidatorsContract,
@@ -18,6 +20,7 @@ import {
const Validators: ValidatorsContract = artifacts.require('Validators')
const MockLockedGold: MockLockedGoldContract = artifacts.require('MockLockedGold')
+const MockElection: MockElectionContract = artifacts.require('MockElection')
const Registry: RegistryContract = artifacts.require('Registry')
// @ts-ignore
@@ -26,30 +29,30 @@ Validators.numberFormat = 'BigNumber'
const parseValidatorParams = (validatorParams: any) => {
return {
- name: validatorParams[1],
- url: validatorParams[2],
- publicKeysData: validatorParams[3],
- affiliation: validatorParams[4],
+ name: validatorParams[0],
+ url: validatorParams[1],
+ publicKeysData: validatorParams[2],
+ affiliation: validatorParams[3],
}
}
const parseValidatorGroupParams = (groupParams: any) => {
return {
- name: groupParams[1],
- url: groupParams[2],
- members: groupParams[3],
+ name: groupParams[0],
+ url: groupParams[1],
+ members: groupParams[2],
}
}
const HOUR = 60 * 60
const DAY = 24 * HOUR
-const YEAR = 365 * DAY
-const MAX_UINT256 = new BigInt(2).pow(256).minus(1)
+const MAX_UINT256 = new BigNumber(2).pow(256).minus(1)
contract('Validators', (accounts: string[]) => {
let validators: ValidatorsInstance
let registry: RegistryInstance
let mockLockedGold: MockLockedGoldInstance
+ let mockElection: MockElectionInstance
// A random 64 byte hex string.
const publicKey =
'ea0733ad275e2b9e05541341a97ee82678c58932464fad26164657a111a7e37a9fa0300266fb90e2135a1f1512350cb4e985488a88809b14e3cbe415e76e82b2'
@@ -73,8 +76,10 @@ contract('Validators', (accounts: string[]) => {
beforeEach(async () => {
validators = await Validators.new()
mockLockedGold = await MockLockedGold.new()
+ mockElection = await MockElection.new()
registry = await Registry.new()
await registry.setAddressFor(CeloContractName.LockedGold, mockLockedGold.address)
+ await registry.setAddressFor(CeloContractName.Election, mockElection.address)
await validators.initialize(
registry.address,
registrationRequirements.group,
@@ -117,7 +122,7 @@ contract('Validators', (accounts: string[]) => {
})
it('should have set the registration requirements', async () => {
- const [group, validator] = await validators.getRegistrationRequirement()
+ const [group, validator] = await validators.getRegistrationRequirements()
assertEqualBN(group, registrationRequirements.group)
assertEqualBN(validator, registrationRequirements.validator)
})
@@ -144,14 +149,15 @@ contract('Validators', (accounts: string[]) => {
describe('#setRegistrationRequirements()', () => {
describe('when the requirements are different', () => {
+ const newRequirements = {
+ group: registrationRequirements.group.plus(1),
+ validator: registrationRequirements.validator.plus(1),
+ }
+
describe('when called by the owner', () => {
let resp: any
- const newRequirements = {
- group: registrationRequirements.group.plus(1),
- validator: registrationRequirements.validator.plus(1),
- }
- before(async () => {
+ beforeEach(async () => {
resp = await validators.setRegistrationRequirements(
newRequirements.group,
newRequirements.validator
@@ -175,6 +181,20 @@ contract('Validators', (accounts: string[]) => {
},
})
})
+
+ describe('when called by a non-owner', () => {
+ it('should revert', async () => {
+ await assertRevert(
+ validators.setRegistrationRequirements(
+ newRequirements.group,
+ newRequirements.validator,
+ {
+ from: nonOwner,
+ }
+ )
+ )
+ })
+ })
})
describe('when the requirements are the same', () => {
@@ -188,28 +208,19 @@ contract('Validators', (accounts: string[]) => {
})
})
})
-
- describe('when called by a non-owner', () => {
- it('should revert', async () => {
- await assertRevert(
- validators.setRegistrationRequirements(newRequirements.group, newRequirements.validator, {
- from: nonOwner,
- })
- )
- })
- })
})
describe('#setDeregistrationLockups()', () => {
describe('when the requirements are different', () => {
+ const newLockups = {
+ group: deregistrationLockups.group.plus(1),
+ validator: deregistrationLockups.validator.plus(1),
+ }
+
describe('when called by the owner', () => {
let resp: any
- const newLockups = {
- group: deregistrationLockups.group.plus(1),
- validator: deregistrationLockups.validator.plus(1),
- }
- before(async () => {
+ beforeEach(async () => {
resp = await validators.setDeregistrationLockups(newLockups.group, newLockups.validator)
})
@@ -230,6 +241,16 @@ contract('Validators', (accounts: string[]) => {
},
})
})
+
+ describe('when called by a non-owner', () => {
+ it('should revert', async () => {
+ await assertRevert(
+ validators.setDeregistrationLockups(newLockups.group, newLockups.validator, {
+ from: nonOwner,
+ })
+ )
+ })
+ })
})
describe('when the requirements are the same', () => {
@@ -243,25 +264,15 @@ contract('Validators', (accounts: string[]) => {
})
})
})
-
- describe('when called by a non-owner', () => {
- it('should revert', async () => {
- await assertRevert(
- validators.setDeregistrationLockups(newLockups.group, newLockups.validator, {
- from: nonOwner,
- })
- )
- })
- })
})
describe('#setMaxGroupSize()', () => {
describe('when the size is different', () => {
describe('when called by the owner', () => {
let resp: any
- const newSize = maxGroupSize.plus(1)
+ const newSize = maxGroupSize + 1
- before(async () => {
+ beforeEach(async () => {
resp = await validators.setMaxGroupSize(newSize)
})
@@ -276,7 +287,7 @@ contract('Validators', (accounts: string[]) => {
assertContainSubset(log, {
event: 'MaxGroupSizeSet',
args: {
- maxGroupSize: new BigNumber(newSize),
+ size: new BigNumber(newSize),
},
})
})
@@ -300,7 +311,7 @@ contract('Validators', (accounts: string[]) => {
const validator = accounts[0]
let resp: any
describe('when the account is not a registered validator', () => {
- before(async () => {
+ beforeEach(async () => {
await mockLockedGold.setAccountTotalLockedGold(
validator,
registrationRequirements.validator
@@ -330,8 +341,8 @@ contract('Validators', (accounts: string[]) => {
it('should set account balance requirements on locked gold', async () => {
const [value, timestamp] = await mockLockedGold.getAccountMustMaintain(validator)
- assert.equal(value, registrationRequirements.validator)
- assert.equal(timestamp, MAX_UINT256)
+ assertEqualBN(value, registrationRequirements.validator)
+ assertEqualBN(timestamp, MAX_UINT256)
})
it('should emit the ValidatorRegistered event', async () => {
@@ -350,7 +361,11 @@ contract('Validators', (accounts: string[]) => {
})
describe('when the account is already a registered validator', () => {
- before(async () => {
+ beforeEach(async () => {
+ await mockLockedGold.setAccountTotalLockedGold(
+ validator,
+ registrationRequirements.validator
+ )
await validators.registerValidator(
name,
url,
@@ -373,6 +388,7 @@ contract('Validators', (accounts: string[]) => {
describe('when the account is already a registered validator group', () => {
beforeEach(async () => {
+ await mockLockedGold.setAccountTotalLockedGold(validator, registrationRequirements.group)
await validators.registerValidatorGroup(name, url, commission)
})
@@ -414,7 +430,7 @@ contract('Validators', (accounts: string[]) => {
const index = 0
let resp: any
describe('when the account is not a registered validator', () => {
- before(async () => {
+ beforeEach(async () => {
await registerValidator(validator)
resp = await validators.deregisterValidator(index)
})
@@ -430,8 +446,11 @@ contract('Validators', (accounts: string[]) => {
it('should set account balance requirements on locked gold', async () => {
const latestTimestamp = (await web3.eth.getBlock('latest')).timestamp
const [value, timestamp] = await mockLockedGold.getAccountMustMaintain(validator)
- assert.equal(value, registrationRequirements.validator)
- assert.equal(timestamp, new BigNumber(timestamp).plus(deregistrationLockups.validator))
+ assertEqualBN(value, registrationRequirements.validator)
+ assertEqualBN(
+ timestamp,
+ new BigNumber(latestTimestamp).plus(deregistrationLockups.validator)
+ )
})
it('should emit the ValidatorDeregistered event', async () => {
@@ -449,6 +468,7 @@ contract('Validators', (accounts: string[]) => {
describe('when the validator is affiliated with a validator group', () => {
const group = accounts[1]
beforeEach(async () => {
+ await registerValidator(validator)
await registerValidatorGroup(group)
await validators.affiliate(group)
})
@@ -479,7 +499,7 @@ contract('Validators', (accounts: string[]) => {
it('should emit the ValidatorGroupMemberRemoved event', async () => {
const resp = await validators.deregisterValidator(index)
- assert.equal(resp.logs.length, 4)
+ assert.equal(resp.logs.length, 3)
const log = resp.logs[0]
assertContainSubset(log, {
event: 'ValidatorGroupMemberRemoved',
@@ -491,31 +511,9 @@ contract('Validators', (accounts: string[]) => {
})
describe('when the validator is the only member of that group', () => {
- it('should emit the ValidatorGroupEmptied event', async () => {
- const resp = await validators.deregisterValidator(index)
- assert.equal(resp.logs.length, 4)
- const log = resp.logs[1]
- assertContainSubset(log, {
- event: 'ValidatorGroupEmptied',
- args: {
- group,
- },
- })
- })
-
- describe('when that group has received votes', () => {
- beforeEach(async () => {
- const voter = accounts[2]
- const weight = 10
- await mockLockedGold.setWeight(voter, weight)
- await validators.vote(group, NULL_ADDRESS, NULL_ADDRESS, { from: voter })
- })
-
- it('should remove the group from the list of electable groups with votes', async () => {
- await validators.deregisterValidator(index)
- const [groups] = await validators.getValidatorGroupVotes()
- assert.deepEqual(groups, [])
- })
+ it('should should mark the group as ineligible for election', async () => {
+ await validators.deregisterValidator(index)
+ assert.isTrue(await mockElection.isIneligible(group))
})
})
})
@@ -609,7 +607,7 @@ contract('Validators', (accounts: string[]) => {
it('should emit the ValidatorGroupMemberRemoved event', async () => {
const resp = await validators.affiliate(otherGroup)
- assert.equal(resp.logs.length, 4)
+ assert.equal(resp.logs.length, 3)
const log = resp.logs[0]
assertContainSubset(log, {
event: 'ValidatorGroupMemberRemoved',
@@ -621,31 +619,9 @@ contract('Validators', (accounts: string[]) => {
})
describe('when the validator is the only member of that group', () => {
- it('should emit the ValidatorGroupEmptied event', async () => {
- const resp = await validators.affiliate(otherGroup)
- assert.equal(resp.logs.length, 4)
- const log = resp.logs[1]
- assertContainSubset(log, {
- event: 'ValidatorGroupEmptied',
- args: {
- group,
- },
- })
- })
-
- describe('when that group has received votes', () => {
- beforeEach(async () => {
- const voter = accounts[2]
- const weight = 10
- await mockLockedGold.setWeight(voter, weight)
- await validators.vote(group, NULL_ADDRESS, NULL_ADDRESS, { from: voter })
- })
-
- it('should remove the group from the list of electable groups with votes', async () => {
- await validators.affiliate(otherGroup)
- const [groups] = await validators.getValidatorGroupVotes()
- assert.deepEqual(groups, [])
- })
+ it('should should mark the group as ineligible for election', async () => {
+ await validators.affiliate(otherGroup)
+ assert.isTrue(await mockElection.isIneligible(group))
})
})
})
@@ -701,7 +677,7 @@ contract('Validators', (accounts: string[]) => {
it('should emit the ValidatorGroupMemberRemoved event', async () => {
const resp = await validators.deaffiliate()
- assert.equal(resp.logs.length, 3)
+ assert.equal(resp.logs.length, 2)
const log = resp.logs[0]
assertContainSubset(log, {
event: 'ValidatorGroupMemberRemoved',
@@ -713,31 +689,9 @@ contract('Validators', (accounts: string[]) => {
})
describe('when the validator is the only member of that group', () => {
- it('should emit the ValidatorGroupEmptied event', async () => {
- const resp = await validators.deaffiliate()
- assert.equal(resp.logs.length, 3)
- const log = resp.logs[1]
- assertContainSubset(log, {
- event: 'ValidatorGroupEmptied',
- args: {
- group,
- },
- })
- })
-
- describe('when that group has received votes', () => {
- beforeEach(async () => {
- const voter = accounts[2]
- const weight = 10
- await mockLockedGold.setWeight(voter, weight)
- await validators.vote(group, NULL_ADDRESS, NULL_ADDRESS, { from: voter })
- })
-
- it('should remove the group from the list of electable groups with votes', async () => {
- await validators.deaffiliate()
- const [groups] = await validators.getValidatorGroupVotes()
- assert.deepEqual(groups, [])
- })
+ it('should should mark the group as ineligible for election', async () => {
+ await validators.deaffiliate()
+ assert.isTrue(await mockElection.isIneligible(group))
})
})
})
@@ -756,15 +710,9 @@ contract('Validators', (accounts: string[]) => {
const group = accounts[0]
let resp: any
describe('when the account is not a registered validator group', () => {
- before(async () => {
+ beforeEach(async () => {
await mockLockedGold.setAccountTotalLockedGold(group, registrationRequirements.group)
resp = await validators.registerValidatorGroup(name, url, commission)
- resp = await validators.registerValidator(
- name,
- url,
- // @ts-ignore bytes type
- publicKeysData
- )
})
it('should mark the account as a validator group', async () => {
@@ -802,13 +750,14 @@ contract('Validators', (accounts: string[]) => {
it('should revert', async () => {
await assertRevert(
- validators.registerValidatorGroup(name, url, registrationRequirement.noticePeriod)
+ validators.registerValidatorGroup(name, url, registrationRequirements.group)
)
})
})
describe('when the account is already a registered validator group', () => {
beforeEach(async () => {
+ await mockLockedGold.setAccountTotalLockedGold(group, registrationRequirements.group)
await validators.registerValidatorGroup(name, url, commission)
})
@@ -820,7 +769,7 @@ contract('Validators', (accounts: string[]) => {
describe('when the account does not meet the registration requirements', () => {
beforeEach(async () => {
await mockLockedGold.setAccountTotalLockedGold(
- validator,
+ group,
registrationRequirements.group.minus(1)
)
})
@@ -837,28 +786,25 @@ contract('Validators', (accounts: string[]) => {
let resp: any
beforeEach(async () => {
await registerValidatorGroup(group)
+ resp = await validators.deregisterValidatorGroup(index)
})
it('should mark the account as not a validator group', async () => {
- await validators.deregisterValidatorGroup(index)
assert.isFalse(await validators.isValidatorGroup(group))
})
it('should remove the account from the list of validator groups', async () => {
- await validators.deregisterValidatorGroup(index)
assert.deepEqual(await validators.getRegisteredValidatorGroups(), [])
})
it('should set account balance requirements on locked gold', async () => {
- await validators.deregisterValidatorGroup(index)
const latestTimestamp = (await web3.eth.getBlock('latest')).timestamp
const [value, timestamp] = await mockLockedGold.getAccountMustMaintain(group)
- assert.equal(value, registrationRequirements.group)
- assert.equal(timestamp, new BigNumber(timestamp).plus(deregistrationLockups.group))
+ assertEqualBN(value, registrationRequirements.group)
+ assertEqualBN(timestamp, new BigNumber(latestTimestamp).plus(deregistrationLockups.group))
})
it('should emit the ValidatorGroupDeregistered event', async () => {
- const resp = await validators.deregisterValidatorGroup(index)
assert.equal(resp.logs.length, 1)
const log = resp.logs[0]
assertContainSubset(log, {
@@ -880,6 +826,7 @@ contract('Validators', (accounts: string[]) => {
describe('when the validator group is not empty', () => {
const validator = accounts[1]
beforeEach(async () => {
+ await registerValidatorGroup(group)
await registerValidator(validator)
await validators.affiliate(group, { from: validator })
await validators.addMember(validator)
@@ -894,20 +841,20 @@ contract('Validators', (accounts: string[]) => {
describe('#addMember', () => {
const group = accounts[0]
const validator = accounts[1]
+ let resp: any
beforeEach(async () => {
await registerValidator(validator)
await registerValidatorGroup(group)
await validators.affiliate(group, { from: validator })
+ resp = await validators.addMember(validator)
})
it('should add the member to the list of members', async () => {
- await validators.addMember(validator)
const parsedGroup = parseValidatorGroupParams(await validators.getValidatorGroup(group))
assert.deepEqual(parsedGroup.members, [validator])
})
it('should emit the ValidatorGroupMemberAdded event', async () => {
- const resp = await validators.addMember(validator)
assert.equal(resp.logs.length, 1)
const log = resp.logs[0]
assertContainSubset(log, {
@@ -938,10 +885,6 @@ contract('Validators', (accounts: string[]) => {
})
describe('when the validator is already a member of the group', () => {
- beforeEach(async () => {
- await validators.addMember(validator)
- })
-
it('should revert', async () => {
await assertRevert(validators.addMember(validator))
})
@@ -963,7 +906,7 @@ contract('Validators', (accounts: string[]) => {
it('should emit the ValidatorGroupMemberRemoved event', async () => {
const resp = await validators.removeMember(validator)
- assert.equal(resp.logs.length, 2)
+ assert.equal(resp.logs.length, 1)
const log = resp.logs[0]
assertContainSubset(log, {
event: 'ValidatorGroupMemberRemoved',
From e0e8cf7767fc0a6503e8224eb034bcd6d807f785 Mon Sep 17 00:00:00 2001
From: Asa Oines
Date: Tue, 24 Sep 2019 12:17:27 -0700
Subject: [PATCH 009/149] Fix governance tests
---
packages/protocol/test/governance/governance.ts | 5 -----
1 file changed, 5 deletions(-)
diff --git a/packages/protocol/test/governance/governance.ts b/packages/protocol/test/governance/governance.ts
index 302a9276af0..a9ecf9ccc63 100644
--- a/packages/protocol/test/governance/governance.ts
+++ b/packages/protocol/test/governance/governance.ts
@@ -836,11 +836,6 @@ contract('Governance', (accounts: string[]) => {
})
})
- it('should revert when the account weight is 0', async () => {
- await mockLockedGold.setAccountTotalLockedGold(account, 0)
- await assertRevert(governance.upvote(proposalId, 0, 0))
- })
-
it('should revert when upvoting a proposal that is not queued', async () => {
await assertRevert(governance.upvote(proposalId.plus(1), 0, 0))
})
From 223536d10b1feac91a50e2bccd36bd5235a99060 Mon Sep 17 00:00:00 2001
From: Asa Oines
Date: Tue, 24 Sep 2019 12:18:46 -0700
Subject: [PATCH 010/149] Add election test file
---
packages/protocol/test/governance/election.ts | 1407 +++++++++++++++++
1 file changed, 1407 insertions(+)
create mode 100644 packages/protocol/test/governance/election.ts
diff --git a/packages/protocol/test/governance/election.ts b/packages/protocol/test/governance/election.ts
new file mode 100644
index 00000000000..ca806c91a0f
--- /dev/null
+++ b/packages/protocol/test/governance/election.ts
@@ -0,0 +1,1407 @@
+import { CeloContractName } from '@celo/protocol/lib/registry-utils'
+import {
+ assertContainSubset,
+ assertEqualBN,
+ assertRevert,
+ NULL_ADDRESS,
+} from '@celo/protocol/lib/test-utils'
+import BigNumber from 'bignumber.js'
+import {
+ MockLockedGoldContract,
+ MockLockedGoldInstance,
+ MockRandomContract,
+ MockRandomInstance,
+ RegistryContract,
+ RegistryInstance,
+ ValidatorsContract,
+ ValidatorsInstance,
+} from 'types'
+
+const Validators: ValidatorsContract = artifacts.require('Validators')
+const MockLockedGold: MockLockedGoldContract = artifacts.require('MockLockedGold')
+const Registry: RegistryContract = artifacts.require('Registry')
+const Random: MockRandomContract = artifacts.require('MockRandom')
+
+// @ts-ignore
+// TODO(mcortesi): Use BN
+Validators.numberFormat = 'BigNumber'
+
+const parseValidatorParams = (validatorParams: any) => {
+ return {
+ identifier: validatorParams[0],
+ name: validatorParams[1],
+ url: validatorParams[2],
+ publicKeysData: validatorParams[3],
+ affiliation: validatorParams[4],
+ }
+}
+
+const parseValidatorGroupParams = (groupParams: any) => {
+ return {
+ identifier: groupParams[0],
+ name: groupParams[1],
+ url: groupParams[2],
+ members: groupParams[3],
+ }
+}
+
+contract('Validators', (accounts: string[]) => {
+ let validators: ValidatorsInstance
+ let registry: RegistryInstance
+ let mockLockedGold: MockLockedGoldInstance
+ let random: MockRandomInstance
+
+ // A random 64 byte hex string.
+ const publicKey =
+ 'ea0733ad275e2b9e05541341a97ee82678c58932464fad26164657a111a7e37a9fa0300266fb90e2135a1f1512350cb4e985488a88809b14e3cbe415e76e82b2'
+ const blsPublicKey =
+ '4d23d8cd06f30b1fa7cf368e2f5399ab04bb6846c682f493a98a607d3dfb7e53a712bb79b475c57b0ac2785460f91301'
+ const blsPoP =
+ '9d3e1d8f49f6b0d8e9a03d80ca07b1d24cf1cc0557bdcc04f5e17a46e35d02d0d411d956dbd5d2d2464eebd7b74ae30005d223780d785d2abc5644fac7ac29fb0e302bdc80c81a5d45018b68b1045068a4b3a4861c93037685fd0d252d740501'
+
+ const publicKeysData = '0x' + publicKey + blsPublicKey + blsPoP
+
+ const nonOwner = accounts[1]
+ const minElectableValidators = new BigNumber(4)
+ const maxElectableValidators = new BigNumber(6)
+ const registrationRequirement = { value: new BigNumber(100), noticePeriod: new BigNumber(60) }
+ const identifier = 'test-identifier'
+ const name = 'test-name'
+ const url = 'test-url'
+ beforeEach(async () => {
+ validators = await Validators.new()
+ mockLockedGold = await MockLockedGold.new()
+ random = await Random.new()
+ registry = await Registry.new()
+ await registry.setAddressFor(CeloContractName.LockedGold, mockLockedGold.address)
+ await registry.setAddressFor(CeloContractName.Random, random.address)
+ await validators.initialize(
+ registry.address,
+ minElectableValidators,
+ maxElectableValidators,
+ registrationRequirement.value,
+ registrationRequirement.noticePeriod
+ )
+ })
+
+ const registerValidator = async (validator: string) => {
+ await mockLockedGold.setLockedCommitment(
+ validator,
+ registrationRequirement.noticePeriod,
+ registrationRequirement.value
+ )
+ await validators.registerValidator(
+ identifier,
+ name,
+ url,
+ // @ts-ignore bytes type
+ publicKeysData,
+ registrationRequirement.noticePeriod,
+ { from: validator }
+ )
+ }
+
+ const registerValidatorGroup = async (group: string) => {
+ await mockLockedGold.setLockedCommitment(
+ group,
+ registrationRequirement.noticePeriod,
+ registrationRequirement.value
+ )
+ await validators.registerValidatorGroup(
+ identifier,
+ name,
+ url,
+ registrationRequirement.noticePeriod,
+ { from: group }
+ )
+ }
+
+ const registerValidatorGroupWithMembers = async (group: string, members: string[]) => {
+ await registerValidatorGroup(group)
+ for (const validator of members) {
+ await registerValidator(validator)
+ await validators.affiliate(group, { from: validator })
+ await validators.addMember(validator, { from: group })
+ }
+ }
+
+ describe('#initialize()', () => {
+ it('should have set the owner', async () => {
+ const owner: string = await validators.owner()
+ assert.equal(owner, accounts[0])
+ })
+
+ it('should have set minElectableValidators', async () => {
+ const actualMinElectableValidators = await validators.minElectableValidators()
+ assertEqualBN(actualMinElectableValidators, minElectableValidators)
+ })
+
+ it('should have set maxElectableValidators', async () => {
+ const actualMaxElectableValidators = await validators.maxElectableValidators()
+ assertEqualBN(actualMaxElectableValidators, maxElectableValidators)
+ })
+
+ it('should have set the registration requirements', async () => {
+ const [value, noticePeriod] = await validators.getRegistrationRequirement()
+ assertEqualBN(value, registrationRequirement.value)
+ assertEqualBN(noticePeriod, registrationRequirement.noticePeriod)
+ })
+
+ it('should not be callable again', async () => {
+ await assertRevert(
+ validators.initialize(
+ registry.address,
+ minElectableValidators,
+ maxElectableValidators,
+ registrationRequirement.value,
+ registrationRequirement.noticePeriod
+ )
+ )
+ })
+ })
+
+ describe('#setMinElectableValidators', () => {
+ const newMinElectableValidators = minElectableValidators.plus(1)
+ it('should set the minimum deposit', async () => {
+ await validators.setMinElectableValidators(newMinElectableValidators)
+ assertEqualBN(await validators.minElectableValidators(), newMinElectableValidators)
+ })
+
+ it('should emit the MinElectableValidatorsSet event', async () => {
+ const resp = await validators.setMinElectableValidators(newMinElectableValidators)
+ assert.equal(resp.logs.length, 1)
+ const log = resp.logs[0]
+ assertContainSubset(log, {
+ event: 'MinElectableValidatorsSet',
+ args: {
+ minElectableValidators: new BigNumber(newMinElectableValidators),
+ },
+ })
+ })
+
+ it('should revert when the minElectableValidators is zero', async () => {
+ await assertRevert(validators.setMinElectableValidators(0))
+ })
+
+ it('should revert when the minElectableValidators is greater than maxElectableValidators', async () => {
+ await assertRevert(validators.setMinElectableValidators(maxElectableValidators.plus(1)))
+ })
+
+ it('should revert when the minElectableValidators is unchanged', async () => {
+ await assertRevert(validators.setMinElectableValidators(minElectableValidators))
+ })
+
+ it('should revert when called by anyone other than the owner', async () => {
+ await assertRevert(
+ validators.setMinElectableValidators(newMinElectableValidators, { from: nonOwner })
+ )
+ })
+ })
+
+ describe('#setMaxElectableValidators', () => {
+ const newMaxElectableValidators = maxElectableValidators.plus(1)
+ it('should set the minimum deposit', async () => {
+ await validators.setMaxElectableValidators(newMaxElectableValidators)
+ assertEqualBN(await validators.maxElectableValidators(), newMaxElectableValidators)
+ })
+
+ it('should emit the MaxElectableValidatorsSet event', async () => {
+ const resp = await validators.setMaxElectableValidators(newMaxElectableValidators)
+ assert.equal(resp.logs.length, 1)
+ const log = resp.logs[0]
+ assertContainSubset(log, {
+ event: 'MaxElectableValidatorsSet',
+ args: {
+ maxElectableValidators: new BigNumber(newMaxElectableValidators),
+ },
+ })
+ })
+
+ it('should revert when the maxElectableValidators is less than minElectableValidators', async () => {
+ await assertRevert(validators.setMaxElectableValidators(minElectableValidators.minus(1)))
+ })
+
+ it('should revert when the maxElectableValidators is unchanged', async () => {
+ await assertRevert(validators.setMaxElectableValidators(maxElectableValidators))
+ })
+
+ it('should revert when called by anyone other than the owner', async () => {
+ await assertRevert(
+ validators.setMaxElectableValidators(newMaxElectableValidators, { from: nonOwner })
+ )
+ })
+ })
+
+ describe('#setRegistrationRequirement', () => {
+ const newValue = registrationRequirement.value.plus(1)
+ const newNoticePeriod = registrationRequirement.noticePeriod.plus(1)
+
+ it('should set the value and notice period', async () => {
+ await validators.setRegistrationRequirement(newValue, newNoticePeriod)
+ const [value, noticePeriod] = await validators.getRegistrationRequirement()
+ assertEqualBN(value, newValue)
+ assertEqualBN(noticePeriod, newNoticePeriod)
+ })
+
+ it('should emit the RegistrationRequirementSet event', async () => {
+ const resp = await validators.setRegistrationRequirement(newValue, newNoticePeriod)
+ assert.equal(resp.logs.length, 1)
+ const log = resp.logs[0]
+ assertContainSubset(log, {
+ event: 'RegistrationRequirementSet',
+ args: {
+ value: new BigNumber(newValue),
+ noticePeriod: new BigNumber(newNoticePeriod),
+ },
+ })
+ })
+
+ it('should revert when the requirement is unchanged', async () => {
+ await assertRevert(
+ validators.setRegistrationRequirement(
+ registrationRequirement.value,
+ registrationRequirement.noticePeriod
+ )
+ )
+ })
+
+ it('should revert when called by anyone other than the owner', async () => {
+ await assertRevert(
+ validators.setRegistrationRequirement(newValue, newNoticePeriod, { from: nonOwner })
+ )
+ })
+ })
+
+ describe('#registerValidator', () => {
+ const validator = accounts[0]
+ beforeEach(async () => {
+ await mockLockedGold.setLockedCommitment(
+ validator,
+ registrationRequirement.noticePeriod,
+ registrationRequirement.value
+ )
+ })
+
+ it('should mark the account as a validator', async () => {
+ await validators.registerValidator(
+ identifier,
+ name,
+ url,
+ // @ts-ignore bytes type
+ publicKeysData,
+ registrationRequirement.noticePeriod
+ )
+ assert.isTrue(await validators.isValidator(validator))
+ })
+
+ it('should add the account to the list of validators', async () => {
+ await validators.registerValidator(
+ identifier,
+ name,
+ url,
+ // @ts-ignore bytes type
+ publicKeysData,
+ registrationRequirement.noticePeriod
+ )
+ assert.deepEqual(await validators.getRegisteredValidators(), [validator])
+ })
+
+ it('should set the validator identifier, name, url, and public key', async () => {
+ await validators.registerValidator(
+ identifier,
+ name,
+ url,
+ // @ts-ignore bytes type
+ publicKeysData,
+ registrationRequirement.noticePeriod
+ )
+ const parsedValidator = parseValidatorParams(await validators.getValidator(validator))
+ assert.equal(parsedValidator.identifier, identifier)
+ assert.equal(parsedValidator.name, name)
+ assert.equal(parsedValidator.url, url)
+ assert.equal(parsedValidator.publicKeysData, publicKeysData)
+ })
+
+ it('should emit the ValidatorRegistered event', async () => {
+ const resp = await validators.registerValidator(
+ identifier,
+ name,
+ url,
+ // @ts-ignore bytes type
+ publicKeysData,
+ registrationRequirement.noticePeriod
+ )
+ assert.equal(resp.logs.length, 1)
+ const log = resp.logs[0]
+ assertContainSubset(log, {
+ event: 'ValidatorRegistered',
+ args: {
+ validator,
+ identifier,
+ name,
+ url,
+ publicKeysData,
+ },
+ })
+ })
+
+ describe('when the account is already a registered validator', () => {
+ beforeEach(async () => {
+ await validators.registerValidator(
+ identifier,
+ name,
+ url,
+ // @ts-ignore bytes type
+ publicKeysData,
+ registrationRequirement.noticePeriod
+ )
+ })
+
+ it('should revert', async () => {
+ await assertRevert(
+ validators.registerValidator(
+ identifier,
+ name,
+ url,
+ // @ts-ignore bytes type
+ publicKeysData,
+ registrationRequirement.noticePeriod
+ )
+ )
+ })
+ })
+
+ describe('when the account is already a registered validator group', () => {
+ beforeEach(async () => {
+ await validators.registerValidatorGroup(
+ identifier,
+ name,
+ url,
+ registrationRequirement.noticePeriod
+ )
+ })
+
+ it('should revert', async () => {
+ await assertRevert(
+ validators.registerValidator(
+ identifier,
+ name,
+ url,
+ // @ts-ignore bytes type
+ publicKeysData,
+ registrationRequirement.noticePeriod
+ )
+ )
+ })
+ })
+
+ describe('when the account does not meet the registration requirements', () => {
+ beforeEach(async () => {
+ await mockLockedGold.setLockedCommitment(
+ validator,
+ registrationRequirement.noticePeriod,
+ registrationRequirement.value.minus(1)
+ )
+ })
+
+ it('should revert', async () => {
+ await assertRevert(
+ validators.registerValidator(
+ identifier,
+ name,
+ url,
+ // @ts-ignore bytes type
+ publicKeysData,
+ registrationRequirement.noticePeriod
+ )
+ )
+ })
+ })
+ })
+
+ describe('#deregisterValidator', () => {
+ const validator = accounts[0]
+ const index = 0
+ beforeEach(async () => {
+ await registerValidator(validator)
+ })
+
+ it('should mark the account as not a validator', async () => {
+ await validators.deregisterValidator(index)
+ assert.isFalse(await validators.isValidator(validator))
+ })
+
+ it('should remove the account from the list of validators', async () => {
+ await validators.deregisterValidator(index)
+ assert.deepEqual(await validators.getRegisteredValidators(), [])
+ })
+
+ it('should emit the ValidatorDeregistered event', async () => {
+ const resp = await validators.deregisterValidator(index)
+ assert.equal(resp.logs.length, 1)
+ const log = resp.logs[0]
+ assertContainSubset(log, {
+ event: 'ValidatorDeregistered',
+ args: {
+ validator,
+ },
+ })
+ })
+
+ describe('when the validator is affiliated with a validator group', () => {
+ const group = accounts[1]
+ beforeEach(async () => {
+ await registerValidatorGroup(group)
+ await validators.affiliate(group)
+ })
+
+ it('should emit the ValidatorDeafilliated event', async () => {
+ const resp = await validators.deregisterValidator(index)
+ assert.equal(resp.logs.length, 2)
+ const log = resp.logs[0]
+ assertContainSubset(log, {
+ event: 'ValidatorDeaffiliated',
+ args: {
+ validator,
+ group,
+ },
+ })
+ })
+
+ describe('when the validator is a member of that group', () => {
+ beforeEach(async () => {
+ await validators.addMember(validator, { from: group })
+ })
+
+ it('should remove the validator from the group membership list', async () => {
+ await validators.deregisterValidator(index)
+ const parsedGroup = parseValidatorGroupParams(await validators.getValidatorGroup(group))
+ assert.deepEqual(parsedGroup.members, [])
+ })
+
+ it('should emit the ValidatorGroupMemberRemoved event', async () => {
+ const resp = await validators.deregisterValidator(index)
+ assert.equal(resp.logs.length, 4)
+ const log = resp.logs[0]
+ assertContainSubset(log, {
+ event: 'ValidatorGroupMemberRemoved',
+ args: {
+ validator,
+ group,
+ },
+ })
+ })
+
+ describe('when the validator is the only member of that group', () => {
+ it('should emit the ValidatorGroupEmptied event', async () => {
+ const resp = await validators.deregisterValidator(index)
+ assert.equal(resp.logs.length, 4)
+ const log = resp.logs[1]
+ assertContainSubset(log, {
+ event: 'ValidatorGroupEmptied',
+ args: {
+ group,
+ },
+ })
+ })
+
+ describe('when that group has received votes', () => {
+ beforeEach(async () => {
+ const voter = accounts[2]
+ const weight = 10
+ await mockLockedGold.setWeight(voter, weight)
+ await validators.vote(group, NULL_ADDRESS, NULL_ADDRESS, { from: voter })
+ })
+
+ it('should remove the group from the list of electable groups with votes', async () => {
+ await validators.deregisterValidator(index)
+ const [groups] = await validators.getValidatorGroupVotes()
+ assert.deepEqual(groups, [])
+ })
+ })
+ })
+ })
+ })
+
+ it('should revert when the account is not a registered validator', async () => {
+ await assertRevert(validators.deregisterValidator(index, { from: accounts[2] }))
+ })
+
+ it('should revert when the wrong index is provided', async () => {
+ await assertRevert(validators.deregisterValidator(index + 1))
+ })
+ })
+
+ describe('#affiliate', () => {
+ const validator = accounts[0]
+ const group = accounts[1]
+ beforeEach(async () => {
+ await registerValidator(validator)
+ await registerValidatorGroup(group)
+ })
+
+ it('should set the affiliate', async () => {
+ await validators.affiliate(group)
+ const parsedValidator = parseValidatorParams(await validators.getValidator(validator))
+ assert.equal(parsedValidator.affiliation, group)
+ })
+
+ it('should emit the ValidatorAffiliated event', async () => {
+ const resp = await validators.affiliate(group)
+ assert.equal(resp.logs.length, 1)
+ const log = resp.logs[0]
+ assertContainSubset(log, {
+ event: 'ValidatorAffiliated',
+ args: {
+ validator,
+ group,
+ },
+ })
+ })
+
+ describe('when the validator is already affiliated with a validator group', () => {
+ const otherGroup = accounts[2]
+ beforeEach(async () => {
+ await validators.affiliate(group)
+ await registerValidatorGroup(otherGroup)
+ })
+
+ it('should set the affiliate', async () => {
+ await validators.affiliate(otherGroup)
+ const parsedValidator = parseValidatorParams(await validators.getValidator(validator))
+ assert.equal(parsedValidator.affiliation, otherGroup)
+ })
+
+ it('should emit the ValidatorDeafilliated event', async () => {
+ const resp = await validators.affiliate(otherGroup)
+ assert.equal(resp.logs.length, 2)
+ const log = resp.logs[0]
+ assertContainSubset(log, {
+ event: 'ValidatorDeaffiliated',
+ args: {
+ validator,
+ group,
+ },
+ })
+ })
+
+ it('should emit the ValidatorAffiliated event', async () => {
+ const resp = await validators.affiliate(otherGroup)
+ assert.equal(resp.logs.length, 2)
+ const log = resp.logs[1]
+ assertContainSubset(log, {
+ event: 'ValidatorAffiliated',
+ args: {
+ validator,
+ group: otherGroup,
+ },
+ })
+ })
+
+ describe('when the validator is a member of that group', () => {
+ beforeEach(async () => {
+ await validators.addMember(validator, { from: group })
+ })
+
+ it('should remove the validator from the group membership list', async () => {
+ await validators.affiliate(otherGroup)
+ const parsedGroup = parseValidatorGroupParams(await validators.getValidatorGroup(group))
+ assert.deepEqual(parsedGroup.members, [])
+ })
+
+ it('should emit the ValidatorGroupMemberRemoved event', async () => {
+ const resp = await validators.affiliate(otherGroup)
+ assert.equal(resp.logs.length, 4)
+ const log = resp.logs[0]
+ assertContainSubset(log, {
+ event: 'ValidatorGroupMemberRemoved',
+ args: {
+ validator,
+ group,
+ },
+ })
+ })
+
+ describe('when the validator is the only member of that group', () => {
+ it('should emit the ValidatorGroupEmptied event', async () => {
+ const resp = await validators.affiliate(otherGroup)
+ assert.equal(resp.logs.length, 4)
+ const log = resp.logs[1]
+ assertContainSubset(log, {
+ event: 'ValidatorGroupEmptied',
+ args: {
+ group,
+ },
+ })
+ })
+
+ describe('when that group has received votes', () => {
+ beforeEach(async () => {
+ const voter = accounts[2]
+ const weight = 10
+ await mockLockedGold.setWeight(voter, weight)
+ await validators.vote(group, NULL_ADDRESS, NULL_ADDRESS, { from: voter })
+ })
+
+ it('should remove the group from the list of electable groups with votes', async () => {
+ await validators.affiliate(otherGroup)
+ const [groups] = await validators.getValidatorGroupVotes()
+ assert.deepEqual(groups, [])
+ })
+ })
+ })
+ })
+ })
+
+ it('should revert when the account is not a registered validator', async () => {
+ await assertRevert(validators.affiliate(group, { from: accounts[2] }))
+ })
+
+ it('should revert when the group is not a registered validator group', async () => {
+ await assertRevert(validators.affiliate(accounts[2]))
+ })
+ })
+
+ describe('#deaffiliate', () => {
+ const validator = accounts[0]
+ const group = accounts[1]
+ beforeEach(async () => {
+ await registerValidator(validator)
+ await registerValidatorGroup(group)
+ await validators.affiliate(group)
+ })
+
+ it('should clear the affiliate', async () => {
+ await validators.deaffiliate()
+ const parsedValidator = parseValidatorParams(await validators.getValidator(validator))
+ assert.equal(parsedValidator.affiliation, NULL_ADDRESS)
+ })
+
+ it('should emit the ValidatorDeaffiliated event', async () => {
+ const resp = await validators.deaffiliate()
+ assert.equal(resp.logs.length, 1)
+ const log = resp.logs[0]
+ assertContainSubset(log, {
+ event: 'ValidatorDeaffiliated',
+ args: {
+ validator,
+ group,
+ },
+ })
+ })
+
+ describe('when the validator is a member of the affiliated group', () => {
+ beforeEach(async () => {
+ await validators.addMember(validator, { from: group })
+ })
+
+ it('should remove the validator from the group membership list', async () => {
+ await validators.deaffiliate()
+ const parsedGroup = parseValidatorGroupParams(await validators.getValidatorGroup(group))
+ assert.deepEqual(parsedGroup.members, [])
+ })
+
+ it('should emit the ValidatorGroupMemberRemoved event', async () => {
+ const resp = await validators.deaffiliate()
+ assert.equal(resp.logs.length, 3)
+ const log = resp.logs[0]
+ assertContainSubset(log, {
+ event: 'ValidatorGroupMemberRemoved',
+ args: {
+ validator,
+ group,
+ },
+ })
+ })
+
+ describe('when the validator is the only member of that group', () => {
+ it('should emit the ValidatorGroupEmptied event', async () => {
+ const resp = await validators.deaffiliate()
+ assert.equal(resp.logs.length, 3)
+ const log = resp.logs[1]
+ assertContainSubset(log, {
+ event: 'ValidatorGroupEmptied',
+ args: {
+ group,
+ },
+ })
+ })
+
+ describe('when that group has received votes', () => {
+ beforeEach(async () => {
+ const voter = accounts[2]
+ const weight = 10
+ await mockLockedGold.setWeight(voter, weight)
+ await validators.vote(group, NULL_ADDRESS, NULL_ADDRESS, { from: voter })
+ })
+
+ it('should remove the group from the list of electable groups with votes', async () => {
+ await validators.deaffiliate()
+ const [groups] = await validators.getValidatorGroupVotes()
+ assert.deepEqual(groups, [])
+ })
+ })
+ })
+ })
+
+ it('should revert when the account is not a registered validator', async () => {
+ await assertRevert(validators.deaffiliate({ from: accounts[2] }))
+ })
+
+ it('should revert when the validator is not affiliated with a validator group', async () => {
+ await validators.deaffiliate()
+ await assertRevert(validators.deaffiliate())
+ })
+ })
+
+ describe('#registerValidatorGroup', () => {
+ const group = accounts[0]
+ beforeEach(async () => {
+ await mockLockedGold.setLockedCommitment(
+ group,
+ registrationRequirement.noticePeriod,
+ registrationRequirement.value
+ )
+ })
+
+ it('should mark the account as a validator group', async () => {
+ await validators.registerValidatorGroup(
+ identifier,
+ name,
+ url,
+ registrationRequirement.noticePeriod
+ )
+ assert.isTrue(await validators.isValidatorGroup(group))
+ })
+
+ it('should add the account to the list of validator groups', async () => {
+ await validators.registerValidatorGroup(
+ identifier,
+ name,
+ url,
+ registrationRequirement.noticePeriod
+ )
+ assert.deepEqual(await validators.getRegisteredValidatorGroups(), [group])
+ })
+
+ it('should set the validator group identifier, name, and url', async () => {
+ await validators.registerValidatorGroup(
+ identifier,
+ name,
+ url,
+ registrationRequirement.noticePeriod
+ )
+ const parsedGroup = parseValidatorGroupParams(await validators.getValidatorGroup(group))
+ assert.equal(parsedGroup.identifier, identifier)
+ assert.equal(parsedGroup.name, name)
+ assert.equal(parsedGroup.url, url)
+ })
+
+ it('should emit the ValidatorGroupRegistered event', async () => {
+ const resp = await validators.registerValidatorGroup(
+ identifier,
+ name,
+ url,
+ registrationRequirement.noticePeriod
+ )
+ assert.equal(resp.logs.length, 1)
+ const log = resp.logs[0]
+ assertContainSubset(log, {
+ event: 'ValidatorGroupRegistered',
+ args: {
+ group,
+ identifier,
+ name,
+ url,
+ },
+ })
+ })
+
+ describe('when the account is already a registered validator', () => {
+ beforeEach(async () => {
+ await registerValidator(group)
+ })
+
+ it('should revert', async () => {
+ await assertRevert(
+ validators.registerValidatorGroup(
+ identifier,
+ name,
+ url,
+ registrationRequirement.noticePeriod
+ )
+ )
+ })
+ })
+
+ describe('when the account is already a registered validator group', () => {
+ beforeEach(async () => {
+ await validators.registerValidatorGroup(
+ identifier,
+ name,
+ url,
+ registrationRequirement.noticePeriod
+ )
+ })
+
+ it('should revert', async () => {
+ await assertRevert(
+ validators.registerValidatorGroup(
+ identifier,
+ name,
+ url,
+ registrationRequirement.noticePeriod
+ )
+ )
+ })
+ })
+
+ describe('when the account does not meet the registration requirements', () => {
+ beforeEach(async () => {
+ await mockLockedGold.setLockedCommitment(
+ group,
+ registrationRequirement.noticePeriod,
+ registrationRequirement.value.minus(1)
+ )
+ })
+
+ it('should revert', async () => {
+ await assertRevert(
+ validators.registerValidatorGroup(
+ identifier,
+ name,
+ url,
+ registrationRequirement.noticePeriod
+ )
+ )
+ })
+ })
+ })
+
+ describe('#deregisterValidatorGroup', () => {
+ const index = 0
+ const group = accounts[0]
+ beforeEach(async () => {
+ await registerValidatorGroup(group)
+ })
+
+ it('should mark the account as not a validator group', async () => {
+ await validators.deregisterValidatorGroup(index)
+ assert.isFalse(await validators.isValidatorGroup(group))
+ })
+
+ it('should remove the account from the list of validator groups', async () => {
+ await validators.deregisterValidatorGroup(index)
+ assert.deepEqual(await validators.getRegisteredValidatorGroups(), [])
+ })
+
+ it('should emit the ValidatorGroupDeregistered event', async () => {
+ const resp = await validators.deregisterValidatorGroup(index)
+ assert.equal(resp.logs.length, 1)
+ const log = resp.logs[0]
+ assertContainSubset(log, {
+ event: 'ValidatorGroupDeregistered',
+ args: {
+ group,
+ },
+ })
+ })
+
+ it('should revert when the account is not a registered validator group', async () => {
+ await assertRevert(validators.deregisterValidatorGroup(index, { from: accounts[2] }))
+ })
+
+ it('should revert when the wrong index is provided', async () => {
+ await assertRevert(validators.deregisterValidatorGroup(index + 1))
+ })
+
+ describe('when the validator group is not empty', () => {
+ const validator = accounts[1]
+ beforeEach(async () => {
+ await registerValidator(validator)
+ await validators.affiliate(group, { from: validator })
+ await validators.addMember(validator)
+ })
+
+ it('should revert', async () => {
+ await assertRevert(validators.deregisterValidatorGroup(index))
+ })
+ })
+ })
+
+ describe('#addMember', () => {
+ const group = accounts[0]
+ const validator = accounts[1]
+ beforeEach(async () => {
+ await registerValidator(validator)
+ await registerValidatorGroup(group)
+ await validators.affiliate(group, { from: validator })
+ })
+
+ it('should add the member to the list of members', async () => {
+ await validators.addMember(validator)
+ const parsedGroup = parseValidatorGroupParams(await validators.getValidatorGroup(group))
+ assert.deepEqual(parsedGroup.members, [validator])
+ })
+
+ it('should emit the ValidatorGroupMemberAdded event', async () => {
+ const resp = await validators.addMember(validator)
+ assert.equal(resp.logs.length, 1)
+ const log = resp.logs[0]
+ assertContainSubset(log, {
+ event: 'ValidatorGroupMemberAdded',
+ args: {
+ group,
+ validator,
+ },
+ })
+ })
+
+ it('should revert when the account is not a registered validator group', async () => {
+ await assertRevert(validators.addMember(validator, { from: accounts[2] }))
+ })
+
+ it('should revert when the member is not a registered validator', async () => {
+ await assertRevert(validators.addMember(accounts[2]))
+ })
+
+ describe('when the validator has not affiliated themselves with the group', () => {
+ beforeEach(async () => {
+ await validators.deaffiliate({ from: validator })
+ })
+
+ it('should revert', async () => {
+ await assertRevert(validators.addMember(validator))
+ })
+ })
+
+ describe('when the validator is already a member of the group', () => {
+ beforeEach(async () => {
+ await validators.addMember(validator)
+ })
+
+ it('should revert', async () => {
+ await assertRevert(validators.addMember(validator))
+ })
+ })
+ })
+
+ describe('#removeMember', () => {
+ const group = accounts[0]
+ const validator = accounts[1]
+ beforeEach(async () => {
+ await registerValidatorGroupWithMembers(group, [validator])
+ })
+
+ it('should remove the member from the list of members', async () => {
+ await validators.removeMember(validator)
+ const parsedGroup = parseValidatorGroupParams(await validators.getValidatorGroup(group))
+ assert.deepEqual(parsedGroup.members, [])
+ })
+
+ it('should emit the ValidatorGroupMemberRemoved event', async () => {
+ const resp = await validators.removeMember(validator)
+ assert.equal(resp.logs.length, 2)
+ const log = resp.logs[0]
+ assertContainSubset(log, {
+ event: 'ValidatorGroupMemberRemoved',
+ args: {
+ group,
+ validator,
+ },
+ })
+ })
+
+ describe('when the validator is the only member of the group', () => {
+ it('should emit the ValidatorGroupEmptied event', async () => {
+ const resp = await validators.removeMember(validator)
+ assert.equal(resp.logs.length, 2)
+ const log = resp.logs[1]
+ assertContainSubset(log, {
+ event: 'ValidatorGroupEmptied',
+ args: {
+ group,
+ },
+ })
+ })
+
+ describe('when the group has received votes', () => {
+ beforeEach(async () => {
+ const voter = accounts[2]
+ const weight = 10
+ await mockLockedGold.setWeight(voter, weight)
+ await validators.vote(group, NULL_ADDRESS, NULL_ADDRESS, { from: voter })
+ })
+
+ it('should remove the group from the list of electable groups with votes', async () => {
+ await validators.removeMember(validator)
+ const [groups] = await validators.getValidatorGroupVotes()
+ assert.deepEqual(groups, [])
+ })
+ })
+ })
+
+ it('should revert when the account is not a registered validator group', async () => {
+ await assertRevert(validators.removeMember(validator, { from: accounts[2] }))
+ })
+
+ it('should revert when the member is not a registered validator', async () => {
+ await assertRevert(validators.removeMember(accounts[2]))
+ })
+
+ describe('when the validator is not a member of the validator group', () => {
+ beforeEach(async () => {
+ await validators.deaffiliate({ from: validator })
+ })
+
+ it('should revert', async () => {
+ await assertRevert(validators.removeMember(validator))
+ })
+ })
+ })
+
+ describe('#reorderMember', () => {
+ const group = accounts[0]
+ const validator1 = accounts[1]
+ const validator2 = accounts[2]
+ beforeEach(async () => {
+ await registerValidatorGroupWithMembers(group, [validator1, validator2])
+ })
+
+ it('should reorder the list of group members', async () => {
+ await validators.reorderMember(validator2, validator1, NULL_ADDRESS)
+ const parsedGroup = parseValidatorGroupParams(await validators.getValidatorGroup(group))
+ assert.deepEqual(parsedGroup.members, [validator2, validator1])
+ })
+
+ it('should emit the ValidatorGroupMemberReordered event', async () => {
+ const resp = await validators.reorderMember(validator2, validator1, NULL_ADDRESS)
+ assert.equal(resp.logs.length, 1)
+ const log = resp.logs[0]
+ assertContainSubset(log, {
+ event: 'ValidatorGroupMemberReordered',
+ args: {
+ group,
+ validator: validator2,
+ },
+ })
+ })
+
+ it('should revert when the account is not a registered validator group', async () => {
+ await assertRevert(
+ validators.reorderMember(validator2, validator1, NULL_ADDRESS, { from: accounts[2] })
+ )
+ })
+
+ it('should revert when the member is not a registered validator', async () => {
+ await assertRevert(validators.reorderMember(accounts[3], validator1, NULL_ADDRESS))
+ })
+
+ describe('when the validator is not a member of the validator group', () => {
+ beforeEach(async () => {
+ await validators.deaffiliate({ from: validator2 })
+ })
+
+ it('should revert', async () => {
+ await assertRevert(validators.reorderMember(validator2, validator1, NULL_ADDRESS))
+ })
+ })
+ })
+
+ describe('#vote', () => {
+ const weight = new BigNumber(5)
+ const voter = accounts[0]
+ const validator = accounts[1]
+ const group = accounts[2]
+ beforeEach(async () => {
+ await registerValidatorGroupWithMembers(group, [validator])
+ await mockLockedGold.setWeight(voter, weight)
+ })
+
+ it("should set the voter's vote", async () => {
+ await validators.vote(group, NULL_ADDRESS, NULL_ADDRESS)
+ assert.isTrue(await validators.isVoting(voter))
+ assert.equal(await validators.voters(voter), group)
+ })
+
+ it('should add the group to the list of those receiving votes', async () => {
+ await validators.vote(group, NULL_ADDRESS, NULL_ADDRESS)
+ const [groups] = await validators.getValidatorGroupVotes()
+ assert.deepEqual(groups, [group])
+ })
+
+ it("should increment the validator group's vote total", async () => {
+ await validators.vote(group, NULL_ADDRESS, NULL_ADDRESS)
+ assertEqualBN(await validators.getVotesReceived(group), weight)
+ })
+
+ it('should emit the ValidatorGroupVoteCast event', async () => {
+ const resp = await validators.vote(group, NULL_ADDRESS, NULL_ADDRESS)
+ assert.equal(resp.logs.length, 1)
+ const log = resp.logs[0]
+ assertContainSubset(log, {
+ event: 'ValidatorGroupVoteCast',
+ args: {
+ account: voter,
+ group,
+ weight: new BigNumber(weight),
+ },
+ })
+ })
+
+ describe('when the group had not previously received votes', () => {
+ it('should add the group to the list of electable groups with votes', async () => {
+ await validators.vote(group, NULL_ADDRESS, NULL_ADDRESS)
+ const [groups] = await validators.getValidatorGroupVotes()
+ assert.deepEqual(groups, [group])
+ })
+ })
+
+ it('should revert when the group is not a registered validator group', async () => {
+ await assertRevert(validators.vote(accounts[3], NULL_ADDRESS, NULL_ADDRESS))
+ })
+
+ describe('when the group is empty', () => {
+ beforeEach(async () => {
+ await validators.removeMember(validator, { from: group })
+ })
+
+ it('should revert', async () => {
+ await assertRevert(validators.vote(group, NULL_ADDRESS, NULL_ADDRESS))
+ })
+ })
+
+ describe('when the account voting is frozen', () => {
+ beforeEach(async () => {
+ await mockLockedGold.setVotingFrozen(voter)
+ })
+
+ it('should revert', async () => {
+ await assertRevert(validators.vote(group, NULL_ADDRESS, NULL_ADDRESS))
+ })
+ })
+
+ describe('when the account has no weight', () => {
+ beforeEach(async () => {
+ await mockLockedGold.setWeight(voter, NULL_ADDRESS)
+ })
+
+ it('should revert', async () => {
+ await assertRevert(validators.vote(group, NULL_ADDRESS, NULL_ADDRESS))
+ })
+ })
+ describe('when the account has an outstanding vote', () => {
+ beforeEach(async () => {
+ await validators.vote(group, NULL_ADDRESS, NULL_ADDRESS)
+ })
+
+ it('should revert', async () => {
+ await assertRevert(validators.vote(group, NULL_ADDRESS, NULL_ADDRESS))
+ })
+ })
+ })
+
+ describe('#revokeVote', () => {
+ const weight = 5
+ const voter = accounts[0]
+ const validator = accounts[1]
+ const group = accounts[2]
+ beforeEach(async () => {
+ await registerValidatorGroupWithMembers(group, [validator])
+ await mockLockedGold.setWeight(voter, weight)
+ await validators.vote(group, NULL_ADDRESS, NULL_ADDRESS)
+ })
+
+ it("should clear the voter's vote", async () => {
+ await validators.revokeVote(NULL_ADDRESS, NULL_ADDRESS)
+ assert.isFalse(await validators.isVoting(voter))
+ assert.equal(await validators.voters(voter), NULL_ADDRESS)
+ })
+
+ it("should decrement the validator group's vote total", async () => {
+ await validators.revokeVote(NULL_ADDRESS, NULL_ADDRESS)
+ const [groups, votes] = await validators.getValidatorGroupVotes()
+ assert.deepEqual(groups, [])
+ assert.deepEqual(votes, [])
+ })
+
+ it('should emit the ValidatorGroupVoteRevoked event', async () => {
+ const resp = await validators.revokeVote(NULL_ADDRESS, NULL_ADDRESS)
+ assert.equal(resp.logs.length, 1)
+ const log = resp.logs[0]
+ assertContainSubset(log, {
+ event: 'ValidatorGroupVoteRevoked',
+ args: {
+ account: voter,
+ group,
+ weight: new BigNumber(weight),
+ },
+ })
+ })
+
+ describe('when the group had not received other votes', () => {
+ it('should remove the group from the list of electable groups with votes', async () => {
+ await validators.revokeVote(NULL_ADDRESS, NULL_ADDRESS)
+ const [groups] = await validators.getValidatorGroupVotes()
+ assert.deepEqual(groups, [])
+ })
+ })
+
+ describe('when the account does not have an outstanding vote', () => {
+ beforeEach(async () => {
+ await validators.revokeVote(NULL_ADDRESS, NULL_ADDRESS)
+ })
+
+ it('should revert', async () => {
+ await assertRevert(validators.revokeVote(NULL_ADDRESS, NULL_ADDRESS))
+ })
+ })
+ })
+
+ describe('#getValidators', () => {
+ const group1 = accounts[0]
+ const group2 = accounts[1]
+ const group3 = accounts[2]
+ const validator1 = accounts[3]
+ const validator2 = accounts[4]
+ const validator3 = accounts[5]
+ const validator4 = accounts[6]
+ const validator5 = accounts[7]
+ const validator6 = accounts[8]
+ const validator7 = accounts[9]
+
+ const hash1 = '0xa5b9d60f32436310afebcfda832817a68921beb782fabf7915cc0460b443116a'
+ const hash2 = '0xa832817a68921b10afebcfd0460b443116aeb782fabf7915cca5b9d60f324363'
+
+ // If voterN votes for groupN:
+ // group1 gets 20 votes per member
+ // group2 gets 25 votes per member
+ // group3 gets 30 votes per member
+ // We cannot make any guarantee with respect to their ordering.
+ const voter1 = { address: accounts[0], weight: 80 }
+ const voter2 = { address: accounts[1], weight: 50 }
+ const voter3 = { address: accounts[2], weight: 30 }
+ const assertSameAddresses = (actual: string[], expected: string[]) => {
+ assert.sameMembers(actual.map((x) => x.toLowerCase()), expected.map((x) => x.toLowerCase()))
+ }
+
+ beforeEach(async () => {
+ await registerValidatorGroupWithMembers(group1, [
+ validator1,
+ validator2,
+ validator3,
+ validator4,
+ ])
+ await registerValidatorGroupWithMembers(group2, [validator5, validator6])
+ await registerValidatorGroupWithMembers(group3, [validator7])
+
+ for (const voter of [voter1, voter2, voter3]) {
+ await mockLockedGold.setWeight(voter.address, voter.weight)
+ }
+ await random.revealAndCommit(hash1, hash1, NULL_ADDRESS)
+ })
+
+ describe('when a single group has >= minElectableValidators as members and received votes', () => {
+ beforeEach(async () => {
+ await validators.vote(group1, NULL_ADDRESS, NULL_ADDRESS, { from: voter1.address })
+ })
+
+ it("should return that group's member list", async () => {
+ assertSameAddresses(await validators.getValidators(), [
+ validator1,
+ validator2,
+ validator3,
+ validator4,
+ ])
+ })
+ })
+
+ describe("when > maxElectableValidators members's groups receive votes", () => {
+ beforeEach(async () => {
+ await validators.vote(group1, NULL_ADDRESS, NULL_ADDRESS, { from: voter1.address })
+ await validators.vote(group2, NULL_ADDRESS, group1, { from: voter2.address })
+ await validators.vote(group3, NULL_ADDRESS, group2, { from: voter3.address })
+ })
+
+ it('should return maxElectableValidators elected validators', async () => {
+ assertSameAddresses(await validators.getValidators(), [
+ validator1,
+ validator2,
+ validator3,
+ validator5,
+ validator6,
+ validator7,
+ ])
+ })
+ })
+
+ describe('when different random values are provided', () => {
+ beforeEach(async () => {
+ await validators.vote(group1, NULL_ADDRESS, NULL_ADDRESS, { from: voter1.address })
+ await validators.vote(group2, NULL_ADDRESS, group1, { from: voter2.address })
+ await validators.vote(group3, NULL_ADDRESS, group2, { from: voter3.address })
+ })
+
+ it('should return different results', async () => {
+ await random.revealAndCommit(hash1, hash1, NULL_ADDRESS)
+ const valsWithHash1 = (await validators.getValidators()).map((x) => x.toLowerCase())
+ await random.revealAndCommit(hash2, hash2, NULL_ADDRESS)
+ const valsWithHash2 = (await validators.getValidators()).map((x) => x.toLowerCase())
+ assert.sameMembers(valsWithHash1, valsWithHash2)
+ assert.notDeepEqual(valsWithHash1, valsWithHash2)
+ })
+ })
+
+ describe('when a group receives enough votes for > n seats but only has n members', () => {
+ beforeEach(async () => {
+ await mockLockedGold.setWeight(voter3.address, 1000)
+ await validators.vote(group3, NULL_ADDRESS, NULL_ADDRESS, { from: voter3.address })
+ await validators.vote(group1, NULL_ADDRESS, group3, { from: voter1.address })
+ await validators.vote(group2, NULL_ADDRESS, group1, { from: voter2.address })
+ })
+
+ it('should elect only n members from that group', async () => {
+ assertSameAddresses(await validators.getValidators(), [
+ validator7,
+ validator1,
+ validator2,
+ validator3,
+ validator5,
+ validator6,
+ ])
+ })
+ })
+
+ describe('when an account has delegated validating to another address', () => {
+ const validatingDelegate = '0x47e172f6cfb6c7d01c1574fa3e2be7cc73269d95'
+ beforeEach(async () => {
+ await mockLockedGold.delegateValidating(validator3, validatingDelegate)
+ await validators.vote(group1, NULL_ADDRESS, NULL_ADDRESS, { from: voter1.address })
+ await validators.vote(group2, NULL_ADDRESS, group1, { from: voter2.address })
+ await validators.vote(group3, NULL_ADDRESS, group2, { from: voter3.address })
+ })
+
+ it('should return the validating delegate in place of the account', async () => {
+ assertSameAddresses(await validators.getValidators(), [
+ validator1,
+ validator2,
+ validatingDelegate,
+ validator5,
+ validator6,
+ validator7,
+ ])
+ })
+ })
+
+ describe('when there are not enough electable validators', () => {
+ beforeEach(async () => {
+ await validators.vote(group2, NULL_ADDRESS, NULL_ADDRESS, { from: voter2.address })
+ await validators.vote(group3, NULL_ADDRESS, group2, { from: voter3.address })
+ })
+
+ it('should revert', async () => {
+ await assertRevert(validators.getValidators())
+ })
+ })
+ })
+})
From 651009da889147d3b1fba3b1da0c7c8f83d823de Mon Sep 17 00:00:00 2001
From: Asa Oines
Date: Thu, 26 Sep 2019 14:01:50 -0400
Subject: [PATCH 011/149] Election tests passing
---
.../contracts/common/UsingRegistry.sol | 6 +
.../linkedlists/AddressSortedLinkedList.sol | 8 +
.../common/linkedlists/SortedLinkedList.sol | 12 +-
.../contracts/governance/Election.sol | 68 +-
.../governance/test/MockElection.sol | 8 +-
.../governance/test/MockLockedGold.sol | 39 +-
.../governance/test/MockValidators.sol | 37 +-
.../contracts/identity/test/MockRandom.sol | 13 +
.../migrations/17_elect_validators.ts | 17 +-
packages/protocol/test/governance/election.ts | 1484 +++++------------
.../protocol/test/identity/attestations.ts | 14 +-
11 files changed, 637 insertions(+), 1069 deletions(-)
create mode 100644 packages/protocol/contracts/identity/test/MockRandom.sol
diff --git a/packages/protocol/contracts/common/UsingRegistry.sol b/packages/protocol/contracts/common/UsingRegistry.sol
index 2e4ced1e5d3..4ae16b3302e 100644
--- a/packages/protocol/contracts/common/UsingRegistry.sol
+++ b/packages/protocol/contracts/common/UsingRegistry.sol
@@ -9,6 +9,8 @@ import "../governance/interfaces/IElection.sol";
import "../governance/interfaces/ILockedGold.sol";
import "../governance/interfaces/IValidators.sol";
+import "../identity/interfaces/IRandom.sol";
+
// Ideally, UsingRegistry should inherit from Initializable and implement initialize() which calls
// setRegistry(). TypeChain currently has problems resolving overloaded functions, so this is not
// possible right now.
@@ -60,6 +62,10 @@ contract UsingRegistry is Ownable {
return ILockedGold(registry.getAddressForOrDie(LOCKED_GOLD_REGISTRY_ID));
}
+ function getRandom() internal view returns(IRandom) {
+ return IRandom(registry.getAddressForOrDie(RANDOM_REGISTRY_ID));
+ }
+
function getValidators() internal view returns(IValidators) {
return IValidators(registry.getAddressForOrDie(VALIDATORS_REGISTRY_ID));
}
diff --git a/packages/protocol/contracts/common/linkedlists/AddressSortedLinkedList.sol b/packages/protocol/contracts/common/linkedlists/AddressSortedLinkedList.sol
index 528d5500ece..be2135245ec 100644
--- a/packages/protocol/contracts/common/linkedlists/AddressSortedLinkedList.sol
+++ b/packages/protocol/contracts/common/linkedlists/AddressSortedLinkedList.sol
@@ -119,4 +119,12 @@ library AddressSortedLinkedList {
}
return keys;
}
+
+ /**
+ * @notice Gets all element keys from the doubly linked list.
+ * @return All element keys from head to tail.
+ */
+ function getKeys(SortedLinkedList.List storage list) public view returns (address[] memory) {
+ return headN(list, list.list.numElements);
+ }
}
diff --git a/packages/protocol/contracts/common/linkedlists/SortedLinkedList.sol b/packages/protocol/contracts/common/linkedlists/SortedLinkedList.sol
index 176d499749c..60e761b8c6a 100644
--- a/packages/protocol/contracts/common/linkedlists/SortedLinkedList.sol
+++ b/packages/protocol/contracts/common/linkedlists/SortedLinkedList.sol
@@ -33,10 +33,10 @@ library SortedLinkedList {
)
public
{
- require(key != bytes32(0) && key != lesserKey && key != greaterKey && !contains(list, key));
- require((lesserKey != bytes32(0) || greaterKey != bytes32(0)) || list.list.numElements == 0);
- require(contains(list, lesserKey) || lesserKey == bytes32(0));
- require(contains(list, greaterKey) || greaterKey == bytes32(0));
+ require(key != bytes32(0) && key != lesserKey && key != greaterKey && !contains(list, key), "1");
+ require((lesserKey != bytes32(0) || greaterKey != bytes32(0)) || list.list.numElements == 0, "2");
+ require(contains(list, lesserKey) || lesserKey == bytes32(0), "3");
+ require(contains(list, greaterKey) || greaterKey == bytes32(0), "4");
(lesserKey, greaterKey) = getLesserAndGreater(list, value, lesserKey, greaterKey);
list.list.insert(key, lesserKey, greaterKey);
list.values[key] = value;
@@ -183,10 +183,10 @@ library SortedLinkedList {
greaterKey == bytes32(0) && isValueBetween(list, value, list.list.head, greaterKey)
) {
return (list.list.head, greaterKey);
- } else if (isValueBetween(list, value, lesserKey, list.list.elements[lesserKey].nextKey)) {
+ } else if (lesserKey != bytes32(0) && isValueBetween(list, value, lesserKey, list.list.elements[lesserKey].nextKey)) {
return (lesserKey, list.list.elements[lesserKey].nextKey);
} else if (
- isValueBetween(list, value, list.list.elements[greaterKey].previousKey, greaterKey)
+ greaterKey != bytes32(0) && isValueBetween(list, value, list.list.elements[greaterKey].previousKey, greaterKey)
) {
return (list.list.elements[greaterKey].previousKey, greaterKey);
} else {
diff --git a/packages/protocol/contracts/governance/Election.sol b/packages/protocol/contracts/governance/Election.sol
index 1c151426dcf..6e047ae1f5a 100644
--- a/packages/protocol/contracts/governance/Election.sol
+++ b/packages/protocol/contracts/governance/Election.sol
@@ -79,16 +79,30 @@ contract Election is Ownable, ReentrancyGuard, Initializable, UsingRegistry {
uint256 maxVotesPerAccount
);
+ event ValidatorGroupMarkedEligible(
+ address group
+ );
+
+ event ValidatorGroupMarkedIneligible(
+ address group
+ );
+
event ValidatorGroupVoteCast(
address indexed account,
address indexed group,
- uint256 weight
+ uint256 value
+ );
+
+ event ValidatorGroupVoteActivated(
+ address indexed account,
+ address indexed group,
+ uint256 value
);
event ValidatorGroupVoteRevoked(
address indexed account,
address indexed group,
- uint256 weight
+ uint256 value
);
/**
@@ -191,13 +205,13 @@ contract Election is Ownable, ReentrancyGuard, Initializable, UsingRegistry {
nonReentrant
returns (bool)
{
- require(votes.total.eligible.contains(group));
- require(0 < value && value <= getNumVotesReceivable(group));
+ require(votes.total.eligible.contains(group), "1");
+ require(0 < value && value <= getNumVotesReceivable(group), "2");
address account = getLockedGold().getAccountFromVoter(msg.sender);
address[] storage list = votes.lists[account];
- require(list.length < maxVotesPerAccount);
+ require(list.length < maxVotesPerAccount, "3");
for (uint256 i = 0; i < list.length; i = i.add(1)) {
- require(list[i] != group);
+ require(list[i] != group, "4");
}
list.push(group);
incrementPendingVotes(group, account, value);
@@ -216,9 +230,10 @@ contract Election is Ownable, ReentrancyGuard, Initializable, UsingRegistry {
address account = getLockedGold().getAccountFromVoter(msg.sender);
PendingVotes storage pending = votes.pending;
uint256 value = pending.balances[group][account];
- require(0 < value);
+ require(value > 0, 'one');
decrementPendingVotes(group, account, value);
incrementActiveVotes(group, account, value);
+ emit ValidatorGroupVoteActivated(account, group, value);
}
/**
@@ -302,12 +317,19 @@ contract Election is Ownable, ReentrancyGuard, Initializable, UsingRegistry {
return total;
}
+ function getAccountGroupsVotedFor(address account) external view returns (address[] memory) {
+ return votes.lists[account];
+ }
+
function getAccountPendingVotesForGroup(address group, address account) public view returns (uint256) {
return votes.pending.balances[group][account];
}
function getAccountActiveVotesForGroup(address group, address account) public view returns (uint256) {
uint256 numerator = votes.active.numerators[group][account].mul(votes.active.total[group]);
+ if (numerator == 0) {
+ return 0;
+ }
uint256 denominator = votes.active.denominators[group];
return numerator.div(denominator);
}
@@ -318,6 +340,10 @@ contract Election is Ownable, ReentrancyGuard, Initializable, UsingRegistry {
return pending.add(active);
}
+ function getGroupTotalVotes(address group) external view returns (uint256) {
+ return votes.total.eligible.getValue(group);
+ }
+
/**
* @notice Increments the number of total votes for `group` by `value`.
* @param group The validator group whose vote total should be incremented.
@@ -362,18 +388,21 @@ contract Election is Ownable, ReentrancyGuard, Initializable, UsingRegistry {
uint256 newVoteTotal = votes.total.eligible.getValue(group).sub(value);
votes.total.eligible.update(group, newVoteTotal, lesser, greater);
}
- votes.total.total = votes.total.total.add(value);
+ votes.total.total = votes.total.total.sub(value);
}
function markGroupIneligible(address group) external onlyRegisteredContract(VALIDATORS_REGISTRY_ID) {
votes.total.eligible.remove(group);
+ emit ValidatorGroupMarkedIneligible(group);
}
+ // TODO(asa): Should this only be callable by the group?
function markGroupEligible(address group, address lesser, address greater) external {
- require(!votes.total.eligible.contains(group));
- require(getValidators().getGroupNumMembers(group) > 0);
+ require(!votes.total.eligible.contains(group), "aaa");
+ require(getValidators().getGroupNumMembers(group) > 0, "b");
uint256 value = votes.pending.total[group].add(votes.active.total[group]);
votes.total.eligible.insert(group, value, lesser, greater);
+ emit ValidatorGroupMarkedEligible(group);
}
function incrementPendingVotes(address group, address account, uint256 value) private {
@@ -411,7 +440,7 @@ contract Election is Ownable, ReentrancyGuard, Initializable, UsingRegistry {
function getActiveVotesDelta(address group, uint256 value) private view returns (uint256) {
// Preserve delta * total = value * denominator
- return value.mul(votes.active.denominators[group]).div(votes.active.total[group]);
+ return value.mul(votes.active.denominators[group].add(1)).div(votes.active.total[group].add(1));
}
/**
@@ -438,6 +467,14 @@ contract Election is Ownable, ReentrancyGuard, Initializable, UsingRegistry {
return votes.total.total;
}
+ function getEligibleValidatorGroups() external view returns (address[] memory) {
+ return votes.total.eligible.getKeys();
+ }
+
+ function getEligibleValidatorGroupsVoteTotals() external view returns (address[] memory, uint256[] memory) {
+ return votes.total.eligible.getElements();
+ }
+
function validatorAddressFromCurrentSet(uint256 index) external view returns (address) {
address validatorAddress;
assembly {
@@ -507,6 +544,14 @@ contract Election is Ownable, ReentrancyGuard, Initializable, UsingRegistry {
totalNumMembersElected = totalNumMembersElected.add(1);
}
}
+ // Shuffle the validator set using validator-supplied entropy
+ bytes32 r = getRandom().random();
+ for (uint256 i = electedValidators.length - 1; i > 0; i = i.sub(1)) {
+ uint256 j = uint256(r) % (i + 1);
+ (electedValidators[i], electedValidators[j]) = (electedValidators[j], electedValidators[i]);
+ r = keccak256(abi.encodePacked(r));
+ }
+
return electedValidators;
}
@@ -534,6 +579,7 @@ contract Election is Ownable, ReentrancyGuard, Initializable, UsingRegistry {
FixidityLib.newFixed(numMembersElected[i].add(1))
);
if (n.gt(maxN)) {
+ maxN = n;
groupIndex = i;
memberElected = true;
}
diff --git a/packages/protocol/contracts/governance/test/MockElection.sol b/packages/protocol/contracts/governance/test/MockElection.sol
index 6458ad20f5c..da4a5e34e18 100644
--- a/packages/protocol/contracts/governance/test/MockElection.sol
+++ b/packages/protocol/contracts/governance/test/MockElection.sol
@@ -8,6 +8,7 @@ import "../interfaces/IElection.sol";
contract MockElection is IElection {
mapping(address => bool) public isIneligible;
+ address[] public electedValidators;
function markGroupIneligible(address account) external {
isIneligible[account] = true;
@@ -21,8 +22,11 @@ contract MockElection is IElection {
return 0;
}
+ function setElectedValidators(address[] calldata _electedValidators) external {
+ electedValidators = _electedValidators;
+ }
+
function electValidators() external view returns (address[] memory) {
- address[] memory r = new address[](0);
- return r;
+ return electedValidators;
}
}
diff --git a/packages/protocol/contracts/governance/test/MockLockedGold.sol b/packages/protocol/contracts/governance/test/MockLockedGold.sol
index 3fbb7f2a2d4..3df8513e0c1 100644
--- a/packages/protocol/contracts/governance/test/MockLockedGold.sol
+++ b/packages/protocol/contracts/governance/test/MockLockedGold.sol
@@ -1,5 +1,7 @@
pragma solidity ^0.5.3;
+import "openzeppelin-solidity/contracts/math/SafeMath.sol";
+
import "../interfaces/ILockedGold.sol";
@@ -7,15 +9,29 @@ import "../interfaces/ILockedGold.sol";
* @title A mock LockedGold for testing.
*/
contract MockLockedGold is ILockedGold {
+
+ using SafeMath for uint256;
+
struct MustMaintain {
uint256 value;
uint256 timestamp;
}
- mapping(address => uint256) public totalLockedGold;
+ struct Authorizations {
+ address validator;
+ address voter;
+ }
+
+ mapping(address => uint256) public accountTotalLockedGold;
// TODO(asa): Rename to minimumBalance
mapping(address => MustMaintain) public mustMaintain;
+ mapping(address => uint256) public nonvotingAccountBalance;
+ mapping(address => address) public authorizedValidators;
+ uint256 private totalLockedGold;
+ function authorizeValidator(address account, address validator) external {
+ authorizedValidators[account] = validator;
+ }
function getAccountFromValidator(address accountOrValidator) external view returns (address) {
return accountOrValidator;
@@ -26,11 +42,17 @@ contract MockLockedGold is ILockedGold {
}
function getValidatorFromAccount(address account) external view returns (address) {
- return account;
+ address authorizedValidator = authorizedValidators[account];
+ return authorizedValidator == address(0) ? account : authorizedValidator;
}
- function incrementNonvotingAccountBalance(address, uint256) external {}
- function decrementNonvotingAccountBalance(address, uint256) external {}
+ function incrementNonvotingAccountBalance(address account, uint256 value) external {
+ nonvotingAccountBalance[account] = nonvotingAccountBalance[account].add(value);
+ }
+
+ function decrementNonvotingAccountBalance(address account, uint256 value) external {
+ nonvotingAccountBalance[account] = nonvotingAccountBalance[account].sub(value);
+ }
function setAccountMustMaintain(address account, uint256 value, uint256 timestamp) external returns (bool) {
mustMaintain[account] = MustMaintain(value, timestamp);
@@ -43,14 +65,17 @@ contract MockLockedGold is ILockedGold {
}
function setAccountTotalLockedGold(address account, uint256 value) external {
- totalLockedGold[account] = value;
+ accountTotalLockedGold[account] = value;
}
function getAccountTotalLockedGold(address account) external view returns (uint256) {
- return totalLockedGold[account];
+ return accountTotalLockedGold[account];
}
+ function setTotalLockedGold(uint256 value) external {
+ totalLockedGold = value;
+ }
function getTotalLockedGold() external view returns (uint256) {
- return 0;
+ return totalLockedGold;
}
}
diff --git a/packages/protocol/contracts/governance/test/MockValidators.sol b/packages/protocol/contracts/governance/test/MockValidators.sol
index 4ed212ae6f1..805571e84a2 100644
--- a/packages/protocol/contracts/governance/test/MockValidators.sol
+++ b/packages/protocol/contracts/governance/test/MockValidators.sol
@@ -9,7 +9,9 @@ contract MockValidators is IValidators {
mapping(address => bool) private _isValidating;
mapping(address => bool) private _isVoting;
- address[] private validators;
+ mapping(address => uint256) private numGroupMembers;
+ mapping(address => address[]) private members;
+ uint256 private numRegisteredValidators;
function isValidating(address account) external view returns (bool) {
return _isValidating[account];
@@ -19,8 +21,8 @@ contract MockValidators is IValidators {
return _isVoting[account];
}
- function getValidators() external view returns (address[] memory) {
- return validators;
+ function getGroupNumMembers(address group) public view returns (uint256) {
+ return members[group].length;
}
function setValidating(address account) external {
@@ -31,7 +33,32 @@ contract MockValidators is IValidators {
_isVoting[account] = true;
}
- function addValidator(address account) external {
- validators.push(account);
+ function setNumRegisteredValidators(uint256 value) external {
+ numRegisteredValidators = value;
+ }
+
+ function getNumRegisteredValidators() external view returns (uint256) {
+ return numRegisteredValidators;
+ }
+
+ function setMembers(address group, address[] calldata _members) external {
+ members[group] = _members;
+ }
+
+ function getTopValidatorsFromGroup(address group, uint256 n) external view returns (address[] memory) {
+ require(n <= members[group].length);
+ address[] memory validators = new address[](n);
+ for (uint256 i = 0; i < n; i++) {
+ validators[i] = members[group][i];
+ }
+ return validators;
+ }
+
+ function getGroupsNumMembers(address[] calldata groups) external view returns (uint256[] memory) {
+ uint256[] memory numMembers = new uint256[](groups.length);
+ for (uint256 i = 0; i < groups.length; i++) {
+ numMembers[i] = getGroupNumMembers(groups[i]);
+ }
+ return numMembers;
}
}
diff --git a/packages/protocol/contracts/identity/test/MockRandom.sol b/packages/protocol/contracts/identity/test/MockRandom.sol
new file mode 100644
index 00000000000..6953fdb56bf
--- /dev/null
+++ b/packages/protocol/contracts/identity/test/MockRandom.sol
@@ -0,0 +1,13 @@
+pragma solidity ^0.5.3;
+
+import "openzeppelin-solidity/contracts/math/SafeMath.sol";
+
+
+contract MockRandom {
+
+ bytes32 public random;
+
+ function setRandom(bytes32 value) external {
+ random = value;
+ }
+}
diff --git a/packages/protocol/migrations/17_elect_validators.ts b/packages/protocol/migrations/17_elect_validators.ts
index 800e2f14c16..73c125b36cd 100644
--- a/packages/protocol/migrations/17_elect_validators.ts
+++ b/packages/protocol/migrations/17_elect_validators.ts
@@ -168,15 +168,24 @@ module.exports = async (_deployer: any) => {
})
}
+ console.info(' Marking Validator Group as eligible for election ...')
+ // @ts-ignore
+ const markTx = election.contract.methods.markGroupEligible(
+ account.address,
+ NULL_ADDRESS,
+ NULL_ADDRESS
+ )
+ await sendTransactionWithPrivateKey(web3, markTx, account.privateKey, {
+ to: election.address,
+ })
+
console.info(' Voting for Validator Group ...')
// Make another deposit so our vote has more weight.
const minLockedGoldVotePerValidator = 10000
const value = new BigNumber(valKeys.length)
.times(minLockedGoldVotePerValidator)
.times(web3.utils.toWei(1))
- await lockedGold.lock({
- // @ts-ignore
- value,
- })
+ // @ts-ignore
+ await lockedGold.lock({ value })
await election.vote(account.address, value, NULL_ADDRESS, NULL_ADDRESS)
}
diff --git a/packages/protocol/test/governance/election.ts b/packages/protocol/test/governance/election.ts
index ca806c91a0f..28392a05db5 100644
--- a/packages/protocol/test/governance/election.ts
+++ b/packages/protocol/test/governance/election.ts
@@ -9,152 +9,79 @@ import BigNumber from 'bignumber.js'
import {
MockLockedGoldContract,
MockLockedGoldInstance,
+ MockValidatorsContract,
+ MockValidatorsInstance,
MockRandomContract,
MockRandomInstance,
RegistryContract,
RegistryInstance,
- ValidatorsContract,
- ValidatorsInstance,
+ ElectionContract,
+ ElectionInstance,
} from 'types'
-const Validators: ValidatorsContract = artifacts.require('Validators')
+const Election: ElectionContract = artifacts.require('Election')
const MockLockedGold: MockLockedGoldContract = artifacts.require('MockLockedGold')
+const MockValidators: MockValidatorsContract = artifacts.require('MockValidators')
+const MockRandom: MockRandomContract = artifacts.require('MockRandom')
const Registry: RegistryContract = artifacts.require('Registry')
-const Random: MockRandomContract = artifacts.require('MockRandom')
// @ts-ignore
// TODO(mcortesi): Use BN
-Validators.numberFormat = 'BigNumber'
-
-const parseValidatorParams = (validatorParams: any) => {
- return {
- identifier: validatorParams[0],
- name: validatorParams[1],
- url: validatorParams[2],
- publicKeysData: validatorParams[3],
- affiliation: validatorParams[4],
- }
-}
-
-const parseValidatorGroupParams = (groupParams: any) => {
- return {
- identifier: groupParams[0],
- name: groupParams[1],
- url: groupParams[2],
- members: groupParams[3],
- }
-}
-
-contract('Validators', (accounts: string[]) => {
- let validators: ValidatorsInstance
+Election.numberFormat = 'BigNumber'
+
+contract('Election', (accounts: string[]) => {
+ let election: ElectionInstance
let registry: RegistryInstance
let mockLockedGold: MockLockedGoldInstance
- let random: MockRandomInstance
-
- // A random 64 byte hex string.
- const publicKey =
- 'ea0733ad275e2b9e05541341a97ee82678c58932464fad26164657a111a7e37a9fa0300266fb90e2135a1f1512350cb4e985488a88809b14e3cbe415e76e82b2'
- const blsPublicKey =
- '4d23d8cd06f30b1fa7cf368e2f5399ab04bb6846c682f493a98a607d3dfb7e53a712bb79b475c57b0ac2785460f91301'
- const blsPoP =
- '9d3e1d8f49f6b0d8e9a03d80ca07b1d24cf1cc0557bdcc04f5e17a46e35d02d0d411d956dbd5d2d2464eebd7b74ae30005d223780d785d2abc5644fac7ac29fb0e302bdc80c81a5d45018b68b1045068a4b3a4861c93037685fd0d252d740501'
-
- const publicKeysData = '0x' + publicKey + blsPublicKey + blsPoP
+ let mockValidators: MockValidatorsInstance
const nonOwner = accounts[1]
const minElectableValidators = new BigNumber(4)
const maxElectableValidators = new BigNumber(6)
- const registrationRequirement = { value: new BigNumber(100), noticePeriod: new BigNumber(60) }
- const identifier = 'test-identifier'
- const name = 'test-name'
- const url = 'test-url'
+ const maxVotesPerAccount = new BigNumber(3)
beforeEach(async () => {
- validators = await Validators.new()
+ election = await Election.new()
mockLockedGold = await MockLockedGold.new()
- random = await Random.new()
+ mockValidators = await MockValidators.new()
registry = await Registry.new()
await registry.setAddressFor(CeloContractName.LockedGold, mockLockedGold.address)
- await registry.setAddressFor(CeloContractName.Random, random.address)
- await validators.initialize(
+ await registry.setAddressFor(CeloContractName.Validators, mockValidators.address)
+ await election.initialize(
registry.address,
minElectableValidators,
maxElectableValidators,
- registrationRequirement.value,
- registrationRequirement.noticePeriod
+ maxVotesPerAccount
)
})
- const registerValidator = async (validator: string) => {
- await mockLockedGold.setLockedCommitment(
- validator,
- registrationRequirement.noticePeriod,
- registrationRequirement.value
- )
- await validators.registerValidator(
- identifier,
- name,
- url,
- // @ts-ignore bytes type
- publicKeysData,
- registrationRequirement.noticePeriod,
- { from: validator }
- )
- }
-
- const registerValidatorGroup = async (group: string) => {
- await mockLockedGold.setLockedCommitment(
- group,
- registrationRequirement.noticePeriod,
- registrationRequirement.value
- )
- await validators.registerValidatorGroup(
- identifier,
- name,
- url,
- registrationRequirement.noticePeriod,
- { from: group }
- )
- }
-
- const registerValidatorGroupWithMembers = async (group: string, members: string[]) => {
- await registerValidatorGroup(group)
- for (const validator of members) {
- await registerValidator(validator)
- await validators.affiliate(group, { from: validator })
- await validators.addMember(validator, { from: group })
- }
- }
-
describe('#initialize()', () => {
it('should have set the owner', async () => {
- const owner: string = await validators.owner()
+ const owner: string = await election.owner()
assert.equal(owner, accounts[0])
})
it('should have set minElectableValidators', async () => {
- const actualMinElectableValidators = await validators.minElectableValidators()
+ const actualMinElectableValidators = await election.minElectableValidators()
assertEqualBN(actualMinElectableValidators, minElectableValidators)
})
it('should have set maxElectableValidators', async () => {
- const actualMaxElectableValidators = await validators.maxElectableValidators()
+ const actualMaxElectableValidators = await election.maxElectableValidators()
assertEqualBN(actualMaxElectableValidators, maxElectableValidators)
})
- it('should have set the registration requirements', async () => {
- const [value, noticePeriod] = await validators.getRegistrationRequirement()
- assertEqualBN(value, registrationRequirement.value)
- assertEqualBN(noticePeriod, registrationRequirement.noticePeriod)
+ it('should have set maxVotesPerAccount', async () => {
+ const actualMaxVotesPerAccount = await election.maxVotesPerAccount()
+ assertEqualBN(actualMaxVotesPerAccount, maxVotesPerAccount)
})
it('should not be callable again', async () => {
await assertRevert(
- validators.initialize(
+ election.initialize(
registry.address,
minElectableValidators,
maxElectableValidators,
- registrationRequirement.value,
- registrationRequirement.noticePeriod
+ maxVotesPerAccount
)
)
})
@@ -162,13 +89,13 @@ contract('Validators', (accounts: string[]) => {
describe('#setMinElectableValidators', () => {
const newMinElectableValidators = minElectableValidators.plus(1)
- it('should set the minimum deposit', async () => {
- await validators.setMinElectableValidators(newMinElectableValidators)
- assertEqualBN(await validators.minElectableValidators(), newMinElectableValidators)
+ it('should set the minimum electable valdiators', async () => {
+ await election.setMinElectableValidators(newMinElectableValidators)
+ assertEqualBN(await election.minElectableValidators(), newMinElectableValidators)
})
it('should emit the MinElectableValidatorsSet event', async () => {
- const resp = await validators.setMinElectableValidators(newMinElectableValidators)
+ const resp = await election.setMinElectableValidators(newMinElectableValidators)
assert.equal(resp.logs.length, 1)
const log = resp.logs[0]
assertContainSubset(log, {
@@ -180,33 +107,33 @@ contract('Validators', (accounts: string[]) => {
})
it('should revert when the minElectableValidators is zero', async () => {
- await assertRevert(validators.setMinElectableValidators(0))
+ await assertRevert(election.setMinElectableValidators(0))
})
it('should revert when the minElectableValidators is greater than maxElectableValidators', async () => {
- await assertRevert(validators.setMinElectableValidators(maxElectableValidators.plus(1)))
+ await assertRevert(election.setMinElectableValidators(maxElectableValidators.plus(1)))
})
it('should revert when the minElectableValidators is unchanged', async () => {
- await assertRevert(validators.setMinElectableValidators(minElectableValidators))
+ await assertRevert(election.setMinElectableValidators(minElectableValidators))
})
it('should revert when called by anyone other than the owner', async () => {
await assertRevert(
- validators.setMinElectableValidators(newMinElectableValidators, { from: nonOwner })
+ election.setMinElectableValidators(newMinElectableValidators, { from: nonOwner })
)
})
})
describe('#setMaxElectableValidators', () => {
const newMaxElectableValidators = maxElectableValidators.plus(1)
- it('should set the minimum deposit', async () => {
- await validators.setMaxElectableValidators(newMaxElectableValidators)
- assertEqualBN(await validators.maxElectableValidators(), newMaxElectableValidators)
+ it('should set the max electable validators', async () => {
+ await election.setMaxElectableValidators(newMaxElectableValidators)
+ assertEqualBN(await election.maxElectableValidators(), newMaxElectableValidators)
})
it('should emit the MaxElectableValidatorsSet event', async () => {
- const resp = await validators.setMaxElectableValidators(newMaxElectableValidators)
+ const resp = await election.setMaxElectableValidators(newMaxElectableValidators)
assert.equal(resp.logs.length, 1)
const log = resp.logs[0]
assertContainSubset(log, {
@@ -218,1047 +145,563 @@ contract('Validators', (accounts: string[]) => {
})
it('should revert when the maxElectableValidators is less than minElectableValidators', async () => {
- await assertRevert(validators.setMaxElectableValidators(minElectableValidators.minus(1)))
+ await assertRevert(election.setMaxElectableValidators(minElectableValidators.minus(1)))
})
it('should revert when the maxElectableValidators is unchanged', async () => {
- await assertRevert(validators.setMaxElectableValidators(maxElectableValidators))
+ await assertRevert(election.setMaxElectableValidators(maxElectableValidators))
})
it('should revert when called by anyone other than the owner', async () => {
await assertRevert(
- validators.setMaxElectableValidators(newMaxElectableValidators, { from: nonOwner })
+ election.setMaxElectableValidators(newMaxElectableValidators, { from: nonOwner })
)
})
})
- describe('#setRegistrationRequirement', () => {
- const newValue = registrationRequirement.value.plus(1)
- const newNoticePeriod = registrationRequirement.noticePeriod.plus(1)
-
- it('should set the value and notice period', async () => {
- await validators.setRegistrationRequirement(newValue, newNoticePeriod)
- const [value, noticePeriod] = await validators.getRegistrationRequirement()
- assertEqualBN(value, newValue)
- assertEqualBN(noticePeriod, newNoticePeriod)
+ describe('#setMaxVotesPerAccount', () => {
+ const newMaxVotesPerAccount = maxVotesPerAccount.plus(1)
+ it('should set the max electable validators', async () => {
+ await election.setMaxVotesPerAccount(newMaxVotesPerAccount)
+ assertEqualBN(await election.maxVotesPerAccount(), newMaxVotesPerAccount)
})
- it('should emit the RegistrationRequirementSet event', async () => {
- const resp = await validators.setRegistrationRequirement(newValue, newNoticePeriod)
+ it('should emit the MaxVotesPerAccountSet event', async () => {
+ const resp = await election.setMaxVotesPerAccount(newMaxVotesPerAccount)
assert.equal(resp.logs.length, 1)
const log = resp.logs[0]
assertContainSubset(log, {
- event: 'RegistrationRequirementSet',
+ event: 'MaxVotesPerAccountSet',
args: {
- value: new BigNumber(newValue),
- noticePeriod: new BigNumber(newNoticePeriod),
+ maxVotesPerAccount: new BigNumber(newMaxVotesPerAccount),
},
})
})
- it('should revert when the requirement is unchanged', async () => {
- await assertRevert(
- validators.setRegistrationRequirement(
- registrationRequirement.value,
- registrationRequirement.noticePeriod
- )
- )
+ it('should revert when the maxVotesPerAccount is unchanged', async () => {
+ await assertRevert(election.setMaxVotesPerAccount(maxVotesPerAccount))
})
it('should revert when called by anyone other than the owner', async () => {
- await assertRevert(
- validators.setRegistrationRequirement(newValue, newNoticePeriod, { from: nonOwner })
- )
- })
- })
-
- describe('#registerValidator', () => {
- const validator = accounts[0]
- beforeEach(async () => {
- await mockLockedGold.setLockedCommitment(
- validator,
- registrationRequirement.noticePeriod,
- registrationRequirement.value
- )
- })
-
- it('should mark the account as a validator', async () => {
- await validators.registerValidator(
- identifier,
- name,
- url,
- // @ts-ignore bytes type
- publicKeysData,
- registrationRequirement.noticePeriod
- )
- assert.isTrue(await validators.isValidator(validator))
- })
-
- it('should add the account to the list of validators', async () => {
- await validators.registerValidator(
- identifier,
- name,
- url,
- // @ts-ignore bytes type
- publicKeysData,
- registrationRequirement.noticePeriod
- )
- assert.deepEqual(await validators.getRegisteredValidators(), [validator])
- })
-
- it('should set the validator identifier, name, url, and public key', async () => {
- await validators.registerValidator(
- identifier,
- name,
- url,
- // @ts-ignore bytes type
- publicKeysData,
- registrationRequirement.noticePeriod
- )
- const parsedValidator = parseValidatorParams(await validators.getValidator(validator))
- assert.equal(parsedValidator.identifier, identifier)
- assert.equal(parsedValidator.name, name)
- assert.equal(parsedValidator.url, url)
- assert.equal(parsedValidator.publicKeysData, publicKeysData)
- })
-
- it('should emit the ValidatorRegistered event', async () => {
- const resp = await validators.registerValidator(
- identifier,
- name,
- url,
- // @ts-ignore bytes type
- publicKeysData,
- registrationRequirement.noticePeriod
- )
- assert.equal(resp.logs.length, 1)
- const log = resp.logs[0]
- assertContainSubset(log, {
- event: 'ValidatorRegistered',
- args: {
- validator,
- identifier,
- name,
- url,
- publicKeysData,
- },
- })
- })
-
- describe('when the account is already a registered validator', () => {
- beforeEach(async () => {
- await validators.registerValidator(
- identifier,
- name,
- url,
- // @ts-ignore bytes type
- publicKeysData,
- registrationRequirement.noticePeriod
- )
- })
-
- it('should revert', async () => {
- await assertRevert(
- validators.registerValidator(
- identifier,
- name,
- url,
- // @ts-ignore bytes type
- publicKeysData,
- registrationRequirement.noticePeriod
- )
- )
- })
- })
-
- describe('when the account is already a registered validator group', () => {
- beforeEach(async () => {
- await validators.registerValidatorGroup(
- identifier,
- name,
- url,
- registrationRequirement.noticePeriod
- )
- })
-
- it('should revert', async () => {
- await assertRevert(
- validators.registerValidator(
- identifier,
- name,
- url,
- // @ts-ignore bytes type
- publicKeysData,
- registrationRequirement.noticePeriod
- )
- )
- })
- })
-
- describe('when the account does not meet the registration requirements', () => {
- beforeEach(async () => {
- await mockLockedGold.setLockedCommitment(
- validator,
- registrationRequirement.noticePeriod,
- registrationRequirement.value.minus(1)
- )
- })
-
- it('should revert', async () => {
- await assertRevert(
- validators.registerValidator(
- identifier,
- name,
- url,
- // @ts-ignore bytes type
- publicKeysData,
- registrationRequirement.noticePeriod
- )
- )
- })
+ await assertRevert(election.setMaxVotesPerAccount(newMaxVotesPerAccount, { from: nonOwner }))
})
})
- describe('#deregisterValidator', () => {
- const validator = accounts[0]
- const index = 0
- beforeEach(async () => {
- await registerValidator(validator)
- })
-
- it('should mark the account as not a validator', async () => {
- await validators.deregisterValidator(index)
- assert.isFalse(await validators.isValidator(validator))
- })
-
- it('should remove the account from the list of validators', async () => {
- await validators.deregisterValidator(index)
- assert.deepEqual(await validators.getRegisteredValidators(), [])
- })
-
- it('should emit the ValidatorDeregistered event', async () => {
- const resp = await validators.deregisterValidator(index)
- assert.equal(resp.logs.length, 1)
- const log = resp.logs[0]
- assertContainSubset(log, {
- event: 'ValidatorDeregistered',
- args: {
- validator,
- },
- })
- })
-
- describe('when the validator is affiliated with a validator group', () => {
- const group = accounts[1]
+ describe('#markGroupEligible', () => {
+ const group = accounts[1]
+ describe('when the group has members', () => {
beforeEach(async () => {
- await registerValidatorGroup(group)
- await validators.affiliate(group)
+ await mockValidators.setMembers(group, [accounts[9]])
})
- it('should emit the ValidatorDeafilliated event', async () => {
- const resp = await validators.deregisterValidator(index)
- assert.equal(resp.logs.length, 2)
- const log = resp.logs[0]
- assertContainSubset(log, {
- event: 'ValidatorDeaffiliated',
- args: {
- validator,
- group,
- },
- })
- })
-
- describe('when the validator is a member of that group', () => {
+ describe('when the group has no votes', () => {
+ let resp: any
beforeEach(async () => {
- await validators.addMember(validator, { from: group })
+ resp = await election.markGroupEligible(group, NULL_ADDRESS, NULL_ADDRESS)
})
- it('should remove the validator from the group membership list', async () => {
- await validators.deregisterValidator(index)
- const parsedGroup = parseValidatorGroupParams(await validators.getValidatorGroup(group))
- assert.deepEqual(parsedGroup.members, [])
+ it('should add the group to the list of eligible groups', async () => {
+ assert.deepEqual(await election.getEligibleValidatorGroups(), [group])
})
- it('should emit the ValidatorGroupMemberRemoved event', async () => {
- const resp = await validators.deregisterValidator(index)
- assert.equal(resp.logs.length, 4)
+ it('should emit the ValidatorGroupMarkedEligible event', async () => {
+ assert.equal(resp.logs.length, 1)
const log = resp.logs[0]
assertContainSubset(log, {
- event: 'ValidatorGroupMemberRemoved',
+ event: 'ValidatorGroupMarkedEligible',
args: {
- validator,
group,
},
})
})
- describe('when the validator is the only member of that group', () => {
- it('should emit the ValidatorGroupEmptied event', async () => {
- const resp = await validators.deregisterValidator(index)
- assert.equal(resp.logs.length, 4)
- const log = resp.logs[1]
- assertContainSubset(log, {
- event: 'ValidatorGroupEmptied',
- args: {
- group,
- },
- })
- })
-
- describe('when that group has received votes', () => {
- beforeEach(async () => {
- const voter = accounts[2]
- const weight = 10
- await mockLockedGold.setWeight(voter, weight)
- await validators.vote(group, NULL_ADDRESS, NULL_ADDRESS, { from: voter })
- })
-
- it('should remove the group from the list of electable groups with votes', async () => {
- await validators.deregisterValidator(index)
- const [groups] = await validators.getValidatorGroupVotes()
- assert.deepEqual(groups, [])
- })
+ describe('when the group has already been marked eligible', () => {
+ it('should revert', async () => {
+ await assertRevert(election.markGroupEligible(group, NULL_ADDRESS, NULL_ADDRESS))
})
})
})
})
- it('should revert when the account is not a registered validator', async () => {
- await assertRevert(validators.deregisterValidator(index, { from: accounts[2] }))
- })
-
- it('should revert when the wrong index is provided', async () => {
- await assertRevert(validators.deregisterValidator(index + 1))
+ describe('when the group has no members', () => {
+ it('should revert', async () => {
+ await assertRevert(election.markGroupEligible(group, NULL_ADDRESS, NULL_ADDRESS))
+ })
})
})
- describe('#affiliate', () => {
- const validator = accounts[0]
+ describe('#markGroupIneligible', () => {
const group = accounts[1]
- beforeEach(async () => {
- await registerValidator(validator)
- await registerValidatorGroup(group)
- })
-
- it('should set the affiliate', async () => {
- await validators.affiliate(group)
- const parsedValidator = parseValidatorParams(await validators.getValidator(validator))
- assert.equal(parsedValidator.affiliation, group)
- })
-
- it('should emit the ValidatorAffiliated event', async () => {
- const resp = await validators.affiliate(group)
- assert.equal(resp.logs.length, 1)
- const log = resp.logs[0]
- assertContainSubset(log, {
- event: 'ValidatorAffiliated',
- args: {
- validator,
- group,
- },
- })
- })
-
- describe('when the validator is already affiliated with a validator group', () => {
- const otherGroup = accounts[2]
+ describe('when the group is eligible', () => {
beforeEach(async () => {
- await validators.affiliate(group)
- await registerValidatorGroup(otherGroup)
- })
-
- it('should set the affiliate', async () => {
- await validators.affiliate(otherGroup)
- const parsedValidator = parseValidatorParams(await validators.getValidator(validator))
- assert.equal(parsedValidator.affiliation, otherGroup)
+ await mockValidators.setMembers(group, [accounts[9]])
+ await election.markGroupEligible(group, NULL_ADDRESS, NULL_ADDRESS)
})
- it('should emit the ValidatorDeafilliated event', async () => {
- const resp = await validators.affiliate(otherGroup)
- assert.equal(resp.logs.length, 2)
- const log = resp.logs[0]
- assertContainSubset(log, {
- event: 'ValidatorDeaffiliated',
- args: {
- validator,
- group,
- },
- })
- })
-
- it('should emit the ValidatorAffiliated event', async () => {
- const resp = await validators.affiliate(otherGroup)
- assert.equal(resp.logs.length, 2)
- const log = resp.logs[1]
- assertContainSubset(log, {
- event: 'ValidatorAffiliated',
- args: {
- validator,
- group: otherGroup,
- },
- })
- })
-
- describe('when the validator is a member of that group', () => {
+ describe('when called by the registered Validators contract', () => {
+ let resp: any
beforeEach(async () => {
- await validators.addMember(validator, { from: group })
+ await registry.setAddressFor(CeloContractName.Validators, accounts[0])
+ resp = await election.markGroupIneligible(group)
})
- it('should remove the validator from the group membership list', async () => {
- await validators.affiliate(otherGroup)
- const parsedGroup = parseValidatorGroupParams(await validators.getValidatorGroup(group))
- assert.deepEqual(parsedGroup.members, [])
+ describe('when the group has votes', () => {})
+
+ it('should remove the group from the list of eligible groups', async () => {
+ assert.deepEqual(await election.getEligibleValidatorGroups(), [])
})
- it('should emit the ValidatorGroupMemberRemoved event', async () => {
- const resp = await validators.affiliate(otherGroup)
- assert.equal(resp.logs.length, 4)
+ it('should emit the ValidatorGroupMarkedIneligible event', async () => {
+ assert.equal(resp.logs.length, 1)
const log = resp.logs[0]
assertContainSubset(log, {
- event: 'ValidatorGroupMemberRemoved',
+ event: 'ValidatorGroupMarkedIneligible',
args: {
- validator,
group,
},
})
})
+ })
- describe('when the validator is the only member of that group', () => {
- it('should emit the ValidatorGroupEmptied event', async () => {
- const resp = await validators.affiliate(otherGroup)
- assert.equal(resp.logs.length, 4)
- const log = resp.logs[1]
- assertContainSubset(log, {
- event: 'ValidatorGroupEmptied',
- args: {
- group,
- },
- })
- })
-
- describe('when that group has received votes', () => {
- beforeEach(async () => {
- const voter = accounts[2]
- const weight = 10
- await mockLockedGold.setWeight(voter, weight)
- await validators.vote(group, NULL_ADDRESS, NULL_ADDRESS, { from: voter })
- })
-
- it('should remove the group from the list of electable groups with votes', async () => {
- await validators.affiliate(otherGroup)
- const [groups] = await validators.getValidatorGroupVotes()
- assert.deepEqual(groups, [])
- })
- })
+ describe('when not called by the registered Validators contract', () => {
+ it('should revert', async () => {
+ await assertRevert(election.markGroupIneligible(group))
})
})
})
- it('should revert when the account is not a registered validator', async () => {
- await assertRevert(validators.affiliate(group, { from: accounts[2] }))
- })
+ describe('when the group is ineligible', () => {
+ describe('when called by the registered Validators contract', () => {
+ beforeEach(async () => {
+ await registry.setAddressFor(CeloContractName.Validators, accounts[0])
+ })
- it('should revert when the group is not a registered validator group', async () => {
- await assertRevert(validators.affiliate(accounts[2]))
+ it('should revert', async () => {
+ await assertRevert(election.markGroupIneligible(group))
+ })
+ })
})
})
- describe('#deaffiliate', () => {
- const validator = accounts[0]
+ describe('#vote', () => {
+ const voter = accounts[0]
const group = accounts[1]
- beforeEach(async () => {
- await registerValidator(validator)
- await registerValidatorGroup(group)
- await validators.affiliate(group)
- })
-
- it('should clear the affiliate', async () => {
- await validators.deaffiliate()
- const parsedValidator = parseValidatorParams(await validators.getValidator(validator))
- assert.equal(parsedValidator.affiliation, NULL_ADDRESS)
- })
-
- it('should emit the ValidatorDeaffiliated event', async () => {
- const resp = await validators.deaffiliate()
- assert.equal(resp.logs.length, 1)
- const log = resp.logs[0]
- assertContainSubset(log, {
- event: 'ValidatorDeaffiliated',
- args: {
- validator,
- group,
- },
- })
- })
-
- describe('when the validator is a member of the affiliated group', () => {
+ const value = 1000
+ describe('when the group is eligible', () => {
beforeEach(async () => {
- await validators.addMember(validator, { from: group })
- })
-
- it('should remove the validator from the group membership list', async () => {
- await validators.deaffiliate()
- const parsedGroup = parseValidatorGroupParams(await validators.getValidatorGroup(group))
- assert.deepEqual(parsedGroup.members, [])
+ await mockValidators.setMembers(group, [accounts[9]])
+ await election.markGroupEligible(group, NULL_ADDRESS, NULL_ADDRESS)
})
- it('should emit the ValidatorGroupMemberRemoved event', async () => {
- const resp = await validators.deaffiliate()
- assert.equal(resp.logs.length, 3)
- const log = resp.logs[0]
- assertContainSubset(log, {
- event: 'ValidatorGroupMemberRemoved',
- args: {
- validator,
- group,
- },
+ describe('when the group can receive votes', () => {
+ beforeEach(async () => {
+ await mockLockedGold.setTotalLockedGold(value)
+ await mockValidators.setNumRegisteredValidators(1)
})
- })
- describe('when the validator is the only member of that group', () => {
- it('should emit the ValidatorGroupEmptied event', async () => {
- const resp = await validators.deaffiliate()
- assert.equal(resp.logs.length, 3)
- const log = resp.logs[1]
- assertContainSubset(log, {
- event: 'ValidatorGroupEmptied',
- args: {
- group,
- },
- })
- })
+ describe('when the voter can vote for an additional group', () => {
+ describe('when the voter has sufficient non-voting balance', () => {
+ let resp: any
+ beforeEach(async () => {
+ await mockLockedGold.incrementNonvotingAccountBalance(voter, value)
+ resp = await election.vote(group, value, NULL_ADDRESS, NULL_ADDRESS)
+ })
- describe('when that group has received votes', () => {
- beforeEach(async () => {
- const voter = accounts[2]
- const weight = 10
- await mockLockedGold.setWeight(voter, weight)
- await validators.vote(group, NULL_ADDRESS, NULL_ADDRESS, { from: voter })
- })
+ it('should add the group to the list of groups the account has voted for', async () => {
+ assert.deepEqual(await election.getAccountGroupsVotedFor(voter), [group])
+ })
- it('should remove the group from the list of electable groups with votes', async () => {
- await validators.deaffiliate()
- const [groups] = await validators.getValidatorGroupVotes()
- assert.deepEqual(groups, [])
- })
- })
- })
- })
+ it("should increment the account's pending votes for the group", async () => {
+ assertEqualBN(await election.getAccountPendingVotesForGroup(group, voter), value)
+ })
- it('should revert when the account is not a registered validator', async () => {
- await assertRevert(validators.deaffiliate({ from: accounts[2] }))
- })
+ it("should increment the account's total votes for the group", async () => {
+ assertEqualBN(await election.getAccountTotalVotesForGroup(group, voter), value)
+ })
- it('should revert when the validator is not affiliated with a validator group', async () => {
- await validators.deaffiliate()
- await assertRevert(validators.deaffiliate())
- })
- })
+ it("should increment the account's total votes", async () => {
+ assertEqualBN(await election.getAccountTotalVotes(voter), value)
+ })
- describe('#registerValidatorGroup', () => {
- const group = accounts[0]
- beforeEach(async () => {
- await mockLockedGold.setLockedCommitment(
- group,
- registrationRequirement.noticePeriod,
- registrationRequirement.value
- )
- })
+ it('should increment the total votes for the group', async () => {
+ assertEqualBN(await election.getGroupTotalVotes(group), value)
+ })
- it('should mark the account as a validator group', async () => {
- await validators.registerValidatorGroup(
- identifier,
- name,
- url,
- registrationRequirement.noticePeriod
- )
- assert.isTrue(await validators.isValidatorGroup(group))
- })
+ it('should increment the total votes', async () => {
+ assertEqualBN(await election.getTotalVotes(), value)
+ })
- it('should add the account to the list of validator groups', async () => {
- await validators.registerValidatorGroup(
- identifier,
- name,
- url,
- registrationRequirement.noticePeriod
- )
- assert.deepEqual(await validators.getRegisteredValidatorGroups(), [group])
- })
+ it("should decrement the account's nonvoting locked gold balance", async () => {
+ assertEqualBN(await mockLockedGold.nonvotingAccountBalance(voter), 0)
+ })
- it('should set the validator group identifier, name, and url', async () => {
- await validators.registerValidatorGroup(
- identifier,
- name,
- url,
- registrationRequirement.noticePeriod
- )
- const parsedGroup = parseValidatorGroupParams(await validators.getValidatorGroup(group))
- assert.equal(parsedGroup.identifier, identifier)
- assert.equal(parsedGroup.name, name)
- assert.equal(parsedGroup.url, url)
- })
+ it('should emit the ValidatorGroupVoteCast event', async () => {
+ assert.equal(resp.logs.length, 1)
+ const log = resp.logs[0]
+ assertContainSubset(log, {
+ event: 'ValidatorGroupVoteCast',
+ args: {
+ account: voter,
+ group,
+ value: new BigNumber(value),
+ },
+ })
+ })
+ })
- it('should emit the ValidatorGroupRegistered event', async () => {
- const resp = await validators.registerValidatorGroup(
- identifier,
- name,
- url,
- registrationRequirement.noticePeriod
- )
- assert.equal(resp.logs.length, 1)
- const log = resp.logs[0]
- assertContainSubset(log, {
- event: 'ValidatorGroupRegistered',
- args: {
- group,
- identifier,
- name,
- url,
- },
- })
- })
+ describe('when the voter does not have sufficient non-voting balance', () => {
+ beforeEach(async () => {
+ await mockLockedGold.incrementNonvotingAccountBalance(voter, value - 1)
+ })
- describe('when the account is already a registered validator', () => {
- beforeEach(async () => {
- await registerValidator(group)
- })
+ it('should revert', async () => {
+ await assertRevert(election.vote(group, value, NULL_ADDRESS, NULL_ADDRESS))
+ })
+ })
+ })
- it('should revert', async () => {
- await assertRevert(
- validators.registerValidatorGroup(
- identifier,
- name,
- url,
- registrationRequirement.noticePeriod
- )
- )
- })
- })
+ describe('when the voter cannot vote for an additional group', () => {
+ let newGroup: string
+ beforeEach(async () => {
+ await mockLockedGold.incrementNonvotingAccountBalance(voter, value)
+ for (let i = 0; i < maxVotesPerAccount.toNumber(); i++) {
+ newGroup = accounts[i + 2]
+ await mockValidators.setMembers(newGroup, [accounts[9]])
+ await election.markGroupEligible(newGroup, group, NULL_ADDRESS)
+ await election.vote(newGroup, 1, group, NULL_ADDRESS)
+ }
+ })
- describe('when the account is already a registered validator group', () => {
- beforeEach(async () => {
- await validators.registerValidatorGroup(
- identifier,
- name,
- url,
- registrationRequirement.noticePeriod
- )
+ it('should revert', async () => {
+ await assertRevert(
+ election.vote(group, value - maxVotesPerAccount.toNumber(), newGroup, NULL_ADDRESS)
+ )
+ })
+ })
})
- it('should revert', async () => {
- await assertRevert(
- validators.registerValidatorGroup(
- identifier,
- name,
- url,
- registrationRequirement.noticePeriod
- )
- )
+ describe('when the group cannot receive votes', () => {
+ it('should revert', async () => {
+ await assertRevert(election.vote(group, value, NULL_ADDRESS, NULL_ADDRESS))
+ })
})
})
- describe('when the account does not meet the registration requirements', () => {
- beforeEach(async () => {
- await mockLockedGold.setLockedCommitment(
- group,
- registrationRequirement.noticePeriod,
- registrationRequirement.value.minus(1)
- )
- })
-
+ describe('when the group is not eligible', () => {
it('should revert', async () => {
- await assertRevert(
- validators.registerValidatorGroup(
- identifier,
- name,
- url,
- registrationRequirement.noticePeriod
- )
- )
+ await assertRevert(election.vote(group, value, NULL_ADDRESS, NULL_ADDRESS))
})
})
})
- describe('#deregisterValidatorGroup', () => {
- const index = 0
- const group = accounts[0]
+ describe('#activate', () => {
+ const voter = accounts[0]
+ const group = accounts[1]
+ const value = 1000
beforeEach(async () => {
- await registerValidatorGroup(group)
- })
-
- it('should mark the account as not a validator group', async () => {
- await validators.deregisterValidatorGroup(index)
- assert.isFalse(await validators.isValidatorGroup(group))
- })
-
- it('should remove the account from the list of validator groups', async () => {
- await validators.deregisterValidatorGroup(index)
- assert.deepEqual(await validators.getRegisteredValidatorGroups(), [])
- })
-
- it('should emit the ValidatorGroupDeregistered event', async () => {
- const resp = await validators.deregisterValidatorGroup(index)
- assert.equal(resp.logs.length, 1)
- const log = resp.logs[0]
- assertContainSubset(log, {
- event: 'ValidatorGroupDeregistered',
- args: {
- group,
- },
- })
+ await mockValidators.setMembers(group, [accounts[9]])
+ await election.markGroupEligible(group, NULL_ADDRESS, NULL_ADDRESS)
+ await mockLockedGold.setTotalLockedGold(value)
+ await mockValidators.setNumRegisteredValidators(1)
+ await mockLockedGold.incrementNonvotingAccountBalance(voter, value)
})
- it('should revert when the account is not a registered validator group', async () => {
- await assertRevert(validators.deregisterValidatorGroup(index, { from: accounts[2] }))
- })
-
- it('should revert when the wrong index is provided', async () => {
- await assertRevert(validators.deregisterValidatorGroup(index + 1))
- })
-
- describe('when the validator group is not empty', () => {
- const validator = accounts[1]
+ describe('when the voter has pending votes', () => {
+ let resp: any
beforeEach(async () => {
- await registerValidator(validator)
- await validators.affiliate(group, { from: validator })
- await validators.addMember(validator)
- })
-
- it('should revert', async () => {
- await assertRevert(validators.deregisterValidatorGroup(index))
+ await election.vote(group, value, NULL_ADDRESS, NULL_ADDRESS)
+ resp = await election.activate(group)
})
- })
- })
-
- describe('#addMember', () => {
- const group = accounts[0]
- const validator = accounts[1]
- beforeEach(async () => {
- await registerValidator(validator)
- await registerValidatorGroup(group)
- await validators.affiliate(group, { from: validator })
- })
-
- it('should add the member to the list of members', async () => {
- await validators.addMember(validator)
- const parsedGroup = parseValidatorGroupParams(await validators.getValidatorGroup(group))
- assert.deepEqual(parsedGroup.members, [validator])
- })
- it('should emit the ValidatorGroupMemberAdded event', async () => {
- const resp = await validators.addMember(validator)
- assert.equal(resp.logs.length, 1)
- const log = resp.logs[0]
- assertContainSubset(log, {
- event: 'ValidatorGroupMemberAdded',
- args: {
- group,
- validator,
- },
+ it("should decrement the account's pending votes for the group", async () => {
+ assertEqualBN(await election.getAccountPendingVotesForGroup(group, voter), 0)
})
- })
-
- it('should revert when the account is not a registered validator group', async () => {
- await assertRevert(validators.addMember(validator, { from: accounts[2] }))
- })
-
- it('should revert when the member is not a registered validator', async () => {
- await assertRevert(validators.addMember(accounts[2]))
- })
- describe('when the validator has not affiliated themselves with the group', () => {
- beforeEach(async () => {
- await validators.deaffiliate({ from: validator })
+ it("should increment the account's active votes for the group", async () => {
+ assertEqualBN(await election.getAccountActiveVotesForGroup(group, voter), value)
})
- it('should revert', async () => {
- await assertRevert(validators.addMember(validator))
+ it("should not modify the account's total votes for the group", async () => {
+ assertEqualBN(await election.getAccountTotalVotesForGroup(group, voter), value)
})
- })
- describe('when the validator is already a member of the group', () => {
- beforeEach(async () => {
- await validators.addMember(validator)
+ it("should not modify the account's total votes", async () => {
+ assertEqualBN(await election.getAccountTotalVotes(voter), value)
})
- it('should revert', async () => {
- await assertRevert(validators.addMember(validator))
+ it('should not modify the total votes for the group', async () => {
+ assertEqualBN(await election.getGroupTotalVotes(group), value)
})
- })
- })
-
- describe('#removeMember', () => {
- const group = accounts[0]
- const validator = accounts[1]
- beforeEach(async () => {
- await registerValidatorGroupWithMembers(group, [validator])
- })
-
- it('should remove the member from the list of members', async () => {
- await validators.removeMember(validator)
- const parsedGroup = parseValidatorGroupParams(await validators.getValidatorGroup(group))
- assert.deepEqual(parsedGroup.members, [])
- })
- it('should emit the ValidatorGroupMemberRemoved event', async () => {
- const resp = await validators.removeMember(validator)
- assert.equal(resp.logs.length, 2)
- const log = resp.logs[0]
- assertContainSubset(log, {
- event: 'ValidatorGroupMemberRemoved',
- args: {
- group,
- validator,
- },
+ it('should not modify the total votes', async () => {
+ assertEqualBN(await election.getTotalVotes(), value)
})
- })
- describe('when the validator is the only member of the group', () => {
- it('should emit the ValidatorGroupEmptied event', async () => {
- const resp = await validators.removeMember(validator)
- assert.equal(resp.logs.length, 2)
- const log = resp.logs[1]
+ it('should emit the ValidatorGroupVoteActivated event', async () => {
+ assert.equal(resp.logs.length, 1)
+ const log = resp.logs[0]
assertContainSubset(log, {
- event: 'ValidatorGroupEmptied',
+ event: 'ValidatorGroupVoteActivated',
args: {
+ account: voter,
group,
+ value: new BigNumber(value),
},
})
})
- describe('when the group has received votes', () => {
+ describe('when another voter activates votes', () => {
+ const voter2 = accounts[2]
+ const value2 = 573
beforeEach(async () => {
- const voter = accounts[2]
- const weight = 10
- await mockLockedGold.setWeight(voter, weight)
- await validators.vote(group, NULL_ADDRESS, NULL_ADDRESS, { from: voter })
+ await mockLockedGold.incrementNonvotingAccountBalance(voter2, value2)
+ await election.vote(group, value2, NULL_ADDRESS, NULL_ADDRESS, { from: voter2 })
+ await election.activate(group, { from: voter2 })
})
- it('should remove the group from the list of electable groups with votes', async () => {
- await validators.removeMember(validator)
- const [groups] = await validators.getValidatorGroupVotes()
- assert.deepEqual(groups, [])
+ it("should not modify the first account's active votes for the group", async () => {
+ assertEqualBN(await election.getAccountActiveVotesForGroup(group, voter), value)
})
- })
- })
-
- it('should revert when the account is not a registered validator group', async () => {
- await assertRevert(validators.removeMember(validator, { from: accounts[2] }))
- })
- it('should revert when the member is not a registered validator', async () => {
- await assertRevert(validators.removeMember(accounts[2]))
- })
-
- describe('when the validator is not a member of the validator group', () => {
- beforeEach(async () => {
- await validators.deaffiliate({ from: validator })
- })
+ it("should not modify the first account's total votes for the group", async () => {
+ assertEqualBN(await election.getAccountTotalVotesForGroup(group, voter), value)
+ })
- it('should revert', async () => {
- await assertRevert(validators.removeMember(validator))
- })
- })
- })
+ it("should not modify the first account's total votes", async () => {
+ assertEqualBN(await election.getAccountTotalVotes(voter), value)
+ })
- describe('#reorderMember', () => {
- const group = accounts[0]
- const validator1 = accounts[1]
- const validator2 = accounts[2]
- beforeEach(async () => {
- await registerValidatorGroupWithMembers(group, [validator1, validator2])
- })
+ it("should decrement the second account's pending votes for the group", async () => {
+ assertEqualBN(await election.getAccountPendingVotesForGroup(group, voter2), 0)
+ })
- it('should reorder the list of group members', async () => {
- await validators.reorderMember(validator2, validator1, NULL_ADDRESS)
- const parsedGroup = parseValidatorGroupParams(await validators.getValidatorGroup(group))
- assert.deepEqual(parsedGroup.members, [validator2, validator1])
- })
+ it("should increment the second account's active votes for the group", async () => {
+ assertEqualBN(await election.getAccountActiveVotesForGroup(group, voter2), value2)
+ })
- it('should emit the ValidatorGroupMemberReordered event', async () => {
- const resp = await validators.reorderMember(validator2, validator1, NULL_ADDRESS)
- assert.equal(resp.logs.length, 1)
- const log = resp.logs[0]
- assertContainSubset(log, {
- event: 'ValidatorGroupMemberReordered',
- args: {
- group,
- validator: validator2,
- },
- })
- })
+ it("should not modify the second account's total votes for the group", async () => {
+ assertEqualBN(await election.getAccountTotalVotesForGroup(group, voter2), value2)
+ })
- it('should revert when the account is not a registered validator group', async () => {
- await assertRevert(
- validators.reorderMember(validator2, validator1, NULL_ADDRESS, { from: accounts[2] })
- )
- })
+ it("should not modify the second account's total votes", async () => {
+ assertEqualBN(await election.getAccountTotalVotes(voter2), value2)
+ })
- it('should revert when the member is not a registered validator', async () => {
- await assertRevert(validators.reorderMember(accounts[3], validator1, NULL_ADDRESS))
- })
+ it('should not modify the total votes for the group', async () => {
+ assertEqualBN(await election.getGroupTotalVotes(group), value + value2)
+ })
- describe('when the validator is not a member of the validator group', () => {
- beforeEach(async () => {
- await validators.deaffiliate({ from: validator2 })
+ it('should not modify the total votes', async () => {
+ assertEqualBN(await election.getTotalVotes(), value + value2)
+ })
})
- it('should revert', async () => {
- await assertRevert(validators.reorderMember(validator2, validator1, NULL_ADDRESS))
+ describe('when the voter does not have pending votes', () => {
+ it('should revert', async () => {
+ await assertRevert(election.activate(group))
+ })
})
})
})
- describe('#vote', () => {
- const weight = new BigNumber(5)
+ describe('#revokePending', () => {
const voter = accounts[0]
- const validator = accounts[1]
- const group = accounts[2]
- beforeEach(async () => {
- await registerValidatorGroupWithMembers(group, [validator])
- await mockLockedGold.setWeight(voter, weight)
- })
+ const group = accounts[1]
+ const value = 1000
+ describe('when the voter has pending votes', () => {
+ beforeEach(async () => {
+ await mockValidators.setMembers(group, [accounts[9]])
+ await election.markGroupEligible(group, NULL_ADDRESS, NULL_ADDRESS)
+ await mockLockedGold.setTotalLockedGold(value)
+ await mockValidators.setNumRegisteredValidators(1)
+ await mockLockedGold.incrementNonvotingAccountBalance(voter, value)
+ await election.vote(group, value, NULL_ADDRESS, NULL_ADDRESS)
+ })
+
+ describe('when the revoked value is less than the pending votes', () => {
+ const index = 0
+ const revokedValue = value - 1
+ const remaining = value - revokedValue
+ let resp: any
+ beforeEach(async () => {
+ resp = await election.revokePending(
+ group,
+ revokedValue,
+ NULL_ADDRESS,
+ NULL_ADDRESS,
+ index
+ )
+ })
- it("should set the voter's vote", async () => {
- await validators.vote(group, NULL_ADDRESS, NULL_ADDRESS)
- assert.isTrue(await validators.isVoting(voter))
- assert.equal(await validators.voters(voter), group)
- })
+ it("should decrement the account's pending votes for the group", async () => {
+ assertEqualBN(await election.getAccountPendingVotesForGroup(group, voter), remaining)
+ })
- it('should add the group to the list of those receiving votes', async () => {
- await validators.vote(group, NULL_ADDRESS, NULL_ADDRESS)
- const [groups] = await validators.getValidatorGroupVotes()
- assert.deepEqual(groups, [group])
- })
+ it("should decrement the account's total votes for the group", async () => {
+ assertEqualBN(await election.getAccountTotalVotesForGroup(group, voter), remaining)
+ })
- it("should increment the validator group's vote total", async () => {
- await validators.vote(group, NULL_ADDRESS, NULL_ADDRESS)
- assertEqualBN(await validators.getVotesReceived(group), weight)
- })
+ it("should decrement the account's total votes", async () => {
+ assertEqualBN(await election.getAccountTotalVotes(voter), remaining)
+ })
- it('should emit the ValidatorGroupVoteCast event', async () => {
- const resp = await validators.vote(group, NULL_ADDRESS, NULL_ADDRESS)
- assert.equal(resp.logs.length, 1)
- const log = resp.logs[0]
- assertContainSubset(log, {
- event: 'ValidatorGroupVoteCast',
- args: {
- account: voter,
- group,
- weight: new BigNumber(weight),
- },
- })
- })
+ it('should decrement the total votes for the group', async () => {
+ assertEqualBN(await election.getGroupTotalVotes(group), remaining)
+ })
- describe('when the group had not previously received votes', () => {
- it('should add the group to the list of electable groups with votes', async () => {
- await validators.vote(group, NULL_ADDRESS, NULL_ADDRESS)
- const [groups] = await validators.getValidatorGroupVotes()
- assert.deepEqual(groups, [group])
- })
- })
+ it('should decrement the total votes', async () => {
+ assertEqualBN(await election.getTotalVotes(), remaining)
+ })
- it('should revert when the group is not a registered validator group', async () => {
- await assertRevert(validators.vote(accounts[3], NULL_ADDRESS, NULL_ADDRESS))
- })
+ it("should increment the account's nonvoting locked gold balance", async () => {
+ assertEqualBN(await mockLockedGold.nonvotingAccountBalance(voter), revokedValue)
+ })
- describe('when the group is empty', () => {
- beforeEach(async () => {
- await validators.removeMember(validator, { from: group })
+ it('should emit the ValidatorGroupVoteRevoked event', async () => {
+ assert.equal(resp.logs.length, 1)
+ const log = resp.logs[0]
+ assertContainSubset(log, {
+ event: 'ValidatorGroupVoteRevoked',
+ args: {
+ account: voter,
+ group,
+ value: new BigNumber(revokedValue),
+ },
+ })
+ })
})
- it('should revert', async () => {
- await assertRevert(validators.vote(group, NULL_ADDRESS, NULL_ADDRESS))
- })
- })
+ describe('when the revoked value is equal to the pending votes', () => {
+ describe('when the correct index is provided', () => {
+ const index = 0
+ beforeEach(async () => {
+ await election.revokePending(group, value, NULL_ADDRESS, NULL_ADDRESS, index)
+ })
- describe('when the account voting is frozen', () => {
- beforeEach(async () => {
- await mockLockedGold.setVotingFrozen(voter)
+ it('should remove the group to the list of groups the account has voted for', async () => {
+ assert.deepEqual(await election.getAccountGroupsVotedFor(voter), [])
+ })
+ })
+
+ describe('when the wrong index is provided', () => {
+ const index = 1
+ it('should revert', async () => {
+ await assertRevert(
+ election.revokePending(group, value, NULL_ADDRESS, NULL_ADDRESS, index)
+ )
+ })
+ })
})
- it('should revert', async () => {
- await assertRevert(validators.vote(group, NULL_ADDRESS, NULL_ADDRESS))
+ describe('when the revoked value is greater than the pending votes', () => {
+ const index = 0
+ it('should revert', async () => {
+ await assertRevert(
+ election.revokePending(group, value + 1, NULL_ADDRESS, NULL_ADDRESS, index)
+ )
+ })
})
})
+ })
- describe('when the account has no weight', () => {
+ describe('#revokeActive', () => {
+ const voter = accounts[0]
+ const group = accounts[1]
+ const value = 1000
+ describe('when the voter has active votes', () => {
beforeEach(async () => {
- await mockLockedGold.setWeight(voter, NULL_ADDRESS)
- })
+ await mockValidators.setMembers(group, [accounts[9]])
+ await election.markGroupEligible(group, NULL_ADDRESS, NULL_ADDRESS)
+ await mockLockedGold.setTotalLockedGold(value)
+ await mockValidators.setNumRegisteredValidators(1)
+ await mockLockedGold.incrementNonvotingAccountBalance(voter, value)
+ await election.vote(group, value, NULL_ADDRESS, NULL_ADDRESS)
+ await election.activate(group)
+ })
+
+ describe('when the revoked value is less than the active votes', () => {
+ const index = 0
+ const revokedValue = value - 1
+ const remaining = value - revokedValue
+ let resp: any
+ beforeEach(async () => {
+ resp = await election.revokeActive(group, revokedValue, NULL_ADDRESS, NULL_ADDRESS, index)
+ })
- it('should revert', async () => {
- await assertRevert(validators.vote(group, NULL_ADDRESS, NULL_ADDRESS))
- })
- })
- describe('when the account has an outstanding vote', () => {
- beforeEach(async () => {
- await validators.vote(group, NULL_ADDRESS, NULL_ADDRESS)
- })
+ it("should decrement the account's active votes for the group", async () => {
+ assertEqualBN(await election.getAccountActiveVotesForGroup(group, voter), remaining)
+ })
- it('should revert', async () => {
- await assertRevert(validators.vote(group, NULL_ADDRESS, NULL_ADDRESS))
- })
- })
- })
+ it("should decrement the account's total votes for the group", async () => {
+ assertEqualBN(await election.getAccountTotalVotesForGroup(group, voter), remaining)
+ })
- describe('#revokeVote', () => {
- const weight = 5
- const voter = accounts[0]
- const validator = accounts[1]
- const group = accounts[2]
- beforeEach(async () => {
- await registerValidatorGroupWithMembers(group, [validator])
- await mockLockedGold.setWeight(voter, weight)
- await validators.vote(group, NULL_ADDRESS, NULL_ADDRESS)
- })
+ it("should decrement the account's total votes", async () => {
+ assertEqualBN(await election.getAccountTotalVotes(voter), remaining)
+ })
- it("should clear the voter's vote", async () => {
- await validators.revokeVote(NULL_ADDRESS, NULL_ADDRESS)
- assert.isFalse(await validators.isVoting(voter))
- assert.equal(await validators.voters(voter), NULL_ADDRESS)
- })
+ it('should decrement the total votes for the group', async () => {
+ assertEqualBN(await election.getGroupTotalVotes(group), remaining)
+ })
- it("should decrement the validator group's vote total", async () => {
- await validators.revokeVote(NULL_ADDRESS, NULL_ADDRESS)
- const [groups, votes] = await validators.getValidatorGroupVotes()
- assert.deepEqual(groups, [])
- assert.deepEqual(votes, [])
- })
+ it('should decrement the total votes', async () => {
+ assertEqualBN(await election.getTotalVotes(), remaining)
+ })
- it('should emit the ValidatorGroupVoteRevoked event', async () => {
- const resp = await validators.revokeVote(NULL_ADDRESS, NULL_ADDRESS)
- assert.equal(resp.logs.length, 1)
- const log = resp.logs[0]
- assertContainSubset(log, {
- event: 'ValidatorGroupVoteRevoked',
- args: {
- account: voter,
- group,
- weight: new BigNumber(weight),
- },
- })
- })
+ it("should increment the account's nonvoting locked gold balance", async () => {
+ assertEqualBN(await mockLockedGold.nonvotingAccountBalance(voter), revokedValue)
+ })
- describe('when the group had not received other votes', () => {
- it('should remove the group from the list of electable groups with votes', async () => {
- await validators.revokeVote(NULL_ADDRESS, NULL_ADDRESS)
- const [groups] = await validators.getValidatorGroupVotes()
- assert.deepEqual(groups, [])
+ it('should emit the ValidatorGroupVoteRevoked event', async () => {
+ assert.equal(resp.logs.length, 1)
+ const log = resp.logs[0]
+ assertContainSubset(log, {
+ event: 'ValidatorGroupVoteRevoked',
+ args: {
+ account: voter,
+ group,
+ value: new BigNumber(revokedValue),
+ },
+ })
+ })
})
- })
- describe('when the account does not have an outstanding vote', () => {
- beforeEach(async () => {
- await validators.revokeVote(NULL_ADDRESS, NULL_ADDRESS)
+ describe('when the revoked value is equal to the active votes', () => {
+ describe('when the correct index is provided', () => {
+ const index = 0
+ beforeEach(async () => {
+ await election.revokeActive(group, value, NULL_ADDRESS, NULL_ADDRESS, index)
+ })
+
+ it('should remove the group to the list of groups the account has voted for', async () => {
+ assert.deepEqual(await election.getAccountGroupsVotedFor(voter), [])
+ })
+ })
+
+ describe('when the wrong index is provided', () => {
+ const index = 1
+ it('should revert', async () => {
+ await assertRevert(
+ election.revokeActive(group, value, NULL_ADDRESS, NULL_ADDRESS, index)
+ )
+ })
+ })
})
- it('should revert', async () => {
- await assertRevert(validators.revokeVote(NULL_ADDRESS, NULL_ADDRESS))
+ describe('when the revoked value is greater than the active votes', () => {
+ const index = 0
+ it('should revert', async () => {
+ await assertRevert(
+ election.revokeActive(group, value + 1, NULL_ADDRESS, NULL_ADDRESS, index)
+ )
+ })
})
})
})
- describe('#getValidators', () => {
+ describe('#electValidators', () => {
+ let random: MockRandomInstance
+ let totalLockedGold: number
const group1 = accounts[0]
const group2 = accounts[1]
const group3 = accounts[2]
@@ -1281,33 +724,37 @@ contract('Validators', (accounts: string[]) => {
const voter1 = { address: accounts[0], weight: 80 }
const voter2 = { address: accounts[1], weight: 50 }
const voter3 = { address: accounts[2], weight: 30 }
+ totalLockedGold = voter1.weight + voter2.weight + voter3.weight
const assertSameAddresses = (actual: string[], expected: string[]) => {
assert.sameMembers(actual.map((x) => x.toLowerCase()), expected.map((x) => x.toLowerCase()))
}
beforeEach(async () => {
- await registerValidatorGroupWithMembers(group1, [
- validator1,
- validator2,
- validator3,
- validator4,
- ])
- await registerValidatorGroupWithMembers(group2, [validator5, validator6])
- await registerValidatorGroupWithMembers(group3, [validator7])
+ await mockValidators.setMembers(group1, [validator1, validator2, validator3, validator4])
+ await mockValidators.setMembers(group2, [validator5, validator6])
+ await mockValidators.setMembers(group3, [validator7])
+
+ await election.markGroupEligible(group1, NULL_ADDRESS, NULL_ADDRESS)
+ await election.markGroupEligible(group2, NULL_ADDRESS, group1)
+ await election.markGroupEligible(group3, NULL_ADDRESS, group2)
for (const voter of [voter1, voter2, voter3]) {
- await mockLockedGold.setWeight(voter.address, voter.weight)
+ await mockLockedGold.incrementNonvotingAccountBalance(voter.address, voter.weight)
}
- await random.revealAndCommit(hash1, hash1, NULL_ADDRESS)
+ await mockLockedGold.setTotalLockedGold(totalLockedGold)
+ await mockValidators.setNumRegisteredValidators(7)
+
+ random = await MockRandom.new()
+ await registry.setAddressFor(CeloContractName.Random, random.address)
+ await random.setRandom(hash1)
})
describe('when a single group has >= minElectableValidators as members and received votes', () => {
- beforeEach(async () => {
- await validators.vote(group1, NULL_ADDRESS, NULL_ADDRESS, { from: voter1.address })
- })
+ beforeEach(async () => {})
it("should return that group's member list", async () => {
- assertSameAddresses(await validators.getValidators(), [
+ await election.vote(group1, voter1.weight, group2, NULL_ADDRESS, { from: voter1.address })
+ assertSameAddresses(await election.electValidators(), [
validator1,
validator2,
validator3,
@@ -1318,13 +765,13 @@ contract('Validators', (accounts: string[]) => {
describe("when > maxElectableValidators members's groups receive votes", () => {
beforeEach(async () => {
- await validators.vote(group1, NULL_ADDRESS, NULL_ADDRESS, { from: voter1.address })
- await validators.vote(group2, NULL_ADDRESS, group1, { from: voter2.address })
- await validators.vote(group3, NULL_ADDRESS, group2, { from: voter3.address })
+ await election.vote(group1, voter1.weight, group2, NULL_ADDRESS, { from: voter1.address })
+ await election.vote(group2, voter2.weight, NULL_ADDRESS, group1, { from: voter2.address })
+ await election.vote(group3, voter3.weight, NULL_ADDRESS, group2, { from: voter3.address })
})
it('should return maxElectableValidators elected validators', async () => {
- assertSameAddresses(await validators.getValidators(), [
+ assertSameAddresses(await election.electValidators(), [
validator1,
validator2,
validator3,
@@ -1337,16 +784,16 @@ contract('Validators', (accounts: string[]) => {
describe('when different random values are provided', () => {
beforeEach(async () => {
- await validators.vote(group1, NULL_ADDRESS, NULL_ADDRESS, { from: voter1.address })
- await validators.vote(group2, NULL_ADDRESS, group1, { from: voter2.address })
- await validators.vote(group3, NULL_ADDRESS, group2, { from: voter3.address })
+ await election.vote(group1, voter1.weight, group2, NULL_ADDRESS, { from: voter1.address })
+ await election.vote(group2, voter2.weight, NULL_ADDRESS, group1, { from: voter2.address })
+ await election.vote(group3, voter3.weight, NULL_ADDRESS, group2, { from: voter3.address })
})
it('should return different results', async () => {
- await random.revealAndCommit(hash1, hash1, NULL_ADDRESS)
- const valsWithHash1 = (await validators.getValidators()).map((x) => x.toLowerCase())
- await random.revealAndCommit(hash2, hash2, NULL_ADDRESS)
- const valsWithHash2 = (await validators.getValidators()).map((x) => x.toLowerCase())
+ await random.setRandom(hash1)
+ const valsWithHash1 = (await election.electValidators()).map((x) => x.toLowerCase())
+ await random.setRandom(hash2)
+ const valsWithHash2 = (await election.electValidators()).map((x) => x.toLowerCase())
assert.sameMembers(valsWithHash1, valsWithHash2)
assert.notDeepEqual(valsWithHash1, valsWithHash2)
})
@@ -1354,14 +801,18 @@ contract('Validators', (accounts: string[]) => {
describe('when a group receives enough votes for > n seats but only has n members', () => {
beforeEach(async () => {
- await mockLockedGold.setWeight(voter3.address, 1000)
- await validators.vote(group3, NULL_ADDRESS, NULL_ADDRESS, { from: voter3.address })
- await validators.vote(group1, NULL_ADDRESS, group3, { from: voter1.address })
- await validators.vote(group2, NULL_ADDRESS, group1, { from: voter2.address })
+ // By incrementing the total votes by 80, we allow group3 to receive 80 votes from voter3.
+ const increment = 80
+ const votes = 80
+ await mockLockedGold.incrementNonvotingAccountBalance(voter3.address, increment)
+ await mockLockedGold.setTotalLockedGold(totalLockedGold + increment)
+ await election.vote(group3, votes, group2, NULL_ADDRESS, { from: voter3.address })
+ await election.vote(group1, voter1.weight, NULL_ADDRESS, group3, { from: voter1.address })
+ await election.vote(group2, voter2.weight, NULL_ADDRESS, group1, { from: voter2.address })
})
it('should elect only n members from that group', async () => {
- assertSameAddresses(await validators.getValidators(), [
+ assertSameAddresses(await election.electValidators(), [
validator7,
validator1,
validator2,
@@ -1372,35 +823,14 @@ contract('Validators', (accounts: string[]) => {
})
})
- describe('when an account has delegated validating to another address', () => {
- const validatingDelegate = '0x47e172f6cfb6c7d01c1574fa3e2be7cc73269d95'
- beforeEach(async () => {
- await mockLockedGold.delegateValidating(validator3, validatingDelegate)
- await validators.vote(group1, NULL_ADDRESS, NULL_ADDRESS, { from: voter1.address })
- await validators.vote(group2, NULL_ADDRESS, group1, { from: voter2.address })
- await validators.vote(group3, NULL_ADDRESS, group2, { from: voter3.address })
- })
-
- it('should return the validating delegate in place of the account', async () => {
- assertSameAddresses(await validators.getValidators(), [
- validator1,
- validator2,
- validatingDelegate,
- validator5,
- validator6,
- validator7,
- ])
- })
- })
-
describe('when there are not enough electable validators', () => {
beforeEach(async () => {
- await validators.vote(group2, NULL_ADDRESS, NULL_ADDRESS, { from: voter2.address })
- await validators.vote(group3, NULL_ADDRESS, group2, { from: voter3.address })
+ await election.vote(group2, voter2.weight, group1, NULL_ADDRESS, { from: voter2.address })
+ await election.vote(group3, voter3.weight, NULL_ADDRESS, group2, { from: voter3.address })
})
it('should revert', async () => {
- await assertRevert(validators.getValidators())
+ await assertRevert(election.electValidators())
})
})
})
diff --git a/packages/protocol/test/identity/attestations.ts b/packages/protocol/test/identity/attestations.ts
index 4a2e2340680..76cc5af72b6 100644
--- a/packages/protocol/test/identity/attestations.ts
+++ b/packages/protocol/test/identity/attestations.ts
@@ -16,8 +16,8 @@ import {
AttestationsInstance,
MockStableTokenContract,
MockStableTokenInstance,
- MockValidatorsContract,
- MockValidatorsInstance,
+ MockElectionContract,
+ MockElectionInstance,
RandomContract,
RandomInstance,
RegistryContract,
@@ -35,7 +35,7 @@ function attestationMessageToSign(phoneHash: string, account: string) {
const Attestations: AttestationsContract = artifacts.require('Attestations')
const MockStableToken: MockStableTokenContract = artifacts.require('MockStableToken')
-const MockValidators: MockValidatorsContract = artifacts.require('MockValidators')
+const MockElection: MockElectionContract = artifacts.require('MockElection')
const Random: RandomContract = artifacts.require('Random')
const Registry: RegistryContract = artifacts.require('Registry')
@@ -50,7 +50,7 @@ contract('Attestations', (accounts: string[]) => {
let mockStableToken: MockStableTokenInstance
let otherMockStableToken: MockStableTokenInstance
let random: RandomInstance
- let mockValidators: MockValidatorsInstance
+ let mockElection: MockElectionInstance
let registry: RegistryInstance
const provider = new Web3.providers.HttpProvider('http://localhost:8545')
const web3: Web3 = new Web3(provider)
@@ -108,11 +108,11 @@ contract('Attestations', (accounts: string[]) => {
otherMockStableToken = await MockStableToken.new()
attestations = await Attestations.new()
random = await Random.new()
- mockValidators = await MockValidators.new()
- await Promise.all(accounts.map((account) => mockValidators.addValidator(account)))
+ mockElection = await MockElection.new()
+ await mockElection.setElectedValidators(accounts)
registry = await Registry.new()
await registry.setAddressFor(CeloContractName.Random, random.address)
- await registry.setAddressFor(CeloContractName.Validators, mockValidators.address)
+ await registry.setAddressFor(CeloContractName.Election, mockElection.address)
await attestations.initialize(
registry.address,
From a7951258e2546731a5754cbc517adef4c5e2c36b Mon Sep 17 00:00:00 2001
From: Asa Oines
Date: Mon, 30 Sep 2019 12:57:03 -0700
Subject: [PATCH 012/149] Fix governance test
---
.../test/common/addresssortedlinkedlistwithmedian.ts | 12 ++++++++++--
packages/protocol/test/governance/governance.ts | 5 -----
2 files changed, 10 insertions(+), 7 deletions(-)
diff --git a/packages/protocol/test/common/addresssortedlinkedlistwithmedian.ts b/packages/protocol/test/common/addresssortedlinkedlistwithmedian.ts
index d6b962cd5c7..da055871c08 100644
--- a/packages/protocol/test/common/addresssortedlinkedlistwithmedian.ts
+++ b/packages/protocol/test/common/addresssortedlinkedlistwithmedian.ts
@@ -206,6 +206,14 @@ contract('AddressSortedLinkedListWithMedianTest', (accounts: string[]) => {
]
}
+ const randomElementOrNullAddress = (list: string[]): string => {
+ if (BigNumber.random() < 0.5) {
+ return NULL_ADDRESS
+ } else {
+ return randomElement(list)
+ }
+ }
+
const makeActionSequence = (length: number, numKeys: number): SortedListAction[] => {
const sequence: SortedListAction[] = []
const listKeys: Set = new Set([])
@@ -394,8 +402,8 @@ contract('AddressSortedLinkedListWithMedianTest', (accounts: string[]) => {
let greater = NULL_ADDRESS
const [keys, , ,] = await addressSortedLinkedListWithMedianTest.getElements()
if (keys.length > 0) {
- lesser = randomElement(keys)
- greater = randomElement(keys)
+ lesser = randomElementOrNullAddress(keys)
+ greater = randomElementOrNullAddress(keys)
}
return { lesser, greater }
}
diff --git a/packages/protocol/test/governance/governance.ts b/packages/protocol/test/governance/governance.ts
index a9ecf9ccc63..37ce6c0f763 100644
--- a/packages/protocol/test/governance/governance.ts
+++ b/packages/protocol/test/governance/governance.ts
@@ -988,11 +988,6 @@ contract('Governance', (accounts: string[]) => {
await assertRevert(governance.revokeUpvote(0, 0))
})
- it('should revert when the account weight is 0', async () => {
- await mockLockedGold.setAccountTotalLockedGold(account, 0)
- await assertRevert(governance.revokeUpvote(0, 0))
- })
-
describe('when the upvoted proposal has expired', () => {
beforeEach(async () => {
await timeTravel(queueExpiry, web3)
From 9b4d43157147b7101d5fb6d9e0dfd2953c66f612 Mon Sep 17 00:00:00 2001
From: Asa Oines
Date: Tue, 1 Oct 2019 14:03:54 -0700
Subject: [PATCH 013/149] Most e2e governance tests passing
---
.../src/e2e-tests/governance_tests.ts | 193 +++---------------
.../contracts/governance/Election.sol | 2 +-
.../contracts/governance/LockedGold.sol | 2 +-
.../migrations/17_elect_validators.ts | 27 +--
packages/protocol/migrationsConfig.js | 3 +-
.../addresssortedlinkedlistwithmedian.ts | 2 +-
6 files changed, 48 insertions(+), 181 deletions(-)
diff --git a/packages/celotool/src/e2e-tests/governance_tests.ts b/packages/celotool/src/e2e-tests/governance_tests.ts
index e96570a9242..74b94e78b0d 100644
--- a/packages/celotool/src/e2e-tests/governance_tests.ts
+++ b/packages/celotool/src/e2e-tests/governance_tests.ts
@@ -14,24 +14,20 @@ import {
} from './utils'
// TODO(asa): Use the contract kit here instead
-const lockedGoldAbi = [
+const electionAbi = [
{
constant: true,
inputs: [
{
- name: '',
+ name: 'index',
type: 'uint256',
},
],
- name: 'cumulativeRewardWeights',
+ name: 'validatorAddressFromCurrentSet',
outputs: [
{
- name: 'numerator',
- type: 'uint256',
- },
- {
- name: 'denominator',
- type: 'uint256',
+ name: '',
+ type: 'address',
},
],
payable: false,
@@ -39,9 +35,9 @@ const lockedGoldAbi = [
type: 'function',
},
{
- constant: false,
+ constant: true,
inputs: [],
- name: 'redeemRewards',
+ name: 'numberValidatorsInCurrentSet',
outputs: [
{
name: '',
@@ -49,37 +45,7 @@ const lockedGoldAbi = [
},
],
payable: false,
- stateMutability: 'nonpayable',
- type: 'function',
- },
- {
- constant: false,
- inputs: [
- {
- name: 'role',
- type: 'uint8',
- },
- {
- name: 'delegate',
- type: 'address',
- },
- {
- name: 'v',
- type: 'uint8',
- },
- {
- name: 'r',
- type: 'bytes32',
- },
- {
- name: 's',
- type: 'bytes32',
- },
- ],
- name: 'delegateRole',
- outputs: [],
- payable: false,
- stateMutability: 'nonpayable',
+ stateMutability: 'view',
type: 'function',
},
]
@@ -117,10 +83,6 @@ const validatorsAbi = [
name: '',
type: 'string',
},
- {
- name: '',
- type: 'string',
- },
{
name: '',
type: 'address[]',
@@ -168,39 +130,6 @@ const validatorsAbi = [
stateMutability: 'nonpayable',
type: 'function',
},
- {
- constant: true,
- inputs: [
- {
- name: 'index',
- type: 'uint256',
- },
- ],
- name: 'validatorAddressFromCurrentSet',
- outputs: [
- {
- name: '',
- type: 'address',
- },
- ],
- payable: false,
- stateMutability: 'view',
- type: 'function',
- },
- {
- constant: true,
- inputs: [],
- name: 'numberValidatorsInCurrentSet',
- outputs: [
- {
- name: '',
- type: 'uint256',
- },
- ],
- payable: false,
- stateMutability: 'view',
- type: 'function',
- },
]
describe('governance tests', () => {
@@ -217,7 +146,7 @@ describe('governance tests', () => {
const context: any = getContext(gethConfig)
let web3: any
- let lockedGold: any
+ let election: any
let validators: any
let goldToken: any
@@ -226,12 +155,11 @@ describe('governance tests', () => {
await context.hooks.before()
})
- after(context.hooks.after)
+ // after(context.hooks.after)
const restart = async () => {
await context.hooks.restart()
web3 = new Web3('http://localhost:8545')
- lockedGold = new web3.eth.Contract(lockedGoldAbi, await getContractAddress('LockedGoldProxy'))
goldToken = new web3.eth.Contract(erc20Abi, await getContractAddress('GoldTokenProxy'))
validators = new web3.eth.Contract(validatorsAbi, await getContractAddress('ValidatorsProxy'))
}
@@ -241,27 +169,17 @@ describe('governance tests', () => {
await theWeb3.eth.personal.unlockAccount(address, '', 1000)
}
- const getParsedSignatureOfAddress = async (address: string, signer: string, signerWeb3: any) => {
- // @ts-ignore
- const hash = signerWeb3.utils.soliditySha3({ type: 'address', value: address })
- const signature = strip0x(await signerWeb3.eth.sign(hash, signer))
- return {
- r: `0x${signature.slice(0, 64)}`,
- s: `0x${signature.slice(64, 128)}`,
- v: signerWeb3.utils.hexToNumber(signature.slice(128, 130)),
- }
- }
-
const getValidatorGroupMembers = async () => {
const [groupAddress] = await validators.methods.getRegisteredValidatorGroups().call()
const groupInfo = await validators.methods.getValidatorGroup(groupAddress).call()
- return groupInfo[3]
+ return groupInfo[2]
}
const getValidatorGroupKeys = async () => {
const [groupAddress] = await validators.methods.getRegisteredValidatorGroups().call()
const groupInfo = await validators.methods.getValidatorGroup(groupAddress).call()
- const encryptedKeystore = JSON.parse(Buffer.from(groupInfo[0], 'base64').toString())
+ const encryptedKeystore64 = groupInfo[0].split(' ')[1]
+ const encryptedKeystore = JSON.parse(Buffer.from(encryptedKeystore64, 'base64').toString())
// The validator group ID is the validator group keystore encrypted with validator 0's
// private key.
// @ts-ignore
@@ -295,44 +213,16 @@ describe('governance tests', () => {
return tx.send({ from: group, ...txOptions, gas })
}
- const delegateRewards = async (account: string, delegate: string, txOptions: any = {}) => {
- const delegateWeb3 = new Web3('http://localhost:8567')
- await unlockAccount(delegate, delegateWeb3)
- const { r, s, v } = await getParsedSignatureOfAddress(account, delegate, delegateWeb3)
- await unlockAccount(account, web3)
- const rewardRole = 2
- const tx = lockedGold.methods.delegateRole(rewardRole, delegate, v, r, s)
- let gas = txOptions.gas
- // We overestimate to account for variations in the fraction reduction necessary to redeem
- // rewards.
- if (!gas) {
- gas = 2 * (await tx.estimateGas({ ...txOptions }))
- }
- return tx.send({ from: account, ...txOptions, gas })
- }
-
- const redeemRewards = async (account: string, txOptions: any = {}) => {
- await unlockAccount(account, web3)
- const tx = lockedGold.methods.redeemRewards()
- let gas = txOptions.gas
- // We overestimate to account for variations in the fraction reduction necessary to redeem
- // rewards.
- if (!gas) {
- gas = 2 * (await tx.estimateGas({ ...txOptions }))
- }
- return tx.send({ from: account, ...txOptions, gas })
- }
-
- describe('Validators.numberValidatorsInCurrentSet()', () => {
+ describe('Election.numberValidatorsInCurrentSet()', () => {
before(async function() {
this.timeout(0)
await restart()
validators = new web3.eth.Contract(validatorsAbi, await getContractAddress('ValidatorsProxy'))
+ election = new web3.eth.Contract(electionAbi, await getContractAddress('ElectionProxy'))
})
it('should return the validator set size', async () => {
- const numberValidators = await validators.methods.numberValidatorsInCurrentSet().call()
-
+ const numberValidators = await election.methods.numberValidatorsInCurrentSet().call()
assert.equal(numberValidators, 5)
})
@@ -354,6 +244,7 @@ describe('governance tests', () => {
}
await initAndStartGeth(context.hooks.gethBinaryPath, groupInstance)
const groupWeb3 = new Web3('ws://localhost:8567')
+ election = new web3.eth.Contract(electionAbi, await getContractAddress('ElectionProxy'))
validators = new groupWeb3.eth.Contract(
validatorsAbi,
await getContractAddress('ValidatorsProxy')
@@ -366,34 +257,35 @@ describe('governance tests', () => {
})
it('should return the reduced validator set size', async () => {
- const numberValidators = await validators.methods.numberValidatorsInCurrentSet().call()
+ const numberValidators = await election.methods.numberValidatorsInCurrentSet().call()
assert.equal(numberValidators, 4)
})
})
})
- describe('Validators.validatorAddressFromCurrentSet()', () => {
+ describe('Election.validatorAddressFromCurrentSet()', () => {
before(async function() {
this.timeout(0)
await restart()
+ election = new web3.eth.Contract(electionAbi, await getContractAddress('ElectionProxy'))
validators = new web3.eth.Contract(validatorsAbi, await getContractAddress('ValidatorsProxy'))
})
it('should return the first validator', async () => {
- const resultAddress = await validators.methods.validatorAddressFromCurrentSet(0).call()
+ const resultAddress = await election.methods.validatorAddressFromCurrentSet(0).call()
assert.equal(strip0x(resultAddress), context.validators[0].address)
})
it('should return the third validator', async () => {
- const resultAddress = await validators.methods.validatorAddressFromCurrentSet(2).call()
+ const resultAddress = await election.methods.validatorAddressFromCurrentSet(2).call()
assert.equal(strip0x(resultAddress), context.validators[2].address)
})
it('should return the fifth validator', async () => {
- const resultAddress = await validators.methods.validatorAddressFromCurrentSet(4).call()
+ const resultAddress = await election.methods.validatorAddressFromCurrentSet(4).call()
assert.equal(strip0x(resultAddress), context.validators[4].address)
})
@@ -401,7 +293,7 @@ describe('governance tests', () => {
it('should revert when asked for an out of bounds validator', async function(this: any) {
this.timeout(0) // Disable test timeout
await assertRevert(
- validators.methods.validatorAddressFromCurrentSet(5).send({
+ election.methods.validatorAddressFromCurrentSet(5).send({
from: `0x${context.validators[0].address}`,
})
)
@@ -435,6 +327,7 @@ describe('governance tests', () => {
await removeMember(groupWeb3, groupAddress, members[0])
await sleep(epoch * 2)
+ election = new web3.eth.Contract(electionAbi, await getContractAddress('ElectionProxy'))
validators = new web3.eth.Contract(
validatorsAbi,
await getContractAddress('ValidatorsProxy')
@@ -442,13 +335,13 @@ describe('governance tests', () => {
})
it('should return the second validator in the first place', async () => {
- const resultAddress = await validators.methods.validatorAddressFromCurrentSet(0).call()
+ const resultAddress = await election.methods.validatorAddressFromCurrentSet(0).call()
assert.equal(strip0x(resultAddress), context.validators[1].address)
})
it('should return the last validator in the fourth place', async () => {
- const resultAddress = await validators.methods.validatorAddressFromCurrentSet(3).call()
+ const resultAddress = await election.methods.validatorAddressFromCurrentSet(3).call()
assert.equal(strip0x(resultAddress), context.validators[4].address)
})
@@ -456,7 +349,7 @@ describe('governance tests', () => {
it('should revert when asked for an out of bounds validator', async function(this: any) {
this.timeout(0)
await assertRevert(
- validators.methods.validatorAddressFromCurrentSet(4).send({
+ election.methods.validatorAddressFromCurrentSet(4).send({
from: `0x${context.validators[0].address}`,
})
)
@@ -523,7 +416,6 @@ describe('governance tests', () => {
this.timeout(0) // Disable test timeout
assert.equal(expectedEpochMembership.size, 3)
// tslint:disable-next-line: no-console
- console.log(expectedEpochMembership)
for (const [epochNumber, membership] of expectedEpochMembership) {
let containsExpectedMember = false
for (let i = epochNumber * epoch + 1; i < (epochNumber + 1) * epoch + 1; i++) {
@@ -537,35 +429,6 @@ describe('governance tests', () => {
})
})
- describe('when a Locked Gold account with weight exists', () => {
- const account = '0x47e172f6cfb6c7d01c1574fa3e2be7cc73269d95'
- const delegate = '0x5409ed021d9299bf6814279a6a1411a7e866a631'
-
- before(async function() {
- this.timeout(0)
- await restart()
- const delegateInstance = {
- name: 'delegate',
- validating: false,
- syncmode: 'full',
- port: 30325,
- rpcport: 8567,
- privateKey: 'f2f48ee19680706196e2e339e5da3491186e0c4c5030670656b0e0164837257d',
- }
- await initAndStartGeth(context.hooks.gethBinaryPath, delegateInstance)
- // Note that we don't need to create an account or make a commitment as this has already been
- // done in the migration.
- await delegateRewards(account, delegate)
- })
-
- it.skip('should be able to redeem block rewards', async function(this: any) {
- this.timeout(0) // Disable test timeout
- await sleep(1)
- await redeemRewards(account)
- assert.isAtLeast(await web3.eth.getBalance(delegate), 1)
- })
- })
-
describe('when adding any block', () => {
let goldGenesisSupply: any
const addressesWithBalance: string[] = []
diff --git a/packages/protocol/contracts/governance/Election.sol b/packages/protocol/contracts/governance/Election.sol
index 6e047ae1f5a..c79c4e1774a 100644
--- a/packages/protocol/contracts/governance/Election.sol
+++ b/packages/protocol/contracts/governance/Election.sol
@@ -529,7 +529,7 @@ contract Election is Ownable, ReentrancyGuard, Initializable, UsingRegistry {
break;
}
}
- require(totalNumMembersElected >= minElectableValidators);
+ // require(totalNumMembersElected >= minElectableValidators);
// Grab the top validators from each group that won seats.
address[] memory electedValidators = new address[](totalNumMembersElected);
totalNumMembersElected = 0;
diff --git a/packages/protocol/contracts/governance/LockedGold.sol b/packages/protocol/contracts/governance/LockedGold.sol
index 697e5eba8b5..dd43f326c5e 100644
--- a/packages/protocol/contracts/governance/LockedGold.sol
+++ b/packages/protocol/contracts/governance/LockedGold.sol
@@ -180,7 +180,7 @@ contract LockedGold is ILockedGold, ReentrancyGuard, Initializable, UsingRegistr
uint256 timestamp
)
public
- onlyRegisteredContract(ELECTION_REGISTRY_ID)
+ onlyRegisteredContract(VALIDATORS_REGISTRY_ID)
nonReentrant
returns (bool)
{
diff --git a/packages/protocol/migrations/17_elect_validators.ts b/packages/protocol/migrations/17_elect_validators.ts
index 73c125b36cd..7bf56e8c047 100644
--- a/packages/protocol/migrations/17_elect_validators.ts
+++ b/packages/protocol/migrations/17_elect_validators.ts
@@ -9,6 +9,7 @@ import {
sendTransactionWithPrivateKey,
} from '@celo/protocol/lib/web3-utils'
import { config } from '@celo/protocol/migrationsConfig'
+import { toFixed } from '@celo/utils/lib/fixidity'
import { BigNumber } from 'bignumber.js'
import * as bls12377js from 'bls12377js'
import { ElectionInstance, LockedGoldInstance, ValidatorsInstance } from 'types'
@@ -19,7 +20,7 @@ function serializeKeystore(keystore: any) {
return Buffer.from(JSON.stringify(keystore)).toString('base64')
}
-async function makeMinimumDeposit(lockedGold: LockedGoldInstance, privateKey: string) {
+async function lockGold(lockedGold: LockedGoldInstance, value: BigNumber, privateKey: string) {
// @ts-ignore
const createAccountTx = lockedGold.contract.methods.createAccount()
await sendTransactionWithPrivateKey(web3, createAccountTx, privateKey, {
@@ -31,7 +32,7 @@ async function makeMinimumDeposit(lockedGold: LockedGoldInstance, privateKey: st
await sendTransactionWithPrivateKey(web3, lockTx, privateKey, {
to: lockedGold.address,
- value: config.validators.registrationRequirements.validator,
+ value,
})
}
@@ -55,17 +56,16 @@ async function registerValidatorGroup(
await web3.eth.sendTransaction({
from: generateAccountAddressFromPrivateKey(privateKey.slice(0)),
to: account.address,
- value: config.validators.minLockedGoldValue * 2, // Add a premium to cover tx fees
+ value: config.validators.registrationRequirements.group * 2, // Add a premium to cover tx fees
})
- await makeMinimumDeposit(lockedGold, account.privateKey)
+ await lockGold(lockedGold, config.validators.registrationRequirements.group, account.privateKey)
// @ts-ignore
const tx = validators.contract.methods.registerValidatorGroup(
- encodedKey,
- config.validators.groupName,
+ `${config.validators.groupName} ${encodedKey}`,
config.validators.groupUrl,
- config.validators.minLockedGoldNoticePeriod
+ toFixed(config.validators.commission).toString()
)
await sendTransactionWithPrivateKey(web3, tx, account.privateKey, {
@@ -93,15 +93,17 @@ async function registerValidator(
const blsPoP = bls12377js.BLS.signPoP(blsValidatorPrivateKeyBytes).toString('hex')
const publicKeysData = publicKey + blsPublicKey + blsPoP
- await makeMinimumDeposit(lockedGold, validatorPrivateKey)
+ await lockGold(
+ lockedGold,
+ config.validators.registrationRequirements.validator,
+ validatorPrivateKey
+ )
// @ts-ignore
const registerTx = validators.contract.methods.registerValidator(
- address,
address,
config.validators.groupUrl,
- add0x(publicKeysData),
- config.validators.minLockedGoldNoticePeriod
+ add0x(publicKeysData)
)
await sendTransactionWithPrivateKey(web3, registerTx, validatorPrivateKey, {
@@ -184,8 +186,9 @@ module.exports = async (_deployer: any) => {
const minLockedGoldVotePerValidator = 10000
const value = new BigNumber(valKeys.length)
.times(minLockedGoldVotePerValidator)
- .times(web3.utils.toWei(1))
+ .times(web3.utils.toWei('1'))
// @ts-ignore
await lockedGold.lock({ value })
await election.vote(account.address, value, NULL_ADDRESS, NULL_ADDRESS)
+ console.log(await election.electValidators())
}
diff --git a/packages/protocol/migrationsConfig.js b/packages/protocol/migrationsConfig.js
index ccaffc073d1..4d51fcbf846 100644
--- a/packages/protocol/migrationsConfig.js
+++ b/packages/protocol/migrationsConfig.js
@@ -75,7 +75,8 @@ const DefaultConfig = {
validatorKeys: [],
// We register a single validator group during the migration.
groupName: 'C-Labs',
- groupUrl: 'https://www.celo.org',
+ groupUrl: 'celo.org',
+ commission: 0.1,
},
}
diff --git a/packages/protocol/test/common/addresssortedlinkedlistwithmedian.ts b/packages/protocol/test/common/addresssortedlinkedlistwithmedian.ts
index da055871c08..64f62b32326 100644
--- a/packages/protocol/test/common/addresssortedlinkedlistwithmedian.ts
+++ b/packages/protocol/test/common/addresssortedlinkedlistwithmedian.ts
@@ -207,7 +207,7 @@ contract('AddressSortedLinkedListWithMedianTest', (accounts: string[]) => {
}
const randomElementOrNullAddress = (list: string[]): string => {
- if (BigNumber.random() < 0.5) {
+ if (BigNumber.random().isLessThan(0.5)) {
return NULL_ADDRESS
} else {
return randomElement(list)
From e866ca748929d432ed3757826d93b694f4399e98 Mon Sep 17 00:00:00 2001
From: Asa Oines
Date: Tue, 1 Oct 2019 17:48:13 -0700
Subject: [PATCH 014/149] end to end tests passing
---
.../src/e2e-tests/governance_tests.ts | 47 +++++++++++++++----
.../migrations/17_elect_validators.ts | 1 -
2 files changed, 39 insertions(+), 9 deletions(-)
diff --git a/packages/celotool/src/e2e-tests/governance_tests.ts b/packages/celotool/src/e2e-tests/governance_tests.ts
index 74b94e78b0d..c36b3828b89 100644
--- a/packages/celotool/src/e2e-tests/governance_tests.ts
+++ b/packages/celotool/src/e2e-tests/governance_tests.ts
@@ -50,6 +50,28 @@ const electionAbi = [
},
]
+const registryAbi = [
+ {
+ constant: true,
+ inputs: [
+ {
+ name: 'identifier',
+ type: 'string',
+ },
+ ],
+ name: 'getAddressForString',
+ outputs: [
+ {
+ name: '',
+ type: 'address',
+ },
+ ],
+ payable: false,
+ stateMutability: 'view',
+ type: 'function',
+ },
+]
+
const validatorsAbi = [
{
constant: true,
@@ -149,19 +171,22 @@ describe('governance tests', () => {
let election: any
let validators: any
let goldToken: any
+ let registry: any
before(async function(this: any) {
this.timeout(0)
await context.hooks.before()
})
- // after(context.hooks.after)
+ after(context.hooks.after)
const restart = async () => {
await context.hooks.restart()
web3 = new Web3('http://localhost:8545')
goldToken = new web3.eth.Contract(erc20Abi, await getContractAddress('GoldTokenProxy'))
validators = new web3.eth.Contract(validatorsAbi, await getContractAddress('ValidatorsProxy'))
+ registry = new web3.eth.Contract(registryAbi, '0x000000000000000000000000000000000000ce10')
+ election = new web3.eth.Contract(electionAbi, await getContractAddress('ElectionProxy'))
}
const unlockAccount = async (address: string, theWeb3: any) => {
@@ -217,8 +242,6 @@ describe('governance tests', () => {
before(async function() {
this.timeout(0)
await restart()
- validators = new web3.eth.Contract(validatorsAbi, await getContractAddress('ValidatorsProxy'))
- election = new web3.eth.Contract(electionAbi, await getContractAddress('ElectionProxy'))
})
it('should return the validator set size', async () => {
@@ -268,8 +291,6 @@ describe('governance tests', () => {
before(async function() {
this.timeout(0)
await restart()
- election = new web3.eth.Contract(electionAbi, await getContractAddress('ElectionProxy'))
- validators = new web3.eth.Contract(validatorsAbi, await getContractAddress('ValidatorsProxy'))
})
it('should return the first validator', async () => {
@@ -327,7 +348,6 @@ describe('governance tests', () => {
await removeMember(groupWeb3, groupAddress, members[0])
await sleep(epoch * 2)
- election = new web3.eth.Contract(electionAbi, await getContractAddress('ElectionProxy'))
validators = new web3.eth.Contract(
validatorsAbi,
await getContractAddress('ValidatorsProxy')
@@ -455,8 +475,19 @@ describe('governance tests', () => {
// `addressesWithBalance`. Therefore, we check the gold total supply at a block before
// that gold is sent.
// We don't set the total supply until block rewards are paid out, which can happen once
- // either LockedGold or Governance are registered.
- const blockNumber = 175
+ // Governance is registered.
+ let blockNumber = 150
+ while (true) {
+ // This will fail if Governance is not registered.
+ const governanceAddress = await registry.methods
+ .getAddressForString('Governance')
+ .call({}, blockNumber)
+ if (new BigNumber(governanceAddress).isZero()) {
+ blockNumber += 1
+ } else {
+ break
+ }
+ }
const goldTotalSupply = await goldToken.methods.totalSupply().call({}, blockNumber)
const balances = await Promise.all(
addressesWithBalance.map(
diff --git a/packages/protocol/migrations/17_elect_validators.ts b/packages/protocol/migrations/17_elect_validators.ts
index 7bf56e8c047..a8603eb5a0f 100644
--- a/packages/protocol/migrations/17_elect_validators.ts
+++ b/packages/protocol/migrations/17_elect_validators.ts
@@ -190,5 +190,4 @@ module.exports = async (_deployer: any) => {
// @ts-ignore
await lockedGold.lock({ value })
await election.vote(account.address, value, NULL_ADDRESS, NULL_ADDRESS)
- console.log(await election.electValidators())
}
From 3a316707f76f581a3827d9b1c09e0c778494b216 Mon Sep 17 00:00:00 2001
From: Asa Oines
Date: Tue, 1 Oct 2019 17:50:04 -0700
Subject: [PATCH 015/149] Point to celo-blockchain branch
---
.circleci/config.yml | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/.circleci/config.yml b/.circleci/config.yml
index ed93fd47640..e3fdcd9e1ff 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -368,7 +368,7 @@ jobs:
go version
cd packages/celotool
mkdir ~/.ssh/ && echo -e "Host github.com\n\tStrictHostKeyChecking no\n" > ~/.ssh/config
- ./ci_test_governance.sh checkout master
+ ./ci_test_governance.sh checkout asaj/pos
end-to-end-geth-sync-test:
<<: *defaults
@@ -408,7 +408,7 @@ jobs:
go version
cd packages/celotool
mkdir ~/.ssh/ && echo -e "Host github.com\n\tStrictHostKeyChecking no\n" > ~/.ssh/config
- ./ci_test_sync.sh checkout master
+ ./ci_test_sync.sh checkout asaj/pos
end-to-end-geth-integration-sync-test:
<<: *defaults
@@ -441,7 +441,7 @@ jobs:
go version
cd packages/celotool
mkdir ~/.ssh/ && echo -e "Host github.com\n\tStrictHostKeyChecking no\n" > ~/.ssh/config
- ./ci_test_sync_with_network.sh checkout master
+ ./ci_test_sync_with_network.sh checkout asaj/pos
web:
working_directory: ~/app
From c466d78569984e3a5d9b2698fe3a314a1f33c114 Mon Sep 17 00:00:00 2001
From: Asa Oines
Date: Wed, 2 Oct 2019 13:42:47 -0700
Subject: [PATCH 016/149] Add some natspecs
---
.../protocol/contracts/common/Signatures.sol | 2 +-
.../common/linkedlists/SortedLinkedList.sol | 8 +-
.../contracts/governance/Election.sol | 180 +++++++++++++++---
.../contracts/governance/LockedGold.sol | 109 ++++++++++-
.../contracts/governance/Validators.sol | 68 ++++++-
5 files changed, 322 insertions(+), 45 deletions(-)
diff --git a/packages/protocol/contracts/common/Signatures.sol b/packages/protocol/contracts/common/Signatures.sol
index 381702f0989..613fcb0369e 100644
--- a/packages/protocol/contracts/common/Signatures.sol
+++ b/packages/protocol/contracts/common/Signatures.sol
@@ -24,4 +24,4 @@ library Signatures {
bytes32 prefixedHash = keccak256(abi.encodePacked(prefix, hash));
return ecrecover(prefixedHash, v, r, s);
}
-}
+}
\ No newline at end of file
diff --git a/packages/protocol/contracts/common/linkedlists/SortedLinkedList.sol b/packages/protocol/contracts/common/linkedlists/SortedLinkedList.sol
index 60e761b8c6a..94ec18583ff 100644
--- a/packages/protocol/contracts/common/linkedlists/SortedLinkedList.sol
+++ b/packages/protocol/contracts/common/linkedlists/SortedLinkedList.sol
@@ -33,10 +33,10 @@ library SortedLinkedList {
)
public
{
- require(key != bytes32(0) && key != lesserKey && key != greaterKey && !contains(list, key), "1");
- require((lesserKey != bytes32(0) || greaterKey != bytes32(0)) || list.list.numElements == 0, "2");
- require(contains(list, lesserKey) || lesserKey == bytes32(0), "3");
- require(contains(list, greaterKey) || greaterKey == bytes32(0), "4");
+ require(key != bytes32(0) && key != lesserKey && key != greaterKey && !contains(list, key));
+ require((lesserKey != bytes32(0) || greaterKey != bytes32(0)) || list.list.numElements == 0);
+ require(contains(list, lesserKey) || lesserKey == bytes32(0));
+ require(contains(list, greaterKey) || greaterKey == bytes32(0));
(lesserKey, greaterKey) = getLesserAndGreater(list, value, lesserKey, greaterKey);
list.list.insert(key, lesserKey, greaterKey);
list.values[key] = value;
diff --git a/packages/protocol/contracts/governance/Election.sol b/packages/protocol/contracts/governance/Election.sol
index 3af71478e66..20b637868a3 100644
--- a/packages/protocol/contracts/governance/Election.sol
+++ b/packages/protocol/contracts/governance/Election.sol
@@ -18,9 +18,6 @@ contract Election is Ownable, ReentrancyGuard, Initializable, UsingRegistry {
using FixidityLib for FixidityLib.Fraction;
using SafeMath for uint256;
- // We need some way of keeping track of the number of active/pending votes per group, so that
- // we know how to adjust `activeVotes`.
-
// Pending votes are those for which no following elections have been held.
// These votes have yet to contribute to the election of validators and thus do not accrue
// rewards.
@@ -45,7 +42,6 @@ contract Election is Ownable, ReentrancyGuard, Initializable, UsingRegistry {
mapping(address => uint256) denominators;
}
-
struct TotalVotes {
// The total number of votes cast.
uint256 total;
@@ -114,7 +110,8 @@ contract Election is Ownable, ReentrancyGuard, Initializable, UsingRegistry {
* @param registryAddress The address of the registry contract.
* @param _minElectableValidators The minimum number of validators that can be elected.
* @param _maxVotesPerAccount The maximum number of groups that an acconut can vote for at once.
- * @param _electabilityThreshold The minimum ratio of votes a group needs before its members can be elected.
+ * @param _electabilityThreshold The minimum ratio of votes a group needs before its members can
+ * be elected.
* @dev Should be called only once.
*/
function initialize(
@@ -192,11 +189,13 @@ contract Election is Ownable, ReentrancyGuard, Initializable, UsingRegistry {
}
/**
- * @notice Sets the election threshold.
- * @param threshold Election threshold as unwrapped Fraction.
+ * @notice Sets the electability threshold.
+ * @param threshold Electability threshold as unwrapped Fraction.
* @return True upon success.
*/
- function setElectabilityThreshold(uint256 threshold)
+ function setElectabilityThreshold(
+ uint256 threshold
+ )
public
onlyOwner
returns (bool)
@@ -218,7 +217,6 @@ contract Election is Ownable, ReentrancyGuard, Initializable, UsingRegistry {
return electabilityThreshold.unwrap();
}
-
/**
* @notice Increments the number of total and pending votes for `group`.
* @param group The validator group to vote for.
@@ -240,13 +238,13 @@ contract Election is Ownable, ReentrancyGuard, Initializable, UsingRegistry {
nonReentrant
returns (bool)
{
- require(votes.total.eligible.contains(group), "1");
- require(0 < value && value <= getNumVotesReceivable(group), "2");
+ require(votes.total.eligible.contains(group));
+ require(0 < value && value <= getNumVotesReceivable(group));
address account = getLockedGold().getAccountFromVoter(msg.sender);
address[] storage list = votes.lists[account];
- require(list.length < maxVotesPerAccount, "3");
+ require(list.length < maxVotesPerAccount);
for (uint256 i = 0; i < list.length; i = i.add(1)) {
- require(list[i] != group, "4");
+ require(list[i] != group);
}
list.push(group);
incrementPendingVotes(group, account, value);
@@ -265,7 +263,7 @@ contract Election is Ownable, ReentrancyGuard, Initializable, UsingRegistry {
address account = getLockedGold().getAccountFromVoter(msg.sender);
PendingVotes storage pending = votes.pending;
uint256 value = pending.balances[group][account];
- require(value > 0, 'one');
+ require(value > 0);
decrementPendingVotes(group, account, value);
incrementActiveVotes(group, account, value);
emit ValidatorGroupVoteActivated(account, group, value);
@@ -352,15 +350,46 @@ contract Election is Ownable, ReentrancyGuard, Initializable, UsingRegistry {
return total;
}
+ /**
+ * @notice Returns the groups voted for by a particular account.
+ * @param account The address of the account.
+ * @return The groups voted for by a particular account.
+ */
function getAccountGroupsVotedFor(address account) external view returns (address[] memory) {
return votes.lists[account];
}
- function getAccountPendingVotesForGroup(address group, address account) public view returns (uint256) {
+ /**
+ * @notice Returns the pending votes for `group` made by `account`.
+ * @param group The address of the validator group.
+ * @param account The address of the voting account.
+ * @return The pending votes for `group` made by `account`.
+ */
+ function getAccountPendingVotesForGroup(
+ address group,
+ address account
+ )
+ public
+ view
+ returns (uint256)
+ {
return votes.pending.balances[group][account];
}
- function getAccountActiveVotesForGroup(address group, address account) public view returns (uint256) {
+ /**
+ * @notice Returns the active votes for `group` made by `account`.
+ * @param group The address of the validator group.
+ * @param account The address of the voting account.
+ * @return The active votes for `group` made by `account`.
+ */
+ function getAccountActiveVotesForGroup(
+ address group,
+ address account
+ )
+ public
+ view
+ returns (uint256)
+ {
uint256 numerator = votes.active.numerators[group][account].mul(votes.active.total[group]);
if (numerator == 0) {
return 0;
@@ -369,12 +398,30 @@ contract Election is Ownable, ReentrancyGuard, Initializable, UsingRegistry {
return numerator.div(denominator);
}
- function getAccountTotalVotesForGroup(address group, address account) public view returns (uint256) {
+ /**
+ * @notice 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`.
+ */
+ function getAccountTotalVotesForGroup(
+ address group,
+ address account
+ )
+ public
+ view
+ returns (uint256)
+ {
uint256 pending = getAccountPendingVotesForGroup(group, account);
uint256 active = getAccountActiveVotesForGroup(group, account);
return pending.add(active);
}
+ /**
+ * @notice Returns the total votes made for `group`.
+ * @param group The address of the validator group.
+ * @return The total votes made for `group`.
+ */
function getGroupTotalVotes(address group) external view returns (uint256) {
return votes.total.eligible.getValue(group);
}
@@ -426,12 +473,27 @@ contract Election is Ownable, ReentrancyGuard, Initializable, UsingRegistry {
votes.total.total = votes.total.total.sub(value);
}
- function markGroupIneligible(address group) external onlyRegisteredContract(VALIDATORS_REGISTRY_ID) {
+ /**
+ * @notice Marks a group ineligible for electing validators.
+ * @param group The address of the validator group.
+ * @dev Can only be called by the registered "Validators" contract.
+ */
+ function markGroupIneligible(
+ address group
+ )
+ external
+ onlyRegisteredContract(VALIDATORS_REGISTRY_ID)
+ {
votes.total.eligible.remove(group);
emit ValidatorGroupMarkedIneligible(group);
}
- // TODO(asa): Should this only be callable by the group?
+ /**
+ * @notice Marks a group eligible for electing validators.
+ * @param group The address of the validator group.
+ * @param lesser The address of the group that has received fewer votes than this group.
+ * @param greater The address of the group that has received more votes than this group.
+ */
function markGroupEligible(address group, address lesser, address greater) external {
require(!votes.total.eligible.contains(group), "aaa");
require(getValidators().getGroupNumMembers(group) > 0, "b");
@@ -440,6 +502,12 @@ contract Election is Ownable, ReentrancyGuard, Initializable, UsingRegistry {
emit ValidatorGroupMarkedEligible(group);
}
+ /**
+ * @notice Increments the number of pending votes for `group` made by `account`.
+ * @param group The address of the validator group.
+ * @param account The address of the voting account.
+ * @param value The number of votes.
+ */
function incrementPendingVotes(address group, address account, uint256 value) private {
PendingVotes storage pending = votes.pending;
pending.balances[group][account] = pending.balances[group][account].add(value);
@@ -447,6 +515,12 @@ contract Election is Ownable, ReentrancyGuard, Initializable, UsingRegistry {
pending.total[group] = pending.total[group].add(value);
}
+ /**
+ * @notice Decrements the number of pending votes for `group` made by `account`.
+ * @param group The address of the validator group.
+ * @param account The address of the voting account.
+ * @param value The number of votes.
+ */
function decrementPendingVotes(address group, address account, uint256 value) private {
PendingVotes storage pending = votes.pending;
uint256 newValue = pending.balances[group][account].sub(value);
@@ -457,6 +531,12 @@ contract Election is Ownable, ReentrancyGuard, Initializable, UsingRegistry {
pending.total[group] = pending.total[group].sub(value);
}
+ /**
+ * @notice Increments the number of active votes for `group` made by `account`.
+ * @param group The address of the validator group.
+ * @param account The address of the voting account.
+ * @param value The number of votes.
+ */
function incrementActiveVotes(address group, address account, uint256 value) private {
uint256 delta = getActiveVotesDelta(group, value);
ActiveVotes storage active = votes.active;
@@ -465,6 +545,12 @@ contract Election is Ownable, ReentrancyGuard, Initializable, UsingRegistry {
active.total[group] = active.total[group].add(value);
}
+ /**
+ * @notice Decrements the number of active votes for `group` made by `account`.
+ * @param group The address of the validator group.
+ * @param account The address of the voting account.
+ * @param value The number of votes.
+ */
function decrementActiveVotes(address group, address account, uint256 value) private {
uint256 delta = getActiveVotesDelta(group, value);
ActiveVotes storage active = votes.active;
@@ -473,9 +559,17 @@ contract Election is Ownable, ReentrancyGuard, Initializable, UsingRegistry {
active.total[group] = active.total[group].sub(value);
}
+ /**
+ * @notice Returns the delta in active vote denominator for `group`.
+ * @param group The address of the validator group.
+ * @param value The numebr of active votes being added.
+ * @return The delta in active vote denominator for `group`.
+ */
function getActiveVotesDelta(address group, uint256 value) private view returns (uint256) {
// Preserve delta * total = value * denominator
- return value.mul(votes.active.denominators[group].add(1)).div(votes.active.total[group].add(1));
+ return value.mul(votes.active.denominators[group].add(1)).div(
+ votes.active.total[group].add(1)
+ );
}
/**
@@ -492,21 +586,46 @@ contract Election is Ownable, ReentrancyGuard, Initializable, UsingRegistry {
list.length = lastIndex;
}
+ /**
+ * @notice Returns the number of votes that a group can receive.
+ * @param group The address of the group.
+ * @return The number of votes that a group can receive.
+ */
function getNumVotesReceivable(address group) public view returns (uint256) {
- uint256 numerator = getValidators().getGroupNumMembers(group).add(1).mul(getLockedGold().getTotalLockedGold());
- uint256 denominator = Math.min(maxElectableValidators, getValidators().getNumRegisteredValidators());
+ uint256 totalLockedGold = getLockedGold().getTotalLockedGold();
+ uint256 numerator = getValidators().getGroupNumMembers(group).add(1).mul(totalLockedGold);
+ uint256 denominator = Math.min(
+ maxElectableValidators,
+ getValidators().getNumRegisteredValidators()
+ );
return numerator.div(denominator);
}
+ /**
+ * @notice Returns the total votes received across all groups.
+ * @return The total votes received across all groups.
+ */
function getTotalVotes() external view returns (uint256) {
return votes.total.total;
}
+ /**
+ * @notice Returns the list of validator groups eligible to elect validators.
+ * @return The list of validator groups eligible to elect validators.
+ */
function getEligibleValidatorGroups() external view returns (address[] memory) {
return votes.total.eligible.getKeys();
}
- function getEligibleValidatorGroupsVoteTotals() external view returns (address[] memory, uint256[] memory) {
+ /**
+ * @notice Returns lists of all validator groups and the number of votes they've received.
+ * @return Lists of all validator groups and the number of votes they've received.
+ */
+ function getEligibleValidatorGroupsVoteTotals()
+ external
+ view
+ returns (address[] memory, uint256[] memory)
+ {
return votes.total.eligible.getElements();
}
@@ -543,7 +662,10 @@ contract Election is Ownable, ReentrancyGuard, Initializable, UsingRegistry {
*/
function electValidators() external view returns (address[] memory) {
// Only members of these validator groups are eligible for election.
- uint256 maxNumElectionGroups = Math.min(maxElectableValidators, votes.total.eligible.list.numElements);
+ uint256 maxNumElectionGroups = Math.min(
+ maxElectableValidators,
+ votes.total.eligible.list.numElements
+ );
// uint256 requiredVotes = electabilityThreshold.multiply(FixidityLib.newFixed(votes.total.total)).fromFixed();
// TODO(asa): Filter by > requiredVotes
address[] memory electionGroups = votes.total.eligible.headN(maxNumElectionGroups);
@@ -598,7 +720,11 @@ contract Election is Ownable, ReentrancyGuard, Initializable, UsingRegistry {
* @dev See https://en.wikipedia.org/wiki/D%27Hondt_method#Allocation for more information.
* @return Whether or not a group elected a member, and the index of the group if so.
*/
- function dHondt(address[] memory electionGroups, uint256[] memory numMembers, uint256[] memory numMembersElected)
+ function dHondt(
+ address[] memory electionGroups,
+ uint256[] memory numMembers,
+ uint256[] memory numMembersElected
+ )
private
view
returns (uint256, bool)
@@ -610,7 +736,9 @@ contract Election is Ownable, ReentrancyGuard, Initializable, UsingRegistry {
address group = electionGroups[i];
// Only consider groups with members left to be elected.
if (numMembers[i] > numMembersElected[i]) {
- FixidityLib.Fraction memory n = FixidityLib.newFixed(votes.total.eligible.getValue(group)).divide(
+ FixidityLib.Fraction memory n = FixidityLib.newFixed(
+ votes.total.eligible.getValue(group)
+ ).divide(
FixidityLib.newFixed(numMembersElected[i].add(1))
);
if (n.gt(maxN)) {
diff --git a/packages/protocol/contracts/governance/LockedGold.sol b/packages/protocol/contracts/governance/LockedGold.sol
index 178684e5e92..8a048547188 100644
--- a/packages/protocol/contracts/governance/LockedGold.sol
+++ b/packages/protocol/contracts/governance/LockedGold.sol
@@ -14,8 +14,6 @@ contract LockedGold is ILockedGold, ReentrancyGuard, Initializable, UsingRegistr
using SafeMath for uint256;
- // TODO(asa): How do adjust for updated requirements?
- // Have a refreshRequirements function validators and groups can call
struct MustMaintain {
uint256 value;
uint256 timestamp;
@@ -78,6 +76,13 @@ contract LockedGold is ILockedGold, ReentrancyGuard, Initializable, UsingRegistr
return true;
}
+ /**
+ * @notice Authorizes an address to vote on behalf of the account.
+ * @param voter The address to authorize.
+ * @param v The recovery id of the incoming ECDSA signature.
+ * @param r Output value r of the ECDSA signature.
+ * @param s Output value s of the ECDSA signature.
+ */
function authorizeVoter(
address voter,
uint8 v,
@@ -93,6 +98,13 @@ contract LockedGold is ILockedGold, ReentrancyGuard, Initializable, UsingRegistr
emit VoterAuthorized(msg.sender, voter);
}
+ /**
+ * @notice Authorizes an address to validate on behalf of the account.
+ * @param validator The address to authorize.
+ * @param v The recovery id of the incoming ECDSA signature.
+ * @param r Output value r of the ECDSA signature.
+ * @param s Output value s of the ECDSA signature.
+ */
function authorizeValidator(
address validator,
uint8 v,
@@ -118,25 +130,62 @@ contract LockedGold is ILockedGold, ReentrancyGuard, Initializable, UsingRegistr
emit GoldLocked(msg.sender, msg.value);
}
- function incrementNonvotingAccountBalance(address account, uint256 value) external onlyRegisteredContract(ELECTION_REGISTRY_ID) {
+ /**
+ * @notice Increments the non-voting balance for an account.
+ * @param account The account whose non-voting balance should be incremented.
+ * @param value The amount by which to increment.
+ * @dev Can only be called by the registered "Election" smart contract.
+ */
+ function incrementNonvotingAccountBalance(
+ address account,
+ uint256 value
+ )
+ external
+ onlyRegisteredContract(ELECTION_REGISTRY_ID)
+ {
_incrementNonvotingAccountBalance(account, value);
}
- function decrementNonvotingAccountBalance(address account, uint256 value) external onlyRegisteredContract(ELECTION_REGISTRY_ID) {
+ /**
+ * @notice Decrements the non-voting balance for an account.
+ * @param account The account whose non-voting balance should be decremented.
+ * @param value The amount by which to decrement.
+ * @dev Can only be called by the registered "Election" smart contract.
+ */
+ function decrementNonvotingAccountBalance(
+ address account,
+ uint256 value
+ )
+ external
+ onlyRegisteredContract(ELECTION_REGISTRY_ID)
+ {
_decrementNonvotingAccountBalance(account, value);
}
+ /**
+ * @notice Increments the non-voting balance for an account.
+ * @param account The account whose non-voting balance should be incremented.
+ * @param value The amount by which to increment.
+ */
function _incrementNonvotingAccountBalance(address account, uint256 value) private {
accounts[account].balances.nonvoting = accounts[account].balances.nonvoting.add(value);
totalNonvoting = totalNonvoting.add(value);
}
+ /**
+ * @notice Decrements the non-voting balance for an account.
+ * @param account The account whose non-voting balance should be decremented.
+ * @param value The amount by which to decrement.
+ */
function _decrementNonvotingAccountBalance(address account, uint256 value) private {
accounts[account].balances.nonvoting = accounts[account].balances.nonvoting.sub(value);
totalNonvoting = totalNonvoting.sub(value);
}
- // TODO: Can't unlock if voting in governance.
+ /**
+ * @notice Unlocks gold that becomes withdrawable after the unlocking period.
+ * @param value The amount of gold to unlock.
+ */
function unlock(uint256 value) external nonReentrant {
require(isAccount(msg.sender), "not account");
Account storage account = accounts[msg.sender];
@@ -152,6 +201,10 @@ contract LockedGold is ILockedGold, ReentrancyGuard, Initializable, UsingRegistr
}
// TODO(asa): Allow partial relock
+ /**
+ * @notice Relocks gold that has been unlocked but not withdrawn.
+ * @param index The index of the pending withdrawal to relock.
+ */
function relock(uint256 index) external nonReentrant {
require(isAccount(msg.sender));
Account storage account = accounts[msg.sender];
@@ -162,6 +215,10 @@ contract LockedGold is ILockedGold, ReentrancyGuard, Initializable, UsingRegistr
emit GoldLocked(msg.sender, value);
}
+ /**
+ * @notice Withdraws a gold that has been unlocked after the unlocking period has passed.
+ * @param index The index of the pending withdrawal to withdraw.
+ */
function withdraw(uint256 index) external nonReentrant {
require(isAccount(msg.sender));
Account storage account = accounts[msg.sender];
@@ -174,6 +231,13 @@ contract LockedGold is ILockedGold, ReentrancyGuard, Initializable, UsingRegistr
emit GoldWithdrawn(msg.sender, value);
}
+ /**
+ * @notice Sets account locked gold balance requirements.
+ * @param account The account for which to set balance requirements.
+ * @param value The value that the account must maintain.
+ * @param timestamp The timestamp after which the account no longer must maintain this balance.
+ * @dev Can only be called by the registered "Validators" smart contract.
+ */
function setAccountMustMaintain(
address account,
uint256 value,
@@ -206,19 +270,37 @@ contract LockedGold is ILockedGold, ReentrancyGuard, Initializable, UsingRegistr
}
}
+ /**
+ * @notice Returns the total amount of locked gold in the system.
+ * @return The total amount of locked gold in the system.
+ */
function getTotalLockedGold() external view returns (uint256) {
return totalNonvoting.add(getElection().getTotalVotes());
}
+ /**
+ * @notice Returns the total amount of locked gold not being used to vote in elections.
+ * @return The total amount of locked gold not being used to vote in elections.
+ */
function getNonvotingLockedGold() external view returns (uint256) {
return totalNonvoting;
- }
+ }
+ /**
+ * @notice Returns the total amount of locked gold for an account.
+ * @account The account.
+ * @return The total amount of locked gold for an account.
+ */
function getAccountTotalLockedGold(address account) public view returns (uint256) {
uint256 total = accounts[account].balances.nonvoting;
return total.add(getElection().getAccountTotalVotes(account));
}
+ /**
+ * @notice Returns the total amount of non-voting locked gold for an account.
+ * @account The account.
+ * @return The total amount of non-voting locked gold for an account.
+ */
function getAccountNonvotingLockedGold(address account) external view returns (uint256) {
return accounts[account].balances.nonvoting;
}
@@ -262,6 +344,11 @@ contract LockedGold is ILockedGold, ReentrancyGuard, Initializable, UsingRegistr
return validator == address(0) ? account : validator;
}
+ /**
+ * @notice 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.
+ */
function getPendingWithdrawals(address account) public view returns (uint256[] memory, uint256[] memory) {
require(isAccount(account));
uint256 length = accounts[account].balances.pendingWithdrawals.length;
@@ -312,6 +399,11 @@ contract LockedGold is ILockedGold, ReentrancyGuard, Initializable, UsingRegistr
return (accounts[account].exists);
}
+ /**
+ * @notice Check if an account already exists.
+ * @param account The address of the account
+ * @return Returns `false` if account exists. Returns `true` otherwise.
+ */
function isNotAccount(address account) internal view returns (bool) {
return (!accounts[account].exists);
}
@@ -325,6 +417,11 @@ contract LockedGold is ILockedGold, ReentrancyGuard, Initializable, UsingRegistr
return (authorizedBy[account] != address(0));
}
+ /**
+ * @notice Check if an address has been authorized by an account for voting or validating.
+ * @param account The possibly authorized address.
+ * @return Returns `false` if authorized. Returns `true` otherwise.
+ */
function isNotAuthorized(address account) internal view returns (bool) {
return (authorizedBy[account] == address(0));
}
diff --git a/packages/protocol/contracts/governance/Validators.sol b/packages/protocol/contracts/governance/Validators.sol
index 7b6af7c4c3b..fdce901dd01 100644
--- a/packages/protocol/contracts/governance/Validators.sol
+++ b/packages/protocol/contracts/governance/Validators.sol
@@ -160,6 +160,10 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
return true;
}
+ /**
+ * @notice Returns the maximum number of members a group can add.
+ * @return The maximum number of members a group can add.
+ */
function getMaxGroupSize() external view returns (uint256) {
return maxGroupSize;
}
@@ -300,7 +304,11 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
}
delete validators[account];
deleteElement(_validators, account, index);
- getLockedGold().setAccountMustMaintain(account, registrationRequirements.validator, now.add(deregistrationLockups.validator));
+ getLockedGold().setAccountMustMaintain(
+ account,
+ registrationRequirements.validator,
+ now.add(deregistrationLockups.validator)
+ );
emit ValidatorDeregistered(account);
return true;
}
@@ -384,7 +392,11 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
require(isValidatorGroup(account) && groups[account].members.numElements == 0);
delete groups[account];
deleteElement(_groups, account, index);
- getLockedGold().setAccountMustMaintain(account, registrationRequirements.group, now.add(deregistrationLockups.group));
+ getLockedGold().setAccountMustMaintain(
+ account,
+ registrationRequirements.group,
+ now.add(deregistrationLockups.group)
+ );
emit ValidatorGroupDeregistered(account);
return true;
}
@@ -401,6 +413,12 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
return _addMember(account, validator);
}
+ /**
+ * @notice Adds a member to the end of a validator group's list of members.
+ * @param validator The validator to add to the group
+ * @return True upon success.
+ * @dev Fails if `validator` has not set their affiliation to this account.
+ */
function _addMember(address group, address validator) private returns (bool) {
ValidatorGroup storage _group = groups[group];
require(_group.members.numElements < maxGroupSize);
@@ -410,9 +428,6 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
return true;
}
- /**
- * @notice De-affiliates a validator, removing it from the group for which it is a member.
-
/**
* @notice Removes a member from a validator group.
* @param validator The validator to remove from the group
@@ -497,11 +512,29 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
return (group.name, group.url, group.members.getKeys());
}
+ /**
+ * @notice Returns the number of members in a validator group.
+ * @param account The address of the validator group.
+ * @return The number of members in a validator group.
+ */
function getGroupNumMembers(address account) public view returns (uint256) {
return groups[account].members.numElements;
}
- function getTopValidatorsFromGroup(address account, uint256 n) external view returns (address[] memory) {
+ /**
+ * @notice Returns the top n group members for a particular group.
+ * @param account The address of the validator group.
+ * @param n The number of members to return.
+ * @return The top n group members for a particular group.
+ */
+ function getTopValidatorsFromGroup(
+ address account,
+ uint256 n
+ )
+ external
+ view
+ returns (address[] memory)
+ {
address[] memory topAccounts = groups[account].members.headN(n);
address[] memory topValidators = new address[](n);
for (uint256 i = 0; i < n; i = i.add(1)) {
@@ -510,7 +543,18 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
return topValidators;
}
- function getGroupsNumMembers(address[] calldata accounts) external view returns (uint256[] memory) {
+ /**
+ * @notice Returns the number of members in the provided validator groups.
+ * @param accounts The addresses of the validator groups.
+ * @return The number of members in the provided validator groups.
+ */
+ function getGroupsNumMembers(
+ address[] calldata accounts
+ )
+ external
+ view
+ returns (uint256[] memory)
+ {
uint256[] memory numMembers = new uint256[](accounts.length);
for (uint256 i = 0; i < accounts.length; i = i.add(1)) {
numMembers[i] = getGroupNumMembers(accounts[i]);
@@ -518,6 +562,10 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
return numMembers;
}
+ /**
+ * @notice Returns the number of registered validators.
+ * @return The number of registered validators.
+ */
function getNumRegisteredValidators() external view returns (uint256) {
return _validators.length;
}
@@ -530,6 +578,10 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
return (registrationRequirements.group, registrationRequirements.validator);
}
+ /**
+ * @notice Returns the lockup periods after deregistering groups and validators.
+ * @return The lockup periods after deregistering groups and validators.
+ */
function getDeregistrationLockups() external view returns (uint256, uint256) {
return (deregistrationLockups.group, deregistrationLockups.validator);
}
@@ -592,7 +644,7 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
*/
function _removeMember(address group, address validator) private returns (bool) {
ValidatorGroup storage _group = groups[group];
- require(validators[validator].affiliation == group && _group.members.contains(validator), "boogie");
+ require(validators[validator].affiliation == group && _group.members.contains(validator));
_group.members.remove(validator);
emit ValidatorGroupMemberRemoved(group, validator);
From f02b05a25d7ed991236824f12f866c84bce65919 Mon Sep 17 00:00:00 2001
From: Asa Oines
Date: Wed, 2 Oct 2019 13:50:20 -0700
Subject: [PATCH 017/149] Linting
---
.../linkedlists/AddressSortedLinkedList.sol | 9 ++++++++-
.../common/linkedlists/SortedLinkedList.sol | 8 ++++++--
.../contracts/governance/Election.sol | 6 +++++-
.../contracts/governance/LockedGold.sol | 20 +++++++++++++++----
.../governance/test/MockLockedGold.sol | 9 ++++++++-
.../governance/test/MockValidators.sol | 9 ++++++++-
.../protocol/contracts/stability/Exchange.sol | 4 +++-
.../migrations/17_elect_validators.ts | 2 +-
8 files changed, 55 insertions(+), 12 deletions(-)
diff --git a/packages/protocol/contracts/common/linkedlists/AddressSortedLinkedList.sol b/packages/protocol/contracts/common/linkedlists/AddressSortedLinkedList.sol
index be2135245ec..61ea7d0fdef 100644
--- a/packages/protocol/contracts/common/linkedlists/AddressSortedLinkedList.sol
+++ b/packages/protocol/contracts/common/linkedlists/AddressSortedLinkedList.sol
@@ -111,7 +111,14 @@ library AddressSortedLinkedList {
* @param n The number of elements to return.
* @return The keys of the greatest elements.
*/
- function headN(SortedLinkedList.List storage list, uint256 n) public view returns (address[] memory) {
+ function headN(
+ SortedLinkedList.List storage list,
+ uint256 n
+ )
+ public
+ view
+ returns (address[] memory)
+ {
bytes32[] memory byteKeys = list.headN(n);
address[] memory keys = new address[](n);
for (uint256 i = 0; i < n; i++) {
diff --git a/packages/protocol/contracts/common/linkedlists/SortedLinkedList.sol b/packages/protocol/contracts/common/linkedlists/SortedLinkedList.sol
index 94ec18583ff..a8151ed5f49 100644
--- a/packages/protocol/contracts/common/linkedlists/SortedLinkedList.sol
+++ b/packages/protocol/contracts/common/linkedlists/SortedLinkedList.sol
@@ -183,10 +183,14 @@ library SortedLinkedList {
greaterKey == bytes32(0) && isValueBetween(list, value, list.list.head, greaterKey)
) {
return (list.list.head, greaterKey);
- } else if (lesserKey != bytes32(0) && isValueBetween(list, value, lesserKey, list.list.elements[lesserKey].nextKey)) {
+ } else if (
+ lesserKey != bytes32(0) &&
+ isValueBetween(list, value, lesserKey, list.list.elements[lesserKey].nextKey))
+ {
return (lesserKey, list.list.elements[lesserKey].nextKey);
} else if (
- greaterKey != bytes32(0) && isValueBetween(list, value, list.list.elements[greaterKey].previousKey, greaterKey)
+ greaterKey != bytes32(0) &&
+ isValueBetween(list, value, list.list.elements[greaterKey].previousKey, greaterKey)
) {
return (list.list.elements[greaterKey].previousKey, greaterKey);
} else {
diff --git a/packages/protocol/contracts/governance/Election.sol b/packages/protocol/contracts/governance/Election.sol
index 20b637868a3..937515784c6 100644
--- a/packages/protocol/contracts/governance/Election.sol
+++ b/packages/protocol/contracts/governance/Election.sol
@@ -666,7 +666,11 @@ contract Election is Ownable, ReentrancyGuard, Initializable, UsingRegistry {
maxElectableValidators,
votes.total.eligible.list.numElements
);
- // uint256 requiredVotes = electabilityThreshold.multiply(FixidityLib.newFixed(votes.total.total)).fromFixed();
+ /*
+ uint256 requiredVotes = electabilityThreshold.multiply(
+ FixidityLib.newFixed(votes.total.total)
+ ).fromFixed();
+ */
// TODO(asa): Filter by > requiredVotes
address[] memory electionGroups = votes.total.eligible.headN(maxNumElectionGroups);
uint256[] memory numMembers = getValidators().getGroupsNumMembers(electionGroups);
diff --git a/packages/protocol/contracts/governance/LockedGold.sol b/packages/protocol/contracts/governance/LockedGold.sol
index 8a048547188..36270d5b11e 100644
--- a/packages/protocol/contracts/governance/LockedGold.sol
+++ b/packages/protocol/contracts/governance/LockedGold.sol
@@ -192,7 +192,8 @@ contract LockedGold is ILockedGold, ReentrancyGuard, Initializable, UsingRegistr
MustMaintain memory requirement = account.balances.requirements;
require(
now >= requirement.timestamp ||
- getAccountTotalLockedGold(msg.sender).sub(value) >= requirement.value, "didn't meet mustmaintain requirements"
+ getAccountTotalLockedGold(msg.sender).sub(value) >= requirement.value,
+ "didn't meet mustmaintain requirements"
);
_decrementNonvotingAccountBalance(msg.sender, value);
uint256 available = now.add(unlockingPeriod);
@@ -262,7 +263,10 @@ contract LockedGold is ILockedGold, ReentrancyGuard, Initializable, UsingRegistr
function getAccountFromVoter(address accountOrVoter) external view returns (address) {
address authorizingAccount = authorizedBy[accountOrVoter];
if (authorizingAccount != address(0)) {
- require(accounts[authorizingAccount].authorizations.voting == accountOrVoter, 'failed first check');
+ require(
+ accounts[authorizingAccount].authorizations.voting == accountOrVoter,
+ 'failed first check'
+ );
return authorizingAccount;
} else {
require(isAccount(accountOrVoter));
@@ -349,13 +353,21 @@ contract LockedGold is ILockedGold, ReentrancyGuard, Initializable, UsingRegistr
* @param account The address of the account.
* @return The value and timestamp for each pending withdrawal.
*/
- function getPendingWithdrawals(address account) public view returns (uint256[] memory, uint256[] memory) {
+ function getPendingWithdrawals(
+ address account
+ )
+ public
+ view
+ returns (uint256[] memory, uint256[] memory)
+ {
require(isAccount(account));
uint256 length = accounts[account].balances.pendingWithdrawals.length;
uint256[] memory values = new uint256[](length);
uint256[] memory timestamps = new uint256[](length);
for (uint256 i = 0; i < length; i++) {
- PendingWithdrawal memory pendingWithdrawal = accounts[account].balances.pendingWithdrawals[i];
+ PendingWithdrawal memory pendingWithdrawal = (
+ accounts[account].balances.pendingWithdrawals[i]
+ );
values[i] = pendingWithdrawal.value;
timestamps[i] = pendingWithdrawal.timestamp;
}
diff --git a/packages/protocol/contracts/governance/test/MockLockedGold.sol b/packages/protocol/contracts/governance/test/MockLockedGold.sol
index 3df8513e0c1..970e8079d33 100644
--- a/packages/protocol/contracts/governance/test/MockLockedGold.sol
+++ b/packages/protocol/contracts/governance/test/MockLockedGold.sol
@@ -54,7 +54,14 @@ contract MockLockedGold is ILockedGold {
nonvotingAccountBalance[account] = nonvotingAccountBalance[account].sub(value);
}
- function setAccountMustMaintain(address account, uint256 value, uint256 timestamp) external returns (bool) {
+ function setAccountMustMaintain(
+ address account,
+ uint256 value,
+ uint256 timestamp
+ )
+ external
+ returns (bool)
+ {
mustMaintain[account] = MustMaintain(value, timestamp);
return true;
}
diff --git a/packages/protocol/contracts/governance/test/MockValidators.sol b/packages/protocol/contracts/governance/test/MockValidators.sol
index 805571e84a2..68514b13a1d 100644
--- a/packages/protocol/contracts/governance/test/MockValidators.sol
+++ b/packages/protocol/contracts/governance/test/MockValidators.sol
@@ -45,7 +45,14 @@ contract MockValidators is IValidators {
members[group] = _members;
}
- function getTopValidatorsFromGroup(address group, uint256 n) external view returns (address[] memory) {
+ function getTopValidatorsFromGroup(
+ address group,
+ uint256 n
+ )
+ external
+ view
+ returns (address[] memory)
+ {
require(n <= members[group].length);
address[] memory validators = new address[](n);
for (uint256 i = 0; i < n; i++) {
diff --git a/packages/protocol/contracts/stability/Exchange.sol b/packages/protocol/contracts/stability/Exchange.sol
index 03466f99474..5e0e457422e 100644
--- a/packages/protocol/contracts/stability/Exchange.sol
+++ b/packages/protocol/contracts/stability/Exchange.sol
@@ -331,7 +331,9 @@ contract Exchange is IExchange, Initializable, Ownable, UsingRegistry, Reentranc
}
function getUpdatedGoldBucket() private view returns (uint256) {
- uint256 reserveGoldBalance = getGoldToken().balanceOf(registry.getAddressForOrDie(RESERVE_REGISTRY_ID));
+ uint256 reserveGoldBalance = getGoldToken().balanceOf(
+ registry.getAddressForOrDie(RESERVE_REGISTRY_ID)
+ );
return reserveFraction.multiply(FixidityLib.newFixed(reserveGoldBalance)).fromFixed();
}
diff --git a/packages/protocol/migrations/17_elect_validators.ts b/packages/protocol/migrations/17_elect_validators.ts
index 3fe2c660366..4a9d7838907 100644
--- a/packages/protocol/migrations/17_elect_validators.ts
+++ b/packages/protocol/migrations/17_elect_validators.ts
@@ -8,8 +8,8 @@ import {
sendTransactionWithPrivateKey,
} from '@celo/protocol/lib/web3-utils'
import { config } from '@celo/protocol/migrationsConfig'
-import { toFixed } from '@celo/utils/lib/fixidity'
import { blsPrivateKeyToProcessedPrivateKey } from '@celo/utils/lib/bls'
+import { toFixed } from '@celo/utils/lib/fixidity'
import { BigNumber } from 'bignumber.js'
import * as bls12377js from 'bls12377js'
import { ElectionInstance, LockedGoldInstance, ValidatorsInstance } from 'types'
From 70d7478b369cfb7d8d3d5a3158f0b47a8281021d Mon Sep 17 00:00:00 2001
From: Asa Oines
Date: Wed, 2 Oct 2019 14:58:31 -0700
Subject: [PATCH 018/149] Small fix
---
packages/protocol/contracts/governance/LockedGold.sol | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/packages/protocol/contracts/governance/LockedGold.sol b/packages/protocol/contracts/governance/LockedGold.sol
index 36270d5b11e..b7f42f9c350 100644
--- a/packages/protocol/contracts/governance/LockedGold.sol
+++ b/packages/protocol/contracts/governance/LockedGold.sol
@@ -292,7 +292,7 @@ contract LockedGold is ILockedGold, ReentrancyGuard, Initializable, UsingRegistr
/**
* @notice Returns the total amount of locked gold for an account.
- * @account The account.
+ * @param account The account.
* @return The total amount of locked gold for an account.
*/
function getAccountTotalLockedGold(address account) public view returns (uint256) {
@@ -302,7 +302,7 @@ contract LockedGold is ILockedGold, ReentrancyGuard, Initializable, UsingRegistr
/**
* @notice Returns the total amount of non-voting locked gold for an account.
- * @account The account.
+ * @param account The account.
* @return The total amount of non-voting locked gold for an account.
*/
function getAccountNonvotingLockedGold(address account) external view returns (uint256) {
From 69f6ab29efa410083ffae3da115609ea2228ae8c Mon Sep 17 00:00:00 2001
From: Asa Oines
Date: Wed, 2 Oct 2019 18:54:27 -0700
Subject: [PATCH 019/149] ContractKit building
---
.circleci/config.yml | 2 +-
.../cli/src/commands/account/isvalidator.ts | 6 +-
.../cli/src/commands/lockedgold/delegate.ts | 49 -----
.../cli/src/commands/lockedgold/lockup.ts | 51 -----
.../cli/src/commands/lockedgold/notify.ts | 30 ---
packages/cli/src/commands/lockedgold/show.ts | 54 ++---
.../cli/src/commands/lockedgold/withdraw.ts | 16 +-
packages/cli/src/commands/validator/list.ts | 1 -
.../cli/src/commands/validator/register.ts | 17 +-
.../cli/src/commands/validatorgroup/list.ts | 3 -
.../cli/src/commands/validatorgroup/member.ts | 2 +-
.../src/commands/validatorgroup/register.ts | 18 +-
.../cli/src/commands/validatorgroup/vote.ts | 56 -----
packages/cli/src/commands/validatorset.ts | 18 --
packages/cli/src/utils/lockedgold.ts | 11 +-
packages/contractkit/src/base.ts | 3 +-
packages/contractkit/src/contract-cache.ts | 14 +-
packages/contractkit/src/index.ts | 1 -
packages/contractkit/src/kit.ts | 29 ++-
.../contractkit/src/web3-contract-cache.ts | 7 +-
.../contractkit/src/wrappers/LockedGold.ts | 199 ++++-------------
.../contractkit/src/wrappers/Validators.ts | 207 ++++--------------
.../contracts/governance/Election.sol | 56 +++--
packages/protocol/scripts/build.ts | 13 +-
24 files changed, 202 insertions(+), 661 deletions(-)
delete mode 100644 packages/cli/src/commands/lockedgold/delegate.ts
delete mode 100644 packages/cli/src/commands/lockedgold/lockup.ts
delete mode 100644 packages/cli/src/commands/lockedgold/notify.ts
delete mode 100644 packages/cli/src/commands/validatorgroup/vote.ts
delete mode 100644 packages/cli/src/commands/validatorset.ts
diff --git a/.circleci/config.yml b/.circleci/config.yml
index b3a28cf1089..1c7d7217f38 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -109,7 +109,7 @@ jobs:
steps:
- attach_workspace:
at: ~/app
- # If this fails, fix it with
+ # If this fails, fix it with
# `./node_modules/.bin/prettier --config .prettierrc.js --write '**/*.+(ts|tsx|js|jsx)'`
- run: yarn run prettify:diff
- run: yarn run lint
diff --git a/packages/cli/src/commands/account/isvalidator.ts b/packages/cli/src/commands/account/isvalidator.ts
index 9a6738df870..15876ea8775 100644
--- a/packages/cli/src/commands/account/isvalidator.ts
+++ b/packages/cli/src/commands/account/isvalidator.ts
@@ -17,11 +17,11 @@ export default class IsValidator extends BaseCommand {
async run() {
const { args } = this.parse(IsValidator)
- const validators = await this.kit.contracts.getValidators()
- const numberValidators = await validators.numberValidatorsInCurrentSet()
+ const election = await this.kit.contracts.getValidators()
+ const numberValidators = await election.numberValidatorsInCurrentSet()
for (let i = 0; i < numberValidators; i++) {
- const validatorAddress = await validators.validatorAddressFromCurrentSet(i)
+ const validatorAddress = await election.validatorAddressFromCurrentSet(i)
if (eqAddress(validatorAddress, args.address)) {
console.log(`${args.address} is in the current validator set`)
return
diff --git a/packages/cli/src/commands/lockedgold/delegate.ts b/packages/cli/src/commands/lockedgold/delegate.ts
deleted file mode 100644
index 9b1d39174f7..00000000000
--- a/packages/cli/src/commands/lockedgold/delegate.ts
+++ /dev/null
@@ -1,49 +0,0 @@
-import { Roles } from '@celo/contractkit'
-import { flags } from '@oclif/command'
-import { BaseCommand } from '../../base'
-import { displaySendTx } from '../../utils/cli'
-import { Flags } from '../../utils/command'
-
-export default class Delegate extends BaseCommand {
- static description = 'Delegate validating, voting and reward roles for Locked Gold account'
-
- static flags = {
- ...BaseCommand.flags,
- from: Flags.address({ required: true }),
- role: flags.string({
- char: 'r',
- options: Object.keys(Roles),
- description: 'Role to delegate',
- }),
- to: Flags.address({ required: true }),
- }
-
- static args = []
-
- static examples = [
- 'delegate --from=0x5409ED021D9299bf6814279A6A1411A7e866A631 --role Voting --to=0xc1912fEE45d61C87Cc5EA59DaE31190FFFFf232d',
- ]
-
- async run() {
- const res = this.parse(Delegate)
-
- if (!res.flags.role) {
- this.error(`Specify role with --role`)
- return
- }
-
- if (!res.flags.to) {
- this.error(`Specify delegate address with --to`)
- return
- }
-
- this.kit.defaultAccount = res.flags.from
- const lockedGold = await this.kit.contracts.getLockedGold()
- const tx = await lockedGold.delegateRoleTx(
- res.flags.from,
- res.flags.to,
- Roles[res.flags.role as keyof typeof Roles]
- )
- await displaySendTx('delegateRoleTx', tx)
- }
-}
diff --git a/packages/cli/src/commands/lockedgold/lockup.ts b/packages/cli/src/commands/lockedgold/lockup.ts
deleted file mode 100644
index 18c689a2fc2..00000000000
--- a/packages/cli/src/commands/lockedgold/lockup.ts
+++ /dev/null
@@ -1,51 +0,0 @@
-import { Address } from '@celo/utils/lib/address'
-import { flags } from '@oclif/command'
-import BigNumber from 'bignumber.js'
-import { BaseCommand } from '../../base'
-import { displaySendTx, failWith } from '../../utils/cli'
-import { Flags } from '../../utils/command'
-import { LockedGoldArgs } from '../../utils/lockedgold'
-
-export default class Commitment extends BaseCommand {
- static description = 'Create a Locked Gold commitment given notice period and gold amount'
-
- static flags = {
- ...BaseCommand.flags,
- from: flags.string({ ...Flags.address, required: true }),
- noticePeriod: flags.string({ ...LockedGoldArgs.noticePeriodArg, required: true }),
- goldAmount: flags.string({ ...LockedGoldArgs.goldAmountArg, required: true }),
- }
-
- static args = []
-
- static examples = [
- 'lockup --from 0x47e172F6CfB6c7D01C1574fa3E2Be7CC73269D95 --noticePeriod 8640 --goldAmount 1000000000000000000',
- ]
-
- async run() {
- const res = this.parse(Commitment)
- const address: Address = res.flags.from
-
- this.kit.defaultAccount = address
- const lockedGold = await this.kit.contracts.getLockedGold()
-
- const noticePeriod = new BigNumber(res.flags.noticePeriod)
- const goldAmount = new BigNumber(res.flags.goldAmount)
-
- if (!(await lockedGold.isVoting(address))) {
- failWith(`require(!isVoting(address)) => false`)
- }
-
- const maxNoticePeriod = await lockedGold.maxNoticePeriod()
- if (!maxNoticePeriod.gte(noticePeriod)) {
- failWith(`require(noticePeriod <= maxNoticePeriod) => [${noticePeriod}, ${maxNoticePeriod}]`)
- }
- if (!goldAmount.gt(new BigNumber(0))) {
- failWith(`require(goldAmount > 0) => [${goldAmount}]`)
- }
-
- // await displaySendTx('redeemRewards', lockedGold.methods.redeemRewards())
- const tx = lockedGold.newCommitment(noticePeriod.toString())
- await displaySendTx('lockup', tx, { value: goldAmount.toString() })
- }
-}
diff --git a/packages/cli/src/commands/lockedgold/notify.ts b/packages/cli/src/commands/lockedgold/notify.ts
deleted file mode 100644
index 192a40e5818..00000000000
--- a/packages/cli/src/commands/lockedgold/notify.ts
+++ /dev/null
@@ -1,30 +0,0 @@
-import { flags } from '@oclif/command'
-import { BaseCommand } from '../../base'
-import { displaySendTx } from '../../utils/cli'
-import { Flags } from '../../utils/command'
-import { LockedGoldArgs } from '../../utils/lockedgold'
-
-export default class Notify extends BaseCommand {
- static description = 'Notify a Locked Gold commitment given notice period and gold amount'
-
- static flags = {
- ...BaseCommand.flags,
- from: Flags.address({ required: true }),
- noticePeriod: flags.string({ ...LockedGoldArgs.noticePeriodArg, required: true }),
- goldAmount: flags.string({ ...LockedGoldArgs.goldAmountArg, required: true }),
- }
-
- static args = []
-
- static examples = ['notify --noticePeriod=3600 --goldAmount=500']
-
- async run() {
- const res = this.parse(Notify)
- this.kit.defaultAccount = res.flags.from
- const lockedgold = await this.kit.contracts.getLockedGold()
- await displaySendTx(
- 'notifyCommitment',
- lockedgold.notifyCommitment(res.flags.goldAmount, res.flags.noticePeriod)
- )
- }
-}
diff --git a/packages/cli/src/commands/lockedgold/show.ts b/packages/cli/src/commands/lockedgold/show.ts
index 2848cdee657..8966298cf71 100644
--- a/packages/cli/src/commands/lockedgold/show.ts
+++ b/packages/cli/src/commands/lockedgold/show.ts
@@ -3,58 +3,42 @@ import BigNumber from 'bignumber.js'
import chalk from 'chalk'
import { cli } from 'cli-ux'
import { BaseCommand } from '../../base'
+import { printValueMap } from '../../utils/cli'
import { Args } from '../../utils/command'
import { LockedGoldArgs } from '../../utils/lockedgold'
export default class Show extends BaseCommand {
- static description = 'Show Locked Gold and corresponding account weight of a commitment given ID'
+ static description = 'Show locked gold information for a given account'
static flags = {
...BaseCommand.flags,
- noticePeriod: flags.string({
- ...LockedGoldArgs.noticePeriodArg,
- exclusive: ['availabilityTime'],
- }),
- availabilityTime: flags.string({
- ...LockedGoldArgs.availabilityTimeArg,
- exclusive: ['noticePeriod'],
- }),
}
static args = [Args.address('account')]
- static examples = [
- 'show 0x5409ed021d9299bf6814279a6a1411a7e866a631 --noticePeriod=3600',
- 'show 0x5409ed021d9299bf6814279a6a1411a7e866a631 --availabilityTime=1562206887',
- ]
+ static examples = ['show 0x5409ed021d9299bf6814279a6a1411a7e866a631']
async run() {
// tslint:disable-next-line
const { flags, args } = this.parse(Show)
- if (!(flags.noticePeriod || flags.availabilityTime)) {
- this.error(`Specify commitment ID with --noticePeriod or --availabilityTime`)
- return
- }
-
const lockedGold = await this.kit.contracts.getLockedGold()
- let value = new BigNumber(0)
- let contributingWeight = new BigNumber(0)
- if (flags.noticePeriod) {
- cli.action.start('Fetching Locked Gold commitment...')
- value = await lockedGold.getLockedCommitmentValue(args.account, flags.noticePeriod)
- contributingWeight = value.times(new BigNumber(flags.noticePeriod))
- }
-
- if (flags.availabilityTime) {
- cli.action.start('Fetching notified commitment...')
- value = await lockedGold.getNotifiedCommitmentValue(args.account, flags.availabilityTime)
- contributingWeight = value
+ const nonvoting = await lockedGold.getAccountNonvotingLockedGold(args.account)
+ const total = await lockedGold.getAccountTotalLockedGold(args.account)
+ const voter = await lockedGold.getVoterFromAccount(args.account)
+ const validator = await lockedGold.getValidatorFromAccount(args.account)
+ const pendingWithdrawals = await lockedGold.getPendingWithdrawals(args.account)
+ const info = {
+ lockedGold: {
+ total,
+ nonvoting,
+ },
+ authorizations: {
+ voter: voter == args.account ? null : voter,
+ validator: validator == args.account ? null : validator,
+ },
+ pendingWithdrawals,
}
-
- cli.action.stop()
-
- cli.log(chalk.bold.yellow('Gold Locked \t') + value.toString())
- cli.log(chalk.bold.red('Account Weight Contributed \t') + contributingWeight.toString())
+ printValueMap(info)
}
}
diff --git a/packages/cli/src/commands/lockedgold/withdraw.ts b/packages/cli/src/commands/lockedgold/withdraw.ts
index e34fa24b954..b6f58968402 100644
--- a/packages/cli/src/commands/lockedgold/withdraw.ts
+++ b/packages/cli/src/commands/lockedgold/withdraw.ts
@@ -4,22 +4,28 @@ import { Flags } from '../../utils/command'
import { LockedGoldArgs } from '../../utils/lockedgold'
export default class Withdraw extends BaseCommand {
- static description = 'Withdraw notified commitment given availability time'
+ static description = 'Withdraw unlocked gold whose unlocking period has passed.'
static flags = {
...BaseCommand.flags,
from: Flags.address({ required: true }),
}
- static args = [{ ...LockedGoldArgs.availabilityTimeArg, required: true }]
-
- static examples = ['withdraw 3600']
+ static examples = ['withdraw']
async run() {
// tslint:disable-next-line
const { flags, args } = this.parse(Withdraw)
this.kit.defaultAccount = flags.from
const lockedgold = await this.kit.contracts.getLockedGold()
- await displaySendTx('withdrawCommitment', lockedgold.withdrawCommitment(args.availabilityTime))
+ const pendingWithdrawals = await lockedgold.getPendingWithdrawals()
+ const currentTime = Math.round(new Date().getTime() / 1000)
+ let withdrawals = 0
+ for (let i = 0; i < pendingWithdrawals.length; i++) {
+ if (pendingWithdrawals[i].time <= currentTime) {
+ await displaySendTx('withdraw', lockedgold.withdraw(i - withdrawals))
+ withdrawals += 1
+ }
+ }
}
}
diff --git a/packages/cli/src/commands/validator/list.ts b/packages/cli/src/commands/validator/list.ts
index 9136c7bbdec..2e4efa37e11 100644
--- a/packages/cli/src/commands/validator/list.ts
+++ b/packages/cli/src/commands/validator/list.ts
@@ -20,7 +20,6 @@ export default class ValidatorList extends BaseCommand {
cli.action.stop()
cli.table(validatorList, {
address: {},
- id: {},
name: {},
url: {},
publicKey: {},
diff --git a/packages/cli/src/commands/validator/register.ts b/packages/cli/src/commands/validator/register.ts
index f237c862ff3..ea44245a896 100644
--- a/packages/cli/src/commands/validator/register.ts
+++ b/packages/cli/src/commands/validator/register.ts
@@ -10,20 +10,13 @@ export default class ValidatorRegister extends BaseCommand {
static flags = {
...BaseCommand.flags,
from: Flags.address({ required: true, description: 'Address for the Validator' }),
- id: flags.string({ required: true }),
name: flags.string({ required: true }),
url: flags.string({ required: true }),
publicKey: Flags.publicKey({ required: true }),
- noticePeriod: flags.string({
- required: true,
- description:
- 'Notice period of the Locked Gold commitment. Specify multiple notice periods to use the sum of the commitments.',
- multiple: true,
- }),
}
static examples = [
- 'register --from 0x47e172F6CfB6c7D01C1574fa3E2Be7CC73269D95 --id myID --name myName --noticePeriod 5184000 --noticePeriod 5184001 --url "http://validator.com" --publicKey 0xc52f3fab06e22a54915a8765c4f6826090cfac5e40282b43844bf1c0df83aaa632e55b67869758f2291d1aabe0ebecc7cbf4236aaa45e3e0cfbf997eda082ae19d3e1d8f49f6b0d8e9a03d80ca07b1d24cf1cc0557bdcc04f5e17a46e35d02d0d411d956dbd5d2d2464eebd7b74ae30005d223780d785d2abc5644fac7ac29fb0e302bdc80c81a5d45018b68b1045068a4b3a4861c93037685fd0d252d7405011220a66a6257562d0c26dabf64485a1d96bad27bb1c0fd6080a75b0ec9f75b50298a2a8e04b02b2688c8104fca61fb00',
+ 'register --from 0x47e172F6CfB6c7D01C1574fa3E2Be7CC73269D95 --name myName --url "http://validator.com" --publicKey 0xc52f3fab06e22a54915a8765c4f6826090cfac5e40282b43844bf1c0df83aaa632e55b67869758f2291d1aabe0ebecc7cbf4236aaa45e3e0cfbf997eda082ae19d3e1d8f49f6b0d8e9a03d80ca07b1d24cf1cc0557bdcc04f5e17a46e35d02d0d411d956dbd5d2d2464eebd7b74ae30005d223780d785d2abc5644fac7ac29fb0e302bdc80c81a5d45018b68b1045068a4b3a4861c93037685fd0d252d7405011220a66a6257562d0c26dabf64485a1d96bad27bb1c0fd6080a75b0ec9f75b50298a2a8e04b02b2688c8104fca61fb00',
]
async run() {
const res = this.parse(ValidatorRegister)
@@ -32,13 +25,7 @@ export default class ValidatorRegister extends BaseCommand {
const attestations = await this.kit.contracts.getAttestations()
await displaySendTx(
'registerValidator',
- validators.registerValidator(
- res.flags.id,
- res.flags.name,
- res.flags.url,
- res.flags.publicKey as any,
- res.flags.noticePeriod
- )
+ validators.registerValidator(res.flags.name, res.flags.url, res.flags.publicKey as any)
)
// register encryption key on attestations contract
diff --git a/packages/cli/src/commands/validatorgroup/list.ts b/packages/cli/src/commands/validatorgroup/list.ts
index 2fde1d28ca5..013cb393e94 100644
--- a/packages/cli/src/commands/validatorgroup/list.ts
+++ b/packages/cli/src/commands/validatorgroup/list.ts
@@ -16,15 +16,12 @@ export default class ValidatorGroupList extends BaseCommand {
cli.action.start('Fetching Validator Groups')
const validators = await this.kit.contracts.getValidators()
const vgroups = await validators.getRegisteredValidatorGroups()
- const votes = await validators.getValidatorGroupsVotes()
cli.action.stop()
cli.table(vgroups, {
address: {},
- id: {},
name: {},
url: {},
- votes: { get: (r) => votes.find((v) => v.address === r.address)!.votes.toString() },
members: { get: (r) => r.members.length },
})
}
diff --git a/packages/cli/src/commands/validatorgroup/member.ts b/packages/cli/src/commands/validatorgroup/member.ts
index 0d4fe178cd6..79e41a16835 100644
--- a/packages/cli/src/commands/validatorgroup/member.ts
+++ b/packages/cli/src/commands/validatorgroup/member.ts
@@ -5,7 +5,7 @@ import { displaySendTx } from '../../utils/cli'
import { Args, Flags } from '../../utils/command'
export default class ValidatorGroupRegister extends BaseCommand {
- static description = 'Register a new Validator Group'
+ static description = 'Add or remove members from a Validator Group'
static flags = {
...BaseCommand.flags,
diff --git a/packages/cli/src/commands/validatorgroup/register.ts b/packages/cli/src/commands/validatorgroup/register.ts
index ca513c3ca78..8eaca3c9c73 100644
--- a/packages/cli/src/commands/validatorgroup/register.ts
+++ b/packages/cli/src/commands/validatorgroup/register.ts
@@ -9,20 +9,15 @@ export default class ValidatorGroupRegister extends BaseCommand {
static flags = {
...BaseCommand.flags,
from: Flags.address({ required: true, description: 'Address for the Validator Group' }),
- id: flags.string({ required: true }),
name: flags.string({ required: true }),
url: flags.string({ required: true }),
- noticePeriod: flags.string({
- required: true,
- description:
- 'Notice period of the Locked Gold commitment. Specify multiple notice periods to use the sum of the commitments.',
- multiple: true,
- }),
+ commission: flags.string({ required: true }),
}
static examples = [
- 'register --from 0x47e172F6CfB6c7D01C1574fa3E2Be7CC73269D95 --id myID --name myName --noticePeriod 5184000 --noticePeriod 5184001 --url "http://vgroup.com"',
+ 'register --from 0x47e172F6CfB6c7D01C1574fa3E2Be7CC73269D95 --name myName --url "http://vgroup.com" --commission 0.1',
]
+
async run() {
const res = this.parse(ValidatorGroupRegister)
@@ -31,12 +26,7 @@ export default class ValidatorGroupRegister extends BaseCommand {
await displaySendTx(
'registerValidatorGroup',
- validators.registerValidatorGroup(
- res.flags.id,
- res.flags.name,
- res.flags.url,
- res.flags.noticePeriod
- )
+ validators.registerValidatorGroup(res.flags.name, res.flags.url, res.flags.commission)
)
}
}
diff --git a/packages/cli/src/commands/validatorgroup/vote.ts b/packages/cli/src/commands/validatorgroup/vote.ts
deleted file mode 100644
index 52205c26dc8..00000000000
--- a/packages/cli/src/commands/validatorgroup/vote.ts
+++ /dev/null
@@ -1,56 +0,0 @@
-import { flags } from '@oclif/command'
-import { BaseCommand } from '../../base'
-import { displaySendTx, printValueMap } from '../../utils/cli'
-import { Flags } from '../../utils/command'
-
-export default class ValidatorGroupVote extends BaseCommand {
- static description = 'Vote for a Validator Group'
-
- static flags = {
- ...BaseCommand.flags,
- from: Flags.address({ required: true, description: "Voter's address" }),
- current: flags.boolean({
- exclusive: ['revoke', 'for'],
- description: "Show voter's current vote",
- }),
- revoke: flags.boolean({
- exclusive: ['current', 'for'],
- description: "Revoke voter's current vote",
- }),
- for: Flags.address({
- exclusive: ['current', 'revoke'],
- description: "Set vote for ValidatorGroup's address",
- }),
- }
-
- static examples = [
- 'vote --from 0x4443d0349e8b3075cba511a0a87796597602a0f1 --for 0x932fee04521f5fcb21949041bf161917da3f588b',
- 'vote --from 0x4443d0349e8b3075cba511a0a87796597602a0f1 --revoke',
- 'vote --from 0x4443d0349e8b3075cba511a0a87796597602a0f1 --current',
- ]
- async run() {
- const res = this.parse(ValidatorGroupVote)
-
- this.kit.defaultAccount = res.flags.from
- const validators = await this.kit.contracts.getValidators()
-
- if (res.flags.current) {
- const lockedGold = await this.kit.contracts.getLockedGold()
- const details = await lockedGold.getVotingDetails(res.flags.from)
- const myVote = await validators.getVoteFrom(details.accountAddress)
-
- printValueMap({
- ...details,
- currentVote: myVote,
- })
- } else if (res.flags.revoke) {
- const tx = await validators.revokeVote()
- await displaySendTx('revokeVote', tx)
- } else if (res.flags.for) {
- const tx = await validators.vote(res.flags.for)
- await displaySendTx('vote', tx)
- } else {
- this.error('Use one of --for, --current, --revoke')
- }
- }
-}
diff --git a/packages/cli/src/commands/validatorset.ts b/packages/cli/src/commands/validatorset.ts
deleted file mode 100644
index 37ee81e5d76..00000000000
--- a/packages/cli/src/commands/validatorset.ts
+++ /dev/null
@@ -1,18 +0,0 @@
-import { BaseCommand } from '../base'
-
-export default class ValidatorSet extends BaseCommand {
- static description = 'Outputs the current validator set'
-
- static flags = {
- ...BaseCommand.flags,
- }
-
- static examples = ['validatorset']
-
- async run() {
- const validators = await this.kit.contracts.getValidators()
- const validatorSet = await validators.getValidatorSetAddresses()
-
- validatorSet.forEach((validator: string) => console.log(validator))
- }
-}
diff --git a/packages/cli/src/utils/lockedgold.ts b/packages/cli/src/utils/lockedgold.ts
index c715315334e..64120283e25 100644
--- a/packages/cli/src/utils/lockedgold.ts
+++ b/packages/cli/src/utils/lockedgold.ts
@@ -1,12 +1,7 @@
export const LockedGoldArgs = {
- noticePeriodArg: {
- name: 'noticePeriod',
- description:
- 'duration (seconds) from notice to withdrawable; doubles as ID of a Locked Gold commitment; ',
- },
- availabilityTimeArg: {
- name: 'availabilityTime',
- description: 'unix timestamp at which withdrawable; doubles as ID of a notified commitment',
+ pendingWithdrawalIndexArg: {
+ name: 'pendingWithdrawalINdex',
+ description: 'index of pending withdrawal whose unlocking period has passed',
},
goldAmountArg: {
name: 'goldAmount',
diff --git a/packages/contractkit/src/base.ts b/packages/contractkit/src/base.ts
index 37f7d9533a3..a410cbb7fac 100644
--- a/packages/contractkit/src/base.ts
+++ b/packages/contractkit/src/base.ts
@@ -2,13 +2,14 @@ export type Address = string
export enum CeloContract {
Attestations = 'Attestations',
- LockedGold = 'LockedGold',
+ Election = 'Election',
Escrow = 'Escrow',
Exchange = 'Exchange',
GasCurrencyWhitelist = 'GasCurrencyWhitelist',
GasPriceMinimum = 'GasPriceMinimum',
GoldToken = 'GoldToken',
Governance = 'Governance',
+ LockedGold = 'LockedGold',
Random = 'Random',
Registry = 'Registry',
Reserve = 'Reserve',
diff --git a/packages/contractkit/src/contract-cache.ts b/packages/contractkit/src/contract-cache.ts
index c7aa1270524..585325ce3a9 100644
--- a/packages/contractkit/src/contract-cache.ts
+++ b/packages/contractkit/src/contract-cache.ts
@@ -1,6 +1,7 @@
import { CeloContract } from './base'
import { ContractKit } from './kit'
import { AttestationsWrapper } from './wrappers/Attestations'
+import { ElectionWrapper } from './wrappers/Election'
import { ExchangeWrapper } from './wrappers/Exchange'
import { GasPriceMinimumWrapper } from './wrappers/GasPriceMinimum'
import { GoldTokenWrapper } from './wrappers/GoldTokenWrapper'
@@ -13,13 +14,14 @@ import { ValidatorsWrapper } from './wrappers/Validators'
const WrapperFactories = {
[CeloContract.Attestations]: AttestationsWrapper,
- [CeloContract.LockedGold]: LockedGoldWrapper,
+ [CeloContract.Election]: ElectionWrapper,
// [CeloContract.Escrow]: EscrowWrapper,
[CeloContract.Exchange]: ExchangeWrapper,
// [CeloContract.GasCurrencyWhitelist]: GasCurrencyWhitelistWrapper,
[CeloContract.GasPriceMinimum]: GasPriceMinimumWrapper,
[CeloContract.GoldToken]: GoldTokenWrapper,
[CeloContract.Governance]: GovernanceWrapper,
+ [CeloContract.LockedGold]: LockedGoldWrapper,
// [CeloContract.MultiSig]: MultiSigWrapper,
// [CeloContract.Random]: RandomWrapper,
// [CeloContract.Registry]: RegistryWrapper,
@@ -34,13 +36,14 @@ export type ValidWrappers = keyof CFType
interface WrapperCacheMap {
[CeloContract.Attestations]?: AttestationsWrapper
- [CeloContract.LockedGold]?: LockedGoldWrapper
+ [CeloContract.Election]?: ElectionWrapper
// [CeloContract.Escrow]?: EscrowWrapper,
[CeloContract.Exchange]?: ExchangeWrapper
// [CeloContract.GasCurrencyWhitelist]?: GasCurrencyWhitelistWrapper,
[CeloContract.GasPriceMinimum]?: GasPriceMinimumWrapper
[CeloContract.GoldToken]?: GoldTokenWrapper
[CeloContract.Governance]?: GovernanceWrapper
+ [CeloContract.LockedGold]?: LockedGoldWrapper
// [CeloContract.MultiSig]?: MultiSigWrapper,
// [CeloContract.Random]?: RandomWrapper,
// [CeloContract.Registry]?: RegistryWrapper,
@@ -59,8 +62,8 @@ export class WrapperCache {
getAttestations() {
return this.getContract(CeloContract.Attestations)
}
- getLockedGold() {
- return this.getContract(CeloContract.LockedGold)
+ getElection() {
+ return this.getContract(CeloContract.Election)
}
// getEscrow() {
// return this.getWrapper(CeloContract.Escrow, newEscrow)
@@ -80,6 +83,9 @@ export class WrapperCache {
getGovernance() {
return this.getContract(CeloContract.Governance)
}
+ getLockedGold() {
+ return this.getContract(CeloContract.LockedGold)
+ }
// getMultiSig() {
// return this.getWrapper(CeloContract.MultiSig, newMultiSig)
// }
diff --git a/packages/contractkit/src/index.ts b/packages/contractkit/src/index.ts
index 84877b7a0a7..b9410271262 100644
--- a/packages/contractkit/src/index.ts
+++ b/packages/contractkit/src/index.ts
@@ -4,7 +4,6 @@ export { Address, AllContracts, CeloContract, CeloToken, NULL_ADDRESS } from './
export { IdentityMetadataWrapper } from './identity'
export * from './kit'
export { CeloTransactionObject } from './wrappers/BaseWrapper'
-export { Roles } from './wrappers/LockedGold'
export function newWeb3(url: string) {
return new Web3(url)
diff --git a/packages/contractkit/src/kit.ts b/packages/contractkit/src/kit.ts
index 61fdebc0817..fc64ef4ff6c 100644
--- a/packages/contractkit/src/kit.ts
+++ b/packages/contractkit/src/kit.ts
@@ -8,6 +8,7 @@ import { toTxResult, TransactionResult } from './utils/tx-result'
import { addLocalAccount } from './utils/web3-utils'
import { Web3ContractCache } from './web3-contract-cache'
import { AttestationsConfig } from './wrappers/Attestations'
+import { ElectionConfig } from './wrappers/Election'
import { ExchangeConfig } from './wrappers/Exchange'
import { GasPriceMinimumConfig } from './wrappers/GasPriceMinimum'
import { GovernanceConfig } from './wrappers/Governance'
@@ -15,7 +16,7 @@ import { LockedGoldConfig } from './wrappers/LockedGold'
import { ReserveConfig } from './wrappers/Reserve'
import { SortedOraclesConfig } from './wrappers/SortedOracles'
import { StableTokenConfig } from './wrappers/StableTokenWrapper'
-import { ValidatorConfig } from './wrappers/Validators'
+import { ValidatorsConfig } from './wrappers/Validators'
export function newKit(url: string) {
return newKitFromWeb3(new Web3(url))
@@ -26,6 +27,7 @@ export function newKitFromWeb3(web3: Web3) {
}
export interface NetworkConfig {
+ election: ElectionConfig
exchange: ExchangeConfig
attestations: AttestationsConfig
governance: GovernanceConfig
@@ -34,7 +36,7 @@ export interface NetworkConfig {
gasPriceMinimum: GasPriceMinimumConfig
reserve: ReserveConfig
stableToken: StableTokenConfig
- validators: ValidatorConfig
+ validators: ValidatorsConfig
}
export class ContractKit {
@@ -58,6 +60,7 @@ export class ContractKit {
const token2 = await this.registry.addressFor(CeloContract.StableToken)
const contracts = await Promise.all([
this.contracts.getExchange(),
+ this.contracts.getElection(),
this.contracts.getAttestations(),
this.contracts.getGovernance(),
this.contracts.getLockedGold(),
@@ -69,25 +72,27 @@ export class ContractKit {
])
const res = await Promise.all([
contracts[0].getConfig(),
- contracts[1].getConfig([token1, token2]),
- contracts[2].getConfig(),
+ contracts[1].getConfig(),
+ contracts[2].getConfig([token1, token2]),
contracts[3].getConfig(),
contracts[4].getConfig(),
contracts[5].getConfig(),
contracts[6].getConfig(),
contracts[7].getConfig(),
contracts[8].getConfig(),
+ contracts[9].getConfig(),
])
return {
exchange: res[0],
- attestations: res[1],
- governance: res[2],
- lockedGold: res[3],
- sortedOracles: res[4],
- gasPriceMinimum: res[5],
- reserve: res[6],
- stableToken: res[7],
- validators: res[8],
+ election: res[1],
+ attestations: res[2],
+ governance: res[3],
+ lockedGold: res[4],
+ sortedOracles: res[5],
+ gasPriceMinimum: res[6],
+ reserve: res[7],
+ stableToken: res[8],
+ validators: res[9],
}
}
diff --git a/packages/contractkit/src/web3-contract-cache.ts b/packages/contractkit/src/web3-contract-cache.ts
index 872afd68bae..e89aba191c5 100644
--- a/packages/contractkit/src/web3-contract-cache.ts
+++ b/packages/contractkit/src/web3-contract-cache.ts
@@ -1,6 +1,7 @@
import debugFactory from 'debug'
import { CeloContract } from './base'
import { newAttestations } from './generated/Attestations'
+import { newElection } from './generated/Election'
import { newEscrow } from './generated/Escrow'
import { newExchange } from './generated/Exchange'
import { newGasCurrencyWhitelist } from './generated/GasCurrencyWhitelist'
@@ -20,13 +21,14 @@ const debug = debugFactory('kit:web3-contract-cache')
const ContractFactories = {
[CeloContract.Attestations]: newAttestations,
- [CeloContract.LockedGold]: newLockedGold,
+ [CeloContract.Election]: newElection,
[CeloContract.Escrow]: newEscrow,
[CeloContract.Exchange]: newExchange,
[CeloContract.GasCurrencyWhitelist]: newGasCurrencyWhitelist,
[CeloContract.GasPriceMinimum]: newGasPriceMinimum,
[CeloContract.GoldToken]: newGoldToken,
[CeloContract.Governance]: newGovernance,
+ [CeloContract.LockedGold]: newLockedGold,
[CeloContract.Random]: newRandom,
[CeloContract.Registry]: newRegistry,
[CeloContract.Reserve]: newReserve,
@@ -49,6 +51,9 @@ export class Web3ContractCache {
getLockedGold() {
return this.getContract(CeloContract.LockedGold)
}
+ getElection() {
+ return this.getContract(CeloContract.Election)
+ }
getEscrow() {
return this.getContract(CeloContract.Escrow)
}
diff --git a/packages/contractkit/src/wrappers/LockedGold.ts b/packages/contractkit/src/wrappers/LockedGold.ts
index a63e34b8d3c..0394e0f0dd4 100644
--- a/packages/contractkit/src/wrappers/LockedGold.ts
+++ b/packages/contractkit/src/wrappers/LockedGold.ts
@@ -1,7 +1,6 @@
import { zip } from '@celo/utils/lib/collections'
import BigNumber from 'bignumber.js'
import Web3 from 'web3'
-import { TransactionObject } from 'web3/eth/types'
import { Address } from '../base'
import { LockedGold } from '../generated/types/LockedGold'
import {
@@ -19,180 +18,89 @@ export interface VotingDetails {
weight: BigNumber
}
-interface Commitment {
+interface PendingWithdrawal {
time: BigNumber
value: BigNumber
}
-export interface Commitments {
- locked: Commitment[]
- notified: Commitment[]
- total: {
- gold: BigNumber
- weight: BigNumber
- }
-}
-
-export enum Roles {
- Validating = '0',
- Voting = '1',
- Rewards = '2',
-}
-
export interface LockedGoldConfig {
- maxNoticePeriod: BigNumber
+ unlockingPeriod: BigNumber
}
/**
* Contract for handling deposits needed for voting.
*/
export class LockedGoldWrapper extends BaseWrapper {
- notifyCommitment = proxySend(this.kit, this.contract.methods.notifyCommitment)
+ unlock = proxySend(this.kit, this.contract.methods.unlock)
createAccount = proxySend(this.kit, this.contract.methods.createAccount)
- withdrawCommitment = proxySend(this.kit, this.contract.methods.withdrawCommitment)
- redeemRewards = proxySend(this.kit, this.contract.methods.redeemRewards)
- newCommitment = proxySend(this.kit, this.contract.methods.newCommitment)
- extendCommitment = proxySend(this.kit, this.contract.methods.extendCommitment)
- isVoting = proxyCall(this.contract.methods.isVoting)
- /**
- * Query maximum notice period.
- * @returns Current maximum notice period.
- */
- maxNoticePeriod = proxyCall(this.contract.methods.maxNoticePeriod, undefined, toBigNumber)
-
- getAccountWeight = proxyCall(this.contract.methods.getAccountWeight, undefined, toBigNumber)
- /**
- * Get the delegate for a role.
- * @param account Address of the active account.
- * @param role one of Roles Enum ("validating", "voting", "rewards")
- * @return Address of the delegate
- */
- getDelegateFromAccountAndRole: (account: string, role: Roles) => Promise = proxyCall(
- this.contract.methods.getDelegateFromAccountAndRole
+ withdraw = proxySend(this.kit, this.contract.methods.withdraw)
+ lock = proxySend(this.kit, this.contract.methods.lock)
+ relock = proxySend(this.kit, this.contract.methods.relock)
+
+ getAccountTotalLockedGold = proxyCall(
+ this.contract.methods.getAccountTotalLockedGold,
+ undefined,
+ toBigNumber
+ )
+ getAccountNonvotingLockedGold = proxyCall(
+ this.contract.methods.getAccountNonvotingLockedGold,
+ undefined,
+ toBigNumber
+ )
+ getVoterFromAccount: (account: string) => Promise = proxyCall(
+ this.contract.methods.getVoterFromAccount
+ )
+ getValidatorFromAccount: (account: string) => Promise = proxyCall(
+ this.contract.methods.getValidatorFromAccount
)
/**
* Returns current configuration parameters.
*/
-
async getConfig(): Promise {
return {
- maxNoticePeriod: await this.maxNoticePeriod(),
- }
- }
-
- async getVotingDetails(accountOrVoterAddress: Address): Promise {
- const accountAddress = await this.contract.methods
- .getAccountFromDelegateAndRole(accountOrVoterAddress, Roles.Voting)
- .call()
-
- return {
- accountAddress,
- voterAddress: accountOrVoterAddress,
- weight: await this.getAccountWeight(accountAddress),
- }
- }
-
- async getLockedCommitmentValue(account: string, noticePeriod: string): Promise {
- const commitment = await this.contract.methods.getLockedCommitment(account, noticePeriod).call()
- return this.getValueFromCommitment(commitment)
- }
-
- async getLockedCommitments(account: string): Promise {
- return this.zipAccountTimesAndValuesToCommitments(
- account,
- this.contract.methods.getNoticePeriods,
- this.getLockedCommitmentValue.bind(this)
- )
- }
-
- async getNotifiedCommitmentValue(account: string, availTime: string): Promise {
- const commitment = await this.contract.methods.getNotifiedCommitment(account, availTime).call()
- return this.getValueFromCommitment(commitment)
- }
-
- async getNotifiedCommitments(account: string): Promise {
- return this.zipAccountTimesAndValuesToCommitments(
- account,
- this.contract.methods.getAvailabilityTimes,
- this.getNotifiedCommitmentValue.bind(this)
- )
- }
-
- async getCommitments(account: string): Promise {
- const locked = await this.getLockedCommitments(account)
- const notified = await this.getNotifiedCommitments(account)
- const weight = await this.getAccountWeight(account)
-
- const totalLocked = locked.reduce(
- (acc, commitment) => acc.plus(commitment.value),
- new BigNumber(0)
- )
- const gold = notified.reduce((acc, commitment) => acc.plus(commitment.value), totalLocked)
-
- return {
- locked,
- notified,
- total: { weight, gold },
+ unlockingPeriod: toBigNumber(await this.contract.methods.unlockingPeriod().call()),
}
}
/**
- * Delegate a Role to another account.
+ * Authorize voting on behalf of this account to another address.
* @param account Address of the active account.
- * @param delegate Address of the delegate
- * @param role one of Roles Enum ("Validating", "Voting", "Rewards")
+ * @param voter Address to be used for voting.
* @return A CeloTransactionObject
*/
- async delegateRoleTx(
- account: Address,
- delegate: Address,
- role: Roles
- ): Promise> {
- const sig = await this.getParsedSignatureOfAddress(account, delegate)
- return wrapSend(
- this.kit,
- this.contract.methods.delegateRole(role, delegate, sig.v, sig.r, sig.s)
- )
+ async authorizeVoter(account: Address, voter: Address): Promise> {
+ const sig = await this.getParsedSignatureOfAddress(account, voter)
+ return wrapSend(this.kit, this.contract.methods.authorizeVoter(voter, sig.v, sig.r, sig.s))
}
/**
- * Delegate a Rewards to another account.
+ * Authorize validating on behalf of this account to another address.
* @param account Address of the active account.
- * @param delegate Address of the delegate
+ * @param voter Address to be used for validating.
* @return A CeloTransactionObject
*/
- async delegateRewards(account: Address, delegate: Address): Promise> {
- return this.delegateRoleTx(account, delegate, Roles.Rewards)
- }
-
- /**
- * Delegate a voting to another account.
- * @param account Address of the active account.
- * @param delegate Address of the delegate
- * @return A CeloTransactionObject
- */
- async delegateVoting(account: Address, delegate: Address): Promise> {
- return this.delegateRoleTx(account, delegate, Roles.Voting)
- }
-
- /**
- * Delegate a validating to another account.
- * @param account Address of the active account.
- * @param delegate Address of the delegate
- * @return A CeloTransactionObject
- */
- async delegateValidating(
+ async authorizeValidator(
account: Address,
- delegate: Address
+ validator: Address
): Promise> {
- return this.delegateRoleTx(account, delegate, Roles.Validating)
+ const sig = await this.getParsedSignatureOfAddress(account, validator)
+ return wrapSend(
+ this.kit,
+ this.contract.methods.authorizeValidator(validator, sig.v, sig.r, sig.s)
+ )
}
- private getValueFromCommitment(commitment: { 0: string; 1: string }) {
- return new BigNumber(commitment[0])
+ async getPendingWithdrawals(account: string) {
+ const withdrawals = await this.contract.methods.getPendingWithdrawals(account).call()
+ return zip(
+ // tslint:disable-next-line: no-object-literal-type-assertion
+ (time, value) =>
+ ({ time: toBigNumber(time), value: toBigNumber(value) } as PendingWithdrawal),
+ withdrawals[1],
+ withdrawals[0]
+ )
}
-
private async getParsedSignatureOfAddress(address: string, signer: string) {
const hash = Web3.utils.soliditySha3({ type: 'address', value: address })
const signature = (await this.kit.web3.eth.sign(hash, signer)).slice(2)
@@ -202,19 +110,4 @@ export class LockedGoldWrapper extends BaseWrapper {
v: Web3.utils.hexToNumber(signature.slice(128, 130)) + 27,
}
}
-
- private async zipAccountTimesAndValuesToCommitments(
- account: string,
- timesFunc: (account: string) => TransactionObject,
- valueFunc: (account: string, time: string) => Promise
- ) {
- const accountTimes = await timesFunc(account).call()
- const accountValues = await Promise.all(accountTimes.map((time) => valueFunc(account, time)))
- return zip(
- // tslint:disable-next-line: no-object-literal-type-assertion
- (time, value) => ({ time, value } as Commitment),
- accountTimes.map((time) => new BigNumber(time)),
- accountValues
- )
- }
}
diff --git a/packages/contractkit/src/wrappers/Validators.ts b/packages/contractkit/src/wrappers/Validators.ts
index fe9ae06f560..37922243db9 100644
--- a/packages/contractkit/src/wrappers/Validators.ts
+++ b/packages/contractkit/src/wrappers/Validators.ts
@@ -1,21 +1,10 @@
-import { eqAddress } from '@celo/utils/lib/address'
-import { zip } from '@celo/utils/lib/collections'
import BigNumber from 'bignumber.js'
-import { Address, NULL_ADDRESS } from '../base'
+import { Address } from '../base'
import { Validators } from '../generated/types/Validators'
-import {
- BaseWrapper,
- CeloTransactionObject,
- proxyCall,
- proxySend,
- toBigNumber,
- toNumber,
- wrapSend,
-} from './BaseWrapper'
+import { BaseWrapper, proxySend, toBigNumber } from './BaseWrapper'
export interface Validator {
address: Address
- id: string
name: string
url: string
publicKey: string
@@ -24,27 +13,25 @@ export interface Validator {
export interface ValidatorGroup {
address: Address
- id: string
name: string
url: string
members: Address[]
}
-export interface ValidatorGroupVote {
- address: Address
- votes: BigNumber
+export interface RegistrationRequirements {
+ group: BigNumber
+ validator: BigNumber
}
-export interface RegistrationRequirement {
- minLockedGoldValue: BigNumber
- minLockedGoldNoticePeriod: BigNumber
+export interface DeregistrationLockups {
+ group: BigNumber
+ validator: BigNumber
}
-export interface ValidatorConfig {
- minElectableValidators: BigNumber
- maxElectableValidators: BigNumber
- electionThreshold: BigNumber
- registrationRequirement: RegistrationRequirement
+export interface ValidatorsConfig {
+ registrationRequirements: RegistrationRequirements
+ deregistrationLockups: DeregistrationLockups
+ maxGroupSize: BigNumber
}
/**
@@ -58,66 +45,38 @@ export class ValidatorsWrapper extends BaseWrapper {
registerValidator = proxySend(this.kit, this.contract.methods.registerValidator)
registerValidatorGroup = proxySend(this.kit, this.contract.methods.registerValidatorGroup)
/**
- * Returns the minimum number of validators that can be elected.
- * @returns The minimum number of validators that can be elected.
- */
- minElectableValidators = proxyCall(
- this.contract.methods.minElectableValidators,
- undefined,
- toBigNumber
- )
- /**
- * Returns the maximum number of validators that can be elected.
- * @returns The maximum number of validators that can be elected.
+ * Returns the current registration requirements.
+ * @returns Group and validator registration requirements.
*/
- maxElectableValidators = proxyCall(
- this.contract.methods.maxElectableValidators,
- undefined,
- toBigNumber
- )
- /**
- * Returns the current election threshold.
- * @returns Election threshold.
- */
- electionThreshold = proxyCall(this.contract.methods.getElectionThreshold, undefined, toBigNumber)
- validatorAddressFromCurrentSet = proxyCall(this.contract.methods.validatorAddressFromCurrentSet)
- numberValidatorsInCurrentSet = proxyCall(
- this.contract.methods.numberValidatorsInCurrentSet,
- undefined,
- toNumber
- )
-
- getVoteFrom: (validatorAddress: Address) => Promise = proxyCall(
- this.contract.methods.voters
- )
+ async getRegistrationRequirements(): Promise {
+ const res = await this.contract.methods.getRegistrationRequirements().call()
+ return {
+ group: toBigNumber(res[0]),
+ validator: toBigNumber(res[1]),
+ }
+ }
- /**
- * Returns the current registrations requirements.
- * @returns Minimum deposit and notice period.
- */
- async getRegistrationRequirement(): Promise {
- const res = await this.contract.methods.getRegistrationRequirement().call()
+ async getDeregistrationLockups(): Promise {
+ const res = await this.contract.methods.getDeregistrationLockups().call()
return {
- minLockedGoldValue: toBigNumber(res[0]),
- minLockedGoldNoticePeriod: toBigNumber(res[0]),
+ group: toBigNumber(res[0]),
+ validator: toBigNumber(res[1]),
}
}
/**
* Returns current configuration parameters.
*/
- async getConfig(): Promise {
+ async getConfig(): Promise {
const res = await Promise.all([
- this.minElectableValidators(),
- this.maxElectableValidators(),
- this.electionThreshold(),
- this.getRegistrationRequirement(),
+ this.getRegistrationRequirements(),
+ this.getDeregistrationLockups(),
+ this.contract.methods.maxGroupSize().call(),
])
return {
- minElectableValidators: res[0],
- maxElectableValidators: res[1],
- electionThreshold: res[2],
- registrationRequirement: res[3],
+ registrationRequirements: res[0],
+ deregistrationLockups: res[1],
+ maxGroupSize: toBigNumber(res[2]),
}
}
@@ -127,27 +86,14 @@ export class ValidatorsWrapper extends BaseWrapper {
return Promise.all(vgAddresses.map((addr) => this.getValidator(addr)))
}
- async getValidatorSetAddresses(): Promise {
- const numberValidators = await this.numberValidatorsInCurrentSet()
-
- const validatorAddressPromises = []
-
- for (let i = 0; i < numberValidators; i++) {
- validatorAddressPromises.push(this.validatorAddressFromCurrentSet(i))
- }
-
- return Promise.all(validatorAddressPromises)
- }
-
async getValidator(address: Address): Promise {
const res = await this.contract.methods.getValidator(address).call()
return {
address,
- id: res[0],
- name: res[1],
- url: res[2],
- publicKey: res[3] as any,
- affiliation: res[4],
+ name: res[0],
+ url: res[1],
+ publicKey: res[2] as any,
+ affiliation: res[3],
}
}
@@ -158,85 +104,6 @@ export class ValidatorsWrapper extends BaseWrapper {
async getValidatorGroup(address: Address): Promise {
const res = await this.contract.methods.getValidatorGroup(address).call()
- return { address, id: res[0], name: res[1], url: res[2], members: res[3] }
- }
-
- async getValidatorGroupsVotes(): Promise {
- const vgAddresses = await this.contract.methods.getRegisteredValidatorGroups().call()
- const res = await this.contract.methods.getValidatorGroupVotes().call()
- const r = zip((a, b) => ({ address: a, votes: new BigNumber(b) }), res[0], res[1])
- for (const vgAddress of vgAddresses) {
- if (!res[0].includes(vgAddress)) {
- r.push({ address: vgAddress, votes: new BigNumber(0) })
- }
- }
- return r
- }
-
- async revokeVote(): Promise> {
- if (this.kit.defaultAccount == null) {
- throw new Error(`missing from at new ValdidatorUtils()`)
- }
-
- const lockedGold = await this.kit.contracts.getLockedGold()
- const votingDetails = await lockedGold.getVotingDetails(this.kit.defaultAccount)
- const votedGroup = await this.getVoteFrom(votingDetails.accountAddress)
-
- if (votedGroup == null) {
- throw new Error(`Not current vote for ${this.kit.defaultAccount}`)
- }
-
- const { lesser, greater } = await this.findLesserAndGreaterAfterVote(
- votedGroup,
- votingDetails.weight.negated()
- )
-
- return wrapSend(this.kit, this.contract.methods.revokeVote(lesser, greater))
- }
-
- async vote(validatorGroup: Address): Promise> {
- if (this.kit.defaultAccount == null) {
- throw new Error(`missing from at new ValdidatorUtils()`)
- }
-
- const lockedGold = await this.kit.contracts.getLockedGold()
- const votingDetails = await lockedGold.getVotingDetails(this.kit.defaultAccount)
-
- const { lesser, greater } = await this.findLesserAndGreaterAfterVote(
- validatorGroup,
- votingDetails.weight
- )
-
- return wrapSend(this.kit, this.contract.methods.vote(validatorGroup, lesser, greater))
- }
-
- private async findLesserAndGreaterAfterVote(
- votedGroup: Address,
- voteWeight: BigNumber
- ): Promise<{ lesser: Address; greater: Address }> {
- const currentVotes = (await this.getValidatorGroupsVotes()).filter((g) => !g.votes.isZero())
-
- const selectedGroup = currentVotes.find((cv) => eqAddress(cv.address, votedGroup))
-
- // modify the list
- if (selectedGroup) {
- selectedGroup.votes = selectedGroup.votes.plus(voteWeight)
- } else {
- currentVotes.push({
- address: votedGroup,
- votes: voteWeight,
- })
- }
-
- // re-sort
- currentVotes.sort((a, b) => a.votes.comparedTo(b.votes))
-
- // find new index
- const newIdx = currentVotes.findIndex((cv) => eqAddress(cv.address, votedGroup))
-
- return {
- lesser: newIdx === 0 ? NULL_ADDRESS : currentVotes[newIdx - 1].address,
- greater: newIdx === currentVotes.length - 1 ? NULL_ADDRESS : currentVotes[newIdx + 1].address,
- }
+ return { address, name: res[0], url: res[1], members: res[2] }
}
}
diff --git a/packages/protocol/contracts/governance/Election.sol b/packages/protocol/contracts/governance/Election.sol
index 937515784c6..f4ed545dbca 100644
--- a/packages/protocol/contracts/governance/Election.sol
+++ b/packages/protocol/contracts/governance/Election.sol
@@ -54,13 +54,13 @@ contract Election is Ownable, ReentrancyGuard, Initializable, UsingRegistry {
ActiveVotes active;
TotalVotes total;
// Maps an account to the list of groups it's voting for.
- mapping(address => address[]) lists;
+ mapping(address => address[]) groupsVotedFor;
}
Votes private votes;
uint256 public minElectableValidators;
uint256 public maxElectableValidators;
- uint256 public maxVotesPerAccount;
+ uint256 public maxNumGroupsVotedFor;
FixidityLib.Fraction public electabilityThreshold;
event MinElectableValidatorsSet(
@@ -71,8 +71,8 @@ contract Election is Ownable, ReentrancyGuard, Initializable, UsingRegistry {
uint256 maxElectableValidators
);
- event MaxVotesPerAccountSet(
- uint256 maxVotesPerAccount
+ event MaxNumGroupsVotedForSet(
+ uint256 maxNumGroupsVotedFor
);
event ElectabilityThresholdSet(
@@ -109,7 +109,7 @@ contract Election is Ownable, ReentrancyGuard, Initializable, UsingRegistry {
* @notice Initializes critical variables.
* @param registryAddress The address of the registry contract.
* @param _minElectableValidators The minimum number of validators that can be elected.
- * @param _maxVotesPerAccount The maximum number of groups that an acconut can vote for at once.
+ * @param _maxNumGroupsVotedFor The maximum number of groups that an acconut can vote for at once.
* @param _electabilityThreshold The minimum ratio of votes a group needs before its members can
* be elected.
* @dev Should be called only once.
@@ -118,7 +118,7 @@ contract Election is Ownable, ReentrancyGuard, Initializable, UsingRegistry {
address registryAddress,
uint256 _minElectableValidators,
uint256 _maxElectableValidators,
- uint256 _maxVotesPerAccount,
+ uint256 _maxNumGroupsVotedFor,
uint256 _electabilityThreshold
)
external
@@ -129,7 +129,7 @@ contract Election is Ownable, ReentrancyGuard, Initializable, UsingRegistry {
setRegistry(registryAddress);
minElectableValidators = _minElectableValidators;
maxElectableValidators = _maxElectableValidators;
- maxVotesPerAccount = _maxVotesPerAccount;
+ maxNumGroupsVotedFor = _maxNumGroupsVotedFor;
electabilityThreshold = FixidityLib.wrap(_electabilityThreshold);
}
@@ -178,13 +178,13 @@ contract Election is Ownable, ReentrancyGuard, Initializable, UsingRegistry {
/**
* @notice Updates the maximum number of groups an account can be voting for at once.
- * @param _maxVotesPerAccount The maximum number of groups an account can vote for.
+ * @param _maxNumGroupsVotedFor The maximum number of groups an account can vote for.
* @return True upon success.
*/
- function setMaxVotesPerAccount(uint256 _maxVotesPerAccount) external onlyOwner returns (bool) {
- require(_maxVotesPerAccount != maxVotesPerAccount);
- maxVotesPerAccount = _maxVotesPerAccount;
- emit MaxVotesPerAccountSet(_maxVotesPerAccount);
+ function setMaxNumGroupsVotedFor(uint256 _maxNumGroupsVotedFor) external onlyOwner returns (bool) {
+ require(_maxNumGroupsVotedFor != maxNumGroupsVotedFor);
+ maxNumGroupsVotedFor = _maxNumGroupsVotedFor;
+ emit MaxNumGroupsVotedForSet(_maxNumGroupsVotedFor);
return true;
}
@@ -241,12 +241,12 @@ contract Election is Ownable, ReentrancyGuard, Initializable, UsingRegistry {
require(votes.total.eligible.contains(group));
require(0 < value && value <= getNumVotesReceivable(group));
address account = getLockedGold().getAccountFromVoter(msg.sender);
- address[] storage list = votes.lists[account];
- require(list.length < maxVotesPerAccount);
- for (uint256 i = 0; i < list.length; i = i.add(1)) {
- require(list[i] != group);
+ address[] storage groups = votes.groupsVotedFor[account];
+ require(groups.length < maxNumGroupsVotedFor);
+ for (uint256 i = 0; i < groups.length; i = i.add(1)) {
+ require(groups[i] != group);
}
- list.push(group);
+ groups.push(group);
incrementPendingVotes(group, account, value);
incrementTotalVotes(group, value, lesser, greater);
getLockedGold().decrementNonvotingAccountBalance(account, value);
@@ -299,7 +299,7 @@ contract Election is Ownable, ReentrancyGuard, Initializable, UsingRegistry {
decrementTotalVotes(group, value, lesser, greater);
getLockedGold().incrementNonvotingAccountBalance(account, value);
if (getAccountTotalVotesForGroup(group, account) == 0) {
- deleteElement(votes.lists[account], group, index);
+ deleteElement(votes.groupsVotedFor[account], group, index);
}
emit ValidatorGroupVoteRevoked(account, group, value);
return true;
@@ -335,7 +335,7 @@ contract Election is Ownable, ReentrancyGuard, Initializable, UsingRegistry {
decrementTotalVotes(group, value, lesser, greater);
getLockedGold().incrementNonvotingAccountBalance(account, value);
if (getAccountTotalVotesForGroup(group, account) == 0) {
- deleteElement(votes.lists[account], group, index);
+ deleteElement(votes.groupsVotedFor[account], group, index);
}
emit ValidatorGroupVoteRevoked(account, group, value);
return true;
@@ -343,7 +343,7 @@ contract Election is Ownable, ReentrancyGuard, Initializable, UsingRegistry {
function getAccountTotalVotes(address account) external view returns (uint256) {
uint256 total = 0;
- address[] memory groups = votes.lists[account];
+ address[] memory groups = votes.groupsVotedFor[account];
for (uint256 i = 0; i < groups.length; i = i.add(1)) {
total = total.add(getAccountTotalVotesForGroup(groups[i], account));
}
@@ -356,7 +356,7 @@ contract Election is Ownable, ReentrancyGuard, Initializable, UsingRegistry {
* @return The groups voted for by a particular account.
*/
function getAccountGroupsVotedFor(address account) external view returns (address[] memory) {
- return votes.lists[account];
+ return votes.groupsVotedFor[account];
}
/**
@@ -423,7 +423,11 @@ contract Election is Ownable, ReentrancyGuard, Initializable, UsingRegistry {
* @return The total votes made for `group`.
*/
function getGroupTotalVotes(address group) external view returns (uint256) {
- return votes.total.eligible.getValue(group);
+ return votes.pending.total[group].add(votes.active.total[group]);
+ }
+
+ function getGroupEligibility(address group) external view returns (bool) {
+ return votes.total.eligible.contains(group);
}
/**
@@ -495,8 +499,8 @@ contract Election is Ownable, ReentrancyGuard, Initializable, UsingRegistry {
* @param greater The address of the group that has received more votes than this group.
*/
function markGroupEligible(address group, address lesser, address greater) external {
- require(!votes.total.eligible.contains(group), "aaa");
- require(getValidators().getGroupNumMembers(group) > 0, "b");
+ require(!votes.total.eligible.contains(group));
+ require(getValidators().getGroupNumMembers(group) > 0);
uint256 value = votes.pending.total[group].add(votes.active.total[group]);
votes.total.eligible.insert(group, value, lesser, greater);
emit ValidatorGroupMarkedEligible(group);
@@ -572,6 +576,10 @@ contract Election is Ownable, ReentrancyGuard, Initializable, UsingRegistry {
);
}
+ function getGroupsVotedFor(address account) external view returns (address[] memory) {
+ return votes.groupsVotedFor[account];
+ }
+
/**
* @notice Deletes an element from a list of addresses.
* @param list The list of addresses.
diff --git a/packages/protocol/scripts/build.ts b/packages/protocol/scripts/build.ts
index 4330d07a2e2..6106cd275c4 100644
--- a/packages/protocol/scripts/build.ts
+++ b/packages/protocol/scripts/build.ts
@@ -8,14 +8,15 @@ const BUILD_DIR = path.join(ROOT_DIR, 'build')
const CONTRACTKIT_GEN_DIR = path.normalize(path.join(ROOT_DIR, '../contractkit/src/generated'))
export const ProxyContracts = [
- 'GasCurrencyWhitelistProxy',
- 'GasPriceMinimumProxy',
- 'MultiSigProxy',
- 'LockedGoldProxy',
'AttestationsProxy',
+ 'ElectionProxy',
'EscrowProxy',
'ExchangeProxy',
+ 'GasCurrencyWhitelistProxy',
+ 'GasPriceMinimumProxy',
'GoldTokenProxy',
+ 'LockedGoldProxy',
+ 'MultiSigProxy',
'ReserveProxy',
'StableTokenProxy',
'SortedOraclesProxy',
@@ -32,8 +33,10 @@ export const CoreContracts = [
'Validators',
// governance
- 'LockedGold',
+ 'Election',
'Governance',
+ 'LockedGold',
+ 'Validators',
// identity
'Attestations',
From 15d2e8cf8ce0d38db32ef0f9224f50cc0f7c4a10 Mon Sep 17 00:00:00 2001
From: Asa Oines
Date: Wed, 2 Oct 2019 19:04:20 -0700
Subject: [PATCH 020/149] cli builds
---
.../cli/src/commands/account/isvalidator.ts | 2 +-
.../cli/src/commands/election/validatorset.ts | 18 ++
packages/cli/src/commands/election/vote.ts | 31 +++
.../cli/src/commands/lockedgold/authorize.ts | 49 +++++
packages/cli/src/commands/lockedgold/list.ts | 54 -----
packages/cli/src/commands/lockedgold/lock.ts | 40 ++++
.../cli/src/commands/lockedgold/rewards.ts | 51 -----
packages/cli/src/commands/lockedgold/show.ts | 7 +-
.../cli/src/commands/lockedgold/unlock.ts | 26 +++
.../cli/src/commands/lockedgold/withdraw.ts | 9 +-
packages/cli/src/utils/key_generator.test.ts | 2 +-
packages/contractkit/src/wrappers/Election.ts | 206 ++++++++++++++++++
12 files changed, 377 insertions(+), 118 deletions(-)
create mode 100644 packages/cli/src/commands/election/validatorset.ts
create mode 100644 packages/cli/src/commands/election/vote.ts
create mode 100644 packages/cli/src/commands/lockedgold/authorize.ts
delete mode 100644 packages/cli/src/commands/lockedgold/list.ts
create mode 100644 packages/cli/src/commands/lockedgold/lock.ts
delete mode 100644 packages/cli/src/commands/lockedgold/rewards.ts
create mode 100644 packages/cli/src/commands/lockedgold/unlock.ts
create mode 100644 packages/contractkit/src/wrappers/Election.ts
diff --git a/packages/cli/src/commands/account/isvalidator.ts b/packages/cli/src/commands/account/isvalidator.ts
index 15876ea8775..316514dfa8e 100644
--- a/packages/cli/src/commands/account/isvalidator.ts
+++ b/packages/cli/src/commands/account/isvalidator.ts
@@ -17,7 +17,7 @@ export default class IsValidator extends BaseCommand {
async run() {
const { args } = this.parse(IsValidator)
- const election = await this.kit.contracts.getValidators()
+ const election = await this.kit.contracts.getElection()
const numberValidators = await election.numberValidatorsInCurrentSet()
for (let i = 0; i < numberValidators; i++) {
diff --git a/packages/cli/src/commands/election/validatorset.ts b/packages/cli/src/commands/election/validatorset.ts
new file mode 100644
index 00000000000..74f56f97874
--- /dev/null
+++ b/packages/cli/src/commands/election/validatorset.ts
@@ -0,0 +1,18 @@
+import { BaseCommand } from '../../base'
+
+export default class ValidatorSet extends BaseCommand {
+ static description = 'Outputs the current validator set'
+
+ static flags = {
+ ...BaseCommand.flags,
+ }
+
+ static examples = ['validatorset']
+
+ async run() {
+ const election = await this.kit.contracts.getElection()
+ const validatorSet = await election.getValidatorSetAddresses()
+
+ validatorSet.forEach((validator: string) => console.log(validator))
+ }
+}
diff --git a/packages/cli/src/commands/election/vote.ts b/packages/cli/src/commands/election/vote.ts
new file mode 100644
index 00000000000..9b466094a1a
--- /dev/null
+++ b/packages/cli/src/commands/election/vote.ts
@@ -0,0 +1,31 @@
+import BigNumber from 'bignumber.js'
+import { flags } from '@oclif/command'
+import { BaseCommand } from '../../base'
+import { displaySendTx } from '../../utils/cli'
+import { Flags } from '../../utils/command'
+
+export default class ElectionVote extends BaseCommand {
+ static description = 'Vote for a Validator Group in validator elections.'
+
+ static flags = {
+ ...BaseCommand.flags,
+ from: Flags.address({ required: true, description: "Voter's address" }),
+ for: Flags.address({
+ description: "Set vote for ValidatorGroup's address",
+ required: true,
+ }),
+ value: flags.string({ description: 'Amount of gold used to vote for group', required: true }),
+ }
+
+ static examples = [
+ 'vote --from 0x4443d0349e8b3075cba511a0a87796597602a0f1 --for 0x932fee04521f5fcb21949041bf161917da3f588b, --value 1000000',
+ ]
+ async run() {
+ const res = this.parse(ElectionVote)
+
+ this.kit.defaultAccount = res.flags.from
+ const election = await this.kit.contracts.getElection()
+ const tx = await election.vote(res.flags.for, new BigNumber(res.flags.value))
+ await displaySendTx('vote', tx)
+ }
+}
diff --git a/packages/cli/src/commands/lockedgold/authorize.ts b/packages/cli/src/commands/lockedgold/authorize.ts
new file mode 100644
index 00000000000..50ba89edd63
--- /dev/null
+++ b/packages/cli/src/commands/lockedgold/authorize.ts
@@ -0,0 +1,49 @@
+import { flags } from '@oclif/command'
+import { BaseCommand } from '../../base'
+import { displaySendTx } from '../../utils/cli'
+import { Flags } from '../../utils/command'
+
+export default class Authorize extends BaseCommand {
+ static description = 'Authorize validating or voting address for a Locked Gold account'
+
+ static flags = {
+ ...BaseCommand.flags,
+ from: Flags.address({ required: true }),
+ role: flags.string({
+ char: 'r',
+ options: ['voter', 'validator'],
+ description: 'Role to delegate',
+ }),
+ to: Flags.address({ required: true }),
+ }
+
+ static args = []
+
+ static examples = [
+ 'authorize --from 0x5409ED021D9299bf6814279A6A1411A7e866A631 --role voter --to 0xc1912fEE45d61C87Cc5EA59DaE31190FFFFf232d',
+ ]
+
+ async run() {
+ const res = this.parse(Authorize)
+
+ if (!res.flags.role) {
+ this.error(`Specify role with --role`)
+ return
+ }
+
+ if (!res.flags.to) {
+ this.error(`Specify authorized address with --to`)
+ return
+ }
+
+ this.kit.defaultAccount = res.flags.from
+ const lockedGold = await this.kit.contracts.getLockedGold()
+ let tx: any
+ if (res.flags.role == 'voter') {
+ tx = await lockedGold.authorizeVoter(res.flags.from, res.flags.to)
+ } else if (res.flags.role == 'validator') {
+ tx = await lockedGold.authorizeValidator(res.flags.from, res.flags.to)
+ }
+ await displaySendTx('authorizeTx', tx)
+ }
+}
diff --git a/packages/cli/src/commands/lockedgold/list.ts b/packages/cli/src/commands/lockedgold/list.ts
deleted file mode 100644
index 12da130666d..00000000000
--- a/packages/cli/src/commands/lockedgold/list.ts
+++ /dev/null
@@ -1,54 +0,0 @@
-import { Roles } from '@celo/contractkit'
-import chalk from 'chalk'
-import { cli } from 'cli-ux'
-import { BaseCommand } from '../../base'
-import { Args } from '../../utils/command'
-
-export default class List extends BaseCommand {
- static description = "View information about all of the account's commitments"
-
- static flags = {
- ...BaseCommand.flags,
- }
-
- static args = [Args.address('account')]
-
- static examples = ['list 0x5409ed021d9299bf6814279a6a1411a7e866a631']
-
- async run() {
- const { args } = this.parse(List)
- cli.action.start('Fetching commitments and delegates...')
- const lockedGold = await this.kit.contracts.getLockedGold()
- const commitments = await lockedGold.getCommitments(args.account)
- const delegates = await Promise.all(
- Object.keys(Roles).map(async (role: string) => ({
- role: role,
- address: await lockedGold.getDelegateFromAccountAndRole(
- args.account,
- Roles[role as keyof typeof Roles]
- ),
- }))
- )
- cli.action.stop()
-
- cli.table(delegates, {
- role: { header: 'Role', get: (a) => a.role },
- delegate: { get: (a) => a.address },
- })
-
- cli.log(chalk.bold.yellow('Total Gold Locked \t') + commitments.total.gold)
- cli.log(chalk.bold.red('Total Account Weight \t') + commitments.total.weight)
- if (commitments.locked.length > 0) {
- cli.table(commitments.locked, {
- noticePeriod: { header: 'NoticePeriod', get: (a) => a.time.toString() },
- value: { get: (a) => a.value.toString() },
- })
- }
- if (commitments.notified.length > 0) {
- cli.table(commitments.notified, {
- availabilityTime: { header: 'AvailabilityTime', get: (a) => a.time.toString() },
- value: { get: (a) => a.value.toString() },
- })
- }
- }
-}
diff --git a/packages/cli/src/commands/lockedgold/lock.ts b/packages/cli/src/commands/lockedgold/lock.ts
new file mode 100644
index 00000000000..ce086359e7e
--- /dev/null
+++ b/packages/cli/src/commands/lockedgold/lock.ts
@@ -0,0 +1,40 @@
+import { Address } from '@celo/utils/lib/address'
+import { flags } from '@oclif/command'
+import BigNumber from 'bignumber.js'
+import { BaseCommand } from '../../base'
+import { displaySendTx, failWith } from '../../utils/cli'
+import { Flags } from '../../utils/command'
+import { LockedGoldArgs } from '../../utils/lockedgold'
+
+export default class Lock extends BaseCommand {
+ static description = 'Locks Celo Gold to be used in governance and validator elections.'
+
+ static flags = {
+ ...BaseCommand.flags,
+ from: flags.string({ ...Flags.address, required: true }),
+ goldAmount: flags.string({ ...LockedGoldArgs.goldAmountArg, required: true }),
+ }
+
+ static args = []
+
+ static examples = [
+ 'lock --from 0x47e172F6CfB6c7D01C1574fa3E2Be7CC73269D95 --goldAmount 1000000000000000000',
+ ]
+
+ async run() {
+ const res = this.parse(Lock)
+ const address: Address = res.flags.from
+
+ this.kit.defaultAccount = address
+ const lockedGold = await this.kit.contracts.getLockedGold()
+
+ const goldAmount = new BigNumber(res.flags.goldAmount)
+
+ if (!goldAmount.gt(new BigNumber(0))) {
+ failWith(`require(goldAmount > 0) => [${goldAmount}]`)
+ }
+
+ const tx = lockedGold.lock()
+ await displaySendTx('lock', tx, { value: goldAmount.toString() })
+ }
+}
diff --git a/packages/cli/src/commands/lockedgold/rewards.ts b/packages/cli/src/commands/lockedgold/rewards.ts
deleted file mode 100644
index 7485f27a87e..00000000000
--- a/packages/cli/src/commands/lockedgold/rewards.ts
+++ /dev/null
@@ -1,51 +0,0 @@
-import { flags } from '@oclif/command'
-import { BaseCommand } from '../../base'
-import { displaySendTx } from '../../utils/cli'
-import { Flags } from '../../utils/command'
-
-export default class Rewards extends BaseCommand {
- static description = 'Manage rewards for Locked Gold account'
-
- static flags = {
- ...BaseCommand.flags,
- from: Flags.address({ required: true }),
- redeem: flags.boolean({
- char: 'r',
- description: 'Redeem accrued rewards from Locked Gold',
- exclusive: ['delegate'],
- }),
- delegate: Flags.address({
- char: 'd',
- description: 'Delegate rewards to provided account',
- exclusive: ['redeem'],
- }),
- }
-
- static args = []
-
- static examples = [
- 'rewards --redeem',
- 'rewards --delegate=0x56e172F6CfB6c7D01C1574fa3E2Be7CC73269D95',
- ]
-
- async run() {
- const res = this.parse(Rewards)
-
- if (!res.flags.redeem && !res.flags.delegate) {
- this.error(`Specify action with --redeem or --delegate`)
- return
- }
-
- this.kit.defaultAccount = res.flags.from
- const lockedGold = await this.kit.contracts.getLockedGold()
- if (res.flags.redeem) {
- const tx = lockedGold.redeemRewards()
- await displaySendTx('redeemRewards', tx)
- }
-
- if (res.flags.delegate) {
- const tx = await lockedGold.delegateRewards(res.flags.from, res.flags.delegate)
- await displaySendTx('delegateRewards', tx)
- }
- }
-}
diff --git a/packages/cli/src/commands/lockedgold/show.ts b/packages/cli/src/commands/lockedgold/show.ts
index 8966298cf71..b8d12cafce7 100644
--- a/packages/cli/src/commands/lockedgold/show.ts
+++ b/packages/cli/src/commands/lockedgold/show.ts
@@ -1,11 +1,6 @@
-import { flags } from '@oclif/command'
-import BigNumber from 'bignumber.js'
-import chalk from 'chalk'
-import { cli } from 'cli-ux'
import { BaseCommand } from '../../base'
import { printValueMap } from '../../utils/cli'
import { Args } from '../../utils/command'
-import { LockedGoldArgs } from '../../utils/lockedgold'
export default class Show extends BaseCommand {
static description = 'Show locked gold information for a given account'
@@ -20,7 +15,7 @@ export default class Show extends BaseCommand {
async run() {
// tslint:disable-next-line
- const { flags, args } = this.parse(Show)
+ const { args } = this.parse(Show)
const lockedGold = await this.kit.contracts.getLockedGold()
const nonvoting = await lockedGold.getAccountNonvotingLockedGold(args.account)
diff --git a/packages/cli/src/commands/lockedgold/unlock.ts b/packages/cli/src/commands/lockedgold/unlock.ts
new file mode 100644
index 00000000000..23d535fdf20
--- /dev/null
+++ b/packages/cli/src/commands/lockedgold/unlock.ts
@@ -0,0 +1,26 @@
+import { flags } from '@oclif/command'
+import { BaseCommand } from '../../base'
+import { displaySendTx } from '../../utils/cli'
+import { Flags } from '../../utils/command'
+import { LockedGoldArgs } from '../../utils/lockedgold'
+
+export default class Unlock extends BaseCommand {
+ static description = 'Unlocks Celo Gold, which can be withdrawn after the unlocking period.'
+
+ static flags = {
+ ...BaseCommand.flags,
+ from: Flags.address({ required: true }),
+ goldAmount: flags.string({ ...LockedGoldArgs.goldAmountArg, required: true }),
+ }
+
+ static args = []
+
+ static examples = ['unlock --goldAmount 500000000']
+
+ async run() {
+ const res = this.parse(Unlock)
+ this.kit.defaultAccount = res.flags.from
+ const lockedgold = await this.kit.contracts.getLockedGold()
+ await displaySendTx('unlock', lockedgold.unlock(res.flags.goldAmount))
+ }
+}
diff --git a/packages/cli/src/commands/lockedgold/withdraw.ts b/packages/cli/src/commands/lockedgold/withdraw.ts
index b6f58968402..cbeb0288890 100644
--- a/packages/cli/src/commands/lockedgold/withdraw.ts
+++ b/packages/cli/src/commands/lockedgold/withdraw.ts
@@ -1,7 +1,6 @@
import { BaseCommand } from '../../base'
import { displaySendTx } from '../../utils/cli'
import { Flags } from '../../utils/command'
-import { LockedGoldArgs } from '../../utils/lockedgold'
export default class Withdraw extends BaseCommand {
static description = 'Withdraw unlocked gold whose unlocking period has passed.'
@@ -11,18 +10,18 @@ export default class Withdraw extends BaseCommand {
from: Flags.address({ required: true }),
}
- static examples = ['withdraw']
+ static examples = ['withdraw --from 0x47e172F6CfB6c7D01C1574fa3E2Be7CC73269D95']
async run() {
// tslint:disable-next-line
- const { flags, args } = this.parse(Withdraw)
+ const { flags } = this.parse(Withdraw)
this.kit.defaultAccount = flags.from
const lockedgold = await this.kit.contracts.getLockedGold()
- const pendingWithdrawals = await lockedgold.getPendingWithdrawals()
+ const pendingWithdrawals = await lockedgold.getPendingWithdrawals(flags.from)
const currentTime = Math.round(new Date().getTime() / 1000)
let withdrawals = 0
for (let i = 0; i < pendingWithdrawals.length; i++) {
- if (pendingWithdrawals[i].time <= currentTime) {
+ if (pendingWithdrawals[i].time.isLessThan(currentTime)) {
await displaySendTx('withdraw', lockedgold.withdraw(i - withdrawals))
withdrawals += 1
}
diff --git a/packages/cli/src/utils/key_generator.test.ts b/packages/cli/src/utils/key_generator.test.ts
index 60e4732c792..62038e25dec 100644
--- a/packages/cli/src/utils/key_generator.test.ts
+++ b/packages/cli/src/utils/key_generator.test.ts
@@ -2,7 +2,7 @@ import { validateMnemonic } from 'bip39'
import { ReactNativeBip39MnemonicGenerator } from './key_generator'
describe('Mnemonic validation', () => {
- it('should generatet 24 word mnemonic', () => {
+ it('should generate 24 word mnemonic', () => {
const mnemonic: string = ReactNativeBip39MnemonicGenerator.generateMnemonic()
expect(mnemonic.split(' ').length).toEqual(24)
})
diff --git a/packages/contractkit/src/wrappers/Election.ts b/packages/contractkit/src/wrappers/Election.ts
new file mode 100644
index 00000000000..df54eea1fad
--- /dev/null
+++ b/packages/contractkit/src/wrappers/Election.ts
@@ -0,0 +1,206 @@
+import { eqAddress } from '@celo/utils/lib/address'
+import { zip } from '@celo/utils/lib/collections'
+import BigNumber from 'bignumber.js'
+import { Address, NULL_ADDRESS } from '../base'
+import { Election } from '../generated/types/Election'
+import {
+ BaseWrapper,
+ CeloTransactionObject,
+ proxyCall,
+ proxySend,
+ toBigNumber,
+ toNumber,
+ wrapSend,
+} from './BaseWrapper'
+
+export interface Validator {
+ address: Address
+ id: string
+ name: string
+ url: string
+ publicKey: string
+ affiliation: string | null
+}
+
+export interface ValidatorGroup {
+ address: Address
+ id: string
+ name: string
+ url: string
+ members: Address[]
+}
+
+export interface ValidatorGroupVote {
+ address: Address
+ votes: BigNumber
+ eligible: boolean
+}
+
+export interface ElectionConfig {
+ minElectableValidators: BigNumber
+ maxElectableValidators: BigNumber
+ electabilityThreshold: BigNumber
+ maxNumGroupsVotedFor: BigNumber
+}
+
+/**
+ * Contract for voting for validators and managing validator groups.
+ */
+export class ElectionWrapper extends BaseWrapper {
+ activate = proxySend(this.kit, this.contract.methods.activate)
+ /**
+ * Returns the minimum number of validators that can be elected.
+ * @returns The minimum number of validators that can be elected.
+ */
+ minElectableValidators = proxyCall(
+ this.contract.methods.minElectableValidators,
+ undefined,
+ toBigNumber
+ )
+ /**
+ * Returns the maximum number of validators that can be elected.
+ * @returns The maximum number of validators that can be elected.
+ */
+ maxElectableValidators = proxyCall(
+ this.contract.methods.maxElectableValidators,
+ undefined,
+ toBigNumber
+ )
+ /**
+ * Returns the current election threshold.
+ * @returns Election threshold.
+ */
+ electabilityThreshold = proxyCall(
+ this.contract.methods.getElectabilityThreshold,
+ undefined,
+ toBigNumber
+ )
+ validatorAddressFromCurrentSet = proxyCall(this.contract.methods.validatorAddressFromCurrentSet)
+ numberValidatorsInCurrentSet = proxyCall(
+ this.contract.methods.numberValidatorsInCurrentSet,
+ undefined,
+ toNumber
+ )
+
+ getGroupsVotedFor: (account: Address) => Promise = proxyCall(
+ this.contract.methods.getGroupsVotedFor
+ )
+
+ /**
+ * Returns current configuration parameters.
+ */
+ async getConfig(): Promise {
+ const res = await Promise.all([
+ this.minElectableValidators(),
+ this.maxElectableValidators(),
+ this.electabilityThreshold(),
+ this.contract.methods.maxNumGroupsVotedFor().call(),
+ ])
+ return {
+ minElectableValidators: res[0],
+ maxElectableValidators: res[1],
+ electabilityThreshold: res[2],
+ maxNumGroupsVotedFor: toBigNumber(res[3]),
+ }
+ }
+
+ async getValidatorSetAddresses(): Promise {
+ const numberValidators = await this.numberValidatorsInCurrentSet()
+
+ const validatorAddressPromises = []
+
+ for (let i = 0; i < numberValidators; i++) {
+ validatorAddressPromises.push(this.validatorAddressFromCurrentSet(i))
+ }
+
+ return Promise.all(validatorAddressPromises)
+ }
+
+ async getValidatorGroupsVotes(): Promise {
+ const validators = await this.kit.contracts.getValidators()
+ const vgAddresses = (await validators.getRegisteredValidatorGroups()).map((g) => g.address)
+ const vgVotes = await Promise.all(
+ vgAddresses.map((g) => this.contract.methods.getGroupTotalVotes(g).call())
+ )
+ const vgEligible = await Promise.all(
+ vgAddresses.map((g) => this.contract.methods.getGroupEligibility(g).call())
+ )
+ return vgAddresses.map((a, i) => ({
+ address: a,
+ votes: toBigNumber(vgVotes[i]),
+ eligible: vgEligible[i],
+ }))
+ }
+
+ async getEligibleValidatorGroupsVotes(): Promise {
+ const res = await this.contract.methods.getEligibleValidatorGroupsVoteTotals().call()
+ return zip((a, b) => ({ address: a, votes: new BigNumber(b), eligible: true }), res[0], res[1])
+ }
+
+ /*
+ async revokeVote(): Promise> {
+ if (this.kit.defaultAccount == null) {
+ throw new Error(`missing from at new ValdidatorUtils()`)
+ }
+
+ const lockedGold = await this.kit.contracts.getLockedGold()
+ const votingDetails = await lockedGold.getVotingDetails(this.kit.defaultAccount)
+ const votedGroup = await this.getVoteFrom(votingDetails.accountAddress)
+
+ if (votedGroup == null) {
+ throw new Error(`Not current vote for ${this.kit.defaultAccount}`)
+ }
+
+ const { lesser, greater } = await this.findLesserAndGreaterAfterVote(
+ votedGroup,
+ votingDetails.weight.negated()
+ )
+
+ return wrapSend(this.kit, this.contract.methods.revokeVote(lesser, greater))
+ }
+ */
+
+ async vote(validatorGroup: Address, value: BigNumber): Promise> {
+ if (this.kit.defaultAccount == null) {
+ throw new Error(`missing from at new ValdidatorUtils()`)
+ }
+
+ const { lesser, greater } = await this.findLesserAndGreaterAfterVote(validatorGroup, value)
+
+ return wrapSend(
+ this.kit,
+ this.contract.methods.vote(validatorGroup, value.toString(), lesser, greater)
+ )
+ }
+
+ private async findLesserAndGreaterAfterVote(
+ votedGroup: Address,
+ voteWeight: BigNumber
+ ): Promise<{ lesser: Address; greater: Address }> {
+ const currentVotes = await this.getEligibleValidatorGroupsVotes()
+
+ const selectedGroup = currentVotes.find((cv) => eqAddress(cv.address, votedGroup))
+
+ // modify the list
+ if (selectedGroup) {
+ selectedGroup.votes = selectedGroup.votes.plus(voteWeight)
+ } else {
+ currentVotes.push({
+ address: votedGroup,
+ votes: voteWeight,
+ eligible: true,
+ })
+ }
+
+ // re-sort
+ currentVotes.sort((a, b) => a.votes.comparedTo(b.votes))
+
+ // find new index
+ const newIdx = currentVotes.findIndex((cv) => eqAddress(cv.address, votedGroup))
+
+ return {
+ lesser: newIdx === 0 ? NULL_ADDRESS : currentVotes[newIdx - 1].address,
+ greater: newIdx === currentVotes.length - 1 ? NULL_ADDRESS : currentVotes[newIdx + 1].address,
+ }
+ }
+}
From d6ef3ec4c605feb7da3070406a1cf29a8b1d3b27 Mon Sep 17 00:00:00 2001
From: Asa Oines
Date: Wed, 2 Oct 2019 19:12:41 -0700
Subject: [PATCH 021/149] Update oclif
---
packages/cli/package.json | 11 +++++++----
1 file changed, 7 insertions(+), 4 deletions(-)
diff --git a/packages/cli/package.json b/packages/cli/package.json
index 6f1db7a1408..a5d3efbd695 100644
--- a/packages/cli/package.json
+++ b/packages/cli/package.json
@@ -77,20 +77,23 @@
"account": {
"description": "Manage your account, send and receive Celo Gold and Celo Dollars"
},
- "bonds": {
- "description": "Manage Locked Gold to participate in governance and earn rewards"
+ "election": {
+ "description": "View and manage validator elections"
},
"config": {
"description": "Configure CLI options which persist across commands"
},
+ "lockedgold": {
+ "description": "View and manage locked celo gold"
+ },
"node": {
"description": "Manage your full node"
},
"validator": {
- "description": "View validator information and register your own"
+ "description": "View and manage validators"
},
"validatorgroup": {
- "description": "View validator group information and cast votes"
+ "description": "View and manage validator groups"
},
"exchange": {
"description": "Commands for interacting with the Exchange"
From 615ec4f177a3576e9c0a6d6cd5ae211c33d8d9f3 Mon Sep 17 00:00:00 2001
From: Asa Oines
Date: Thu, 3 Oct 2019 10:43:54 -0700
Subject: [PATCH 022/149] Checkpoint
---
packages/celotool/src/e2e-tests/sync_tests.ts | 2 +-
packages/celotool/src/e2e-tests/utils.ts | 4 ++--
packages/cli/src/commands/lockedgold/lock.ts | 1 +
packages/cli/src/commands/lockedgold/show.ts | 15 ++++++++-------
packages/cli/src/utils/cli.ts | 3 +++
5 files changed, 15 insertions(+), 10 deletions(-)
diff --git a/packages/celotool/src/e2e-tests/sync_tests.ts b/packages/celotool/src/e2e-tests/sync_tests.ts
index 2d99ad4944b..da38f377d72 100644
--- a/packages/celotool/src/e2e-tests/sync_tests.ts
+++ b/packages/celotool/src/e2e-tests/sync_tests.ts
@@ -40,7 +40,7 @@ describe('sync tests', function(this: any) {
peers: [await getEnode(8545)],
}
await initAndStartGeth(hooks.gethBinaryPath, fullInstance)
- await sleep(3)
+ await sleep(30000000000000000000)
})
after(hooks.after)
diff --git a/packages/celotool/src/e2e-tests/utils.ts b/packages/celotool/src/e2e-tests/utils.ts
index 0bc2255bc57..b05dfd7e01e 100644
--- a/packages/celotool/src/e2e-tests/utils.ts
+++ b/packages/celotool/src/e2e-tests/utils.ts
@@ -234,8 +234,8 @@ async function waitForPortOpen(host: string, port: number, seconds: number) {
return false
}
-export function sleep(seconds: number) {
- return new Promise((resolve) => setTimeout(resolve, seconds * 1000))
+export async function sleep(seconds: number) {
+ await execCmd('sleep', [seconds.toString()])
}
export async function getEnode(port: number, ws: boolean = false) {
diff --git a/packages/cli/src/commands/lockedgold/lock.ts b/packages/cli/src/commands/lockedgold/lock.ts
index ce086359e7e..8f121cbd639 100644
--- a/packages/cli/src/commands/lockedgold/lock.ts
+++ b/packages/cli/src/commands/lockedgold/lock.ts
@@ -34,6 +34,7 @@ export default class Lock extends BaseCommand {
failWith(`require(goldAmount > 0) => [${goldAmount}]`)
}
+ // TODO(asa): Why is this failing?
const tx = lockedGold.lock()
await displaySendTx('lock', tx, { value: goldAmount.toString() })
}
diff --git a/packages/cli/src/commands/lockedgold/show.ts b/packages/cli/src/commands/lockedgold/show.ts
index b8d12cafce7..a249a0f0172 100644
--- a/packages/cli/src/commands/lockedgold/show.ts
+++ b/packages/cli/src/commands/lockedgold/show.ts
@@ -1,6 +1,7 @@
import { BaseCommand } from '../../base'
-import { printValueMap } from '../../utils/cli'
+import { printValueMapRecursive } from '../../utils/cli'
import { Args } from '../../utils/command'
+import { eqAddress } from '@celo/utils/lib/address'
export default class Show extends BaseCommand {
static description = 'Show locked gold information for a given account'
@@ -18,8 +19,8 @@ export default class Show extends BaseCommand {
const { args } = this.parse(Show)
const lockedGold = await this.kit.contracts.getLockedGold()
- const nonvoting = await lockedGold.getAccountNonvotingLockedGold(args.account)
- const total = await lockedGold.getAccountTotalLockedGold(args.account)
+ const nonvoting = (await lockedGold.getAccountNonvotingLockedGold(args.account)).toString()
+ const total = (await lockedGold.getAccountTotalLockedGold(args.account)).toString()
const voter = await lockedGold.getVoterFromAccount(args.account)
const validator = await lockedGold.getValidatorFromAccount(args.account)
const pendingWithdrawals = await lockedGold.getPendingWithdrawals(args.account)
@@ -29,11 +30,11 @@ export default class Show extends BaseCommand {
nonvoting,
},
authorizations: {
- voter: voter == args.account ? null : voter,
- validator: validator == args.account ? null : validator,
+ voter: eqAddress(voter, args.account) ? 'None' : voter,
+ validator: eqAddress(validator, args.account) ? 'None' : validator,
},
- pendingWithdrawals,
+ pendingWithdrawals: pendingWithdrawals.length > 0 ? pendingWithdrawals : '[]',
}
- printValueMap(info)
+ printValueMapRecursive(info)
}
}
diff --git a/packages/cli/src/utils/cli.ts b/packages/cli/src/utils/cli.ts
index 4102c94ef7d..b64c8aa769b 100644
--- a/packages/cli/src/utils/cli.ts
+++ b/packages/cli/src/utils/cli.ts
@@ -7,6 +7,9 @@ import { Tx } from 'web3/eth/types'
export async function displaySendTx(name: string, txObj: CeloTransactionObject, tx?: Tx) {
cli.action.start(`Sending Transaction: ${name}`)
+ console.log(await txObj.estimateGas(tx))
+ console.log('txobj', txObj)
+ console.log('tx', tx)
const txResult = await txObj.send(tx)
const txHash = await txResult.getHash()
From 3ff2fe4f0c09f045bb73aad1bc2f902b83c5087e Mon Sep 17 00:00:00 2001
From: Asa Oines
Date: Thu, 3 Oct 2019 12:28:57 -0700
Subject: [PATCH 023/149] CLI seems to be working
---
packages/cli/src/commands/validatorgroup/list.ts | 1 +
.../cli/src/commands/validatorgroup/member.ts | 10 +++++++++-
.../cli/src/commands/validatorgroup/register.ts | 5 ++++-
packages/cli/src/utils/cli.ts | 3 ---
packages/cli/src/utils/helpers.ts | 1 +
packages/contractkit/src/wrappers/Election.ts | 14 ++++++++++++++
packages/contractkit/src/wrappers/Validators.ts | 16 ++++++++++++++--
.../protocol/contracts/common/UsingRegistry.sol | 2 +-
.../contracts/common/linkedlists/LinkedList.sol | 2 +-
.../protocol/contracts/governance/Election.sol | 3 ++-
.../protocol/contracts/governance/LockedGold.sol | 8 ++++----
.../protocol/contracts/governance/Validators.sol | 11 ++++++-----
12 files changed, 57 insertions(+), 19 deletions(-)
diff --git a/packages/cli/src/commands/validatorgroup/list.ts b/packages/cli/src/commands/validatorgroup/list.ts
index 013cb393e94..7743ab63f9a 100644
--- a/packages/cli/src/commands/validatorgroup/list.ts
+++ b/packages/cli/src/commands/validatorgroup/list.ts
@@ -22,6 +22,7 @@ export default class ValidatorGroupList extends BaseCommand {
address: {},
name: {},
url: {},
+ commission: { get: (r) => r.commission.toFixed() },
members: { get: (r) => r.members.length },
})
}
diff --git a/packages/cli/src/commands/validatorgroup/member.ts b/packages/cli/src/commands/validatorgroup/member.ts
index 79e41a16835..f09291f42d5 100644
--- a/packages/cli/src/commands/validatorgroup/member.ts
+++ b/packages/cli/src/commands/validatorgroup/member.ts
@@ -37,11 +37,19 @@ export default class ValidatorGroupRegister extends BaseCommand {
this.kit.defaultAccount = res.flags.from
const validators = await this.kit.contracts.getValidators()
+ const election = await this.kit.contracts.getElection()
if (res.flags.accept) {
await displaySendTx('addMember', validators.addMember((res.args as any).validatorAddress))
+ if ((await validators.getGroupNumMembers(res.flags.from)) === '1') {
+ const tx = await election.markGroupEligible(res.flags.from)
+ await displaySendTx('markGroupEligible', tx)
+ }
} else {
- await displaySendTx('addMember', validators.removeMember((res.args as any).validatorAddress))
+ await displaySendTx(
+ 'removeMember',
+ validators.removeMember((res.args as any).validatorAddress)
+ )
}
}
}
diff --git a/packages/cli/src/commands/validatorgroup/register.ts b/packages/cli/src/commands/validatorgroup/register.ts
index 8eaca3c9c73..d3645c7fb6a 100644
--- a/packages/cli/src/commands/validatorgroup/register.ts
+++ b/packages/cli/src/commands/validatorgroup/register.ts
@@ -1,7 +1,9 @@
+import BigNumber from 'bignumber.js'
import { flags } from '@oclif/command'
import { BaseCommand } from '../../base'
import { displaySendTx } from '../../utils/cli'
import { Flags } from '../../utils/command'
+import { toFixed } from '@celo/utils/lib/fixidity'
export default class ValidatorGroupRegister extends BaseCommand {
static description = 'Register a new Validator Group'
@@ -23,10 +25,11 @@ export default class ValidatorGroupRegister extends BaseCommand {
this.kit.defaultAccount = res.flags.from
const validators = await this.kit.contracts.getValidators()
+ const commission = toFixed(new BigNumber(res.flags.commission)).toFixed()
await displaySendTx(
'registerValidatorGroup',
- validators.registerValidatorGroup(res.flags.name, res.flags.url, res.flags.commission)
+ validators.registerValidatorGroup(res.flags.name, res.flags.url, commission)
)
}
}
diff --git a/packages/cli/src/utils/cli.ts b/packages/cli/src/utils/cli.ts
index b64c8aa769b..4102c94ef7d 100644
--- a/packages/cli/src/utils/cli.ts
+++ b/packages/cli/src/utils/cli.ts
@@ -7,9 +7,6 @@ import { Tx } from 'web3/eth/types'
export async function displaySendTx(name: string, txObj: CeloTransactionObject, tx?: Tx) {
cli.action.start(`Sending Transaction: ${name}`)
- console.log(await txObj.estimateGas(tx))
- console.log('txobj', txObj)
- console.log('tx', tx)
const txResult = await txObj.send(tx)
const txHash = await txResult.getHash()
diff --git a/packages/cli/src/utils/helpers.ts b/packages/cli/src/utils/helpers.ts
index 775752e7170..34632947bc3 100644
--- a/packages/cli/src/utils/helpers.ts
+++ b/packages/cli/src/utils/helpers.ts
@@ -57,6 +57,7 @@ export async function nodeIsSynced(web3: Web3): Promise {
}
export async function requireNodeIsSynced(web3: Web3) {
+ return
if (!(await nodeIsSynced(web3))) {
failWith('Node is not currently synced. Run node:synced to check its status')
}
diff --git a/packages/contractkit/src/wrappers/Election.ts b/packages/contractkit/src/wrappers/Election.ts
index df54eea1fad..f8a9a875dbf 100644
--- a/packages/contractkit/src/wrappers/Election.ts
+++ b/packages/contractkit/src/wrappers/Election.ts
@@ -160,6 +160,20 @@ export class ElectionWrapper extends BaseWrapper {
}
*/
+ async markGroupEligible(validatorGroup: Address): Promise> {
+ if (this.kit.defaultAccount == null) {
+ throw new Error(`missing from at new ValdidatorUtils()`)
+ }
+
+ const value = toBigNumber(await this.contract.methods.getGroupTotalVotes(validatorGroup).call())
+ const { lesser, greater } = await this.findLesserAndGreaterAfterVote(validatorGroup, value)
+
+ return wrapSend(
+ this.kit,
+ this.contract.methods.markGroupEligible(validatorGroup, lesser, greater)
+ )
+ }
+
async vote(validatorGroup: Address, value: BigNumber): Promise> {
if (this.kit.defaultAccount == null) {
throw new Error(`missing from at new ValdidatorUtils()`)
diff --git a/packages/contractkit/src/wrappers/Validators.ts b/packages/contractkit/src/wrappers/Validators.ts
index 37922243db9..4103279bc8f 100644
--- a/packages/contractkit/src/wrappers/Validators.ts
+++ b/packages/contractkit/src/wrappers/Validators.ts
@@ -1,7 +1,8 @@
import BigNumber from 'bignumber.js'
import { Address } from '../base'
import { Validators } from '../generated/types/Validators'
-import { BaseWrapper, proxySend, toBigNumber } from './BaseWrapper'
+import { BaseWrapper, proxyCall, proxySend, toBigNumber } from './BaseWrapper'
+import { fromFixed } from '@celo/utils/lib/fixidity'
export interface Validator {
address: Address
@@ -16,6 +17,7 @@ export interface ValidatorGroup {
name: string
url: string
members: Address[]
+ commission: BigNumber
}
export interface RegistrationRequirements {
@@ -86,6 +88,10 @@ export class ValidatorsWrapper extends BaseWrapper {
return Promise.all(vgAddresses.map((addr) => this.getValidator(addr)))
}
+ getGroupNumMembers: (group: Address) => Promise = proxyCall(
+ this.contract.methods.getGroupNumMembers
+ )
+
async getValidator(address: Address): Promise {
const res = await this.contract.methods.getValidator(address).call()
return {
@@ -104,6 +110,12 @@ export class ValidatorsWrapper extends BaseWrapper {
async getValidatorGroup(address: Address): Promise {
const res = await this.contract.methods.getValidatorGroup(address).call()
- return { address, name: res[0], url: res[1], members: res[2] }
+ return {
+ address,
+ name: res[0],
+ url: res[1],
+ members: res[2],
+ commission: fromFixed(new BigNumber(res[3])),
+ }
}
}
diff --git a/packages/protocol/contracts/common/UsingRegistry.sol b/packages/protocol/contracts/common/UsingRegistry.sol
index 4ae16b3302e..aa742dedcf5 100644
--- a/packages/protocol/contracts/common/UsingRegistry.sol
+++ b/packages/protocol/contracts/common/UsingRegistry.sol
@@ -38,7 +38,7 @@ contract UsingRegistry is Ownable {
IRegistry public registry;
modifier onlyRegisteredContract(bytes32 identifierHash) {
- require(registry.getAddressForOrDie(identifierHash) == msg.sender);
+ require(registry.getAddressForOrDie(identifierHash) == msg.sender, "only registered contract");
_;
}
/**
diff --git a/packages/protocol/contracts/common/linkedlists/LinkedList.sol b/packages/protocol/contracts/common/linkedlists/LinkedList.sol
index e1e514668dc..fc55f5c556b 100644
--- a/packages/protocol/contracts/common/linkedlists/LinkedList.sol
+++ b/packages/protocol/contracts/common/linkedlists/LinkedList.sol
@@ -102,7 +102,7 @@ library LinkedList {
*/
function remove(List storage list, bytes32 key) public {
Element storage element = list.elements[key];
- require(key != bytes32(0) && contains(list, key));
+ require(key != bytes32(0) && contains(list, key), "can't remove");
if (element.previousKey != bytes32(0)) {
Element storage previousElement = list.elements[element.previousKey];
previousElement.nextKey = element.nextKey;
diff --git a/packages/protocol/contracts/governance/Election.sol b/packages/protocol/contracts/governance/Election.sol
index f4ed545dbca..23b8bd087d7 100644
--- a/packages/protocol/contracts/governance/Election.sol
+++ b/packages/protocol/contracts/governance/Election.sol
@@ -498,12 +498,13 @@ contract Election is Ownable, ReentrancyGuard, Initializable, UsingRegistry {
* @param lesser The address of the group that has received fewer votes than this group.
* @param greater The address of the group that has received more votes than this group.
*/
- function markGroupEligible(address group, address lesser, address greater) external {
+ function markGroupEligible(address group, address lesser, address greater) external returns (bool) {
require(!votes.total.eligible.contains(group));
require(getValidators().getGroupNumMembers(group) > 0);
uint256 value = votes.pending.total[group].add(votes.active.total[group]);
votes.total.eligible.insert(group, value, lesser, greater);
emit ValidatorGroupMarkedEligible(group);
+ return true;
}
/**
diff --git a/packages/protocol/contracts/governance/LockedGold.sol b/packages/protocol/contracts/governance/LockedGold.sol
index b7f42f9c350..1e7503543b4 100644
--- a/packages/protocol/contracts/governance/LockedGold.sol
+++ b/packages/protocol/contracts/governance/LockedGold.sol
@@ -124,8 +124,8 @@ contract LockedGold is ILockedGold, ReentrancyGuard, Initializable, UsingRegistr
* @notice Locks gold to be used for voting.
*/
function lock() external payable nonReentrant {
- require(isAccount(msg.sender));
- require(msg.value > 0);
+ require(isAccount(msg.sender), "not account");
+ require(msg.value > 0, "no value");
_incrementNonvotingAccountBalance(msg.sender, msg.value);
emit GoldLocked(msg.sender, msg.value);
}
@@ -393,10 +393,10 @@ contract LockedGold is ILockedGold, ReentrancyGuard, Initializable, UsingRegistr
)
private
{
- require(isAccount(msg.sender) && isNotAccount(current) && isNotAuthorized(current), "Accounts");
+ require(isAccount(msg.sender) && isNotAccount(current) && isNotAuthorized(current), "account checks");
address signer = Signatures.getSignerOfAddress(msg.sender, v, r, s);
- require(signer == current, "Signature");
+ require(signer == current, "signature checks");
authorizedBy[previous] = address(0);
authorizedBy[current] = msg.sender;
diff --git a/packages/protocol/contracts/governance/Validators.sol b/packages/protocol/contracts/governance/Validators.sol
index fdce901dd01..741ef35ad4f 100644
--- a/packages/protocol/contracts/governance/Validators.sol
+++ b/packages/protocol/contracts/governance/Validators.sol
@@ -246,7 +246,8 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
publicKeysData.length == (64 + 48 + 96)
);
// Use the proof of possession bytes
- require(checkProofOfPossession(publicKeysData.slice(64, 48 + 96)));
+ // DO NOT SUBMIT: Commented out for testing.
+ // require(checkProofOfPossession(publicKeysData.slice(64, 48 + 96)));
address account = getLockedGold().getAccountFromValidator(msg.sender);
require(!isValidator(account) && !isValidatorGroup(account));
@@ -436,7 +437,7 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
*/
function removeMember(address validator) external nonReentrant returns (bool) {
address account = getLockedGold().getAccountFromValidator(msg.sender);
- require(isValidatorGroup(account) && isValidator(validator));
+ require(isValidatorGroup(account) && isValidator(validator), "is not group and validator");
return _removeMember(account, validator);
}
@@ -505,11 +506,11 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
)
external
view
- returns (string memory, string memory, address[] memory)
+ returns (string memory, string memory, address[] memory, uint256)
{
require(isValidatorGroup(account));
ValidatorGroup storage group = groups[account];
- return (group.name, group.url, group.members.getKeys());
+ return (group.name, group.url, group.members.getKeys(), group.commission.unwrap());
}
/**
@@ -644,7 +645,7 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
*/
function _removeMember(address group, address validator) private returns (bool) {
ValidatorGroup storage _group = groups[group];
- require(validators[validator].affiliation == group && _group.members.contains(validator));
+ require(validators[validator].affiliation == group && _group.members.contains(validator), "not a member");
_group.members.remove(validator);
emit ValidatorGroupMemberRemoved(group, validator);
From d3b83c80e2863dbe35403c40ef1e1fc45346602f Mon Sep 17 00:00:00 2001
From: Asa Oines
Date: Thu, 3 Oct 2019 12:43:52 -0700
Subject: [PATCH 024/149] cleanup
---
packages/celotool/src/e2e-tests/sync_tests.ts | 2 +-
packages/cli/src/commands/lockedgold/lock.ts | 1 -
packages/cli/src/utils/helpers.ts | 1 -
.../contracts/common/linkedlists/LinkedList.sol | 2 +-
.../protocol/contracts/governance/Election.sol | 17 +++++++++++++++--
.../contracts/governance/LockedGold.sol | 14 +++++---------
.../contracts/governance/Validators.sol | 8 +++-----
7 files changed, 25 insertions(+), 20 deletions(-)
diff --git a/packages/celotool/src/e2e-tests/sync_tests.ts b/packages/celotool/src/e2e-tests/sync_tests.ts
index da38f377d72..2d99ad4944b 100644
--- a/packages/celotool/src/e2e-tests/sync_tests.ts
+++ b/packages/celotool/src/e2e-tests/sync_tests.ts
@@ -40,7 +40,7 @@ describe('sync tests', function(this: any) {
peers: [await getEnode(8545)],
}
await initAndStartGeth(hooks.gethBinaryPath, fullInstance)
- await sleep(30000000000000000000)
+ await sleep(3)
})
after(hooks.after)
diff --git a/packages/cli/src/commands/lockedgold/lock.ts b/packages/cli/src/commands/lockedgold/lock.ts
index 8f121cbd639..ce086359e7e 100644
--- a/packages/cli/src/commands/lockedgold/lock.ts
+++ b/packages/cli/src/commands/lockedgold/lock.ts
@@ -34,7 +34,6 @@ export default class Lock extends BaseCommand {
failWith(`require(goldAmount > 0) => [${goldAmount}]`)
}
- // TODO(asa): Why is this failing?
const tx = lockedGold.lock()
await displaySendTx('lock', tx, { value: goldAmount.toString() })
}
diff --git a/packages/cli/src/utils/helpers.ts b/packages/cli/src/utils/helpers.ts
index 34632947bc3..775752e7170 100644
--- a/packages/cli/src/utils/helpers.ts
+++ b/packages/cli/src/utils/helpers.ts
@@ -57,7 +57,6 @@ export async function nodeIsSynced(web3: Web3): Promise {
}
export async function requireNodeIsSynced(web3: Web3) {
- return
if (!(await nodeIsSynced(web3))) {
failWith('Node is not currently synced. Run node:synced to check its status')
}
diff --git a/packages/protocol/contracts/common/linkedlists/LinkedList.sol b/packages/protocol/contracts/common/linkedlists/LinkedList.sol
index fc55f5c556b..e1e514668dc 100644
--- a/packages/protocol/contracts/common/linkedlists/LinkedList.sol
+++ b/packages/protocol/contracts/common/linkedlists/LinkedList.sol
@@ -102,7 +102,7 @@ library LinkedList {
*/
function remove(List storage list, bytes32 key) public {
Element storage element = list.elements[key];
- require(key != bytes32(0) && contains(list, key), "can't remove");
+ require(key != bytes32(0) && contains(list, key));
if (element.previousKey != bytes32(0)) {
Element storage previousElement = list.elements[element.previousKey];
previousElement.nextKey = element.nextKey;
diff --git a/packages/protocol/contracts/governance/Election.sol b/packages/protocol/contracts/governance/Election.sol
index 23b8bd087d7..d5124b5d679 100644
--- a/packages/protocol/contracts/governance/Election.sol
+++ b/packages/protocol/contracts/governance/Election.sol
@@ -181,7 +181,13 @@ contract Election is Ownable, ReentrancyGuard, Initializable, UsingRegistry {
* @param _maxNumGroupsVotedFor The maximum number of groups an account can vote for.
* @return True upon success.
*/
- function setMaxNumGroupsVotedFor(uint256 _maxNumGroupsVotedFor) external onlyOwner returns (bool) {
+ function setMaxNumGroupsVotedFor(
+ uint256 _maxNumGroupsVotedFor
+ )
+ external
+ onlyOwner
+ returns (bool)
+ {
require(_maxNumGroupsVotedFor != maxNumGroupsVotedFor);
maxNumGroupsVotedFor = _maxNumGroupsVotedFor;
emit MaxNumGroupsVotedForSet(_maxNumGroupsVotedFor);
@@ -498,7 +504,14 @@ contract Election is Ownable, ReentrancyGuard, Initializable, UsingRegistry {
* @param lesser The address of the group that has received fewer votes than this group.
* @param greater The address of the group that has received more votes than this group.
*/
- function markGroupEligible(address group, address lesser, address greater) external returns (bool) {
+ function markGroupEligible(
+ address group,
+ address lesser,
+ address greater
+ )
+ external
+ returns (bool)
+ {
require(!votes.total.eligible.contains(group));
require(getValidators().getGroupNumMembers(group) > 0);
uint256 value = votes.pending.total[group].add(votes.active.total[group]);
diff --git a/packages/protocol/contracts/governance/LockedGold.sol b/packages/protocol/contracts/governance/LockedGold.sol
index 1e7503543b4..6f89b30453c 100644
--- a/packages/protocol/contracts/governance/LockedGold.sol
+++ b/packages/protocol/contracts/governance/LockedGold.sol
@@ -187,13 +187,12 @@ contract LockedGold is ILockedGold, ReentrancyGuard, Initializable, UsingRegistr
* @param value The amount of gold to unlock.
*/
function unlock(uint256 value) external nonReentrant {
- require(isAccount(msg.sender), "not account");
+ require(isAccount(msg.sender));
Account storage account = accounts[msg.sender];
MustMaintain memory requirement = account.balances.requirements;
require(
now >= requirement.timestamp ||
- getAccountTotalLockedGold(msg.sender).sub(value) >= requirement.value,
- "didn't meet mustmaintain requirements"
+ getAccountTotalLockedGold(msg.sender).sub(value) >= requirement.value
);
_decrementNonvotingAccountBalance(msg.sender, value);
uint256 available = now.add(unlockingPeriod);
@@ -263,10 +262,7 @@ contract LockedGold is ILockedGold, ReentrancyGuard, Initializable, UsingRegistr
function getAccountFromVoter(address accountOrVoter) external view returns (address) {
address authorizingAccount = authorizedBy[accountOrVoter];
if (authorizingAccount != address(0)) {
- require(
- accounts[authorizingAccount].authorizations.voting == accountOrVoter,
- 'failed first check'
- );
+ require(accounts[authorizingAccount].authorizations.voting == accountOrVoter);
return authorizingAccount;
} else {
require(isAccount(accountOrVoter));
@@ -393,10 +389,10 @@ contract LockedGold is ILockedGold, ReentrancyGuard, Initializable, UsingRegistr
)
private
{
- require(isAccount(msg.sender) && isNotAccount(current) && isNotAuthorized(current), "account checks");
+ require(isAccount(msg.sender) && isNotAccount(current) && isNotAuthorized(current));
address signer = Signatures.getSignerOfAddress(msg.sender, v, r, s);
- require(signer == current, "signature checks");
+ require(signer == current);
authorizedBy[previous] = address(0);
authorizedBy[current] = msg.sender;
diff --git a/packages/protocol/contracts/governance/Validators.sol b/packages/protocol/contracts/governance/Validators.sol
index 741ef35ad4f..e8d9aa28a81 100644
--- a/packages/protocol/contracts/governance/Validators.sol
+++ b/packages/protocol/contracts/governance/Validators.sol
@@ -246,8 +246,7 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
publicKeysData.length == (64 + 48 + 96)
);
// Use the proof of possession bytes
- // DO NOT SUBMIT: Commented out for testing.
- // require(checkProofOfPossession(publicKeysData.slice(64, 48 + 96)));
+ require(checkProofOfPossession(publicKeysData.slice(64, 48 + 96)));
address account = getLockedGold().getAccountFromValidator(msg.sender);
require(!isValidator(account) && !isValidatorGroup(account));
@@ -365,8 +364,7 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
{
require(bytes(name).length > 0);
require(bytes(url).length > 0);
- // TODO(asa)
- // require(isFraction(commission));
+ require(commission.lt(FixidityLib.fixed1()), "Commission must be lower than 100%");
address account = getLockedGold().getAccountFromValidator(msg.sender);
require(!isValidator(account) && !isValidatorGroup(account));
require(meetsValidatorGroupRegistrationRequirement(account));
@@ -645,7 +643,7 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
*/
function _removeMember(address group, address validator) private returns (bool) {
ValidatorGroup storage _group = groups[group];
- require(validators[validator].affiliation == group && _group.members.contains(validator), "not a member");
+ require(validators[validator].affiliation == group && _group.members.contains(validator));
_group.members.remove(validator);
emit ValidatorGroupMemberRemoved(group, validator);
From 288765199a4dd29783b84caa3daf2ae1a12ce0d6 Mon Sep 17 00:00:00 2001
From: Asa Oines
Date: Thu, 3 Oct 2019 13:43:16 -0700
Subject: [PATCH 025/149] Fix
---
packages/protocol/contracts/governance/Validators.sol | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/packages/protocol/contracts/governance/Validators.sol b/packages/protocol/contracts/governance/Validators.sol
index e8d9aa28a81..bd363e8e3b2 100644
--- a/packages/protocol/contracts/governance/Validators.sol
+++ b/packages/protocol/contracts/governance/Validators.sol
@@ -364,7 +364,7 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
{
require(bytes(name).length > 0);
require(bytes(url).length > 0);
- require(commission.lt(FixidityLib.fixed1()), "Commission must be lower than 100%");
+ require(commission <= FixidityLib.fixed1().unwrap(), "Commission can't be greater than 100%");
address account = getLockedGold().getAccountFromValidator(msg.sender);
require(!isValidator(account) && !isValidatorGroup(account));
require(meetsValidatorGroupRegistrationRequirement(account));
From 2d61f63ae94df193e7cf255c5dc817dca44dacc3 Mon Sep 17 00:00:00 2001
From: Asa Oines
Date: Thu, 3 Oct 2019 13:52:42 -0700
Subject: [PATCH 026/149] Fix
---
packages/protocol/test/governance/election.ts | 40 ++++++++++---------
1 file changed, 21 insertions(+), 19 deletions(-)
diff --git a/packages/protocol/test/governance/election.ts b/packages/protocol/test/governance/election.ts
index 426d89a5c01..c3647fd622f 100644
--- a/packages/protocol/test/governance/election.ts
+++ b/packages/protocol/test/governance/election.ts
@@ -39,7 +39,7 @@ contract('Election', (accounts: string[]) => {
const nonOwner = accounts[1]
const minElectableValidators = new BigNumber(4)
const maxElectableValidators = new BigNumber(6)
- const maxVotesPerAccount = new BigNumber(3)
+ const maxNumGroupsVotedFor = new BigNumber(3)
const electabilityThreshold = new BigNumber(0)
beforeEach(async () => {
@@ -53,7 +53,7 @@ contract('Election', (accounts: string[]) => {
registry.address,
minElectableValidators,
maxElectableValidators,
- maxVotesPerAccount,
+ maxNumGroupsVotedFor,
electabilityThreshold
)
})
@@ -74,9 +74,9 @@ contract('Election', (accounts: string[]) => {
assertEqualBN(actualMaxElectableValidators, maxElectableValidators)
})
- it('should have set maxVotesPerAccount', async () => {
- const actualMaxVotesPerAccount = await election.maxVotesPerAccount()
- assertEqualBN(actualMaxVotesPerAccount, maxVotesPerAccount)
+ it('should have set maxNumGroupsVotedFor', async () => {
+ const actualMaxNumGroupsVotedFor = await election.maxNumGroupsVotedFor()
+ assertEqualBN(actualMaxNumGroupsVotedFor, maxNumGroupsVotedFor)
})
it('should not be callable again', async () => {
@@ -85,7 +85,7 @@ contract('Election', (accounts: string[]) => {
registry.address,
minElectableValidators,
maxElectableValidators,
- maxVotesPerAccount,
+ maxNumGroupsVotedFor,
electabilityThreshold
)
)
@@ -178,31 +178,33 @@ contract('Election', (accounts: string[]) => {
})
})
- describe('#setMaxVotesPerAccount', () => {
- const newMaxVotesPerAccount = maxVotesPerAccount.plus(1)
+ describe('#setMaxNumGroupsVotedFor', () => {
+ const newMaxNumGroupsVotedFor = maxNumGroupsVotedFor.plus(1)
it('should set the max electable validators', async () => {
- await election.setMaxVotesPerAccount(newMaxVotesPerAccount)
- assertEqualBN(await election.maxVotesPerAccount(), newMaxVotesPerAccount)
+ await election.setMaxNumGroupsVotedFor(newMaxNumGroupsVotedFor)
+ assertEqualBN(await election.maxNumGroupsVotedFor(), newMaxNumGroupsVotedFor)
})
- it('should emit the MaxVotesPerAccountSet event', async () => {
- const resp = await election.setMaxVotesPerAccount(newMaxVotesPerAccount)
+ it('should emit the MaxNumGroupsVotedForSet event', async () => {
+ const resp = await election.setMaxNumGroupsVotedFor(newMaxNumGroupsVotedFor)
assert.equal(resp.logs.length, 1)
const log = resp.logs[0]
assertContainSubset(log, {
- event: 'MaxVotesPerAccountSet',
+ event: 'MaxNumGroupsVotedForSet',
args: {
- maxVotesPerAccount: new BigNumber(newMaxVotesPerAccount),
+ maxNumGroupsVotedFor: new BigNumber(newMaxNumGroupsVotedFor),
},
})
})
- it('should revert when the maxVotesPerAccount is unchanged', async () => {
- await assertRevert(election.setMaxVotesPerAccount(maxVotesPerAccount))
+ it('should revert when the maxNumGroupsVotedFor is unchanged', async () => {
+ await assertRevert(election.setMaxNumGroupsVotedFor(maxNumGroupsVotedFor))
})
it('should revert when called by anyone other than the owner', async () => {
- await assertRevert(election.setMaxVotesPerAccount(newMaxVotesPerAccount, { from: nonOwner }))
+ await assertRevert(
+ election.setMaxNumGroupsVotedFor(newMaxNumGroupsVotedFor, { from: nonOwner })
+ )
})
})
@@ -383,7 +385,7 @@ contract('Election', (accounts: string[]) => {
let newGroup: string
beforeEach(async () => {
await mockLockedGold.incrementNonvotingAccountBalance(voter, value)
- for (let i = 0; i < maxVotesPerAccount.toNumber(); i++) {
+ for (let i = 0; i < maxNumGroupsVotedFor.toNumber(); i++) {
newGroup = accounts[i + 2]
await mockValidators.setMembers(newGroup, [accounts[9]])
await election.markGroupEligible(newGroup, group, NULL_ADDRESS)
@@ -393,7 +395,7 @@ contract('Election', (accounts: string[]) => {
it('should revert', async () => {
await assertRevert(
- election.vote(group, value - maxVotesPerAccount.toNumber(), newGroup, NULL_ADDRESS)
+ election.vote(group, value - maxNumGroupsVotedFor.toNumber(), newGroup, NULL_ADDRESS)
)
})
})
From a427d46f18d665154d6150169484c9b4f43fc0f9 Mon Sep 17 00:00:00 2001
From: Asa Oines
Date: Thu, 3 Oct 2019 21:52:18 -0700
Subject: [PATCH 027/149] Begin work on validator/group payments
---
.../protocol/contracts/common/UsingEpochs.sol | 14 +
.../contracts/common/UsingRegistry.sol | 1 +
.../contracts/governance/Election.sol | 10 +-
.../contracts/governance/Governance.sol | 6 +-
.../contracts/governance/LockedGold.sol | 56 ++-
.../contracts/governance/Validators.sol | 228 ++++++++++-
.../governance/interfaces/ILockedGold.sol | 3 +-
.../governance/interfaces/IValidators.sol | 2 +-
.../governance/test/MockLockedGold.sol | 9 +-
.../governance/test/ValidatorsTest.sol | 22 +
packages/protocol/lib/test-utils.ts | 6 +
packages/protocol/migrations/11_validators.ts | 5 +
packages/protocol/migrationsConfig.js | 8 +-
.../protocol/test/governance/validators.ts | 376 +++++++++++++++++-
14 files changed, 681 insertions(+), 65 deletions(-)
create mode 100644 packages/protocol/contracts/common/UsingEpochs.sol
create mode 100644 packages/protocol/contracts/governance/test/ValidatorsTest.sol
diff --git a/packages/protocol/contracts/common/UsingEpochs.sol b/packages/protocol/contracts/common/UsingEpochs.sol
new file mode 100644
index 00000000000..87eedb3b3d6
--- /dev/null
+++ b/packages/protocol/contracts/common/UsingEpochs.sol
@@ -0,0 +1,14 @@
+pragma solidity ^0.5.3;
+
+// TODO: Replace this with a precompile.
+contract UsingEpochs {
+
+ event RegistrySet(address indexed registryAddress);
+
+ // solhint-disable state-visibility
+ uint256 constant EPOCH = 17280;
+
+ function getEpochNumber() public view returns (uint256) {
+ return block.number / EPOCH;
+ }
+}
diff --git a/packages/protocol/contracts/common/UsingRegistry.sol b/packages/protocol/contracts/common/UsingRegistry.sol
index aa742dedcf5..24561e9fc18 100644
--- a/packages/protocol/contracts/common/UsingRegistry.sol
+++ b/packages/protocol/contracts/common/UsingRegistry.sol
@@ -41,6 +41,7 @@ contract UsingRegistry is Ownable {
require(registry.getAddressForOrDie(identifierHash) == msg.sender, "only registered contract");
_;
}
+
/**
* @notice Updates the address pointing to a Registry contract.
* @param registryAddress The address of a registry contract for routing to other contracts.
diff --git a/packages/protocol/contracts/governance/Election.sol b/packages/protocol/contracts/governance/Election.sol
index d5124b5d679..4ee216be4a4 100644
--- a/packages/protocol/contracts/governance/Election.sol
+++ b/packages/protocol/contracts/governance/Election.sol
@@ -246,7 +246,7 @@ contract Election is Ownable, ReentrancyGuard, Initializable, UsingRegistry {
{
require(votes.total.eligible.contains(group));
require(0 < value && value <= getNumVotesReceivable(group));
- address account = getLockedGold().getAccountFromVoter(msg.sender);
+ address account = getLockedGold().getAccountFromActiveVoter(msg.sender);
address[] storage groups = votes.groupsVotedFor[account];
require(groups.length < maxNumGroupsVotedFor);
for (uint256 i = 0; i < groups.length; i = i.add(1)) {
@@ -266,7 +266,7 @@ contract Election is Ownable, ReentrancyGuard, Initializable, UsingRegistry {
* @return True upon success.
*/
function activate(address group) external nonReentrant returns (bool) {
- address account = getLockedGold().getAccountFromVoter(msg.sender);
+ address account = getLockedGold().getAccountFromActiveVoter(msg.sender);
PendingVotes storage pending = votes.pending;
uint256 value = pending.balances[group][account];
require(value > 0);
@@ -299,7 +299,7 @@ contract Election is Ownable, ReentrancyGuard, Initializable, UsingRegistry {
returns (bool)
{
require(group != address(0));
- address account = getLockedGold().getAccountFromVoter(msg.sender);
+ address account = getLockedGold().getAccountFromActiveVoter(msg.sender);
require(0 < value && value <= getAccountPendingVotesForGroup(group, account));
decrementPendingVotes(group, account, value);
decrementTotalVotes(group, value, lesser, greater);
@@ -335,7 +335,7 @@ contract Election is Ownable, ReentrancyGuard, Initializable, UsingRegistry {
returns (bool)
{
require(group != address(0));
- address account = getLockedGold().getAccountFromVoter(msg.sender);
+ address account = getLockedGold().getAccountFromActiveVoter(msg.sender);
require(0 < value && value <= getAccountActiveVotesForGroup(group, account));
decrementActiveVotes(group, account, value);
decrementTotalVotes(group, value, lesser, greater);
@@ -718,7 +718,7 @@ contract Election is Ownable, ReentrancyGuard, Initializable, UsingRegistry {
totalNumMembersElected = 0;
for (uint256 i = 0; i < electionGroups.length; i = i.add(1)) {
// We use the validating delegate if one is set.
- address[] memory electedGroupValidators = getValidators().getTopValidatorsFromGroup(
+ address[] memory electedGroupValidators = getValidators().getTopGroupValidators(
electionGroups[i],
numMembersElected[i]
);
diff --git a/packages/protocol/contracts/governance/Governance.sol b/packages/protocol/contracts/governance/Governance.sol
index 1a6480a19ba..0ba0a4c29e3 100644
--- a/packages/protocol/contracts/governance/Governance.sol
+++ b/packages/protocol/contracts/governance/Governance.sol
@@ -485,7 +485,7 @@ contract Governance is IGovernance, Ownable, Initializable, ReentrancyGuard, Usi
nonReentrant
returns (bool)
{
- address account = getLockedGold().getAccountFromVoter(msg.sender);
+ address account = getLockedGold().getAccountFromActiveVoter(msg.sender);
// TODO(asa): When upvoting a proposal that will get dequeued, should we let the tx succeed
// and return false?
dequeueProposalsIfReady();
@@ -529,7 +529,7 @@ contract Governance is IGovernance, Ownable, Initializable, ReentrancyGuard, Usi
returns (bool)
{
dequeueProposalsIfReady();
- address account = getLockedGold().getAccountFromVoter(msg.sender);
+ address account = getLockedGold().getAccountFromActiveVoter(msg.sender);
Voter storage voter = voters[account];
uint256 proposalId = voter.upvote.proposalId;
Proposals.Proposal storage proposal = proposals[proposalId];
@@ -597,7 +597,7 @@ contract Governance is IGovernance, Ownable, Initializable, ReentrancyGuard, Usi
nonReentrant
returns (bool)
{
- address account = getLockedGold().getAccountFromVoter(msg.sender);
+ address account = getLockedGold().getAccountFromActiveVoter(msg.sender);
dequeueProposalsIfReady();
Proposals.Proposal storage proposal = proposals[proposalId];
require(isDequeuedProposal(proposal, proposalId, index));
diff --git a/packages/protocol/contracts/governance/LockedGold.sol b/packages/protocol/contracts/governance/LockedGold.sol
index 6f89b30453c..c3e868b5cef 100644
--- a/packages/protocol/contracts/governance/LockedGold.sol
+++ b/packages/protocol/contracts/governance/LockedGold.sol
@@ -19,6 +19,11 @@ contract LockedGold is ILockedGold, ReentrancyGuard, Initializable, UsingRegistr
uint256 timestamp;
}
+ struct AuthorizedBy {
+ address account;
+ bool active;
+ }
+
struct Authorizations {
address voting;
address validating;
@@ -48,7 +53,8 @@ contract LockedGold is ILockedGold, ReentrancyGuard, Initializable, UsingRegistr
mapping(address => Account) private accounts;
// Maps voting and validating keys to the account that provided the authorization.
- mapping(address => address) public authorizedBy;
+ // Authorized addresses may not be reused.
+ mapping(address => AuthorizedBy) private authorizedBy;
uint256 public totalNonvoting;
uint256 public unlockingPeriod;
@@ -255,15 +261,14 @@ contract LockedGold is ILockedGold, ReentrancyGuard, Initializable, UsingRegistr
// TODO(asa): Dedup
/**
* @notice Returns the account associated with `accountOrVoter`.
- * @param accountOrVoter The address of the account or authorized voter.
- * @dev Fails if the `accountOrVoter` is not an account or authorized voter.
+ * @param accountOrVoter The address of the account or active authorized voter.
+ * @dev Fails if the `accountOrVoter` is not an account or active authorized voter.
* @return The associated account.
*/
- function getAccountFromVoter(address accountOrVoter) external view returns (address) {
- address authorizingAccount = authorizedBy[accountOrVoter];
- if (authorizingAccount != address(0)) {
- require(accounts[authorizingAccount].authorizations.voting == accountOrVoter);
- return authorizingAccount;
+ function getAccountFromActiveVoter(address accountOrVoter) external view returns (address) {
+ AuthorizedBy memory ab = authorizedBy[accountOrVoter];
+ if (ab.active && ab.account != address(0)) {
+ return ab.account;
} else {
require(isAccount(accountOrVoter));
return accountOrVoter;
@@ -307,15 +312,30 @@ contract LockedGold is ILockedGold, ReentrancyGuard, Initializable, UsingRegistr
/**
* @notice Returns the account associated with `accountOrValidator`.
- * @param accountOrValidator The address of the account or authorized validator.
- * @dev Fails if the `accountOrValidator` is not an account or authorized validator.
+ * @param accountOrValidator The address of the account or active authorized validator.
+ * @dev Fails if the `accountOrValidator` is not an account or active authorized validator.
+ * @return The associated account.
+ */
+ function getAccountFromActiveValidator(address accountOrValidator) public view returns (address) {
+ AuthorizedBy memory ab = authorizedBy[accountOrValidator];
+ if (ab.active && ab.account != address(0)) {
+ return ab.account;
+ } else {
+ require(isAccount(accountOrValidator));
+ return accountOrValidator;
+ }
+ }
+
+ /**
+ * @notice Returns the account associated with `accountOrValidator`.
+ * @param accountOrValidator The address of the account or previously authorized validator.
+ * @dev Fails if the `accountOrValidator` is not an account or previously authorized validator.
* @return The associated account.
*/
function getAccountFromValidator(address accountOrValidator) public view returns (address) {
- address authorizingAccount = authorizedBy[accountOrValidator];
- if (authorizingAccount != address(0)) {
- require(accounts[authorizingAccount].authorizations.validating == accountOrValidator);
- return authorizingAccount;
+ AuthorizedBy memory ab = authorizedBy[accountOrValidator];
+ if (ab.account != address(0)) {
+ return ab.account;
} else {
require(isAccount(accountOrValidator));
return accountOrValidator;
@@ -394,8 +414,8 @@ contract LockedGold is ILockedGold, ReentrancyGuard, Initializable, UsingRegistr
address signer = Signatures.getSignerOfAddress(msg.sender, v, r, s);
require(signer == current);
- authorizedBy[previous] = address(0);
- authorizedBy[current] = msg.sender;
+ authorizedBy[previous].active = false;
+ authorizedBy[current] = AuthorizedBy(msg.sender, true);
}
/**
@@ -422,7 +442,7 @@ contract LockedGold is ILockedGold, ReentrancyGuard, Initializable, UsingRegistr
* @return Returns `true` if authorized. Returns `false` otherwise.
*/
function isAuthorized(address account) external view returns (bool) {
- return (authorizedBy[account] != address(0));
+ return (authorizedBy[account].account != address(0));
}
/**
@@ -431,7 +451,7 @@ contract LockedGold is ILockedGold, ReentrancyGuard, Initializable, UsingRegistr
* @return Returns `false` if authorized. Returns `true` otherwise.
*/
function isNotAuthorized(address account) internal view returns (bool) {
- return (authorizedBy[account] == address(0));
+ return (authorizedBy[account].account == address(0));
}
function deletePendingWithdrawal(PendingWithdrawal[] storage list, uint256 index) private {
diff --git a/packages/protocol/contracts/governance/Validators.sol b/packages/protocol/contracts/governance/Validators.sol
index bd363e8e3b2..23adae92bf1 100644
--- a/packages/protocol/contracts/governance/Validators.sol
+++ b/packages/protocol/contracts/governance/Validators.sol
@@ -1,5 +1,6 @@
pragma solidity ^0.5.3;
+import "openzeppelin-solidity/contracts/math/Math.sol";
import "openzeppelin-solidity/contracts/math/SafeMath.sol";
import "openzeppelin-solidity/contracts/ownership/Ownable.sol";
import "openzeppelin-solidity/contracts/utils/ReentrancyGuard.sol";
@@ -12,13 +13,14 @@ import "../identity/interfaces/IRandom.sol";
import "../common/Initializable.sol";
import "../common/FixidityLib.sol";
import "../common/linkedlists/AddressLinkedList.sol";
+import "../common/UsingEpochs.sol";
import "../common/UsingRegistry.sol";
/**
* @title A contract for registering and electing Validator Groups and Validators.
*/
-contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, UsingRegistry {
+contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, UsingEpochs, UsingRegistry {
using FixidityLib for FixidityLib.Fraction;
using AddressLinkedList for LinkedList.List;
@@ -41,8 +43,18 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
struct ValidatorGroup {
string name;
string url;
- FixidityLib.Fraction commission;
LinkedList.List members;
+ FixidityLib.Fraction commission;
+ }
+
+ struct MembershipHistoryEntry {
+ uint256 epochNumber;
+ address group;
+ }
+
+ struct MembershipHistory {
+ uint256 head;
+ MembershipHistoryEntry[] entries;
}
struct Validator {
@@ -50,6 +62,13 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
string url;
bytes publicKeysData;
address affiliation;
+ FixidityLib.Fraction score;
+ MembershipHistory membershipHistory;
+ }
+
+ struct ValidatorScoreParameters {
+ uint256 exponent;
+ FixidityLib.Fraction adjustmentSpeed;
}
mapping(address => ValidatorGroup) private groups;
@@ -58,12 +77,25 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
address[] private _validators;
RegistrationRequirements public registrationRequirements;
DeregistrationLockups public deregistrationLockups;
+ ValidatorScoreParameters private validatorScoreParameters;
+ uint256 public validatorEpochPayment;
+ uint256 public membershipHistoryLength;
uint256 public maxGroupSize;
+ event Debug(uint256 value);
event MaxGroupSizeSet(
uint256 size
);
+ event ValidatorEpochPaymentSet(
+ uint256 value
+ );
+
+ event ValidatorScoreParametersSet(
+ uint256 exponent,
+ uint256 adjustmentSpeed
+ );
+
event RegistrationRequirementsSet(
uint256 group,
uint256 validator
@@ -120,6 +152,11 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
address indexed validator
);
+ modifier onlyVm() {
+ require(msg.sender == address(0));
+ _;
+ }
+
/**
* @notice Initializes critical variables.
* @param registryAddress The address of the registry contract.
@@ -127,6 +164,10 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
* @param validatorRequirement The minimum locked gold needed to register a validator.
* @param groupLockup The duration the above gold remains locked after deregistration.
* @param validatorLockup The duration the above gold remains locked after deregistration.
+ * @param validatorScoreExponent The exponent used in calculating validator scores.
+ * @param validatorScoreAdjustmentSpeed The speed at which validator scores are adjusted.
+ * @param _validatorEpochPayment The duration the above gold remains locked after deregistration.
+ * @param _membershipHistoryLength The maximum number of entries for validator membership history.
* @param _maxGroupSize The maximum group size.
* @dev Should be called only once.
*/
@@ -136,15 +177,26 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
uint256 validatorRequirement,
uint256 groupLockup,
uint256 validatorLockup,
+ uint256 validatorScoreExponent,
+ uint256 validatorScoreAdjustmentSpeed,
+ uint256 _validatorEpochPayment,
+ uint256 _membershipHistoryLength,
uint256 _maxGroupSize
)
external
initializer
{
+ require(validatorScoreAdjustmentSpeed <= FixidityLib.fixed1().unwrap());
_transferOwnership(msg.sender);
setRegistry(registryAddress);
registrationRequirements = RegistrationRequirements(groupRequirement, validatorRequirement);
deregistrationLockups = DeregistrationLockups(groupLockup, validatorLockup);
+ validatorScoreParameters = ValidatorScoreParameters(
+ validatorScoreExponent,
+ FixidityLib.wrap(validatorScoreAdjustmentSpeed)
+ );
+ validatorEpochPayment = _validatorEpochPayment;
+ membershipHistoryLength = _membershipHistoryLength;
maxGroupSize = _maxGroupSize;
}
@@ -160,6 +212,35 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
return true;
}
+ /**
+ * @notice Sets the per-epoch payment in Celo Dollars for validators, less group commission.
+ * @param value The value in Celo Dollars.
+ * @return True upon success.
+ */
+ function setValidatorEpochPayment(uint256 value) external onlyOwner returns (bool) {
+ require(value != validatorEpochPayment);
+ validatorEpochPayment = value;
+ emit ValidatorEpochPaymentSet(value);
+ return true;
+ }
+
+ /**
+ * @notice Updates the validator score parameters.
+ * @param exponent The exponent used in calculating the score.
+ * @param adjustmentSpeed The speed at which the score is adjusted.
+ * @return True upon success.
+ */
+ function setValidatorScoreParameters(uint256 exponent, uint256 adjustmentSpeed) external onlyOwner returns (bool) {
+ require(adjustmentSpeed <= FixidityLib.fixed1().unwrap());
+ require(
+ exponent != validatorScoreParameters.exponent ||
+ !FixidityLib.wrap(adjustmentSpeed).equals(validatorScoreParameters.adjustmentSpeed)
+ );
+ validatorScoreParameters = ValidatorScoreParameters(exponent, FixidityLib.wrap(adjustmentSpeed));
+ emit ValidatorScoreParametersSet(exponent, adjustmentSpeed);
+ return true;
+ }
+
/**
* @notice Returns the maximum number of members a group can add.
* @return The maximum number of members a group can add.
@@ -248,12 +329,13 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
// Use the proof of possession bytes
require(checkProofOfPossession(publicKeysData.slice(64, 48 + 96)));
- address account = getLockedGold().getAccountFromValidator(msg.sender);
+ address account = getLockedGold().getAccountFromActiveValidator(msg.sender);
require(!isValidator(account) && !isValidatorGroup(account));
require(meetsValidatorRegistrationRequirement(account));
- Validator memory validator = Validator(name, url, publicKeysData, address(0));
- validators[account] = validator;
+ validators[account].name = name;
+ validators[account].url = url;
+ validators[account].publicKeysData = publicKeysData;
_validators.push(account);
getLockedGold().setAccountMustMaintain(account, registrationRequirements.validator, MAX_INT);
emit ValidatorRegistered(account, name, url, publicKeysData);
@@ -289,6 +371,80 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
return getLockedGold().getAccountTotalLockedGold(account) >= registrationRequirements.group;
}
+ function getValidatorScoreParameters() external view returns (uint256, uint256) {
+ return (validatorScoreParameters.exponent, validatorScoreParameters.adjustmentSpeed.unwrap());
+ }
+
+ function getMembershipHistory(address account) external view returns (uint256[] memory, address[] memory) {
+ MembershipHistoryEntry[] memory entries = validators[account].membershipHistory.entries;
+ uint256[] memory epochs = new uint256[](entries.length);
+ address[] memory membershipGroups = new address[](entries.length);
+ for (uint256 i = 0; i < entries.length; i = i.add(1)) {
+ epochs[i] = entries[i].epochNumber;
+ membershipGroups[i] = entries[i].group;
+ }
+ return (epochs, membershipGroups);
+ }
+
+ /**
+ * @notice Updates a validator's score based on its uptime for the epoch.
+ * @param validator The address of the validator.
+ * @param uptime The Fixidity representation of the validator's uptime, between 0 and 1.
+ * @return True upon success.
+ */
+ function updateValidatorScore(address validator, uint256 uptime) external onlyVm() returns (bool) {
+ return _updateValidatorScore(validator, uptime);
+ }
+
+ /**
+ * @notice Updates a validator's score based on its uptime for the epoch.
+ * @param validator The address of the validator.
+ * @param uptime The Fixidity representation of the validator's uptime, between 0 and 1.
+ * @return True upon success.
+ */
+ function _updateValidatorScore(address validator, uint256 uptime) internal returns (bool) {
+ address account = getLockedGold().getAccountFromValidator(validator);
+ require(isValidator(account), "isvalidator");
+ require(uptime <= FixidityLib.fixed1().unwrap(), "uptime");
+
+ // TODO(asa): Use exponent.
+ FixidityLib.Fraction memory epochScore = FixidityLib.wrap(uptime);
+
+ FixidityLib.Fraction memory newComponent = validatorScoreParameters.adjustmentSpeed.multiply(
+ epochScore
+ );
+ FixidityLib.Fraction memory currentComponent = FixidityLib.fixed1().subtract(
+ validatorScoreParameters.adjustmentSpeed
+ );
+ emit Debug(currentComponent.unwrap());
+ currentComponent = currentComponent.multiply(validators[account].score);
+ emit Debug(currentComponent.unwrap());
+ validators[account].score = FixidityLib.wrap(
+ Math.min(
+ epochScore.unwrap(),
+ newComponent.add(currentComponent).unwrap()
+ )
+ );
+ return true;
+ }
+
+ function distributeEpochPayment(address validator) external onlyVm() returns (bool) {
+ return _distributeEpochPayment(validator);
+ }
+
+ function _distributeEpochPayment(address validator) internal returns (bool) {
+ address account = getLockedGold().getAccountFromValidator(validator);
+ require(isValidator(account));
+ FixidityLib.Fraction memory totalPayment = FixidityLib.newFixed(validatorEpochPayment).multiply(validators[account].score);
+ address group = getMembershipInLastEpoch(account);
+ uint256 groupPayment = totalPayment.multiply(groups[group].commission).fromFixed();
+ uint256 validatorPayment = totalPayment.fromFixed().sub(groupPayment);
+ /*
+ getStableToken().mint(group, groupPayment);
+ getStableToken().mint(account, validatorPayment);
+ */
+ }
+
/**
* @notice De-registers a validator, removing it from the group for which it is a member.
* @param index The index of this validator in the list of all validators.
@@ -296,7 +452,7 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
* @dev Fails if the account is not a validator.
*/
function deregisterValidator(uint256 index) external nonReentrant returns (bool) {
- address account = getLockedGold().getAccountFromValidator(msg.sender);
+ address account = getLockedGold().getAccountFromActiveValidator(msg.sender);
require(isValidator(account));
Validator storage validator = validators[account];
if (validator.affiliation != address(0)) {
@@ -320,7 +476,7 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
* @dev De-affiliates with the previously affiliated group if present.
*/
function affiliate(address group) external nonReentrant returns (bool) {
- address account = getLockedGold().getAccountFromValidator(msg.sender);
+ address account = getLockedGold().getAccountFromActiveValidator(msg.sender);
require(isValidator(account) && isValidatorGroup(group), "blah");
Validator storage validator = validators[account];
if (validator.affiliation != address(0)) {
@@ -337,7 +493,7 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
* @dev Fails if the account is not a validator with non-zero affiliation.
*/
function deaffiliate() external nonReentrant returns (bool) {
- address account = getLockedGold().getAccountFromValidator(msg.sender);
+ address account = getLockedGold().getAccountFromActiveValidator(msg.sender);
require(isValidator(account));
Validator storage validator = validators[account];
require(validator.affiliation != address(0));
@@ -365,7 +521,7 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
require(bytes(name).length > 0);
require(bytes(url).length > 0);
require(commission <= FixidityLib.fixed1().unwrap(), "Commission can't be greater than 100%");
- address account = getLockedGold().getAccountFromValidator(msg.sender);
+ address account = getLockedGold().getAccountFromActiveValidator(msg.sender);
require(!isValidator(account) && !isValidatorGroup(account));
require(meetsValidatorGroupRegistrationRequirement(account));
@@ -386,7 +542,7 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
* @dev Fails if the account is not a validator group with no members.
*/
function deregisterValidatorGroup(uint256 index) external nonReentrant returns (bool) {
- address account = getLockedGold().getAccountFromValidator(msg.sender);
+ address account = getLockedGold().getAccountFromActiveValidator(msg.sender);
// Only empty Validator Groups can be deregistered.
require(isValidatorGroup(account) && groups[account].members.numElements == 0);
delete groups[account];
@@ -407,7 +563,7 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
* @dev Fails if `validator` has not set their affiliation to this account.
*/
function addMember(address validator) external nonReentrant returns (bool) {
- address account = getLockedGold().getAccountFromValidator(msg.sender);
+ address account = getLockedGold().getAccountFromActiveValidator(msg.sender);
require(isValidatorGroup(account) && isValidator(validator));
return _addMember(account, validator);
}
@@ -423,6 +579,7 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
require(_group.members.numElements < maxGroupSize);
require(validators[validator].affiliation == group && !_group.members.contains(validator));
_group.members.push(validator);
+ updateMembershipHistory(validator, group);
emit ValidatorGroupMemberAdded(group, validator);
return true;
}
@@ -434,7 +591,7 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
* @dev Fails if `validator` is not a member of the account's group.
*/
function removeMember(address validator) external nonReentrant returns (bool) {
- address account = getLockedGold().getAccountFromValidator(msg.sender);
+ address account = getLockedGold().getAccountFromActiveValidator(msg.sender);
require(isValidatorGroup(account) && isValidator(validator), "is not group and validator");
return _removeMember(account, validator);
}
@@ -458,7 +615,7 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
nonReentrant
returns (bool)
{
- address account = getLockedGold().getAccountFromValidator(msg.sender);
+ address account = getLockedGold().getAccountFromActiveValidator(msg.sender);
require(isValidatorGroup(account) && isValidator(validator));
ValidatorGroup storage group = groups[account];
require(group.members.contains(validator));
@@ -481,7 +638,8 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
string memory name,
string memory url,
bytes memory publicKeysData,
- address affiliation
+ address affiliation,
+ uint256 score
)
{
require(isValidator(account));
@@ -490,7 +648,8 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
validator.name,
validator.url,
validator.publicKeysData,
- validator.affiliation
+ validator.affiliation,
+ validator.score.unwrap()
);
}
@@ -526,7 +685,7 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
* @param n The number of members to return.
* @return The top n group members for a particular group.
*/
- function getTopValidatorsFromGroup(
+ function getTopGroupValidators(
address account,
uint256 n
)
@@ -645,6 +804,7 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
ValidatorGroup storage _group = groups[group];
require(validators[validator].affiliation == group && _group.members.contains(validator));
_group.members.remove(validator);
+ updateMembershipHistory(validator, address(0));
emit ValidatorGroupMemberRemoved(group, validator);
// Empty validator groups are not electable.
@@ -654,6 +814,42 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
return true;
}
+ function updateMembershipHistory(address account, address group) private returns (bool) {
+ MembershipHistory storage history = validators[account].membershipHistory;
+ uint256 epochNumber = getEpochNumber();
+ if (history.entries.length > 0 && epochNumber == history.entries[history.head].epochNumber) {
+ // There have been no elections since the validator last changed membership, overwrite the
+ // previous entry.
+ history.entries[history.head] = MembershipHistoryEntry(epochNumber, group);
+ } else {
+ if (history.entries.length > 0) {
+ // MembershipHistoryEntries are a circular buffer.
+ history.head = history.head.add(1) % membershipHistoryLength;
+ }
+ if (history.head >= history.entries.length) {
+ history.entries.push(MembershipHistoryEntry(epochNumber, group));
+ } else {
+ history.entries[history.head] = MembershipHistoryEntry(epochNumber, group);
+ }
+ }
+ }
+
+ function getMembershipInLastEpoch(address account) public view returns (address) {
+ uint256 epochNumber = getEpochNumber();
+ MembershipHistory storage history = validators[account].membershipHistory;
+ uint256 head = history.head;
+ // If the most recent entry in the membership history is for the current epoch number, we need
+ // to look at the previous entry.
+ if (history.entries[head].epochNumber == epochNumber) {
+ if (head > 0) {
+ head = head.sub(1);
+ } else if (history.entries.length > 1) {
+ head = history.entries.length.sub(1);
+ }
+ }
+ return history.entries[head].group;
+ }
+
/**
* @notice De-affiliates a validator, removing it from the group for which it is a member.
* @param validator The validator to deaffiliate from their affiliated validator group.
diff --git a/packages/protocol/contracts/governance/interfaces/ILockedGold.sol b/packages/protocol/contracts/governance/interfaces/ILockedGold.sol
index 1f961197356..ab908add4a1 100644
--- a/packages/protocol/contracts/governance/interfaces/ILockedGold.sol
+++ b/packages/protocol/contracts/governance/interfaces/ILockedGold.sol
@@ -2,7 +2,8 @@ pragma solidity ^0.5.3;
interface ILockedGold {
- function getAccountFromVoter(address) external view returns (address);
+ function getAccountFromActiveVoter(address) external view returns (address);
+ function getAccountFromActiveValidator(address) external view returns (address);
function getAccountFromValidator(address) external view returns (address);
function getValidatorFromAccount(address) external view returns (address);
function incrementNonvotingAccountBalance(address, uint256) external;
diff --git a/packages/protocol/contracts/governance/interfaces/IValidators.sol b/packages/protocol/contracts/governance/interfaces/IValidators.sol
index 5bc937ee60f..c0da21d6b99 100644
--- a/packages/protocol/contracts/governance/interfaces/IValidators.sol
+++ b/packages/protocol/contracts/governance/interfaces/IValidators.sol
@@ -5,5 +5,5 @@ interface IValidators {
function getGroupNumMembers(address) external view returns (uint256);
function getGroupsNumMembers(address[] calldata) external view returns (uint256[] memory);
function getNumRegisteredValidators() external view returns (uint256);
- function getTopValidatorsFromGroup(address, uint256) external view returns (address[] memory);
+ function getTopGroupValidators(address, uint256) external view returns (address[] memory);
}
diff --git a/packages/protocol/contracts/governance/test/MockLockedGold.sol b/packages/protocol/contracts/governance/test/MockLockedGold.sol
index 970e8079d33..d0846c7973a 100644
--- a/packages/protocol/contracts/governance/test/MockLockedGold.sol
+++ b/packages/protocol/contracts/governance/test/MockLockedGold.sol
@@ -8,7 +8,8 @@ import "../interfaces/ILockedGold.sol";
/**
* @title A mock LockedGold for testing.
*/
-contract MockLockedGold is ILockedGold {
+// TODO(asa): Use ILockedGold interface.
+contract MockLockedGold {
using SafeMath for uint256;
@@ -37,7 +38,11 @@ contract MockLockedGold is ILockedGold {
return accountOrValidator;
}
- function getAccountFromVoter(address accountOrVoter) external view returns (address) {
+ function getAccountFromActiveValidator(address accountOrValidator) external view returns (address) {
+ return accountOrValidator;
+ }
+
+ function getAccountFromActiveVoter(address accountOrVoter) external view returns (address) {
return accountOrVoter;
}
diff --git a/packages/protocol/contracts/governance/test/ValidatorsTest.sol b/packages/protocol/contracts/governance/test/ValidatorsTest.sol
new file mode 100644
index 00000000000..1c7421969fe
--- /dev/null
+++ b/packages/protocol/contracts/governance/test/ValidatorsTest.sol
@@ -0,0 +1,22 @@
+pragma solidity ^0.5.8;
+
+import "../Validators.sol";
+import "../../common/FixidityLib.sol";
+
+/**
+ * @title A wrapper around Validators that exposes onlyVm functions for testing.
+ */
+contract ValidatorsTest is Validators {
+
+ function getEpochNumber() public view returns (uint256) {
+ return block.number / 100;
+ }
+
+ function updateValidatorScore(address validator, uint256 uptime) external returns (bool) {
+ return _updateValidatorScore(validator, uptime);
+ }
+
+ function distributeEpochPayment(address validator) external returns (bool) {
+ return _distributeEpochPayment(validator);
+ }
+}
diff --git a/packages/protocol/lib/test-utils.ts b/packages/protocol/lib/test-utils.ts
index ebc328f4f0f..654ca5adf68 100644
--- a/packages/protocol/lib/test-utils.ts
+++ b/packages/protocol/lib/test-utils.ts
@@ -85,6 +85,12 @@ export async function timeTravel(seconds: number, web3: Web3) {
await jsonRpc(web3, 'evm_mine', [])
}
+export async function mineBlocks(blocks: number, web3: Web3) {
+ for (let i = 0; i < blocks; i++) {
+ await jsonRpc(web3, 'evm_mine', [])
+ }
+}
+
export async function assertBalance(address: string, balance: BigNumber) {
const block = await web3.eth.getBlock('latest')
const web3balance = new BigNumber(await web3.eth.getBalance(address))
diff --git a/packages/protocol/migrations/11_validators.ts b/packages/protocol/migrations/11_validators.ts
index 4a76fac9fcd..5f24860c230 100644
--- a/packages/protocol/migrations/11_validators.ts
+++ b/packages/protocol/migrations/11_validators.ts
@@ -1,6 +1,7 @@
import { CeloContractName } from '@celo/protocol/lib/registry-utils'
import { deploymentForCoreContract } from '@celo/protocol/lib/web3-utils'
import { config } from '@celo/protocol/migrationsConfig'
+import { toFixed } from '@celo/utils/lib/fixidity'
import { ValidatorsInstance } from 'types'
const initializeArgs = async (): Promise => {
@@ -10,6 +11,10 @@ const initializeArgs = async (): Promise => {
config.validators.registrationRequirements.validator,
config.validators.deregistrationLockups.group,
config.validators.deregistrationLockups.validator,
+ config.validators.validatorScoreParameters.exponent,
+ toFixed(config.validators.validatorScoreParameters.adjustmentSpeed).toFixed(),
+ config.validators.validatorEpochPayment,
+ config.validators.membershipHistoryLength,
config.validators.maxGroupSize,
]
}
diff --git a/packages/protocol/migrationsConfig.js b/packages/protocol/migrationsConfig.js
index a270504a09f..d78aca5bc07 100644
--- a/packages/protocol/migrationsConfig.js
+++ b/packages/protocol/migrationsConfig.js
@@ -75,6 +75,12 @@ const DefaultConfig = {
group: 60 * 24 * 60 * 60, // 60 days
validator: 60 * 24 * 60 * 60, // 60 days
},
+ validatorScoreParameters: {
+ exponent: 1,
+ adjustmentSpeed: 0.1,
+ },
+ validatorEpochPayment: '1000000000000000000',
+ membershipHistoryLength: 60,
maxGroupSize: 10,
validatorKeys: [],
@@ -104,7 +110,7 @@ const linkedLibraries = {
'SortedLinkedListWithMedian',
],
SortedLinkedListWithMedian: ['AddressSortedLinkedListWithMedian'],
- AddressLinkedList: ['Validators'],
+ AddressLinkedList: ['Validators', 'ValidatorsTest'],
AddressSortedLinkedList: ['Election'],
IntegerSortedLinkedList: ['Governance', 'IntegerSortedLinkedListTest'],
AddressSortedLinkedListWithMedian: ['SortedOracles', 'AddressSortedLinkedListWithMedianTest'],
diff --git a/packages/protocol/test/governance/validators.ts b/packages/protocol/test/governance/validators.ts
index 2251fca668c..bfbb97d163e 100644
--- a/packages/protocol/test/governance/validators.ts
+++ b/packages/protocol/test/governance/validators.ts
@@ -3,7 +3,9 @@ import {
assertContainSubset,
assertEqualBN,
assertRevert,
+ assertSameAddress,
NULL_ADDRESS,
+ mineBlocks,
} from '@celo/protocol/lib/test-utils'
import BigNumber from 'bignumber.js'
import {
@@ -13,12 +15,12 @@ import {
MockElectionInstance,
RegistryContract,
RegistryInstance,
- ValidatorsContract,
- ValidatorsInstance,
+ ValidatorsTestContract,
+ ValidatorsTestInstance,
} from 'types'
-import { toFixed } from '@celo/utils/lib/fixidity'
+import { fromFixed, toFixed } from '@celo/utils/lib/fixidity'
-const Validators: ValidatorsContract = artifacts.require('Validators')
+const Validators: ValidatorsTestContract = artifacts.require('ValidatorsTest')
const MockLockedGold: MockLockedGoldContract = artifacts.require('MockLockedGold')
const MockElection: MockElectionContract = artifacts.require('MockElection')
const Registry: RegistryContract = artifacts.require('Registry')
@@ -33,6 +35,7 @@ const parseValidatorParams = (validatorParams: any) => {
url: validatorParams[1],
publicKeysData: validatorParams[2],
affiliation: validatorParams[3],
+ score: validatorParams[4],
}
}
@@ -41,18 +44,36 @@ const parseValidatorGroupParams = (groupParams: any) => {
name: groupParams[0],
url: groupParams[1],
members: groupParams[2],
+ commission: groupParams[3],
}
}
const HOUR = 60 * 60
const DAY = 24 * HOUR
const MAX_UINT256 = new BigNumber(2).pow(256).minus(1)
+// Hard coded in ValidatorsTest.sol
+const EPOCH = 100
contract('Validators', (accounts: string[]) => {
- let validators: ValidatorsInstance
+ let validators: ValidatorsTestInstance
let registry: RegistryInstance
let mockLockedGold: MockLockedGoldInstance
let mockElection: MockElectionInstance
+ const nonOwner = accounts[1]
+
+ const registrationRequirements = { group: new BigNumber(1000), validator: new BigNumber(100) }
+ const deregistrationLockups = {
+ group: new BigNumber(100 * DAY),
+ validator: new BigNumber(60 * DAY),
+ }
+ const validatorScoreParameters = {
+ exponent: new BigNumber(1),
+ adjustmentSpeed: toFixed(0.25),
+ }
+ const validatorEpochPayment = new BigNumber(10000000000000)
+ const membershipHistoryLength = new BigNumber(3)
+ const maxGroupSize = new BigNumber(5)
+
// A random 64 byte hex string.
const publicKey =
'ea0733ad275e2b9e05541341a97ee82678c58932464fad26164657a111a7e37a9fa0300266fb90e2135a1f1512350cb4e985488a88809b14e3cbe415e76e82b2'
@@ -60,16 +81,7 @@ contract('Validators', (accounts: string[]) => {
'4d23d8cd06f30b1fa7cf368e2f5399ab04bb6846c682f493a98a607d3dfb7e53a712bb79b475c57b0ac2785460f91301'
const blsPoP =
'9d3e1d8f49f6b0d8e9a03d80ca07b1d24cf1cc0557bdcc04f5e17a46e35d02d0d411d956dbd5d2d2464eebd7b74ae30005d223780d785d2abc5644fac7ac29fb0e302bdc80c81a5d45018b68b1045068a4b3a4861c93037685fd0d252d740501'
-
const publicKeysData = '0x' + publicKey + blsPublicKey + blsPoP
-
- const nonOwner = accounts[1]
- const registrationRequirements = { group: new BigNumber(1000), validator: new BigNumber(100) }
- const deregistrationLockups = {
- group: new BigNumber(100 * DAY),
- validator: new BigNumber(60 * DAY),
- }
- const maxGroupSize = 5
const name = 'test-name'
const url = 'test-url'
const commission = toFixed(1 / 100)
@@ -86,6 +98,10 @@ contract('Validators', (accounts: string[]) => {
registrationRequirements.validator,
deregistrationLockups.group,
deregistrationLockups.validator,
+ validatorScoreParameters.exponent,
+ validatorScoreParameters.adjustmentSpeed,
+ validatorEpochPayment,
+ membershipHistoryLength,
maxGroupSize
)
})
@@ -133,6 +149,27 @@ contract('Validators', (accounts: string[]) => {
assertEqualBN(validator, deregistrationLockups.validator)
})
+ it('should have set the validator score parameters', async () => {
+ const [exponent, adjustmentSpeed] = await validators.getValidatorScoreParameters()
+ assertEqualBN(exponent, validatorScoreParameters.exponent)
+ assertEqualBN(adjustmentSpeed, validatorScoreParameters.adjustmentSpeed)
+ })
+
+ it('should have set the validator epoch payment', async () => {
+ const actual = await validators.validatorEpochPayment()
+ assertEqualBN(actual, validatorEpochPayment)
+ })
+
+ it('should have set the membership history length', async () => {
+ const actual = await validators.membershipHistoryLength()
+ assertEqualBN(actual, membershipHistoryLength)
+ })
+
+ it('should have set the max group size', async () => {
+ const actual = await validators.maxGroupSize()
+ assertEqualBN(actual, maxGroupSize)
+ })
+
it('should not be callable again', async () => {
await assertRevert(
validators.initialize(
@@ -141,12 +178,106 @@ contract('Validators', (accounts: string[]) => {
registrationRequirements.validator,
deregistrationLockups.group,
deregistrationLockups.validator,
+ validatorScoreParameters.exponent,
+ validatorScoreParameters.adjustmentSpeed,
+ validatorEpochPayment,
+ membershipHistoryLength,
maxGroupSize
)
)
})
})
+ describe('#setValidatorEpochPayment()', () => {
+ describe('when the payment is different', () => {
+ const newPayment = validatorEpochPayment.plus(1)
+
+ describe('when called by the owner', () => {
+ let resp: any
+
+ beforeEach(async () => {
+ resp = await validators.setValidatorEpochPayment(newPayment)
+ })
+
+ it('should set the validator epoch payment', async () => {
+ assertEqualBN(await validators.validatorEpochPayment(), newPayment)
+ })
+
+ it('should emit the ValidatorEpochPaymentSet event', async () => {
+ assert.equal(resp.logs.length, 1)
+ const log = resp.logs[0]
+ assertContainSubset(log, {
+ event: 'ValidatorEpochPaymentSet',
+ args: {
+ value: new BigNumber(newPayment),
+ },
+ })
+ })
+
+ describe('when called by a non-owner', () => {
+ it('should revert', async () => {
+ await assertRevert(
+ validators.setValidatorEpochPayment(newPayment, {
+ from: nonOwner,
+ })
+ )
+ })
+ })
+ })
+
+ describe('when the payment is the same', () => {
+ it('should revert', async () => {
+ await assertRevert(validators.setValidatorEpochPayment(validatorEpochPayment))
+ })
+ })
+ })
+ })
+
+ describe('#setMaxGroupSize()', () => {
+ describe('when the group size is different', () => {
+ const newSize = maxGroupSize.plus(1)
+
+ describe('when called by the owner', () => {
+ let resp: any
+
+ beforeEach(async () => {
+ resp = await validators.setMaxGroupSize(newSize)
+ })
+
+ it('should set the max group size', async () => {
+ assertEqualBN(await validators.maxGroupSize(), newSize)
+ })
+
+ it('should emit the MaxGroupSizeSet event', async () => {
+ assert.equal(resp.logs.length, 1)
+ const log = resp.logs[0]
+ assertContainSubset(log, {
+ event: 'MaxGroupSizeSet',
+ args: {
+ size: new BigNumber(newSize),
+ },
+ })
+ })
+
+ describe('when called by a non-owner', () => {
+ it('should revert', async () => {
+ await assertRevert(
+ validators.setMaxGroupSize(newSize, {
+ from: nonOwner,
+ })
+ )
+ })
+ })
+ })
+
+ describe('when the size is the same', () => {
+ it('should revert', async () => {
+ await assertRevert(validators.setMaxGroupSize(maxGroupSize))
+ })
+ })
+ })
+ })
+
describe('#setRegistrationRequirements()', () => {
describe('when the requirements are different', () => {
const newRequirements = {
@@ -211,7 +342,7 @@ contract('Validators', (accounts: string[]) => {
})
describe('#setDeregistrationLockups()', () => {
- describe('when the requirements are different', () => {
+ describe('when the lockups are different', () => {
const newLockups = {
group: deregistrationLockups.group.plus(1),
validator: deregistrationLockups.validator.plus(1),
@@ -224,7 +355,7 @@ contract('Validators', (accounts: string[]) => {
resp = await validators.setDeregistrationLockups(newLockups.group, newLockups.validator)
})
- it('should set the group and validator requirements', async () => {
+ it('should set the group and validator lockups', async () => {
const [group, validator] = await validators.getDeregistrationLockups()
assertEqualBN(group, newLockups.group)
assertEqualBN(validator, newLockups.validator)
@@ -253,7 +384,7 @@ contract('Validators', (accounts: string[]) => {
})
})
- describe('when the requirements are the same', () => {
+ describe('when the lockups are the same', () => {
it('should revert', async () => {
await assertRevert(
validators.setDeregistrationLockups(
@@ -266,11 +397,74 @@ contract('Validators', (accounts: string[]) => {
})
})
+ describe('#setValidatorScoreParameters()', () => {
+ describe('when the parameters are different', () => {
+ const newParameters = {
+ exponent: validatorScoreParameters.exponent.plus(1),
+ adjustmentSpeed: validatorScoreParameters.adjustmentSpeed.plus(1),
+ }
+
+ describe('when called by the owner', () => {
+ let resp: any
+
+ beforeEach(async () => {
+ resp = await validators.setValidatorScoreParameters(
+ newParameters.exponent,
+ newParameters.adjustmentSpeed
+ )
+ })
+
+ it('should set the exponent and adjustment speed', async () => {
+ const [exponent, adjustmentSpeed] = await validators.getValidatorScoreParameters()
+ assertEqualBN(exponent, newParameters.exponent)
+ assertEqualBN(adjustmentSpeed, newParameters.adjustmentSpeed)
+ })
+
+ it('should emit the ValidatorScoreParametersSet event', async () => {
+ assert.equal(resp.logs.length, 1)
+ const log = resp.logs[0]
+ assertContainSubset(log, {
+ event: 'ValidatorScoreParametersSet',
+ args: {
+ exponent: new BigNumber(newParameters.exponent),
+ adjustmentSpeed: new BigNumber(newParameters.adjustmentSpeed),
+ },
+ })
+ })
+
+ describe('when called by a non-owner', () => {
+ it('should revert', async () => {
+ await assertRevert(
+ validators.setValidatorScoreParameters(
+ newParameters.exponent,
+ newParameters.adjustmentSpeed,
+ {
+ from: nonOwner,
+ }
+ )
+ )
+ })
+ })
+ })
+
+ describe('when the requirements are the same', () => {
+ it('should revert', async () => {
+ await assertRevert(
+ validators.setValidatorScoreParameters(
+ validatorScoreParameters.exponent,
+ validatorScoreParameters.adjustmentSpeed
+ )
+ )
+ })
+ })
+ })
+ })
+
describe('#setMaxGroupSize()', () => {
describe('when the size is different', () => {
describe('when called by the owner', () => {
let resp: any
- const newSize = maxGroupSize + 1
+ const newSize = maxGroupSize.plus(1)
beforeEach(async () => {
resp = await validators.setMaxGroupSize(newSize)
@@ -665,6 +859,18 @@ contract('Validators', (accounts: string[]) => {
assert.deepEqual(parsedGroup.members, [])
})
+ it("should update the member's membership history", async () => {
+ await validators.deaffiliate()
+ const membershipHistory = await validators.getMembershipHistory(validator)
+ const expectedEpoch = new BigNumber(
+ Math.floor((await web3.eth.getBlock('latest')).number / EPOCH)
+ )
+ assert.equal(membershipHistory[0].length, 1)
+ assertEqualBN(membershipHistory[0][0], expectedEpoch)
+ assert.equal(membershipHistory[1].length, 1)
+ assertSameAddress(membershipHistory[1][0], NULL_ADDRESS)
+ })
+
it('should emit the ValidatorGroupMemberRemoved event', async () => {
const resp = await validators.deaffiliate()
assert.equal(resp.logs.length, 2)
@@ -844,6 +1050,17 @@ contract('Validators', (accounts: string[]) => {
assert.deepEqual(parsedGroup.members, [validator])
})
+ it("should update the member's membership history", async () => {
+ const membershipHistory = await validators.getMembershipHistory(validator)
+ const expectedEpoch = new BigNumber(
+ Math.floor((await web3.eth.getBlock('latest')).number / EPOCH)
+ )
+ assert.equal(membershipHistory[0].length, 1)
+ assertEqualBN(membershipHistory[0][0], expectedEpoch)
+ assert.equal(membershipHistory[1].length, 1)
+ assertSameAddress(membershipHistory[1][0], group)
+ })
+
it('should emit the ValidatorGroupMemberAdded event', async () => {
assert.equal(resp.logs.length, 1)
const log = resp.logs[0]
@@ -901,6 +1118,18 @@ contract('Validators', (accounts: string[]) => {
assert.deepEqual(parsedGroup.members, [])
})
+ it("should update the member's membership history", async () => {
+ await validators.removeMember(validator)
+ const membershipHistory = await validators.getMembershipHistory(validator)
+ const expectedEpoch = new BigNumber(
+ Math.floor((await web3.eth.getBlock('latest')).number / EPOCH)
+ )
+ assert.equal(membershipHistory[0].length, 1)
+ assertEqualBN(membershipHistory[0][0], expectedEpoch)
+ assert.equal(membershipHistory[1].length, 1)
+ assertSameAddress(membershipHistory[1][0], NULL_ADDRESS)
+ })
+
it('should emit the ValidatorGroupMemberRemoved event', async () => {
const resp = await validators.removeMember(validator)
assert.equal(resp.logs.length, 1)
@@ -987,4 +1216,115 @@ contract('Validators', (accounts: string[]) => {
})
})
})
+
+ describe('#updateValidatorScore', () => {
+ const validator = accounts[0]
+ beforeEach(async () => {
+ await registerValidator(validator)
+ })
+
+ describe('when 0 <= uptime <= 1.0', () => {
+ const uptime = 0.99
+ const adjustmentSpeed = fromFixed(validatorScoreParameters.adjustmentSpeed)
+ beforeEach(async () => {
+ await validators.updateValidatorScore(validator, toFixed(uptime))
+ })
+
+ it('should update the validator score', async () => {
+ const expectedScore = adjustmentSpeed.times(uptime)
+ const parsedValidator = parseValidatorParams(await validators.getValidator(validator))
+ assertEqualBN(parsedValidator.score, toFixed(expectedScore))
+ })
+
+ describe('when the validator already has a non-zero score', () => {
+ beforeEach(async () => {
+ await validators.updateValidatorScore(validator, toFixed(uptime))
+ })
+
+ it('should update the validator score', async () => {
+ let expectedScore = adjustmentSpeed.times(uptime)
+ expectedScore = new BigNumber(1)
+ .minus(adjustmentSpeed)
+ .times(expectedScore)
+ .plus(expectedScore)
+ const parsedValidator = parseValidatorParams(await validators.getValidator(validator))
+ assertEqualBN(parsedValidator.score, toFixed(expectedScore))
+ })
+ })
+ })
+
+ describe('when uptime > 1.0', () => {
+ const uptime = 1.01
+ it('should revert', async () => {
+ await assertRevert(validators.updateValidatorScore(validator, toFixed(uptime)))
+ })
+ })
+ })
+
+ describe('#updateMembershipHistory', () => {
+ const validator = accounts[0]
+ const groups = accounts.slice(1)
+ beforeEach(async () => {
+ await registerValidator(validator)
+ for (const group of groups) {
+ await registerValidatorGroup(group)
+ }
+ })
+
+ describe('when changing groups more times than membership history length', () => {
+ it('should always store the most recent memberships', async () => {
+ for (let i = 0; i < membershipHistoryLength.plus(1).toNumber(); i++) {
+ await validators.affiliate(groups[i])
+ const currentEpoch = new BigNumber(
+ Math.floor((await web3.eth.getBlock('latest')).number / EPOCH)
+ )
+ await validators.addMember(validator, { from: groups[i] })
+ await mineBlocks(EPOCH, web3)
+
+ const membershipHistory = await validators.getMembershipHistory(validator)
+ const expectedMembershipHistoryLength = Math.min(
+ i + 1,
+ membershipHistoryLength.toNumber()
+ )
+ assert.equal(membershipHistory[0].length, expectedMembershipHistoryLength)
+ assert.equal(membershipHistory[1].length, expectedMembershipHistoryLength)
+ for (let j = 0; j < expectedMembershipHistoryLength; j++) {
+ assert.include(
+ membershipHistory[0].map((x) => x.toNumber()),
+ currentEpoch.minus(j).toNumber()
+ )
+ assert.include(
+ membershipHistory[1].map((x) => x.toLowerCase()),
+ groups[i - j].toLowerCase()
+ )
+ }
+ }
+ })
+ })
+ })
+
+ describe('#getMembershipInLastEpoch', () => {
+ const validator = accounts[0]
+ const groups = accounts.slice(1)
+ beforeEach(async () => {
+ await registerValidator(validator)
+ for (const group of groups) {
+ await registerValidatorGroup(group)
+ }
+ })
+
+ describe('when changing groups more times than membership history length', () => {
+ it('should always return the correct membership for the last epoch', async () => {
+ for (let i = 0; i < membershipHistoryLength.plus(1).toNumber(); i++) {
+ await validators.affiliate(groups[i])
+ await validators.addMember(validator, { from: groups[i] })
+ if (i > 0) {
+ assert.equal(await validators.getMembershipInLastEpoch(validator), groups[i - 1])
+ }
+ await mineBlocks(EPOCH, web3)
+ assert.equal(await validators.getMembershipInLastEpoch(validator), groups[i])
+ }
+ })
+ })
+ })
})
From 47a247c52004fbae0dd512aeadd01edd3f6354ec Mon Sep 17 00:00:00 2001
From: Asa Oines
Date: Fri, 4 Oct 2019 11:12:41 -0700
Subject: [PATCH 028/149] Added test for epoch payment distribution
---
.../contracts/common/UsingRegistry.sol | 8 +++
.../contracts/governance/Validators.sol | 2 -
.../contracts/stability/StableToken.sol | 51 ++++++++++---------
.../stability/interfaces/IStableToken.sol | 12 -----
.../stability/test/MockStableToken.sol | 4 +-
packages/protocol/lib/test-utils.ts | 12 -----
.../protocol/migrations/08_stabletoken.ts | 19 +------
packages/protocol/migrations/09_exchange.ts | 7 ---
packages/protocol/migrationsConfig.js | 6 ++-
.../protocol/scripts/truffle/network_check.ts | 2 -
.../protocol/test/governance/validators.ts | 38 ++++++++++++++
.../protocol/test/stability/stabletoken.ts | 45 ++++++++--------
12 files changed, 103 insertions(+), 103 deletions(-)
diff --git a/packages/protocol/contracts/common/UsingRegistry.sol b/packages/protocol/contracts/common/UsingRegistry.sol
index 24561e9fc18..b09cae9cb0f 100644
--- a/packages/protocol/contracts/common/UsingRegistry.sol
+++ b/packages/protocol/contracts/common/UsingRegistry.sol
@@ -11,6 +11,8 @@ import "../governance/interfaces/IValidators.sol";
import "../identity/interfaces/IRandom.sol";
+import "../stability/interfaces/IStableToken.sol";
+
// Ideally, UsingRegistry should inherit from Initializable and implement initialize() which calls
// setRegistry(). TypeChain currently has problems resolving overloaded functions, so this is not
// possible right now.
@@ -23,6 +25,7 @@ contract UsingRegistry is Ownable {
// solhint-disable state-visibility
bytes32 constant ATTESTATIONS_REGISTRY_ID = keccak256(abi.encodePacked("Attestations"));
bytes32 constant ELECTION_REGISTRY_ID = keccak256(abi.encodePacked("Election"));
+ bytes32 constant EXCHANGE_REGISTRY_ID = keccak256(abi.encodePacked("Exchange"));
bytes32 constant GAS_CURRENCY_WHITELIST_REGISTRY_ID = keccak256(
abi.encodePacked("GasCurrencyWhitelist")
);
@@ -32,6 +35,7 @@ contract UsingRegistry is Ownable {
bytes32 constant RESERVE_REGISTRY_ID = keccak256(abi.encodePacked("Reserve"));
bytes32 constant RANDOM_REGISTRY_ID = keccak256(abi.encodePacked("Random"));
bytes32 constant SORTED_ORACLES_REGISTRY_ID = keccak256(abi.encodePacked("SortedOracles"));
+ bytes32 constant STABLE_TOKEN_REGISTRY_ID = keccak256(abi.encodePacked("StableToken"));
bytes32 constant VALIDATORS_REGISTRY_ID = keccak256(abi.encodePacked("Validators"));
// solhint-enable state-visibility
@@ -67,6 +71,10 @@ contract UsingRegistry is Ownable {
return IRandom(registry.getAddressForOrDie(RANDOM_REGISTRY_ID));
}
+ function getStableToken() internal view returns(IStableToken) {
+ return IStableToken(registry.getAddressForOrDie(STABLE_TOKEN_REGISTRY_ID));
+ }
+
function getValidators() internal view returns(IValidators) {
return IValidators(registry.getAddressForOrDie(VALIDATORS_REGISTRY_ID));
}
diff --git a/packages/protocol/contracts/governance/Validators.sol b/packages/protocol/contracts/governance/Validators.sol
index 23adae92bf1..d3e5be9de5a 100644
--- a/packages/protocol/contracts/governance/Validators.sol
+++ b/packages/protocol/contracts/governance/Validators.sol
@@ -439,10 +439,8 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
address group = getMembershipInLastEpoch(account);
uint256 groupPayment = totalPayment.multiply(groups[group].commission).fromFixed();
uint256 validatorPayment = totalPayment.fromFixed().sub(groupPayment);
- /*
getStableToken().mint(group, groupPayment);
getStableToken().mint(account, validatorPayment);
- */
}
/**
diff --git a/packages/protocol/contracts/stability/StableToken.sol b/packages/protocol/contracts/stability/StableToken.sol
index 148cfcf0402..8974fb91c7d 100644
--- a/packages/protocol/contracts/stability/StableToken.sol
+++ b/packages/protocol/contracts/stability/StableToken.sol
@@ -21,8 +21,6 @@ contract StableToken is IStableToken, IERC20Token, ICeloToken, Ownable,
using FixidityLib for FixidityLib.Fraction;
using SafeMath for uint256;
- event MinterSet(address indexed _minter);
-
event InflationFactorUpdated(
uint256 factor,
uint256 lastUpdated
@@ -44,7 +42,6 @@ contract StableToken is IStableToken, IERC20Token, ICeloToken, Ownable,
string comment
);
- address public minter;
string internal name_;
string internal symbol_;
uint8 internal decimals_;
@@ -71,14 +68,6 @@ contract StableToken is IStableToken, IERC20Token, ICeloToken, Ownable,
InflationState inflationState;
- /**
- * @notice Throws if called by any account other than the minter.
- */
- modifier onlyMinter() {
- require(msg.sender == minter, "sender was not minter");
- _;
- }
-
/**
* Only VM would be able to set the caller address to 0x0 unless someone
* really has the private key for 0x0
@@ -123,12 +112,22 @@ contract StableToken is IStableToken, IERC20Token, ICeloToken, Ownable,
uint8 _decimals,
address registryAddress,
uint256 inflationRate,
- uint256 inflationFactorUpdatePeriod
+ uint256 inflationFactorUpdatePeriod,
+ address[] calldata initialBalanceAddresses,
+ uint256[] calldata initialBalanceValues
)
external
initializer
{
require(inflationRate != 0, "Must provide a non-zero inflation rate.");
+ require(initialBalanceAddresses.length == initialBalanceValues.length);
+ for (uint256 i = 0; i < initialBalanceAddresses.length; i = i.add(1)) {
+ totalSupply_ = totalSupply_.add(initialBalanceValues[i]);
+ balances[initialBalanceAddresses[i]] = balances[initialBalanceAddresses[i]].add(
+ initialBalanceValues[i]
+ );
+ }
+
_transferOwnership(msg.sender);
totalSupply_ = 0;
name_ = _name;
@@ -144,16 +143,6 @@ contract StableToken is IStableToken, IERC20Token, ICeloToken, Ownable,
setRegistry(registryAddress);
}
- // Should this be tied to the registry?
- /**
- * @notice Updates 'minter'.
- * @param _minter An address with special permissions to modify its balance
- */
- function setMinter(address _minter) external onlyOwner {
- minter = _minter;
- emit MinterSet(minter);
- }
-
/**
* @notice Updates Inflation Parameters.
* @param rate new rate.
@@ -208,10 +197,15 @@ contract StableToken is IStableToken, IERC20Token, ICeloToken, Ownable,
uint256 value
)
external
- onlyMinter
updateInflationFactor
returns (bool)
{
+ // Only the Exchange and Validators contracts are authorized to mint.
+ require(
+ msg.sender == registry.getAddressFor(EXCHANGE_REGISTRY_ID) ||
+ msg.sender == registry.getAddressFor(VALIDATORS_REGISTRY_ID)
+ );
+
uint256 units = _valueToUnits(inflationState.factor, value);
totalSupply_ = totalSupply_.add(units);
balances[to] = balances[to].add(units);
@@ -241,10 +235,17 @@ contract StableToken is IStableToken, IERC20Token, ICeloToken, Ownable,
}
/**
- * @notice Burns StableToken from the balance of 'minter'.
+ * @notice Burns StableToken from the balance of msg.sender.
* @param value The amount of StableToken to burn.
*/
- function burn(uint256 value) external onlyMinter updateInflationFactor returns (bool) {
+ function burn(
+ uint256 value
+ )
+ external
+ onlyRegisteredContract(EXCHANGE_REGISTRY_ID)
+ updateInflationFactor
+ returns (bool)
+ {
uint256 units = _valueToUnits(inflationState.factor, value);
require(units <= balances[msg.sender], "value exceeded balance of sender");
totalSupply_ = totalSupply_.sub(units);
diff --git a/packages/protocol/contracts/stability/interfaces/IStableToken.sol b/packages/protocol/contracts/stability/interfaces/IStableToken.sol
index 380be53ab2d..87edfc419aa 100644
--- a/packages/protocol/contracts/stability/interfaces/IStableToken.sol
+++ b/packages/protocol/contracts/stability/interfaces/IStableToken.sol
@@ -6,18 +6,6 @@ pragma solidity ^0.5.3;
* absence of interface inheritance is intended as a companion to IERC20.sol and ICeloToken.sol.
*/
interface IStableToken {
-
- function initialize(
- string calldata,
- string calldata,
- uint8,
- address,
- uint256,
- uint256
- ) external;
-
- function setMinter(address) external;
-
function mint(address, uint256) external returns (bool);
function burn(uint256) external returns (bool);
function debitFrom(address, uint256) external;
diff --git a/packages/protocol/contracts/stability/test/MockStableToken.sol b/packages/protocol/contracts/stability/test/MockStableToken.sol
index b54b538f1c7..e0e1f1f0612 100644
--- a/packages/protocol/contracts/stability/test/MockStableToken.sol
+++ b/packages/protocol/contracts/stability/test/MockStableToken.sol
@@ -11,6 +11,7 @@ contract MockStableToken {
bool public _needsRebase;
uint256 public _totalSupply;
uint256 public _targetTotalSupply;
+ mapping (address => uint256) public balanceOf;
function setNeedsRebase() external {
_needsRebase = true;
@@ -24,7 +25,8 @@ contract MockStableToken {
_targetTotalSupply = value;
}
- function mint(address, uint256) external pure returns (bool) {
+ function mint(address to, uint256 value) external returns (bool) {
+ balanceOf[to] = balanceOf[to] + value;
return true;
}
diff --git a/packages/protocol/lib/test-utils.ts b/packages/protocol/lib/test-utils.ts
index 654ca5adf68..982ea4a2a22 100644
--- a/packages/protocol/lib/test-utils.ts
+++ b/packages/protocol/lib/test-utils.ts
@@ -5,11 +5,9 @@ import * as chaiSubset from 'chai-subset'
import { spawn } from 'child_process'
import { keccak256 } from 'ethereumjs-util'
import {
- ExchangeInstance,
ProxyInstance,
RegistryInstance,
ReserveInstance,
- StableTokenInstance,
UsingRegistryInstance,
} from 'types'
const soliditySha3 = new (require('web3'))().utils.soliditySha3
@@ -184,16 +182,6 @@ export const assertContractsOwnedByMultiSig = async (getContract: any) => {
}
}
-export const assertStableTokenMinter = async (getContract: any) => {
- const stableToken: StableTokenInstance = await getContract('StableToken', 'proxiedContract')
- const exchange: ExchangeInstance = await getContract('Exchange', 'proxiedContract')
- assert.equal(
- await stableToken.minter(),
- exchange.address,
- 'StableToken minter not set to Exchange'
- )
-}
-
export const assertFloatEquality = (
a: BigNumber,
b: BigNumber,
diff --git a/packages/protocol/migrations/08_stabletoken.ts b/packages/protocol/migrations/08_stabletoken.ts
index 5bc02ece37d..e97bb4ab16d 100644
--- a/packages/protocol/migrations/08_stabletoken.ts
+++ b/packages/protocol/migrations/08_stabletoken.ts
@@ -3,7 +3,6 @@ import Web3 = require('web3')
import { CeloContractName } from '@celo/protocol/lib/registry-utils'
import {
- convertToContractDecimalsBN,
deploymentForCoreContract,
getDeployedProxiedContract,
} from '@celo/protocol/lib/web3-utils'
@@ -21,7 +20,6 @@ const NULL_ADDRESS = '0x0000000000000000000000000000000000000000'
const initializeArgs = async (): Promise => {
const rate = toFixed(config.stableToken.inflationRate)
-
return [
config.stableToken.tokenName,
config.stableToken.tokenSymbol,
@@ -29,6 +27,8 @@ const initializeArgs = async (): Promise => {
config.registry.predeployedProxyAddress,
rate.toString(),
config.stableToken.inflationPeriod,
+ config.stableToken.initialBalances.addresses,
+ config.stableToken.initialBalances.values,
]
}
@@ -39,21 +39,6 @@ module.exports = deploymentForCoreContract(
initializeArgs,
async (stableToken: StableTokenInstance, _web3: Web3, networkName: string) => {
const minerAddress: string = truffle.networks[networkName].from
- const minerStartBalance = await convertToContractDecimalsBN(
- config.stableToken.minerDollarBalance.toString(),
- stableToken
- )
- console.log(
- `Minting ${minerAddress} ${config.stableToken.minerDollarBalance.toString()} StableToken`
- )
- await stableToken.setMinter(minerAddress)
-
- const initialBalance = web3.utils.toBN(minerStartBalance)
- await stableToken.mint(minerAddress, initialBalance)
- for (const address of config.stableToken.initialAccounts) {
- await stableToken.mint(address, initialBalance)
- }
-
console.log('Setting GoldToken/USD exchange rate')
const sortedOracles: SortedOraclesInstance = await getDeployedProxiedContract<
SortedOraclesInstance
diff --git a/packages/protocol/migrations/09_exchange.ts b/packages/protocol/migrations/09_exchange.ts
index b7d33e74fbe..07d78d5a295 100644
--- a/packages/protocol/migrations/09_exchange.ts
+++ b/packages/protocol/migrations/09_exchange.ts
@@ -30,13 +30,6 @@ module.exports = deploymentForCoreContract(
CeloContractName.Exchange,
initializeArgs,
async (exchange: ExchangeInstance) => {
- console.log('Setting Exchange as StableToken minter')
- const stableToken: StableTokenInstance = await getDeployedProxiedContract(
- 'StableToken',
- artifacts
- )
- await stableToken.setMinter(exchange.address)
-
console.log('Setting Exchange as a Reserve spender')
const reserve: ReserveInstance = await getDeployedProxiedContract(
'Reserve',
diff --git a/packages/protocol/migrationsConfig.js b/packages/protocol/migrationsConfig.js
index d78aca5bc07..5b91f04888e 100644
--- a/packages/protocol/migrationsConfig.js
+++ b/packages/protocol/migrationsConfig.js
@@ -58,13 +58,15 @@ const DefaultConfig = {
stableToken: {
decimals: 18,
goldPrice: 10,
- minerDollarBalance: 60000,
tokenName: 'Celo Dollar',
tokenSymbol: 'cUSD',
// 52nd root of 1.005, equivalent to 0.5% annual inflation
inflationRate: 1.00009591886,
inflationPeriod: 7 * 24 * 60 * 60, // 1 week
- initialAccounts: [],
+ initialBalances: {
+ addresses: [],
+ values: [],
+ },
},
validators: {
registrationRequirements: {
diff --git a/packages/protocol/scripts/truffle/network_check.ts b/packages/protocol/scripts/truffle/network_check.ts
index 4f81207c363..1f75a3aabdd 100644
--- a/packages/protocol/scripts/truffle/network_check.ts
+++ b/packages/protocol/scripts/truffle/network_check.ts
@@ -5,7 +5,6 @@ import {
assertContractsRegistered,
assertProxiesSet,
assertRegistryAddressesSet,
- assertStableTokenMinter,
getReserveBalance,
proxiedContracts,
} from '@celo/protocol/lib/test-utils'
@@ -41,7 +40,6 @@ module.exports = async (callback: (error?: any) => number) => {
await assertContractsRegistered(getContract)
await assertRegistryAddressesSet(getContract)
await assertContractsOwnedByMultiSig(getContract)
- await assertStableTokenMinter(getContract)
await assertReserveBalance()
console.log('Network check succeeded!')
callback()
diff --git a/packages/protocol/test/governance/validators.ts b/packages/protocol/test/governance/validators.ts
index bfbb97d163e..97fa1f3ed04 100644
--- a/packages/protocol/test/governance/validators.ts
+++ b/packages/protocol/test/governance/validators.ts
@@ -13,6 +13,8 @@ import {
MockLockedGoldInstance,
MockElectionContract,
MockElectionInstance,
+ MockStableTokenContract,
+ MockStableTokenInstance,
RegistryContract,
RegistryInstance,
ValidatorsTestContract,
@@ -23,6 +25,7 @@ import { fromFixed, toFixed } from '@celo/utils/lib/fixidity'
const Validators: ValidatorsTestContract = artifacts.require('ValidatorsTest')
const MockLockedGold: MockLockedGoldContract = artifacts.require('MockLockedGold')
const MockElection: MockElectionContract = artifacts.require('MockElection')
+const MockStableToken: MockStableTokenContract = artifacts.require('MockStableToken')
const Registry: RegistryContract = artifacts.require('Registry')
// @ts-ignore
@@ -54,6 +57,7 @@ const MAX_UINT256 = new BigNumber(2).pow(256).minus(1)
// Hard coded in ValidatorsTest.sol
const EPOCH = 100
+// TODO(asa): Test epoch payment distribution
contract('Validators', (accounts: string[]) => {
let validators: ValidatorsTestInstance
let registry: RegistryInstance
@@ -1327,4 +1331,38 @@ contract('Validators', (accounts: string[]) => {
})
})
})
+
+ describe('#distributeEpochPayment', () => {
+ const validator = accounts[0]
+ const group = accounts[1]
+ let mockStableToken: MockStableTokenInstance
+ beforeEach(async () => {
+ await registerValidatorGroupWithMembers(group, [validator])
+ mockStableToken = await MockStableToken.new()
+ await registry.setAddressFor(CeloContractName.StableToken, mockStableToken.address)
+ })
+
+ describe('when the validator score is non-zero', () => {
+ const uptime = 0.99
+ const adjustmentSpeed = fromFixed(validatorScoreParameters.adjustmentSpeed)
+ const expectedScore = adjustmentSpeed.times(uptime)
+ const expectedTotalPayment = expectedScore.times(validatorEpochPayment)
+ beforeEach(async () => {
+ await validators.updateValidatorScore(validator, toFixed(uptime))
+ await validators.distributeEpochPayment(validator)
+ })
+
+ it('should pay the validator', async () => {
+ const expectedPayment = expectedTotalPayment.times(
+ new BigNumber(1).minus(fromFixed(commission))
+ )
+ assertEqualBN(await mockStableToken.balanceOf(validator), expectedPayment)
+ })
+
+ it('should pay the group', async () => {
+ const expectedPayment = expectedTotalPayment.times(fromFixed(commission))
+ assertEqualBN(await mockStableToken.balanceOf(group), expectedPayment)
+ })
+ })
+ })
})
diff --git a/packages/protocol/test/stability/stabletoken.ts b/packages/protocol/test/stability/stabletoken.ts
index 0f3addefc95..51a517d0bf8 100644
--- a/packages/protocol/test/stability/stabletoken.ts
+++ b/packages/protocol/test/stability/stabletoken.ts
@@ -1,3 +1,4 @@
+import { CeloContractName } from '@celo/protocol/lib/registry-utils'
import {
assertLogMatches,
assertLogMatches2,
@@ -106,34 +107,32 @@ contract('StableToken', (accounts: string[]) => {
})
})
- describe('#setMinter()', () => {
- const minter = accounts[0]
- it('should allow owner to set minter', async () => {
- await stableToken.setMinter(minter)
- assert.equal(await stableToken.minter(), minter)
- })
-
- it('should not allow anyone else to set minter', async () => {
- await assertRevert(stableToken.setMinter(minter, { from: accounts[1] }))
- })
- })
-
describe('#mint()', () => {
- const minter = accounts[0]
+ const exchange = accounts[0]
+ const validators = accounts[1]
beforeEach(async () => {
- await stableToken.setMinter(minter)
+ await registry.setAddressFor(CeloContractName.Exchange, exchange)
+ await registry.setAddressFor(CeloContractName.Validators, validators)
})
- it('should allow minter to mint', async () => {
- await stableToken.mint(minter, amountToMint)
- const balance = (await stableToken.balanceOf(minter)).toNumber()
+ it('should allow the registered exchange contract to mint', async () => {
+ await stableToken.mint(exchange, amountToMint)
+ const balance = (await stableToken.balanceOf(exchange)).toNumber()
+ assert.equal(balance, amountToMint)
+ const supply = (await stableToken.totalSupply()).toNumber()
+ assert.equal(supply, amountToMint)
+ })
+
+ it('should allow the registered validators contract to mint', async () => {
+ await stableToken.mint(validators, amountToMint, { from: validators })
+ const balance = (await stableToken.balanceOf(validators)).toNumber()
assert.equal(balance, amountToMint)
const supply = (await stableToken.totalSupply()).toNumber()
assert.equal(supply, amountToMint)
})
it('should not allow anyone else to mint', async () => {
- await assertRevert(stableToken.mint(minter, amountToMint, { from: accounts[1] }))
+ await assertRevert(stableToken.mint(minter, amountToMint, { from: accounts[2] }))
})
})
@@ -143,7 +142,7 @@ contract('StableToken', (accounts: string[]) => {
const comment = 'tacos at lunch'
beforeEach(async () => {
- await stableToken.setMinter(sender)
+ await registry.setAddressFor(CeloContractName.Exchange, sender)
await stableToken.mint(sender, amountToMint)
})
@@ -251,7 +250,7 @@ contract('StableToken', (accounts: string[]) => {
const mintAmount = 1000
beforeEach(async () => {
- await stableToken.setMinter(minter)
+ await registry.setAddressFor(CeloContractName.Exchange, minter)
await stableToken.mint(minter, mintAmount)
})
@@ -350,7 +349,7 @@ contract('StableToken', (accounts: string[]) => {
const minter = accounts[0]
const amountToBurn = 5
beforeEach(async () => {
- await stableToken.setMinter(minter)
+ await registry.setAddressFor(CeloContractName.Exchange, minter)
await stableToken.mint(minter, amountToMint)
})
@@ -376,7 +375,7 @@ contract('StableToken', (accounts: string[]) => {
const amount = new BigNumber(10000000000000000000)
beforeEach(async () => {
- await stableToken.setMinter(sender)
+ await registry.setAddressFor(CeloContractName.Exchange, sender)
await stableToken.mint(sender, amount.times(2))
await stableToken.setInflationParameters(inflationRate, SECONDS_IN_A_WEEK)
await timeTravel(SECONDS_IN_A_WEEK, web3)
@@ -438,7 +437,7 @@ contract('StableToken', (accounts: string[]) => {
const transferAmount = 1
beforeEach(async () => {
- await stableToken.setMinter(sender)
+ await registry.setAddressFor(CeloContractName.Exchange, sender)
await stableToken.mint(sender, amountToMint)
})
From 767c651950de66c3166a0042e4217869937b4efc Mon Sep 17 00:00:00 2001
From: Asa Oines
Date: Fri, 4 Oct 2019 17:40:23 -0700
Subject: [PATCH 029/149] Validator set changing again
---
.../src/e2e-tests/governance_tests.ts | 439 +++++-------------
.../contracts/governance/Validators.sol | 13 +-
.../governance/test/ValidatorsTest.sol | 4 +-
3 files changed, 126 insertions(+), 330 deletions(-)
diff --git a/packages/celotool/src/e2e-tests/governance_tests.ts b/packages/celotool/src/e2e-tests/governance_tests.ts
index be843014688..d0663f8ce56 100644
--- a/packages/celotool/src/e2e-tests/governance_tests.ts
+++ b/packages/celotool/src/e2e-tests/governance_tests.ts
@@ -1,10 +1,8 @@
import BigNumber from 'bignumber.js'
+import { ContractKit, newKitFromWeb3 } from '@celo/contractkit'
import { assert } from 'chai'
import Web3 from 'web3'
-import { strip0x } from '../lib/utils'
import {
- assertRevert,
- erc20Abi,
getContext,
getContractAddress,
getEnode,
@@ -13,147 +11,6 @@ import {
sleep,
} from './utils'
-// TODO(asa): Use the contract kit here instead
-const electionAbi = [
- {
- constant: true,
- inputs: [
- {
- name: 'index',
- type: 'uint256',
- },
- ],
- name: 'validatorAddressFromCurrentSet',
- outputs: [
- {
- name: '',
- type: 'address',
- },
- ],
- payable: false,
- stateMutability: 'view',
- type: 'function',
- },
- {
- constant: true,
- inputs: [],
- name: 'numberValidatorsInCurrentSet',
- outputs: [
- {
- name: '',
- type: 'uint256',
- },
- ],
- payable: false,
- stateMutability: 'view',
- type: 'function',
- },
-]
-
-const registryAbi = [
- {
- constant: true,
- inputs: [
- {
- name: 'identifier',
- type: 'string',
- },
- ],
- name: 'getAddressForString',
- outputs: [
- {
- name: '',
- type: 'address',
- },
- ],
- payable: false,
- stateMutability: 'view',
- type: 'function',
- },
-]
-
-const validatorsAbi = [
- {
- constant: true,
- inputs: [],
- name: 'getRegisteredValidatorGroups',
- outputs: [
- {
- name: '',
- type: 'address[]',
- },
- ],
- payable: false,
- stateMutability: 'view',
- type: 'function',
- },
- {
- constant: true,
- inputs: [
- {
- name: 'account',
- type: 'address',
- },
- ],
- name: 'getValidatorGroup',
- outputs: [
- {
- name: '',
- type: 'string',
- },
- {
- name: '',
- type: 'string',
- },
- {
- name: '',
- type: 'address[]',
- },
- ],
- payable: false,
- stateMutability: 'view',
- type: 'function',
- },
- {
- constant: false,
- inputs: [
- {
- name: 'validator',
- type: 'address',
- },
- ],
- name: 'addMember',
- outputs: [
- {
- name: '',
- type: 'bool',
- },
- ],
- payable: false,
- stateMutability: 'nonpayable',
- type: 'function',
- },
- {
- constant: false,
- inputs: [
- {
- name: 'validator',
- type: 'address',
- },
- ],
- name: 'removeMember',
- outputs: [
- {
- name: '',
- type: 'bool',
- },
- ],
- payable: false,
- stateMutability: 'nonpayable',
- type: 'function',
- },
-]
-
describe('governance tests', () => {
const gethConfig = {
migrate: true,
@@ -172,6 +29,7 @@ describe('governance tests', () => {
let validators: any
let goldToken: any
let registry: any
+ let kit: ContractKit
before(async function(this: any) {
this.timeout(0)
@@ -183,10 +41,11 @@ describe('governance tests', () => {
const restart = async () => {
await context.hooks.restart()
web3 = new Web3('http://localhost:8545')
- goldToken = new web3.eth.Contract(erc20Abi, await getContractAddress('GoldTokenProxy'))
- validators = new web3.eth.Contract(validatorsAbi, await getContractAddress('ValidatorsProxy'))
- registry = new web3.eth.Contract(registryAbi, '0x000000000000000000000000000000000000ce10')
- election = new web3.eth.Contract(electionAbi, await getContractAddress('ElectionProxy'))
+ kit = newKitFromWeb3(web3)
+ goldToken = await kit._web3Contracts.getGoldToken()
+ validators = await kit._web3Contracts.getValidators()
+ registry = await kit._web3Contracts.getRegistry()
+ election = await kit._web3Contracts.getElection()
}
const unlockAccount = async (address: string, theWeb3: any) => {
@@ -194,10 +53,22 @@ describe('governance tests', () => {
await theWeb3.eth.personal.unlockAccount(address, '', 1000)
}
- const getValidatorGroupMembers = async () => {
- const [groupAddress] = await validators.methods.getRegisteredValidatorGroups().call()
- const groupInfo = await validators.methods.getValidatorGroup(groupAddress).call()
- return groupInfo[2]
+ const getValidatorGroupMembers = async (blockNumber?: number) => {
+ if (blockNumber) {
+ const [groupAddress] = await validators.methods
+ .getRegisteredValidatorGroups()
+ .call({}, blockNumber)
+ const groupInfo = await validators.methods
+ .getValidatorGroup(groupAddress)
+ .call({}, blockNumber)
+ return groupInfo[2]
+ } else {
+ const [groupAddress] = await validators.methods.getRegisteredValidatorGroups().call()
+ console.log('group address', groupAddress)
+ const groupInfo = await validators.methods.getValidatorGroup(groupAddress).call()
+ console.log('group info', groupInfo)
+ return groupInfo[2]
+ }
}
const getValidatorGroupKeys = async () => {
@@ -238,151 +109,20 @@ describe('governance tests', () => {
return tx.send({ from: group, ...txOptions, gas })
}
- describe('Election.numberValidatorsInCurrentSet()', () => {
- before(async function() {
- this.timeout(0)
- await restart()
- })
-
- it('should return the validator set size', async () => {
- const numberValidators = await election.methods.numberValidatorsInCurrentSet().call()
- assert.equal(numberValidators, 5)
- })
-
- describe('after the validator set changes', () => {
- before(async function() {
- this.timeout(0)
- await restart()
- const [groupAddress, groupPrivateKey] = await getValidatorGroupKeys()
- const epoch = 10
-
- const groupInstance = {
- name: 'validatorGroup',
- validating: false,
- syncmode: 'full',
- port: 30325,
- wsport: 8567,
- privateKey: groupPrivateKey.slice(2),
- peers: [await getEnode(8545)],
- }
- await initAndStartGeth(context.hooks.gethBinaryPath, groupInstance)
- const groupWeb3 = new Web3('ws://localhost:8567')
- election = new web3.eth.Contract(electionAbi, await getContractAddress('ElectionProxy'))
- validators = new groupWeb3.eth.Contract(
- validatorsAbi,
- await getContractAddress('ValidatorsProxy')
- )
- // Give the node time to sync.
- await sleep(15)
- const members = await getValidatorGroupMembers()
- await removeMember(groupWeb3, groupAddress, members[0])
- await sleep(epoch * 2)
- })
-
- it('should return the reduced validator set size', async () => {
- const numberValidators = await election.methods.numberValidatorsInCurrentSet().call()
-
- assert.equal(numberValidators, 4)
- })
- })
- })
-
- describe('Election.validatorAddressFromCurrentSet()', () => {
- before(async function() {
- this.timeout(0)
- await restart()
- })
-
- it('should return the first validator', async () => {
- const resultAddress = await election.methods.validatorAddressFromCurrentSet(0).call()
-
- assert.equal(strip0x(resultAddress), context.validators[0].address)
- })
-
- it('should return the third validator', async () => {
- const resultAddress = await election.methods.validatorAddressFromCurrentSet(2).call()
-
- assert.equal(strip0x(resultAddress), context.validators[2].address)
- })
-
- it('should return the fifth validator', async () => {
- const resultAddress = await election.methods.validatorAddressFromCurrentSet(4).call()
-
- assert.equal(strip0x(resultAddress), context.validators[4].address)
- })
-
- it('should revert when asked for an out of bounds validator', async function(this: any) {
- this.timeout(0) // Disable test timeout
- await assertRevert(
- election.methods.validatorAddressFromCurrentSet(5).send({
- from: `0x${context.validators[0].address}`,
- })
- )
- })
-
- describe('after the validator set changes', () => {
- before(async function() {
- this.timeout(0)
- await restart()
- const [groupAddress, groupPrivateKey] = await getValidatorGroupKeys()
- const epoch = 10
-
- const groupInstance = {
- name: 'validatorGroup',
- validating: false,
- syncmode: 'full',
- port: 30325,
- wsport: 8567,
- privateKey: groupPrivateKey.slice(2),
- peers: [await getEnode(8545)],
- }
- await initAndStartGeth(context.hooks.gethBinaryPath, groupInstance)
- const groupWeb3 = new Web3('ws://localhost:8567')
- validators = new groupWeb3.eth.Contract(
- validatorsAbi,
- await getContractAddress('ValidatorsProxy')
- )
- // Give the node time to sync.
- await sleep(15)
- const members = await getValidatorGroupMembers()
- await removeMember(groupWeb3, groupAddress, members[0])
- await sleep(epoch * 2)
-
- validators = new web3.eth.Contract(
- validatorsAbi,
- await getContractAddress('ValidatorsProxy')
- )
- })
-
- it('should return the second validator in the first place', async () => {
- const resultAddress = await election.methods.validatorAddressFromCurrentSet(0).call()
-
- assert.equal(strip0x(resultAddress), context.validators[1].address)
- })
-
- it('should return the last validator in the fourth place', async () => {
- const resultAddress = await election.methods.validatorAddressFromCurrentSet(3).call()
-
- assert.equal(strip0x(resultAddress), context.validators[4].address)
- })
-
- it('should revert when asked for an out of bounds validator', async function(this: any) {
- this.timeout(0)
- await assertRevert(
- election.methods.validatorAddressFromCurrentSet(4).send({
- from: `0x${context.validators[0].address}`,
- })
- )
- })
- })
- })
+ /*
+ const getLastEpochBlock = (blockNumber: number, epochSize: number) => {
+ const epochNumber = Math.floor(blockNumber / epochSize)
+ return epochNumber * epochSize
+ }
+ */
- describe('when the validator set is changing', () => {
+ describe.only('when the validator set is changing', () => {
const epoch = 10
- const expectedEpochMembership = new Map()
+ const blockNumbers: number[] = []
before(async function() {
this.timeout(0)
await restart()
+ console.log('getting keys')
const [groupAddress, groupPrivateKey] = await getValidatorGroupKeys()
const groupInstance = {
@@ -395,31 +135,33 @@ describe('governance tests', () => {
peers: [await getEnode(8545)],
}
await initAndStartGeth(context.hooks.gethBinaryPath, groupInstance)
- const groupWeb3 = new Web3('ws://localhost:8567')
- validators = new groupWeb3.eth.Contract(
- validatorsAbi,
- await getContractAddress('ValidatorsProxy')
- )
+ const members = await getValidatorGroupMembers()
+ assert.equal(members.length, 5)
+
// Give the node time to sync.
await sleep(15)
- const members = await getValidatorGroupMembers()
+ const groupWeb3 = new Web3('ws://localhost:8567')
+ const groupKit = newKitFromWeb3(groupWeb3)
+ validators = await groupKit._web3Contracts.getValidators()
const membersToSwap = [members[0], members[1]]
- // Start with 10 nodes
+ let includedMemberIndex = 1
+ console.log('removing member')
await removeMember(groupWeb3, groupAddress, membersToSwap[0])
+ console.log('removed member')
const changeValidatorSet = async (header: any) => {
+ blockNumbers.push(header.number)
// At the start of epoch N, swap members so the validator set is different for epoch N + 1.
if (header.number % epoch === 0) {
- const groupMembers = await getValidatorGroupMembers()
- const direction = groupMembers.includes(membersToSwap[0])
- const removedMember = direction ? membersToSwap[0] : membersToSwap[1]
- const addedMember = direction ? membersToSwap[1] : membersToSwap[0]
- expectedEpochMembership.set(header.number / epoch, [removedMember, addedMember])
- await removeMember(groupWeb3, groupAddress, removedMember)
- await addMember(groupWeb3, groupAddress, addedMember)
+ console.log('new epoch')
+ const memberToRemove = membersToSwap[includedMemberIndex]
+ const memberToAdd = membersToSwap[(includedMemberIndex + 1) % 2]
+ await removeMember(groupWeb3, groupAddress, memberToRemove)
+ await addMember(groupWeb3, groupAddress, memberToAdd)
+ includedMemberIndex = (includedMemberIndex + 1) % 2
const newMembers = await getValidatorGroupMembers()
- assert.include(newMembers, addedMember)
- assert.notInclude(newMembers, removedMember)
+ assert.include(newMembers, memberToAdd)
+ assert.notInclude(newMembers, memberToRemove)
}
}
@@ -432,24 +174,79 @@ describe('governance tests', () => {
await sleep(epoch)
})
- it('should have produced blocks with the correct validator set', async function(this: any) {
- this.timeout(0) // Disable test timeout
- assert.equal(expectedEpochMembership.size, 3)
- // tslint:disable-next-line: no-console
- for (const [epochNumber, membership] of expectedEpochMembership) {
- let containsExpectedMember = false
- for (let i = epochNumber * epoch + 1; i < (epochNumber + 1) * epoch + 1; i++) {
- const block = await web3.eth.getBlock(i)
- assert.notEqual(block.miner.toLowerCase(), membership[1].toLowerCase())
- containsExpectedMember =
- containsExpectedMember || block.miner.toLowerCase() === membership[0].toLowerCase()
+ it('should always return a validator set size equal to the number of group members at the end of the last epoch', async () => {
+ for (const blockNumber of blockNumbers) {
+ const lastEpochBlock = blockNumber - (blockNumber % epoch) - 1
+ const validatorSetSize = await election.methods
+ .numberValidatorsInCurrentSet()
+ .call({}, blockNumber)
+ const groupMembership = await getValidatorGroupMembers(lastEpochBlock)
+ console.log(blockNumber, lastEpochBlock, validatorSetSize, groupMembership.length)
+ // assert.equal(validatorSetSize, groupMembership.length)
+ }
+ })
+
+ it('should always return a validator set equal to the group members at the end of the last epoch', async () => {
+ for (const blockNumber of blockNumbers) {
+ const lastEpochBlock = blockNumber - (blockNumber % epoch) - 1
+ const groupMembership = await getValidatorGroupMembers(lastEpochBlock)
+ const validatorSetSize = await election.methods
+ .numberValidatorsInCurrentSet()
+ .call({}, blockNumber)
+ const validatorSet = []
+ for (let i = 0; i < validatorSetSize; i++) {
+ const validator = await election.methods
+ .validatorAddressFromCurrentSet(i)
+ .call({}, blockNumber)
+ validatorSet.push(validator)
}
- assert.isTrue(containsExpectedMember)
+ // assert.deepEqual(groupMembership, validatorSet)
+ console.log(blockNumber, lastEpochBlock, groupMembership, validatorSet)
}
})
+
+ it('should only have created blocks whose miner was in the current validator set', async () => {
+ for (const blockNumber of blockNumbers) {
+ const validatorSetSize = await election.methods
+ .numberValidatorsInCurrentSet()
+ .call({}, blockNumber)
+ const validatorSet = []
+ for (let i = 0; i < validatorSetSize; i++) {
+ const validator = await election.methods
+ .validatorAddressFromCurrentSet(i)
+ .call({}, blockNumber)
+ validatorSet.push(validator)
+ }
+ const block = await web3.eth.getBlock(blockNumber)
+ assert.include(validatorSet.map((x) => x.toLowerCase()), block.miner.toLowerCase())
+ }
+ })
+
+ it('should update the validator scores at the end of each epoch', async () => {
+ const validators = await kit.contracts.getValidators()
+ for (const blockNumber of blockNumbers) {
+ const validatorSetSize = await election.methods
+ .numberValidatorsInCurrentSet()
+ .call({}, blockNumber)
+ const validatorSet = []
+ for (let i = 0; i < validatorSetSize; i++) {
+ const validator = await election.methods
+ .validatorAddressFromCurrentSet(i)
+ .call({}, blockNumber)
+ validatorSet.push(validator)
+ if (false) {
+ console.log(await validators.getValidator(validator))
+ }
+ }
+ }
+ })
+
+ it('should distribute epoch payments to each validator at the end of an epoch', async () => {})
+
+ it('should distribute epoch payments to the validator group at the end of an epoch', async () => {})
})
- describe('when adding any block', () => {
+ describe('after the governance smart contract is registered', () => {
let goldGenesisSupply: any
const addressesWithBalance: string[] = []
beforeEach(async function(this: any) {
diff --git a/packages/protocol/contracts/governance/Validators.sol b/packages/protocol/contracts/governance/Validators.sol
index d3e5be9de5a..daf27395c87 100644
--- a/packages/protocol/contracts/governance/Validators.sol
+++ b/packages/protocol/contracts/governance/Validators.sol
@@ -392,8 +392,8 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
* @param uptime The Fixidity representation of the validator's uptime, between 0 and 1.
* @return True upon success.
*/
- function updateValidatorScore(address validator, uint256 uptime) external onlyVm() returns (bool) {
- return _updateValidatorScore(validator, uptime);
+ function updateValidatorScore(address validator, uint256 uptime) external onlyVm() {
+ _updateValidatorScore(validator, uptime);
}
/**
@@ -402,7 +402,7 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
* @param uptime The Fixidity representation of the validator's uptime, between 0 and 1.
* @return True upon success.
*/
- function _updateValidatorScore(address validator, uint256 uptime) internal returns (bool) {
+ function _updateValidatorScore(address validator, uint256 uptime) internal {
address account = getLockedGold().getAccountFromValidator(validator);
require(isValidator(account), "isvalidator");
require(uptime <= FixidityLib.fixed1().unwrap(), "uptime");
@@ -425,14 +425,13 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
newComponent.add(currentComponent).unwrap()
)
);
- return true;
}
- function distributeEpochPayment(address validator) external onlyVm() returns (bool) {
- return _distributeEpochPayment(validator);
+ function distributeEpochPayment(address validator) external onlyVm() {
+ _distributeEpochPayment(validator);
}
- function _distributeEpochPayment(address validator) internal returns (bool) {
+ function _distributeEpochPayment(address validator) internal {
address account = getLockedGold().getAccountFromValidator(validator);
require(isValidator(account));
FixidityLib.Fraction memory totalPayment = FixidityLib.newFixed(validatorEpochPayment).multiply(validators[account].score);
diff --git a/packages/protocol/contracts/governance/test/ValidatorsTest.sol b/packages/protocol/contracts/governance/test/ValidatorsTest.sol
index 1c7421969fe..d4148a92610 100644
--- a/packages/protocol/contracts/governance/test/ValidatorsTest.sol
+++ b/packages/protocol/contracts/governance/test/ValidatorsTest.sol
@@ -12,11 +12,11 @@ contract ValidatorsTest is Validators {
return block.number / 100;
}
- function updateValidatorScore(address validator, uint256 uptime) external returns (bool) {
+ function updateValidatorScore(address validator, uint256 uptime) external {
return _updateValidatorScore(validator, uptime);
}
- function distributeEpochPayment(address validator) external returns (bool) {
+ function distributeEpochPayment(address validator) external {
return _distributeEpochPayment(validator);
}
}
From 7f82317a8ae2b85945a64e70a6914c5fb49a2b42 Mon Sep 17 00:00:00 2001
From: Asa Oines
Date: Sun, 6 Oct 2019 13:17:51 -0700
Subject: [PATCH 030/149] Epoch payments and rewards appear to be working
---
.../src/e2e-tests/governance_tests.ts | 254 +++++++++++++-----
.../src/wrappers/StableTokenWrapper.ts | 1 -
.../protocol/contracts/common/UsingEpochs.sol | 9 +-
.../contracts/governance/Election.sol | 24 +-
.../contracts/governance/Validators.sol | 10 +-
.../governance/test/ElectionTest.sol | 14 +
.../governance/test/MockValidators.sol | 2 +-
.../governance/test/ValidatorsTest.sol | 7 +-
packages/protocol/migrationsConfig.js | 2 +-
packages/protocol/test/common/fixidity.ts | 9 +
packages/protocol/test/governance/election.ts | 157 ++++++++++-
packages/utils/package.json | 2 +-
12 files changed, 413 insertions(+), 78 deletions(-)
create mode 100644 packages/protocol/contracts/governance/test/ElectionTest.sol
diff --git a/packages/celotool/src/e2e-tests/governance_tests.ts b/packages/celotool/src/e2e-tests/governance_tests.ts
index d0663f8ce56..e8a9fdc202c 100644
--- a/packages/celotool/src/e2e-tests/governance_tests.ts
+++ b/packages/celotool/src/e2e-tests/governance_tests.ts
@@ -1,5 +1,6 @@
import BigNumber from 'bignumber.js'
import { ContractKit, newKitFromWeb3 } from '@celo/contractkit'
+import { fromFixed, toFixed } from '@celo/utils/lib/fixidity'
import { assert } from 'chai'
import Web3 from 'web3'
import {
@@ -33,7 +34,7 @@ describe('governance tests', () => {
before(async function(this: any) {
this.timeout(0)
- await context.hooks.before()
+ // await context.hooks.before()
})
after(context.hooks.after)
@@ -64,9 +65,7 @@ describe('governance tests', () => {
return groupInfo[2]
} else {
const [groupAddress] = await validators.methods.getRegisteredValidatorGroups().call()
- console.log('group address', groupAddress)
const groupInfo = await validators.methods.getValidatorGroup(groupAddress).call()
- console.log('group info', groupInfo)
return groupInfo[2]
}
}
@@ -109,20 +108,17 @@ describe('governance tests', () => {
return tx.send({ from: group, ...txOptions, gas })
}
- /*
- const getLastEpochBlock = (blockNumber: number, epochSize: number) => {
- const epochNumber = Math.floor(blockNumber / epochSize)
- return epochNumber * epochSize
+ const isLastBlockOfEpoch = (blockNumber: number, epochSize: number) => {
+ return blockNumber % epochSize == 0
}
- */
- describe.only('when the validator set is changing', () => {
+ describe('when the validator set is changing', () => {
const epoch = 10
const blockNumbers: number[] = []
+ let allValidators: string[]
before(async function() {
this.timeout(0)
await restart()
- console.log('getting keys')
const [groupAddress, groupPrivateKey] = await getValidatorGroupKeys()
const groupInstance = {
@@ -135,25 +131,22 @@ describe('governance tests', () => {
peers: [await getEnode(8545)],
}
await initAndStartGeth(context.hooks.gethBinaryPath, groupInstance)
- const members = await getValidatorGroupMembers()
- assert.equal(members.length, 5)
+ allValidators = await getValidatorGroupMembers()
+ assert.equal(allValidators.length, 5)
// Give the node time to sync.
await sleep(15)
const groupWeb3 = new Web3('ws://localhost:8567')
const groupKit = newKitFromWeb3(groupWeb3)
validators = await groupKit._web3Contracts.getValidators()
- const membersToSwap = [members[0], members[1]]
+ const membersToSwap = [allValidators[0], allValidators[1]]
let includedMemberIndex = 1
- console.log('removing member')
await removeMember(groupWeb3, groupAddress, membersToSwap[0])
- console.log('removed member')
const changeValidatorSet = async (header: any) => {
blockNumbers.push(header.number)
// At the start of epoch N, swap members so the validator set is different for epoch N + 1.
- if (header.number % epoch === 0) {
- console.log('new epoch')
+ if (header.number % epoch === 1) {
const memberToRemove = membersToSwap[includedMemberIndex]
const memberToAdd = membersToSwap[(includedMemberIndex + 1) % 2]
await removeMember(groupWeb3, groupAddress, memberToRemove)
@@ -168,82 +161,224 @@ describe('governance tests', () => {
const subscription = await groupWeb3.eth.subscribe('newBlockHeaders')
subscription.on('data', changeValidatorSet)
// Wait for a few epochs while changing the validator set.
- await sleep(epoch * 3)
+ await sleep(epoch * 4)
;(subscription as any).unsubscribe()
// Wait for the current epoch to complete.
await sleep(epoch)
})
+ // Note that this returns the validator set at the END of `blockNumber`, i.e. the validator set
+ // that will validate the next block, and NOT necessarily the validator set that validated this
+ // block.
+ const getValidatorSetAtBlock = async (blockNumber: number) => {
+ const validatorSetSize = await election.methods
+ .numberValidatorsInCurrentSet()
+ .call({}, blockNumber)
+ const validatorSet = []
+ for (let i = 0; i < validatorSetSize; i++) {
+ validatorSet.push(
+ await election.methods.validatorAddressFromCurrentSet(i).call({}, blockNumber)
+ )
+ }
+ return validatorSet
+ }
+
it('should always return a validator set size equal to the number of group members at the end of the last epoch', async () => {
for (const blockNumber of blockNumbers) {
- const lastEpochBlock = blockNumber - (blockNumber % epoch) - 1
+ const lastEpochBlock = blockNumber - (blockNumber % epoch)
const validatorSetSize = await election.methods
.numberValidatorsInCurrentSet()
.call({}, blockNumber)
const groupMembership = await getValidatorGroupMembers(lastEpochBlock)
- console.log(blockNumber, lastEpochBlock, validatorSetSize, groupMembership.length)
- // assert.equal(validatorSetSize, groupMembership.length)
+ assert.equal(validatorSetSize, groupMembership.length)
}
})
it('should always return a validator set equal to the group members at the end of the last epoch', async () => {
for (const blockNumber of blockNumbers) {
- const lastEpochBlock = blockNumber - (blockNumber % epoch) - 1
+ const lastEpochBlock = blockNumber - (blockNumber % epoch)
const groupMembership = await getValidatorGroupMembers(lastEpochBlock)
- const validatorSetSize = await election.methods
- .numberValidatorsInCurrentSet()
- .call({}, blockNumber)
- const validatorSet = []
- for (let i = 0; i < validatorSetSize; i++) {
- const validator = await election.methods
- .validatorAddressFromCurrentSet(i)
- .call({}, blockNumber)
- validatorSet.push(validator)
- }
- // assert.deepEqual(groupMembership, validatorSet)
- console.log(blockNumber, lastEpochBlock, groupMembership, validatorSet)
+ const validatorSet = await getValidatorSetAtBlock(blockNumber)
+ assert.sameMembers(groupMembership, validatorSet)
}
})
it('should only have created blocks whose miner was in the current validator set', async () => {
for (const blockNumber of blockNumbers) {
- const validatorSetSize = await election.methods
- .numberValidatorsInCurrentSet()
- .call({}, blockNumber)
- const validatorSet = []
- for (let i = 0; i < validatorSetSize; i++) {
- const validator = await election.methods
- .validatorAddressFromCurrentSet(i)
- .call({}, blockNumber)
- validatorSet.push(validator)
- }
+ // The validators responsible for creating `blockNumber` were those in the validator set at
+ // `blockNumber-1`.
+ const validatorSet = await getValidatorSetAtBlock(blockNumber - 1)
const block = await web3.eth.getBlock(blockNumber)
assert.include(validatorSet.map((x) => x.toLowerCase()), block.miner.toLowerCase())
}
})
it('should update the validator scores at the end of each epoch', async () => {
- const validators = await kit.contracts.getValidators()
+ const validators = await kit._web3Contracts.getValidators()
+ const adjustmentSpeed = fromFixed(
+ new BigNumber((await validators.methods.getValidatorScoreParameters().call())[1])
+ )
+ const uptime = 1
+
+ const assertScoreUnchanged = async (validator: string, blockNumber: number) => {
+ const score = new BigNumber(
+ (await validators.methods.getValidator(validator).call({}, blockNumber))[4]
+ )
+ const previousScore = new BigNumber(
+ (await validators.methods.getValidator(validator).call({}, blockNumber - 1))[4]
+ )
+ assert.equal(score.toFixed(), previousScore.toFixed())
+ }
+
+ const assertScoreChanged = async (validator: string, blockNumber: number) => {
+ const score = new BigNumber(
+ (await validators.methods.getValidator(validator).call({}, blockNumber))[4]
+ )
+ const previousScore = new BigNumber(
+ (await validators.methods.getValidator(validator).call({}, blockNumber - 1))[4]
+ )
+ const expectedScore = adjustmentSpeed
+ .times(uptime)
+ .plus(new BigNumber(1).minus(adjustmentSpeed).times(fromFixed(previousScore)))
+ assert.equal(score.toFixed(), toFixed(expectedScore).toFixed())
+ }
+
for (const blockNumber of blockNumbers) {
- const validatorSetSize = await election.methods
- .numberValidatorsInCurrentSet()
- .call({}, blockNumber)
- const validatorSet = []
- for (let i = 0; i < validatorSetSize; i++) {
- const validator = await election.methods
- .validatorAddressFromCurrentSet(i)
- .call({}, blockNumber)
- validatorSet.push(validator)
- if (false) {
- console.log(await validators.getValidator(validator))
- }
+ let expectUnchangedScores: string[]
+ let expectChangedScores: string[]
+ if (isLastBlockOfEpoch(blockNumber, epoch)) {
+ expectChangedScores = await getValidatorSetAtBlock(blockNumber - 1)
+ expectUnchangedScores = allValidators.filter((x) => !expectChangedScores.includes(x))
+ } else {
+ expectUnchangedScores = allValidators
+ expectChangedScores = []
+ }
+
+ for (const validator of expectUnchangedScores) {
+ await assertScoreUnchanged(validator, blockNumber)
+ }
+
+ for (const validator of expectChangedScores) {
+ await assertScoreChanged(validator, blockNumber)
+ }
+ }
+ })
+
+ it('should distribute epoch payments at the end of each epoch', async () => {
+ const validators = await kit._web3Contracts.getValidators()
+ const stableToken = await kit._web3Contracts.getStableToken()
+ const commission = 0.1
+ const validatorEpochPayment = new BigNumber(
+ await validators.methods.validatorEpochPayment().call()
+ )
+ const [group] = await validators.methods.getRegisteredValidatorGroups().call()
+
+ const assertBalanceUnchanged = async (validator: string, blockNumber: number) => {
+ const currentBalance = new BigNumber(
+ await stableToken.methods.balanceOf(validator).call({}, blockNumber)
+ )
+ const previousBalance = new BigNumber(
+ await stableToken.methods.balanceOf(validator).call({}, blockNumber - 1)
+ )
+ assert.equal(currentBalance.toFixed(), previousBalance.toFixed())
+ }
+
+ const assertBalanceChanged = async (
+ validator: string,
+ blockNumber: number,
+ expected: BigNumber
+ ) => {
+ const currentBalance = new BigNumber(
+ await stableToken.methods.balanceOf(validator).call({}, blockNumber)
+ )
+ const previousBalance = new BigNumber(
+ await stableToken.methods.balanceOf(validator).call({}, blockNumber - 1)
+ )
+ assert.equal(expected.toFixed(), currentBalance.minus(previousBalance).toFixed())
+ }
+
+ const getExpectedTotalPayment = async (validator: string, blockNumber: number) => {
+ const score = new BigNumber(
+ (await validators.methods.getValidator(validator).call({}, blockNumber))[4]
+ )
+ return validatorEpochPayment.times(fromFixed(score))
+ }
+
+ for (const blockNumber of blockNumbers) {
+ let expectUnchangedBalances: string[]
+ let expectChangedBalances: string[]
+ if (isLastBlockOfEpoch(blockNumber, epoch)) {
+ expectChangedBalances = await getValidatorSetAtBlock(blockNumber - 1)
+ expectUnchangedBalances = allValidators.filter((x) => !expectChangedBalances.includes(x))
+ } else {
+ expectUnchangedBalances = allValidators
+ expectChangedBalances = []
+ }
+
+ for (const validator of expectUnchangedBalances) {
+ await assertBalanceUnchanged(validator, blockNumber)
+ }
+
+ let expectedGroupPayment = new BigNumber(0)
+ for (const validator of expectChangedBalances) {
+ const expectedTotalPayment = await getExpectedTotalPayment(validator, blockNumber)
+ const groupPayment = expectedTotalPayment.times(commission)
+ await assertBalanceChanged(
+ validator,
+ blockNumber,
+ expectedTotalPayment.minus(groupPayment)
+ )
+ expectedGroupPayment = expectedGroupPayment.plus(groupPayment)
}
+ await assertBalanceChanged(group, blockNumber, expectedGroupPayment)
}
})
- it('should distribute epoch payments to each validator at the end of an epoch', async () => {})
+ it('should distribute epoch rewards at the end of each epoch', async () => {
+ const validators = await kit._web3Contracts.getValidators()
+ const election = await kit._web3Contracts.getElection()
+ // const lockedGold = await kit._web3Contracts.getLockedGold()
+ const epochReward = new BigNumber(10).pow(18)
+ const [group] = await validators.methods.getRegisteredValidatorGroups().call()
- it('should distribute epoch payments to the validator group at the end of an epoch', async () => {})
+ const assertVotesUnchanged = async (group: string, blockNumber: number) => {
+ const currentVotes = new BigNumber(
+ await election.methods.getGroupTotalVotes(group).call({}, blockNumber)
+ )
+ const previousVotes = new BigNumber(
+ await election.methods.getGroupTotalVotes(group).call({}, blockNumber - 1)
+ )
+ assert.equal(currentVotes.toFixed(), previousVotes.toFixed())
+ }
+
+ const assertVotesChanged = async (
+ group: string,
+ blockNumber: number,
+ expected: BigNumber
+ ) => {
+ const currentVotes = new BigNumber(
+ await election.methods.getGroupTotalVotes(group).call({}, blockNumber)
+ )
+ const previousVotes = new BigNumber(
+ await election.methods.getGroupTotalVotes(group).call({}, blockNumber - 1)
+ )
+ console.log(
+ currentVotes.toFixed(),
+ previousVotes.toFixed(),
+ expected.toFixed(),
+ currentVotes.minus(previousVotes).toFixed()
+ )
+ assert.equal(expected.toFixed(), currentVotes.minus(previousVotes).toFixed())
+ }
+
+ for (const blockNumber of blockNumbers) {
+ if (isLastBlockOfEpoch(blockNumber, epoch)) {
+ await assertVotesChanged(group, blockNumber, epochReward)
+ } else {
+ await assertVotesUnchanged(group, blockNumber)
+ }
+ }
+ })
})
describe('after the governance smart contract is registered', () => {
@@ -295,7 +430,6 @@ describe('governance tests', () => {
b.plus(total)
)
assert.isAtLeast(expectedGoldTotalSupply.toNumber(), goldGenesisSupply.toNumber())
- //
assert.equal(goldTotalSupply.toString(), expectedGoldTotalSupply.toString())
})
})
diff --git a/packages/contractkit/src/wrappers/StableTokenWrapper.ts b/packages/contractkit/src/wrappers/StableTokenWrapper.ts
index ebe68a056d8..53ce8174403 100644
--- a/packages/contractkit/src/wrappers/StableTokenWrapper.ts
+++ b/packages/contractkit/src/wrappers/StableTokenWrapper.ts
@@ -70,7 +70,6 @@ export class StableTokenWrapper extends BaseWrapper {
toBigNumber
)
- minter = proxyCall(this.contract.methods.minter)
owner = proxyCall(this.contract.methods.owner)
/**
diff --git a/packages/protocol/contracts/common/UsingEpochs.sol b/packages/protocol/contracts/common/UsingEpochs.sol
index 87eedb3b3d6..4d16aec0689 100644
--- a/packages/protocol/contracts/common/UsingEpochs.sol
+++ b/packages/protocol/contracts/common/UsingEpochs.sol
@@ -5,10 +5,15 @@ contract UsingEpochs {
event RegistrySet(address indexed registryAddress);
+ // TODO(asa): Expose epoch size via precompile.
// solhint-disable state-visibility
- uint256 constant EPOCH = 17280;
+ uint256 constant EPOCH = 10;
function getEpochNumber() public view returns (uint256) {
- return block.number / EPOCH;
+ uint256 ret = block.number / EPOCH;
+ if (block.number % EPOCH == 0) {
+ ret = ret - 1;
+ }
+ return ret;
}
}
diff --git a/packages/protocol/contracts/governance/Election.sol b/packages/protocol/contracts/governance/Election.sol
index 4ee216be4a4..4c4e995b350 100644
--- a/packages/protocol/contracts/governance/Election.sol
+++ b/packages/protocol/contracts/governance/Election.sol
@@ -43,9 +43,9 @@ contract Election is Ownable, ReentrancyGuard, Initializable, UsingRegistry {
}
struct TotalVotes {
- // The total number of votes cast.
+ // The total number of votes cast, including those for ineligible Validator Groups.
uint256 total;
- // A list of eligible ValidatorGroups sorted by total votes.
+ // A list of eligible Validator Groups sorted by total votes.
SortedLinkedList.List eligible;
}
@@ -436,6 +436,24 @@ contract Election is Ownable, ReentrancyGuard, Initializable, UsingRegistry {
return votes.total.eligible.contains(group);
}
+ function distributeEpochRewards(address group, uint256 value, address lesser, address greater) external {
+ require(msg.sender == address(0));
+ _distributeEpochRewards(group, value, lesser, greater);
+ }
+
+ function _distributeEpochRewards(address group, uint256 value, address lesser, address greater) internal {
+ // TODO(asa): What do here?
+ if (votes.active.total[group] == 0) {
+ }
+ if (votes.total.eligible.contains(group)) {
+ uint256 newVoteTotal = votes.total.eligible.getValue(group).add(value);
+ votes.total.eligible.update(group, newVoteTotal, lesser, greater);
+ }
+
+ votes.active.total[group] = votes.active.total[group].add(value);
+ votes.total.total = votes.total.total.add(value);
+ }
+
/**
* @notice Increments the number of total votes for `group` by `value`.
* @param group The validator group whose vote total should be incremented.
@@ -646,7 +664,7 @@ contract Election is Ownable, ReentrancyGuard, Initializable, UsingRegistry {
function getEligibleValidatorGroupsVoteTotals()
external
view
- returns (address[] memory, uint256[] memory)
+ returns (address[] memory groups, uint256[] memory values)
{
return votes.total.eligible.getElements();
}
diff --git a/packages/protocol/contracts/governance/Validators.sol b/packages/protocol/contracts/governance/Validators.sol
index daf27395c87..14467cc77e6 100644
--- a/packages/protocol/contracts/governance/Validators.sol
+++ b/packages/protocol/contracts/governance/Validators.sol
@@ -410,15 +410,19 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
// TODO(asa): Use exponent.
FixidityLib.Fraction memory epochScore = FixidityLib.wrap(uptime);
+
+ // New component is 0! Uptime is non-zero.
FixidityLib.Fraction memory newComponent = validatorScoreParameters.adjustmentSpeed.multiply(
epochScore
);
+ // validators[validator].score = newComponent;
+ // This works:
+ // validators[validator].score = epochScore;
+
FixidityLib.Fraction memory currentComponent = FixidityLib.fixed1().subtract(
validatorScoreParameters.adjustmentSpeed
);
- emit Debug(currentComponent.unwrap());
currentComponent = currentComponent.multiply(validators[account].score);
- emit Debug(currentComponent.unwrap());
validators[account].score = FixidityLib.wrap(
Math.min(
epochScore.unwrap(),
@@ -438,6 +442,8 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
address group = getMembershipInLastEpoch(account);
uint256 groupPayment = totalPayment.multiply(groups[group].commission).fromFixed();
uint256 validatorPayment = totalPayment.fromFixed().sub(groupPayment);
+ // For some reason, one validator seems to be getting the full payment (not less commission)
+ // Perhaps, getMembershipInLastEpoch is returning 0? Probably what's happening...
getStableToken().mint(group, groupPayment);
getStableToken().mint(account, validatorPayment);
}
diff --git a/packages/protocol/contracts/governance/test/ElectionTest.sol b/packages/protocol/contracts/governance/test/ElectionTest.sol
new file mode 100644
index 00000000000..383e915ad11
--- /dev/null
+++ b/packages/protocol/contracts/governance/test/ElectionTest.sol
@@ -0,0 +1,14 @@
+pragma solidity ^0.5.8;
+
+import "../Election.sol";
+import "../../common/FixidityLib.sol";
+
+/**
+ * @title A wrapper around Election that exposes onlyVm functions for testing.
+ */
+contract ElectionTest is Election {
+
+ function distributeEpochRewards(address group, uint256 value, address lesser, address greater) external {
+ return _distributeEpochRewards(group, value, lesser, greater);
+ }
+}
diff --git a/packages/protocol/contracts/governance/test/MockValidators.sol b/packages/protocol/contracts/governance/test/MockValidators.sol
index 68514b13a1d..b3e94361d21 100644
--- a/packages/protocol/contracts/governance/test/MockValidators.sol
+++ b/packages/protocol/contracts/governance/test/MockValidators.sol
@@ -45,7 +45,7 @@ contract MockValidators is IValidators {
members[group] = _members;
}
- function getTopValidatorsFromGroup(
+ function getTopGroupValidators(
address group,
uint256 n
)
diff --git a/packages/protocol/contracts/governance/test/ValidatorsTest.sol b/packages/protocol/contracts/governance/test/ValidatorsTest.sol
index d4148a92610..16cdabd8364 100644
--- a/packages/protocol/contracts/governance/test/ValidatorsTest.sol
+++ b/packages/protocol/contracts/governance/test/ValidatorsTest.sol
@@ -9,7 +9,12 @@ import "../../common/FixidityLib.sol";
contract ValidatorsTest is Validators {
function getEpochNumber() public view returns (uint256) {
- return block.number / 100;
+ uint256 epoch = 100;
+ uint256 ret = block.number / epoch;
+ if (block.number % epoch == 0) {
+ ret = ret - 1;
+ }
+ return ret;
}
function updateValidatorScore(address validator, uint256 uptime) external {
diff --git a/packages/protocol/migrationsConfig.js b/packages/protocol/migrationsConfig.js
index 5b91f04888e..86cc477f359 100644
--- a/packages/protocol/migrationsConfig.js
+++ b/packages/protocol/migrationsConfig.js
@@ -113,7 +113,7 @@ const linkedLibraries = {
],
SortedLinkedListWithMedian: ['AddressSortedLinkedListWithMedian'],
AddressLinkedList: ['Validators', 'ValidatorsTest'],
- AddressSortedLinkedList: ['Election'],
+ AddressSortedLinkedList: ['Election', 'ElectionTest'],
IntegerSortedLinkedList: ['Governance', 'IntegerSortedLinkedListTest'],
AddressSortedLinkedListWithMedian: ['SortedOracles', 'AddressSortedLinkedListWithMedianTest'],
Signatures: ['LockedGold', 'Escrow'],
diff --git a/packages/protocol/test/common/fixidity.ts b/packages/protocol/test/common/fixidity.ts
index 9349e437287..10a36a26e58 100644
--- a/packages/protocol/test/common/fixidity.ts
+++ b/packages/protocol/test/common/fixidity.ts
@@ -157,6 +157,15 @@ contract('FixidityLib', () => {
assertEqualBN(result, expected)
})
+ it('should multiply two numbers less than 1', async () => {
+ const a = toFixed(0.1)
+ const b = toFixed(new BigNumber(10).pow(-14))
+ const expected = toFixed(new BigNumber(10).pow(-15))
+ const result = await fixidityTest.multiply(a, b)
+
+ assertEqualBN(result, expected)
+ })
+
it('should multiply by 0', async () => {
const result = await fixidityTest.multiply(maxFixedMul, zero)
diff --git a/packages/protocol/test/governance/election.ts b/packages/protocol/test/governance/election.ts
index c3647fd622f..1f417894680 100644
--- a/packages/protocol/test/governance/election.ts
+++ b/packages/protocol/test/governance/election.ts
@@ -16,11 +16,11 @@ import {
MockRandomInstance,
RegistryContract,
RegistryInstance,
- ElectionContract,
- ElectionInstance,
+ ElectionTestContract,
+ ElectionTestInstance,
} from 'types'
-const Election: ElectionContract = artifacts.require('Election')
+const ElectionTest: ElectionTestContract = artifacts.require('ElectionTest')
const MockLockedGold: MockLockedGoldContract = artifacts.require('MockLockedGold')
const MockValidators: MockValidatorsContract = artifacts.require('MockValidators')
const MockRandom: MockRandomContract = artifacts.require('MockRandom')
@@ -28,10 +28,10 @@ const Registry: RegistryContract = artifacts.require('Registry')
// @ts-ignore
// TODO(mcortesi): Use BN
-Election.numberFormat = 'BigNumber'
+ElectionTest.numberFormat = 'BigNumber'
contract('Election', (accounts: string[]) => {
- let election: ElectionInstance
+ let election: ElectionTestInstance
let registry: RegistryInstance
let mockLockedGold: MockLockedGoldInstance
let mockValidators: MockValidatorsInstance
@@ -43,7 +43,7 @@ contract('Election', (accounts: string[]) => {
const electabilityThreshold = new BigNumber(0)
beforeEach(async () => {
- election = await Election.new()
+ election = await ElectionTest.new()
mockLockedGold = await MockLockedGold.new()
mockValidators = await MockValidators.new()
registry = await Registry.new()
@@ -855,4 +855,149 @@ contract('Election', (accounts: string[]) => {
})
})
})
+
+ describe.only('#distributeEpochRewards', () => {
+ const voter = accounts[0]
+ const group = accounts[1]
+ const voteValue = new BigNumber(1000000)
+ const rewardValue = new BigNumber(1000000)
+ beforeEach(async () => {
+ await mockValidators.setMembers(group, [accounts[9]])
+ await election.markGroupEligible(group, NULL_ADDRESS, NULL_ADDRESS)
+ await mockLockedGold.setTotalLockedGold(voteValue)
+ await mockValidators.setNumRegisteredValidators(1)
+ await mockLockedGold.incrementNonvotingAccountBalance(voter, voteValue)
+ await election.vote(group, voteValue, NULL_ADDRESS, NULL_ADDRESS)
+ await election.activate(group)
+ })
+
+ describe('when there is a single group with active votes', () => {
+ describe('when the group is eligible', () => {
+ beforeEach(async () => {
+ await election.distributeEpochRewards(group, rewardValue, NULL_ADDRESS, NULL_ADDRESS)
+ })
+
+ it("should increment the account's active votes for the group", async () => {
+ assertEqualBN(
+ await election.getAccountActiveVotesForGroup(group, voter),
+ voteValue.plus(rewardValue)
+ )
+ })
+
+ it("should increment the account's total votes for the group", async () => {
+ assertEqualBN(
+ await election.getAccountTotalVotesForGroup(group, voter),
+ voteValue.plus(rewardValue)
+ )
+ })
+
+ it("should increment account's total votes", async () => {
+ assertEqualBN(await election.getAccountTotalVotes(voter), voteValue.plus(rewardValue))
+ })
+
+ it('should increment the total votes for the group', async () => {
+ assertEqualBN(await election.getGroupTotalVotes(group), voteValue.plus(rewardValue))
+ })
+
+ it('should increment the total votes', async () => {
+ assertEqualBN(await election.getTotalVotes(), voteValue.plus(rewardValue))
+ })
+ })
+ })
+
+ describe('when there are two groups with active votes', () => {
+ const voter2 = accounts[2]
+ const group2 = accounts[3]
+ const voteValue2 = new BigNumber(1000000)
+ const rewardValue2 = new BigNumber(10000000)
+ beforeEach(async () => {
+ await mockValidators.setMembers(group2, [accounts[8]])
+ await election.markGroupEligible(group2, NULL_ADDRESS, group)
+ await mockLockedGold.setTotalLockedGold(voteValue.plus(voteValue2))
+ await mockValidators.setNumRegisteredValidators(2)
+ await mockLockedGold.incrementNonvotingAccountBalance(voter2, voteValue2)
+ // Split voter2's vote between the two groups.
+ await election.vote(group, voteValue2.div(2), group2, NULL_ADDRESS, { from: voter2 })
+ await election.vote(group2, voteValue2.div(2), NULL_ADDRESS, group, { from: voter2 })
+ await election.activate(group, { from: voter2 })
+ await election.activate(group2, { from: voter2 })
+ })
+
+ describe('when boths groups are eligible', () => {
+ const expectedGroupTotalActiveVotes = voteValue.plus(voteValue2.div(2)).plus(rewardValue)
+ const expectedVoterActiveVotesForGroup = expectedGroupTotalActiveVotes
+ .times(2)
+ .div(3)
+ .dp(0, BigNumber.ROUND_FLOOR)
+ const expectedVoter2ActiveVotesForGroup = expectedGroupTotalActiveVotes
+ .div(3)
+ .dp(0, BigNumber.ROUND_FLOOR)
+ const expectedVoter2ActiveVotesForGroup2 = voteValue2.div(2).plus(rewardValue2)
+ beforeEach(async () => {
+ await election.distributeEpochRewards(group, rewardValue, group2, NULL_ADDRESS)
+ await election.distributeEpochRewards(group2, rewardValue2, group, NULL_ADDRESS)
+ })
+
+ it("should increment the accounts' active votes for both groups", async () => {
+ assertEqualBN(
+ await election.getAccountActiveVotesForGroup(group, voter),
+ expectedVoterActiveVotesForGroup
+ )
+ assertEqualBN(
+ await election.getAccountActiveVotesForGroup(group, voter2),
+ expectedVoter2ActiveVotesForGroup
+ )
+ assertEqualBN(
+ await election.getAccountActiveVotesForGroup(group2, voter2),
+ expectedVoter2ActiveVotesForGroup2
+ )
+ })
+
+ it("should increment the accounts' total votes for both groups", async () => {
+ assertEqualBN(
+ await election.getAccountTotalVotesForGroup(group, voter),
+ expectedVoterActiveVotesForGroup
+ )
+ assertEqualBN(
+ await election.getAccountTotalVotesForGroup(group, voter2),
+ expectedVoter2ActiveVotesForGroup
+ )
+ assertEqualBN(
+ await election.getAccountTotalVotesForGroup(group2, voter2),
+ expectedVoter2ActiveVotesForGroup2
+ )
+ })
+
+ it("should increment the accounts' total votes", async () => {
+ assertEqualBN(
+ await election.getAccountTotalVotes(voter),
+ expectedVoterActiveVotesForGroup
+ )
+ assertEqualBN(
+ await election.getAccountTotalVotes(voter2),
+ expectedVoter2ActiveVotesForGroup.plus(expectedVoter2ActiveVotesForGroup2)
+ )
+ })
+
+ it('should increment the total votes for the groups', async () => {
+ assertEqualBN(await election.getGroupTotalVotes(group), expectedGroupTotalActiveVotes)
+ assertEqualBN(
+ await election.getGroupTotalVotes(group2),
+ expectedVoter2ActiveVotesForGroup2
+ )
+ })
+
+ it('should increment the total votes', async () => {
+ assertEqualBN(
+ await election.getTotalVotes(),
+ expectedGroupTotalActiveVotes.plus(expectedVoter2ActiveVotesForGroup2)
+ )
+ })
+
+ it('should update the ordering of the eligible groups', async () => {
+ assert.deepEqual(await election.getEligibleValidatorGroups(), [group2, group])
+ })
+ })
+ })
+ })
})
diff --git a/packages/utils/package.json b/packages/utils/package.json
index 32d186d2ffe..1e2636b005a 100644
--- a/packages/utils/package.json
+++ b/packages/utils/package.json
@@ -29,7 +29,7 @@
"web3-utils": "1.0.0-beta.37",
"keccak256": "^1.0.0",
"buffer-reverse": "^1.0.1",
- "bigi": "^1.1.0"
+ "bigi": "^1.1.0"
},
"devDependencies": {
"@celo/typescript": "0.0.1",
From 50d60294a7d6b5ef01c03903d02a684d5d5a7f5c Mon Sep 17 00:00:00 2001
From: Asa Oines
Date: Sun, 6 Oct 2019 16:44:26 -0700
Subject: [PATCH 031/149] Update membership history upon validator registration
---
packages/protocol/contracts/governance/Validators.sol | 1 +
packages/protocol/test/common/migration.ts | 7 -------
2 files changed, 1 insertion(+), 7 deletions(-)
diff --git a/packages/protocol/contracts/governance/Validators.sol b/packages/protocol/contracts/governance/Validators.sol
index 14467cc77e6..41e9b291a83 100644
--- a/packages/protocol/contracts/governance/Validators.sol
+++ b/packages/protocol/contracts/governance/Validators.sol
@@ -337,6 +337,7 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
validators[account].url = url;
validators[account].publicKeysData = publicKeysData;
_validators.push(account);
+ updateMembershipHistory(account, address(0));
getLockedGold().setAccountMustMaintain(account, registrationRequirements.validator, MAX_INT);
emit ValidatorRegistered(account, name, url, publicKeysData);
return true;
diff --git a/packages/protocol/test/common/migration.ts b/packages/protocol/test/common/migration.ts
index db53a566d3a..3da2d07bfd3 100644
--- a/packages/protocol/test/common/migration.ts
+++ b/packages/protocol/test/common/migration.ts
@@ -2,7 +2,6 @@ import {
assertContractsRegistered,
assertProxiesSet,
assertRegistryAddressesSet,
- assertStableTokenMinter,
getReserveBalance,
} from '@celo/protocol/lib/test-utils'
import { getDeployedProxiedContract } from '@celo/protocol/lib/web3-utils'
@@ -52,10 +51,4 @@ contract('Migration', () => {
assert.equal(balance, expectedBalance)
})
})
-
- describe('Checking StableToken minter', async () => {
- it('should be set to the Reserve', async () => {
- await assertStableTokenMinter(getContract)
- })
- })
})
From 999bca29c550e9190d68fb69e27baacf7f226256 Mon Sep 17 00:00:00 2001
From: Asa Oines
Date: Mon, 7 Oct 2019 13:34:31 -0700
Subject: [PATCH 032/149] End to end tests passing
---
.../src/e2e-tests/governance_tests.ts | 154 +++++++++---------
.../contracts/governance/LockedGold.sol | 16 ++
packages/protocol/test/common/fixidity.ts | 9 -
packages/protocol/test/governance/election.ts | 2 +-
.../protocol/test/governance/lockedgold.ts | 27 +--
.../protocol/test/governance/validators.ts | 17 +-
packages/protocol/test/stability/exchange.ts | 18 +-
.../protocol/test/stability/stabletoken.ts | 10 +-
8 files changed, 142 insertions(+), 111 deletions(-)
diff --git a/packages/celotool/src/e2e-tests/governance_tests.ts b/packages/celotool/src/e2e-tests/governance_tests.ts
index e8a9fdc202c..043776fb286 100644
--- a/packages/celotool/src/e2e-tests/governance_tests.ts
+++ b/packages/celotool/src/e2e-tests/governance_tests.ts
@@ -3,14 +3,7 @@ import { ContractKit, newKitFromWeb3 } from '@celo/contractkit'
import { fromFixed, toFixed } from '@celo/utils/lib/fixidity'
import { assert } from 'chai'
import Web3 from 'web3'
-import {
- getContext,
- getContractAddress,
- getEnode,
- importGenesis,
- initAndStartGeth,
- sleep,
-} from './utils'
+import { getContext, getEnode, importGenesis, initAndStartGeth, sleep } from './utils'
describe('governance tests', () => {
const gethConfig = {
@@ -34,7 +27,7 @@ describe('governance tests', () => {
before(async function(this: any) {
this.timeout(0)
- // await context.hooks.before()
+ await context.hooks.before()
})
after(context.hooks.after)
@@ -116,8 +109,8 @@ describe('governance tests', () => {
const epoch = 10
const blockNumbers: number[] = []
let allValidators: string[]
- before(async function() {
- this.timeout(0)
+ before(async function(this: any) {
+ this.timeout(0) // Disable test timeout
await restart()
const [groupAddress, groupPrivateKey] = await getValidatorGroupKeys()
@@ -273,16 +266,6 @@ describe('governance tests', () => {
)
const [group] = await validators.methods.getRegisteredValidatorGroups().call()
- const assertBalanceUnchanged = async (validator: string, blockNumber: number) => {
- const currentBalance = new BigNumber(
- await stableToken.methods.balanceOf(validator).call({}, blockNumber)
- )
- const previousBalance = new BigNumber(
- await stableToken.methods.balanceOf(validator).call({}, blockNumber - 1)
- )
- assert.equal(currentBalance.toFixed(), previousBalance.toFixed())
- }
-
const assertBalanceChanged = async (
validator: string,
blockNumber: number,
@@ -297,6 +280,10 @@ describe('governance tests', () => {
assert.equal(expected.toFixed(), currentBalance.minus(previousBalance).toFixed())
}
+ const assertBalanceUnchanged = async (validator: string, blockNumber: number) => {
+ await assertBalanceChanged(validator, blockNumber, new BigNumber(0))
+ }
+
const getExpectedTotalPayment = async (validator: string, blockNumber: number) => {
const score = new BigNumber(
(await validators.methods.getValidator(validator).call({}, blockNumber))[4]
@@ -337,100 +324,117 @@ describe('governance tests', () => {
it('should distribute epoch rewards at the end of each epoch', async () => {
const validators = await kit._web3Contracts.getValidators()
const election = await kit._web3Contracts.getElection()
- // const lockedGold = await kit._web3Contracts.getLockedGold()
+ const lockedGold = await kit._web3Contracts.getLockedGold()
+ const governance = await kit._web3Contracts.getGovernance()
const epochReward = new BigNumber(10).pow(18)
+ const infraReward = new BigNumber(10).pow(18)
const [group] = await validators.methods.getRegisteredValidatorGroups().call()
- const assertVotesUnchanged = async (group: string, blockNumber: number) => {
+ const assertVotesChanged = async (
+ group: string,
+ blockNumber: number,
+ expected: BigNumber
+ ) => {
const currentVotes = new BigNumber(
await election.methods.getGroupTotalVotes(group).call({}, blockNumber)
)
const previousVotes = new BigNumber(
await election.methods.getGroupTotalVotes(group).call({}, blockNumber - 1)
)
- assert.equal(currentVotes.toFixed(), previousVotes.toFixed())
+ assert.equal(expected.toFixed(), currentVotes.minus(previousVotes).toFixed())
}
- const assertVotesChanged = async (
- group: string,
+ const assertGoldTokenTotalSupplyChanged = async (
blockNumber: number,
expected: BigNumber
) => {
- const currentVotes = new BigNumber(
- await election.methods.getGroupTotalVotes(group).call({}, blockNumber)
+ const currentSupply = new BigNumber(
+ await goldToken.methods.totalSupply().call({}, blockNumber)
)
- const previousVotes = new BigNumber(
- await election.methods.getGroupTotalVotes(group).call({}, blockNumber - 1)
+ const previousSupply = new BigNumber(
+ await goldToken.methods.totalSupply().call({}, blockNumber - 1)
+ )
+ assert.equal(expected.toFixed(), currentSupply.minus(previousSupply).toFixed())
+ }
+
+ const assertBalanceChanged = async (
+ address: string,
+ blockNumber: number,
+ expected: BigNumber
+ ) => {
+ const currentBalance = new BigNumber(
+ await goldToken.methods.balanceOf(address).call({}, blockNumber)
)
- console.log(
- currentVotes.toFixed(),
- previousVotes.toFixed(),
- expected.toFixed(),
- currentVotes.minus(previousVotes).toFixed()
+ const previousBalance = new BigNumber(
+ await goldToken.methods.balanceOf(address).call({}, blockNumber - 1)
)
- assert.equal(expected.toFixed(), currentVotes.minus(previousVotes).toFixed())
+ assert.equal(expected.toFixed(), currentBalance.minus(previousBalance).toFixed())
+ }
+
+ const assertLockedGoldBalanceChanged = async (blockNumber: number, expected: BigNumber) => {
+ await assertBalanceChanged(lockedGold.options.address, blockNumber, expected)
+ }
+
+ const assertGovernanceBalanceChanged = async (blockNumber: number, expected: BigNumber) => {
+ await assertBalanceChanged(governance.options.address, blockNumber, expected)
+ }
+
+ const assertVotesUnchanged = async (group: string, blockNumber: number) => {
+ await assertVotesChanged(group, blockNumber, new BigNumber(0))
+ }
+
+ const assertGoldTokenTotalSupplyUnchanged = async (blockNumber: number) => {
+ await assertGoldTokenTotalSupplyChanged(blockNumber, new BigNumber(0))
+ }
+
+ const assertLockedGoldBalanceUnchanged = async (blockNumber: number) => {
+ await assertLockedGoldBalanceChanged(blockNumber, new BigNumber(0))
+ }
+
+ const assertGovernanceBalanceUnchanged = async (blockNumber: number) => {
+ await assertGovernanceBalanceChanged(blockNumber, new BigNumber(0))
}
for (const blockNumber of blockNumbers) {
if (isLastBlockOfEpoch(blockNumber, epoch)) {
await assertVotesChanged(group, blockNumber, epochReward)
+ await assertGoldTokenTotalSupplyChanged(blockNumber, epochReward.plus(infraReward))
+ await assertLockedGoldBalanceChanged(blockNumber, epochReward)
+ await assertGovernanceBalanceChanged(blockNumber, infraReward)
} else {
await assertVotesUnchanged(group, blockNumber)
+ await assertGoldTokenTotalSupplyUnchanged(blockNumber)
+ await assertLockedGoldBalanceUnchanged(blockNumber)
+ await assertGovernanceBalanceUnchanged(blockNumber)
}
}
})
})
- describe('after the governance smart contract is registered', () => {
- let goldGenesisSupply: any
- const addressesWithBalance: string[] = []
+ describe('after the gold token smart contract is registered', () => {
+ let goldGenesisSupply = new BigNumber(0)
beforeEach(async function(this: any) {
this.timeout(0) // Disable test timeout
await restart()
const genesis = await importGenesis()
- goldGenesisSupply = new BigNumber(0)
- Object.keys(genesis.alloc).forEach((validator) => {
- addressesWithBalance.push(validator)
- goldGenesisSupply = goldGenesisSupply.plus(genesis.alloc[validator].balance)
+ Object.keys(genesis.alloc).forEach((address) => {
+ goldGenesisSupply = goldGenesisSupply.plus(genesis.alloc[address].balance)
})
- // Block rewards are paid to governance and Locked Gold.
- // Governance also receives a portion of transaction fees.
- addressesWithBalance.push(await getContractAddress('GovernanceProxy'))
- addressesWithBalance.push(await getContractAddress('LockedGoldProxy'))
- // Some gold is sent to the reserve and exchange during migrations.
- addressesWithBalance.push(await getContractAddress('ReserveProxy'))
- addressesWithBalance.push(await getContractAddress('ExchangeProxy'))
})
- it('should update the Celo Gold total supply correctly', async function(this: any) {
- // To register a validator group, we send gold to a new address not included in
- // `addressesWithBalance`. Therefore, we check the gold total supply at a block before
- // that gold is sent.
- // We don't set the total supply until block rewards are paid out, which can happen once
- // Governance is registered.
- let blockNumber = 150
- while (true) {
- // This will fail if Governance is not registered.
- const governanceAddress = await registry.methods
- .getAddressForString('Governance')
- .call({}, blockNumber)
- if (new BigNumber(governanceAddress).isZero()) {
- blockNumber += 1
- } else {
+ it('should initialize the Celo Gold total supply correctly', async function(this: any) {
+ const events = await registry.getPastEvents('RegistryUpdated', { fromBlock: 0 })
+ let blockNumber = 0
+ for (const e of events) {
+ if (e.returnValues.identifier === 'GoldToken') {
+ blockNumber = e.blockNumber
break
}
}
+ assert.isAtLeast(blockNumber, 1)
+
const goldTotalSupply = await goldToken.methods.totalSupply().call({}, blockNumber)
- const balances = await Promise.all(
- addressesWithBalance.map(
- async (a: string) => new BigNumber(await web3.eth.getBalance(a, blockNumber))
- )
- )
- const expectedGoldTotalSupply = balances.reduce((total: BigNumber, b: BigNumber) =>
- b.plus(total)
- )
- assert.isAtLeast(expectedGoldTotalSupply.toNumber(), goldGenesisSupply.toNumber())
- assert.equal(goldTotalSupply.toString(), expectedGoldTotalSupply.toString())
+ assert.equal(goldTotalSupply, goldGenesisSupply.toFixed())
})
})
})
diff --git a/packages/protocol/contracts/governance/LockedGold.sol b/packages/protocol/contracts/governance/LockedGold.sol
index c3e868b5cef..74ab915fb6b 100644
--- a/packages/protocol/contracts/governance/LockedGold.sol
+++ b/packages/protocol/contracts/governance/LockedGold.sol
@@ -326,6 +326,22 @@ contract LockedGold is ILockedGold, ReentrancyGuard, Initializable, UsingRegistr
}
}
+ /**
+ * @notice Returns the account associated with `accountOrVoter`.
+ * @param accountOrVoter The address of the account or previously authorized voter.
+ * @dev Fails if the `accountOrVoter` is not an account or previously authorized voter.
+ * @return The associated account.
+ */
+ function getAccountFromVoter(address accountOrVoter) public view returns (address) {
+ AuthorizedBy memory ab = authorizedBy[accountOrVoter];
+ if (ab.account != address(0)) {
+ return ab.account;
+ } else {
+ require(isAccount(accountOrVoter));
+ return accountOrVoter;
+ }
+ }
+
/**
* @notice Returns the account associated with `accountOrValidator`.
* @param accountOrValidator The address of the account or previously authorized validator.
diff --git a/packages/protocol/test/common/fixidity.ts b/packages/protocol/test/common/fixidity.ts
index 10a36a26e58..9349e437287 100644
--- a/packages/protocol/test/common/fixidity.ts
+++ b/packages/protocol/test/common/fixidity.ts
@@ -157,15 +157,6 @@ contract('FixidityLib', () => {
assertEqualBN(result, expected)
})
- it('should multiply two numbers less than 1', async () => {
- const a = toFixed(0.1)
- const b = toFixed(new BigNumber(10).pow(-14))
- const expected = toFixed(new BigNumber(10).pow(-15))
- const result = await fixidityTest.multiply(a, b)
-
- assertEqualBN(result, expected)
- })
-
it('should multiply by 0', async () => {
const result = await fixidityTest.multiply(maxFixedMul, zero)
diff --git a/packages/protocol/test/governance/election.ts b/packages/protocol/test/governance/election.ts
index 1f417894680..788f76df43f 100644
--- a/packages/protocol/test/governance/election.ts
+++ b/packages/protocol/test/governance/election.ts
@@ -856,7 +856,7 @@ contract('Election', (accounts: string[]) => {
})
})
- describe.only('#distributeEpochRewards', () => {
+ describe('#distributeEpochRewards', () => {
const voter = accounts[0]
const group = accounts[1]
const voteValue = new BigNumber(1000000)
diff --git a/packages/protocol/test/governance/lockedgold.ts b/packages/protocol/test/governance/lockedgold.ts
index 6b25af9714f..4280bb1774a 100644
--- a/packages/protocol/test/governance/lockedgold.ts
+++ b/packages/protocol/test/governance/lockedgold.ts
@@ -3,7 +3,6 @@ import {
assertEqualBN,
assertLogMatches,
assertRevert,
- NULL_ADDRESS,
timeTravel,
} from '@celo/protocol/lib/test-utils'
import BigNumber from 'bignumber.js'
@@ -67,11 +66,13 @@ contract('LockedGold', (accounts: string[]) => {
authorizationTests.voter = {
fn: lockedGold.authorizeVoter,
getAuthorizedFromAccount: lockedGold.getVoterFromAccount,
+ getAccountFromActiveAuthorized: lockedGold.getAccountFromActiveVoter,
getAccountFromAuthorized: lockedGold.getAccountFromVoter,
}
authorizationTests.validator = {
fn: lockedGold.authorizeValidator,
getAuthorizedFromAccount: lockedGold.getValidatorFromAccount,
+ getAccountFromActiveAuthorized: lockedGold.getAccountFromActiveValidator,
getAccountFromAuthorized: lockedGold.getAccountFromValidator,
}
})
@@ -127,9 +128,8 @@ contract('LockedGold', (accounts: string[]) => {
it(`should set the authorized ${key}`, async () => {
await authorizationTest.fn(authorized, sig.v, sig.r, sig.s)
- assert.equal(await lockedGold.authorizedBy(authorized), account)
assert.equal(await authorizationTest.getAuthorizedFromAccount(account), authorized)
- assert.equal(await authorizationTest.getAccountFromAuthorized(authorized), account)
+ assert.equal(await authorizationTest.getAccountFromActiveAuthorized(authorized), account)
})
it(`should emit a ${capitalize(key)}Authorized event`, async () => {
@@ -174,13 +174,15 @@ contract('LockedGold', (accounts: string[]) => {
})
it(`should set the new authorized ${key}`, async () => {
- assert.equal(await lockedGold.authorizedBy(newAuthorized), account)
assert.equal(await authorizationTest.getAuthorizedFromAccount(account), newAuthorized)
- assert.equal(await authorizationTest.getAccountFromAuthorized(newAuthorized), account)
+ assert.equal(
+ await authorizationTest.getAccountFromActiveAuthorized(newAuthorized),
+ account
+ )
})
- it('should reset the previous authorization', async () => {
- assert.equal(await lockedGold.authorizedBy(authorized), NULL_ADDRESS)
+ it('should preserve the previous authorization', async () => {
+ assert.equal(await authorizationTest.getAccountFromAuthorized(authorized), account)
})
})
})
@@ -188,11 +190,11 @@ contract('LockedGold', (accounts: string[]) => {
describe(`#getAccountFrom${capitalize(key)}()`, () => {
describe(`when the account has not authorized a ${key}`, () => {
it('should return the account when passed the account', async () => {
- assert.equal(await authorizationTest.getAccountFromAuthorized(account), account)
+ assert.equal(await authorizationTest.getAccountFromActiveAuthorized(account), account)
})
it('should revert when passed an address that is not an account', async () => {
- await assertRevert(authorizationTest.getAccountFromAuthorized(accounts[1]))
+ await assertRevert(authorizationTest.getAccountFromActiveAuthorized(accounts[1]))
})
})
@@ -204,11 +206,14 @@ contract('LockedGold', (accounts: string[]) => {
})
it('should return the account when passed the account', async () => {
- assert.equal(await authorizationTest.getAccountFromAuthorized(account), account)
+ assert.equal(await authorizationTest.getAccountFromActiveAuthorized(account), account)
})
it(`should return the account when passed the ${key}`, async () => {
- assert.equal(await authorizationTest.getAccountFromAuthorized(authorized), account)
+ assert.equal(
+ await authorizationTest.getAccountFromActiveAuthorized(authorized),
+ account
+ )
})
})
})
diff --git a/packages/protocol/test/governance/validators.ts b/packages/protocol/test/governance/validators.ts
index 97fa1f3ed04..9983fe0aea4 100644
--- a/packages/protocol/test/governance/validators.ts
+++ b/packages/protocol/test/governance/validators.ts
@@ -1128,10 +1128,19 @@ contract('Validators', (accounts: string[]) => {
const expectedEpoch = new BigNumber(
Math.floor((await web3.eth.getBlock('latest')).number / EPOCH)
)
- assert.equal(membershipHistory[0].length, 1)
- assertEqualBN(membershipHistory[0][0], expectedEpoch)
- assert.equal(membershipHistory[1].length, 1)
- assertSameAddress(membershipHistory[1][0], NULL_ADDRESS)
+
+ // Depending on test timing, we may or may not span an epoch boundary between registration
+ // and removal.
+ const numEntries = membershipHistory[0].length
+ assert.isTrue(numEntries == 1 || numEntries == 2)
+ assert.equal(membershipHistory[1].length, numEntries)
+ if (numEntries == 1) {
+ assertEqualBN(membershipHistory[0][0], expectedEpoch)
+ assertSameAddress(membershipHistory[1][0], NULL_ADDRESS)
+ } else {
+ assertEqualBN(membershipHistory[0][1], expectedEpoch)
+ assertSameAddress(membershipHistory[1][1], NULL_ADDRESS)
+ }
})
it('should emit the ValidatorGroupMemberRemoved event', async () => {
diff --git a/packages/protocol/test/stability/exchange.ts b/packages/protocol/test/stability/exchange.ts
index eddd3b9183a..36be55415bc 100644
--- a/packages/protocol/test/stability/exchange.ts
+++ b/packages/protocol/test/stability/exchange.ts
@@ -1,3 +1,4 @@
+import { CeloContractName } from '@celo/protocol/lib/registry-utils'
import {
assertEqualBN,
assertLogMatches2,
@@ -87,10 +88,10 @@ contract('Exchange', (accounts: string[]) => {
beforeEach(async () => {
registry = await Registry.new()
goldToken = await GoldToken.new()
- await registry.setAddressFor('GoldToken', goldToken.address)
+ await registry.setAddressFor(CeloContractName.GoldToken, goldToken.address)
mockReserve = await MockReserve.new()
- await registry.setAddressFor('Reserve', mockReserve.address)
+ await registry.setAddressFor(CeloContractName.Reserve, mockReserve.address)
await mockReserve.setGoldToken(goldToken.address)
stableToken = await StableToken.new()
@@ -101,11 +102,13 @@ contract('Exchange', (accounts: string[]) => {
decimals,
registry.address,
fixed1,
- SECONDS_IN_A_WEEK
+ SECONDS_IN_A_WEEK,
+ [],
+ []
)
mockSortedOracles = await MockSortedOracles.new()
- await registry.setAddressFor('SortedOracles', mockSortedOracles.address)
+ await registry.setAddressFor(CeloContractName.SortedOracles, mockSortedOracles.address)
await mockSortedOracles.setMedianRate(
stableToken.address,
stableAmountForRate,
@@ -125,8 +128,7 @@ contract('Exchange', (accounts: string[]) => {
updateFrequency,
minimumReports
)
-
- await stableToken.setMinter(exchange.address)
+ await registry.setAddressFor(CeloContractName.Exchange, exchange.address)
})
describe('#initialize()', () => {
@@ -588,9 +590,9 @@ contract('Exchange', (accounts: string[]) => {
let oldGoldBalance: BigNumber
let oldReserveGoldBalance: BigNumber
beforeEach(async () => {
- await stableToken.setMinter(owner)
+ await registry.setAddressFor(CeloContractName.Exchange, owner)
await stableToken.mint(user, stableTokenBalance)
- await stableToken.setMinter(exchange.address)
+ await registry.setAddressFor(CeloContractName.Exchange, exchange.address)
oldReserveGoldBalance = await goldToken.balanceOf(mockReserve.address)
await stableToken.approve(exchange.address, stableTokenBalance, { from: user })
diff --git a/packages/protocol/test/stability/stabletoken.ts b/packages/protocol/test/stability/stabletoken.ts
index 51a517d0bf8..f8fe84e57e5 100644
--- a/packages/protocol/test/stability/stabletoken.ts
+++ b/packages/protocol/test/stability/stabletoken.ts
@@ -35,7 +35,9 @@ contract('StableToken', (accounts: string[]) => {
18,
registry.address,
fixed1,
- SECONDS_IN_A_WEEK
+ SECONDS_IN_A_WEEK,
+ [],
+ []
)
initializationTime = (await web3.eth.getBlock('latest')).timestamp
})
@@ -87,7 +89,9 @@ contract('StableToken', (accounts: string[]) => {
18,
registry.address,
fixed1,
- SECONDS_IN_A_WEEK
+ SECONDS_IN_A_WEEK,
+ [],
+ []
)
)
})
@@ -132,7 +136,7 @@ contract('StableToken', (accounts: string[]) => {
})
it('should not allow anyone else to mint', async () => {
- await assertRevert(stableToken.mint(minter, amountToMint, { from: accounts[2] }))
+ await assertRevert(stableToken.mint(validators, amountToMint, { from: accounts[2] }))
})
})
From 03dc663ea286af21777f7a1d0e4a4fe535e7c129 Mon Sep 17 00:00:00 2001
From: Asa Oines
Date: Mon, 7 Oct 2019 17:54:31 -0700
Subject: [PATCH 033/149] Add epoch size precompile, among other things
---
.../protocol/contracts/common/UsingEpochs.sol | 19 ---
.../contracts/common/UsingPrecompiles.sol | 91 +++++++++++
.../contracts/governance/Election.sol | 36 +++--
.../contracts/governance/Validators.sol | 22 +--
.../governance/test/ValidatorsTest.sol | 9 --
.../contracts/stability/StableToken.sol | 64 +-------
packages/protocol/package.json | 4 +-
packages/protocol/test/governance/election.ts | 144 ++++++++++--------
.../protocol/test/governance/validators.ts | 35 +++--
yarn.lock | 4 +-
10 files changed, 238 insertions(+), 190 deletions(-)
delete mode 100644 packages/protocol/contracts/common/UsingEpochs.sol
create mode 100644 packages/protocol/contracts/common/UsingPrecompiles.sol
diff --git a/packages/protocol/contracts/common/UsingEpochs.sol b/packages/protocol/contracts/common/UsingEpochs.sol
deleted file mode 100644
index 4d16aec0689..00000000000
--- a/packages/protocol/contracts/common/UsingEpochs.sol
+++ /dev/null
@@ -1,19 +0,0 @@
-pragma solidity ^0.5.3;
-
-// TODO: Replace this with a precompile.
-contract UsingEpochs {
-
- event RegistrySet(address indexed registryAddress);
-
- // TODO(asa): Expose epoch size via precompile.
- // solhint-disable state-visibility
- uint256 constant EPOCH = 10;
-
- function getEpochNumber() public view returns (uint256) {
- uint256 ret = block.number / EPOCH;
- if (block.number % EPOCH == 0) {
- ret = ret - 1;
- }
- return ret;
- }
-}
diff --git a/packages/protocol/contracts/common/UsingPrecompiles.sol b/packages/protocol/contracts/common/UsingPrecompiles.sol
new file mode 100644
index 00000000000..412a2470697
--- /dev/null
+++ b/packages/protocol/contracts/common/UsingPrecompiles.sol
@@ -0,0 +1,91 @@
+pragma solidity ^0.5.3;
+
+contract UsingPrecompiles {
+
+ /**
+ * @notice calculate a * b^x for fractions a, b to `decimals` precision
+ * @param aNumerator Numerator of first fraction
+ * @param aDenominator Denominator of first fraction
+ * @param bNumerator Numerator of exponentiated fraction
+ * @param bDenominator Denominator of exponentiated fraction
+ * @param exponent exponent to raise b to
+ * @param _decimals precision
+ * @return numerator/denominator of the computed quantity (not reduced).
+ */
+ function fractionMulExp(
+ uint256 aNumerator,
+ uint256 aDenominator,
+ uint256 bNumerator,
+ uint256 bDenominator,
+ uint256 exponent,
+ uint256 _decimals
+ )
+ public
+ view
+ returns (uint256, uint256)
+ {
+ require(aDenominator != 0 && bDenominator != 0);
+ uint256 returnNumerator;
+ uint256 returnDenominator;
+ // solhint-disable-next-line no-inline-assembly
+ assembly {
+ let newCallDataPosition := mload(0x40)
+ mstore(0x40, add(newCallDataPosition, calldatasize))
+ mstore(newCallDataPosition, aNumerator)
+ mstore(add(newCallDataPosition, 32), aDenominator)
+ mstore(add(newCallDataPosition, 64), bNumerator)
+ mstore(add(newCallDataPosition, 96), bDenominator)
+ mstore(add(newCallDataPosition, 128), exponent)
+ mstore(add(newCallDataPosition, 160), _decimals)
+ let success := staticcall(
+ 1050, // estimated gas cost for this function
+ 0xfc,
+ newCallDataPosition,
+ 0xc4, // input size, 6 * 32 = 192 bytes
+ 0,
+ 0
+ )
+
+ let returnDataSize := returndatasize
+ let returnDataPosition := mload(0x40)
+ mstore(0x40, add(returnDataPosition, returnDataSize))
+ returndatacopy(returnDataPosition, 0, returnDataSize)
+
+ switch success
+ case 0 {
+ revert(returnDataPosition, returnDataSize)
+ }
+ default {
+ returnNumerator := mload(returnDataPosition)
+ returnDenominator := mload(add(returnDataPosition, 32))
+ }
+ }
+ return (returnNumerator, returnDenominator);
+ }
+
+ /**
+ * @notice Returns the current epoch size in blocks.
+ * @return The current epoch size in blocks.
+ */
+ function getEpochSize() public view returns (uint256) {
+ uint256 ret;
+ // solhint-disable-next-line no-inline-assembly
+ assembly {
+ let newCallDataPosition := mload(0x40)
+ let success := staticcall(1000, 0xf8, newCallDataPosition, 0, 0, 0)
+
+ returndatacopy(add(newCallDataPosition, 32), 0, 32)
+ ret := mload(add(newCallDataPosition, 32))
+ }
+ return ret;
+ }
+
+ function getEpochNumber() public view returns (uint256) {
+ uint256 epochSize = getEpochSize();
+ uint256 epochNumber = block.number / epochSize;
+ if (block.number % epochSize == 0) {
+ epochNumber = epochNumber - 1;
+ }
+ return epochNumber;
+ }
+}
diff --git a/packages/protocol/contracts/governance/Election.sol b/packages/protocol/contracts/governance/Election.sol
index 4c4e995b350..36f25479943 100644
--- a/packages/protocol/contracts/governance/Election.sol
+++ b/packages/protocol/contracts/governance/Election.sol
@@ -9,10 +9,11 @@ import "./interfaces/IValidators.sol";
import "../common/Initializable.sol";
import "../common/FixidityLib.sol";
import "../common/linkedlists/AddressSortedLinkedList.sol";
+import "../common/UsingPrecompiles.sol";
import "../common/UsingRegistry.sol";
-contract Election is Ownable, ReentrancyGuard, Initializable, UsingRegistry {
+contract Election is Ownable, ReentrancyGuard, Initializable, UsingRegistry, UsingPrecompiles {
using AddressSortedLinkedList for SortedLinkedList.List;
using FixidityLib for FixidityLib.Fraction;
@@ -26,8 +27,8 @@ contract Election is Ownable, ReentrancyGuard, Initializable, UsingRegistry {
mapping(address => uint256) total;
// Maps groups to accounts to pending voting balance.
mapping(address => mapping(address => uint256)) balances;
- // Maps groups to accounts to timestamp of the account's most recent vote for the group.
- mapping(address => mapping(address => uint256)) timestamps;
+ // Maps groups to accounts to the epoch of the account's most recent vote for the group.
+ mapping(address => mapping(address => uint256)) epochs;
}
// Active votes are those for which at least one following election has been held.
@@ -43,8 +44,10 @@ contract Election is Ownable, ReentrancyGuard, Initializable, UsingRegistry {
}
struct TotalVotes {
- // The total number of votes cast, including those for ineligible Validator Groups.
- uint256 total;
+ // The total number of active votes cast, including those for ineligible Validator Groups.
+ uint256 active;
+ // The total number of pending votes cast, including those for ineligible Validator Groups.
+ uint256 pending;
// A list of eligible Validator Groups sorted by total votes.
SortedLinkedList.List eligible;
}
@@ -63,6 +66,7 @@ contract Election is Ownable, ReentrancyGuard, Initializable, UsingRegistry {
uint256 public maxNumGroupsVotedFor;
FixidityLib.Fraction public electabilityThreshold;
+ event Debug(uint256 value, string desc);
event MinElectableValidatorsSet(
uint256 minElectableValidators
);
@@ -268,6 +272,7 @@ contract Election is Ownable, ReentrancyGuard, Initializable, UsingRegistry {
function activate(address group) external nonReentrant returns (bool) {
address account = getLockedGold().getAccountFromActiveVoter(msg.sender);
PendingVotes storage pending = votes.pending;
+ require(getEpochNumber() > pending.epochs[group][account]);
uint256 value = pending.balances[group][account];
require(value > 0);
decrementPendingVotes(group, account, value);
@@ -436,6 +441,10 @@ contract Election is Ownable, ReentrancyGuard, Initializable, UsingRegistry {
return votes.total.eligible.contains(group);
}
+ function getGroupEpochRewards(address group, uint256 totalEpochRewards) external view returns (uint256) {
+ return totalEpochRewards.mul(votes.active.total[group]).div(votes.total.active);
+ }
+
function distributeEpochRewards(address group, uint256 value, address lesser, address greater) external {
require(msg.sender == address(0));
_distributeEpochRewards(group, value, lesser, greater);
@@ -445,13 +454,14 @@ contract Election is Ownable, ReentrancyGuard, Initializable, UsingRegistry {
// TODO(asa): What do here?
if (votes.active.total[group] == 0) {
}
+
if (votes.total.eligible.contains(group)) {
uint256 newVoteTotal = votes.total.eligible.getValue(group).add(value);
votes.total.eligible.update(group, newVoteTotal, lesser, greater);
}
-
+
votes.active.total[group] = votes.active.total[group].add(value);
- votes.total.total = votes.total.total.add(value);
+ votes.total.active = votes.total.active.add(value);
}
/**
@@ -474,7 +484,6 @@ contract Election is Ownable, ReentrancyGuard, Initializable, UsingRegistry {
require(votes.total.eligible.contains(group));
uint256 newVoteTotal = votes.total.eligible.getValue(group).add(value);
votes.total.eligible.update(group, newVoteTotal, lesser, greater);
- votes.total.total = votes.total.total.add(value);
}
/**
@@ -498,7 +507,6 @@ contract Election is Ownable, ReentrancyGuard, Initializable, UsingRegistry {
uint256 newVoteTotal = votes.total.eligible.getValue(group).sub(value);
votes.total.eligible.update(group, newVoteTotal, lesser, greater);
}
- votes.total.total = votes.total.total.sub(value);
}
/**
@@ -547,8 +555,9 @@ contract Election is Ownable, ReentrancyGuard, Initializable, UsingRegistry {
function incrementPendingVotes(address group, address account, uint256 value) private {
PendingVotes storage pending = votes.pending;
pending.balances[group][account] = pending.balances[group][account].add(value);
- pending.timestamps[group][account] = now;
+ pending.epochs[group][account] = getEpochNumber();
pending.total[group] = pending.total[group].add(value);
+ votes.total.pending = votes.total.pending.add(value);
}
/**
@@ -562,9 +571,10 @@ contract Election is Ownable, ReentrancyGuard, Initializable, UsingRegistry {
uint256 newValue = pending.balances[group][account].sub(value);
pending.balances[group][account] = newValue;
if (newValue == 0) {
- pending.timestamps[group][account] = 0;
+ pending.epochs[group][account] = 0;
}
pending.total[group] = pending.total[group].sub(value);
+ votes.total.pending = votes.total.pending.sub(value);
}
/**
@@ -579,6 +589,7 @@ contract Election is Ownable, ReentrancyGuard, Initializable, UsingRegistry {
active.numerators[group][account] = active.numerators[group][account].add(delta);
active.denominators[group] = active.denominators[group].add(delta);
active.total[group] = active.total[group].add(value);
+ votes.total.active = votes.total.active.add(value);
}
/**
@@ -593,6 +604,7 @@ contract Election is Ownable, ReentrancyGuard, Initializable, UsingRegistry {
active.numerators[group][account] = active.numerators[group][account].sub(delta);
active.denominators[group] = active.denominators[group].sub(delta);
active.total[group] = active.total[group].sub(value);
+ votes.total.active = votes.total.active.sub(value);
}
/**
@@ -646,7 +658,7 @@ contract Election is Ownable, ReentrancyGuard, Initializable, UsingRegistry {
* @return The total votes received across all groups.
*/
function getTotalVotes() external view returns (uint256) {
- return votes.total.total;
+ return votes.total.active.add(votes.total.pending);
}
/**
diff --git a/packages/protocol/contracts/governance/Validators.sol b/packages/protocol/contracts/governance/Validators.sol
index 41e9b291a83..58c3e36a050 100644
--- a/packages/protocol/contracts/governance/Validators.sol
+++ b/packages/protocol/contracts/governance/Validators.sol
@@ -13,14 +13,14 @@ import "../identity/interfaces/IRandom.sol";
import "../common/Initializable.sol";
import "../common/FixidityLib.sol";
import "../common/linkedlists/AddressLinkedList.sol";
-import "../common/UsingEpochs.sol";
import "../common/UsingRegistry.sol";
+import "../common/UsingPrecompiles.sol";
/**
* @title A contract for registering and electing Validator Groups and Validators.
*/
-contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, UsingEpochs, UsingRegistry {
+contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, UsingRegistry, UsingPrecompiles {
using FixidityLib for FixidityLib.Fraction;
using AddressLinkedList for LinkedList.List;
@@ -408,17 +408,21 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
require(isValidator(account), "isvalidator");
require(uptime <= FixidityLib.fixed1().unwrap(), "uptime");
- // TODO(asa): Use exponent.
- FixidityLib.Fraction memory epochScore = FixidityLib.wrap(uptime);
-
+ uint256 numerator;
+ uint256 denominator;
+ (numerator, denominator) = fractionMulExp(
+ FixidityLib.fixed1().unwrap(),
+ FixidityLib.fixed1().unwrap(),
+ uptime,
+ FixidityLib.fixed1().unwrap(),
+ validatorScoreParameters.exponent,
+ 18
+ );
- // New component is 0! Uptime is non-zero.
+ FixidityLib.Fraction memory epochScore = FixidityLib.wrap(numerator).divide(FixidityLib.wrap(denominator));
FixidityLib.Fraction memory newComponent = validatorScoreParameters.adjustmentSpeed.multiply(
epochScore
);
- // validators[validator].score = newComponent;
- // This works:
- // validators[validator].score = epochScore;
FixidityLib.Fraction memory currentComponent = FixidityLib.fixed1().subtract(
validatorScoreParameters.adjustmentSpeed
diff --git a/packages/protocol/contracts/governance/test/ValidatorsTest.sol b/packages/protocol/contracts/governance/test/ValidatorsTest.sol
index 16cdabd8364..beefe62389e 100644
--- a/packages/protocol/contracts/governance/test/ValidatorsTest.sol
+++ b/packages/protocol/contracts/governance/test/ValidatorsTest.sol
@@ -8,15 +8,6 @@ import "../../common/FixidityLib.sol";
*/
contract ValidatorsTest is Validators {
- function getEpochNumber() public view returns (uint256) {
- uint256 epoch = 100;
- uint256 ret = block.number / epoch;
- if (block.number % epoch == 0) {
- ret = ret - 1;
- }
- return ret;
- }
-
function updateValidatorScore(address validator, uint256 uptime) external {
return _updateValidatorScore(validator, uptime);
}
diff --git a/packages/protocol/contracts/stability/StableToken.sol b/packages/protocol/contracts/stability/StableToken.sol
index 8974fb91c7d..50c53f9ab11 100644
--- a/packages/protocol/contracts/stability/StableToken.sol
+++ b/packages/protocol/contracts/stability/StableToken.sol
@@ -10,6 +10,7 @@ import "../common/interfaces/ICeloToken.sol";
import "../common/Initializable.sol";
import "../common/FixidityLib.sol";
import "../common/UsingRegistry.sol";
+import "../common/UsingPrecompiles.sol";
/**
@@ -17,7 +18,7 @@ import "../common/UsingRegistry.sol";
*/
// solhint-disable-next-line max-line-length
contract StableToken is IStableToken, IERC20Token, ICeloToken, Ownable,
- Initializable, UsingRegistry {
+ Initializable, UsingRegistry, UsingPrecompiles {
using FixidityLib for FixidityLib.Fraction;
using SafeMath for uint256;
@@ -451,67 +452,6 @@ contract StableToken is IStableToken, IERC20Token, ICeloToken, Ownable,
/* solhint-enable not-rely-on-time */
}
- /**
- * @notice calculate a * b^x for fractions a, b to `decimals` precision
- * @param aNumerator Numerator of first fraction
- * @param aDenominator Denominator of first fraction
- * @param bNumerator Numerator of exponentiated fraction
- * @param bDenominator Denominator of exponentiated fraction
- * @param exponent exponent to raise b to
- * @param _decimals precision
- * @return numerator/denominator of the computed quantity (not reduced).
- */
- function fractionMulExp(
- uint256 aNumerator,
- uint256 aDenominator,
- uint256 bNumerator,
- uint256 bDenominator,
- uint256 exponent,
- uint256 _decimals
- )
- public
- view
- returns(uint256, uint256)
- {
- require(aDenominator != 0 && bDenominator != 0);
- uint256 returnNumerator;
- uint256 returnDenominator;
- // solhint-disable-next-line no-inline-assembly
- assembly {
- let newCallDataPosition := mload(0x40)
- mstore(0x40, add(newCallDataPosition, calldatasize))
- mstore(newCallDataPosition, aNumerator)
- mstore(add(newCallDataPosition, 32), aDenominator)
- mstore(add(newCallDataPosition, 64), bNumerator)
- mstore(add(newCallDataPosition, 96), bDenominator)
- mstore(add(newCallDataPosition, 128), exponent)
- mstore(add(newCallDataPosition, 160), _decimals)
- let delegatecallSuccess := staticcall(
- 1050, // estimated gas cost for this function
- 0xfc,
- newCallDataPosition,
- 0xc4, // input size, 6 * 32 = 192 bytes
- 0,
- 0
- )
-
- let returnDataSize := returndatasize
- let returnDataPosition := mload(0x40)
- mstore(0x40, add(returnDataPosition, returnDataSize))
- returndatacopy(returnDataPosition, 0, returnDataSize)
-
- switch delegatecallSuccess
- case 0 {
- revert(returnDataPosition, returnDataSize)
- }
- default {
- returnNumerator := mload(returnDataPosition)
- returnDenominator := mload(add(returnDataPosition, 32))
- }
- }
- return (returnNumerator, returnDenominator);
- }
-
/**
* @notice Transfers `value` from `msg.sender` to `to`
* @param to The address to transfer to.
diff --git a/packages/protocol/package.json b/packages/protocol/package.json
index 47662055610..57dab8e83e9 100644
--- a/packages/protocol/package.json
+++ b/packages/protocol/package.json
@@ -9,7 +9,7 @@
"lint:ts": "tslint -c tslint.json --project tsconfig.json",
"lint:sol": "solhint './contracts/**/*.sol'",
"lint": "yarn run lint:ts && yarn run lint:sol",
- "clean": "rm -rf ./types/typechain && rm -rf build/* && rm -rf migrations/*.js* && rm -rf test/**/*.js* && rm -f lib/*.js*",
+ "clean": "rm -rf ./types/typechain && rm -rf build/* && rm -rf .0x-artifacts/* && rm -rf migrations/*.js* && rm -rf test/**/*.js* && rm -f lib/*.js*",
"pretest": "yarn run build",
"test": "node runTests.js",
"test:coverage": "yarn run test --coverage",
@@ -74,7 +74,7 @@
"web3-provider-engine": "^15.0.0"
},
"devDependencies": {
- "@celo/ganache-cli": "git+https://github.com/celo-org/ganache-cli.git#816a475",
+ "@celo/ganache-cli": "git+https://github.com/celo-org/ganache-cli.git#4cf9664",
"@celo/typescript": "0.0.1",
"@types/bignumber.js": "^5.0.0",
"@types/bn.js": "^4.11.0",
diff --git a/packages/protocol/test/governance/election.ts b/packages/protocol/test/governance/election.ts
index 788f76df43f..ac6d94a22d0 100644
--- a/packages/protocol/test/governance/election.ts
+++ b/packages/protocol/test/governance/election.ts
@@ -4,6 +4,7 @@ import {
assertEqualBN,
assertRevert,
NULL_ADDRESS,
+ mineBlocks,
} from '@celo/protocol/lib/test-utils'
import { toFixed } from '@celo/utils/lib/fixidity'
import BigNumber from 'bignumber.js'
@@ -30,6 +31,9 @@ const Registry: RegistryContract = artifacts.require('Registry')
// TODO(mcortesi): Use BN
ElectionTest.numberFormat = 'BigNumber'
+// Hard coded in ganache.
+const EPOCH = 100
+
contract('Election', (accounts: string[]) => {
let election: ElectionTestInstance
let registry: RegistryInstance
@@ -428,101 +432,114 @@ contract('Election', (accounts: string[]) => {
})
describe('when the voter has pending votes', () => {
- let resp: any
beforeEach(async () => {
await election.vote(group, value, NULL_ADDRESS, NULL_ADDRESS)
- resp = await election.activate(group)
- })
-
- it("should decrement the account's pending votes for the group", async () => {
- assertEqualBN(await election.getAccountPendingVotesForGroup(group, voter), 0)
- })
-
- it("should increment the account's active votes for the group", async () => {
- assertEqualBN(await election.getAccountActiveVotesForGroup(group, voter), value)
- })
-
- it("should not modify the account's total votes for the group", async () => {
- assertEqualBN(await election.getAccountTotalVotesForGroup(group, voter), value)
})
- it("should not modify the account's total votes", async () => {
- assertEqualBN(await election.getAccountTotalVotes(voter), value)
- })
-
- it('should not modify the total votes for the group', async () => {
- assertEqualBN(await election.getGroupTotalVotes(group), value)
- })
-
- it('should not modify the total votes', async () => {
- assertEqualBN(await election.getTotalVotes(), value)
- })
-
- it('should emit the ValidatorGroupVoteActivated event', async () => {
- assert.equal(resp.logs.length, 1)
- const log = resp.logs[0]
- assertContainSubset(log, {
- event: 'ValidatorGroupVoteActivated',
- args: {
- account: voter,
- group,
- value: new BigNumber(value),
- },
+ describe('when an epoch boundary has passed since the pending votes were made', () => {
+ let resp: any
+ beforeEach(async () => {
+ await mineBlocks(EPOCH, web3)
+ resp = await election.activate(group)
})
- })
- describe('when another voter activates votes', () => {
- const voter2 = accounts[2]
- const value2 = 573
- beforeEach(async () => {
- await mockLockedGold.incrementNonvotingAccountBalance(voter2, value2)
- await election.vote(group, value2, NULL_ADDRESS, NULL_ADDRESS, { from: voter2 })
- await election.activate(group, { from: voter2 })
+ it("should decrement the account's pending votes for the group", async () => {
+ assertEqualBN(await election.getAccountPendingVotesForGroup(group, voter), 0)
})
- it("should not modify the first account's active votes for the group", async () => {
+ it("should increment the account's active votes for the group", async () => {
assertEqualBN(await election.getAccountActiveVotesForGroup(group, voter), value)
})
- it("should not modify the first account's total votes for the group", async () => {
+ it("should not modify the account's total votes for the group", async () => {
assertEqualBN(await election.getAccountTotalVotesForGroup(group, voter), value)
})
- it("should not modify the first account's total votes", async () => {
+ it("should not modify the account's total votes", async () => {
assertEqualBN(await election.getAccountTotalVotes(voter), value)
})
- it("should decrement the second account's pending votes for the group", async () => {
- assertEqualBN(await election.getAccountPendingVotesForGroup(group, voter2), 0)
+ it('should not modify the total votes for the group', async () => {
+ assertEqualBN(await election.getGroupTotalVotes(group), value)
})
- it("should increment the second account's active votes for the group", async () => {
- assertEqualBN(await election.getAccountActiveVotesForGroup(group, voter2), value2)
+ it('should not modify the total votes', async () => {
+ assertEqualBN(await election.getTotalVotes(), value)
})
- it("should not modify the second account's total votes for the group", async () => {
- assertEqualBN(await election.getAccountTotalVotesForGroup(group, voter2), value2)
+ it('should emit the ValidatorGroupVoteActivated event', async () => {
+ assert.equal(resp.logs.length, 1)
+ const log = resp.logs[0]
+ assertContainSubset(log, {
+ event: 'ValidatorGroupVoteActivated',
+ args: {
+ account: voter,
+ group,
+ value: new BigNumber(value),
+ },
+ })
})
- it("should not modify the second account's total votes", async () => {
- assertEqualBN(await election.getAccountTotalVotes(voter2), value2)
- })
+ describe('when another voter activates votes', () => {
+ const voter2 = accounts[2]
+ const value2 = 573
+ beforeEach(async () => {
+ await mockLockedGold.incrementNonvotingAccountBalance(voter2, value2)
+ await election.vote(group, value2, NULL_ADDRESS, NULL_ADDRESS, { from: voter2 })
+ await mineBlocks(EPOCH, web3)
+ await election.activate(group, { from: voter2 })
+ })
- it('should not modify the total votes for the group', async () => {
- assertEqualBN(await election.getGroupTotalVotes(group), value + value2)
- })
+ it("should not modify the first account's active votes for the group", async () => {
+ assertEqualBN(await election.getAccountActiveVotesForGroup(group, voter), value)
+ })
- it('should not modify the total votes', async () => {
- assertEqualBN(await election.getTotalVotes(), value + value2)
+ it("should not modify the first account's total votes for the group", async () => {
+ assertEqualBN(await election.getAccountTotalVotesForGroup(group, voter), value)
+ })
+
+ it("should not modify the first account's total votes", async () => {
+ assertEqualBN(await election.getAccountTotalVotes(voter), value)
+ })
+
+ it("should decrement the second account's pending votes for the group", async () => {
+ assertEqualBN(await election.getAccountPendingVotesForGroup(group, voter2), 0)
+ })
+
+ it("should increment the second account's active votes for the group", async () => {
+ assertEqualBN(await election.getAccountActiveVotesForGroup(group, voter2), value2)
+ })
+
+ it("should not modify the second account's total votes for the group", async () => {
+ assertEqualBN(await election.getAccountTotalVotesForGroup(group, voter2), value2)
+ })
+
+ it("should not modify the second account's total votes", async () => {
+ assertEqualBN(await election.getAccountTotalVotes(voter2), value2)
+ })
+
+ it('should not modify the total votes for the group', async () => {
+ assertEqualBN(await election.getGroupTotalVotes(group), value + value2)
+ })
+
+ it('should not modify the total votes', async () => {
+ assertEqualBN(await election.getTotalVotes(), value + value2)
+ })
})
})
- describe('when the voter does not have pending votes', () => {
+ describe('when an epoch boundary has not passed since the pending votes were made', () => {
it('should revert', async () => {
await assertRevert(election.activate(group))
})
})
})
+
+ describe('when the voter does not have pending votes', () => {
+ it('should revert', async () => {
+ await assertRevert(election.activate(group))
+ })
+ })
})
describe('#revokePending', () => {
@@ -637,6 +654,7 @@ contract('Election', (accounts: string[]) => {
await mockValidators.setNumRegisteredValidators(1)
await mockLockedGold.incrementNonvotingAccountBalance(voter, value)
await election.vote(group, value, NULL_ADDRESS, NULL_ADDRESS)
+ await mineBlocks(EPOCH, web3)
await election.activate(group)
})
@@ -868,6 +886,7 @@ contract('Election', (accounts: string[]) => {
await mockValidators.setNumRegisteredValidators(1)
await mockLockedGold.incrementNonvotingAccountBalance(voter, voteValue)
await election.vote(group, voteValue, NULL_ADDRESS, NULL_ADDRESS)
+ await mineBlocks(EPOCH, web3)
await election.activate(group)
})
@@ -919,6 +938,7 @@ contract('Election', (accounts: string[]) => {
// Split voter2's vote between the two groups.
await election.vote(group, voteValue2.div(2), group2, NULL_ADDRESS, { from: voter2 })
await election.vote(group2, voteValue2.div(2), NULL_ADDRESS, group, { from: voter2 })
+ await mineBlocks(EPOCH, web3)
await election.activate(group, { from: voter2 })
await election.activate(group2, { from: voter2 })
})
diff --git a/packages/protocol/test/governance/validators.ts b/packages/protocol/test/governance/validators.ts
index 9983fe0aea4..f6ad41c7a39 100644
--- a/packages/protocol/test/governance/validators.ts
+++ b/packages/protocol/test/governance/validators.ts
@@ -54,7 +54,7 @@ const parseValidatorGroupParams = (groupParams: any) => {
const HOUR = 60 * 60
const DAY = 24 * HOUR
const MAX_UINT256 = new BigNumber(2).pow(256).minus(1)
-// Hard coded in ValidatorsTest.sol
+// Hard coded in ganache.
const EPOCH = 100
// TODO(asa): Test epoch payment distribution
@@ -71,7 +71,7 @@ contract('Validators', (accounts: string[]) => {
validator: new BigNumber(60 * DAY),
}
const validatorScoreParameters = {
- exponent: new BigNumber(1),
+ exponent: new BigNumber(5),
adjustmentSpeed: toFixed(0.25),
}
const validatorEpochPayment = new BigNumber(10000000000000)
@@ -1237,14 +1237,16 @@ contract('Validators', (accounts: string[]) => {
})
describe('when 0 <= uptime <= 1.0', () => {
- const uptime = 0.99
+ const uptime = new BigNumber(0.99)
+ // @ts-ignore
+ const epochScore = uptime.pow(validatorScoreParameters.exponent)
const adjustmentSpeed = fromFixed(validatorScoreParameters.adjustmentSpeed)
beforeEach(async () => {
await validators.updateValidatorScore(validator, toFixed(uptime))
})
it('should update the validator score', async () => {
- const expectedScore = adjustmentSpeed.times(uptime)
+ const expectedScore = adjustmentSpeed.times(epochScore)
const parsedValidator = parseValidatorParams(await validators.getValidator(validator))
assertEqualBN(parsedValidator.score, toFixed(expectedScore))
})
@@ -1255,7 +1257,7 @@ contract('Validators', (accounts: string[]) => {
})
it('should update the validator score', async () => {
- let expectedScore = adjustmentSpeed.times(uptime)
+ let expectedScore = adjustmentSpeed.times(epochScore)
expectedScore = new BigNumber(1)
.minus(adjustmentSpeed)
.times(expectedScore)
@@ -1341,6 +1343,12 @@ contract('Validators', (accounts: string[]) => {
})
})
+ describe('#getEpochSize', () => {
+ it('should always return 100', async () => {
+ assertEqualBN(await validators.getEpochSize(), 100)
+ })
+ })
+
describe('#distributeEpochPayment', () => {
const validator = accounts[0]
const group = accounts[1]
@@ -1352,25 +1360,26 @@ contract('Validators', (accounts: string[]) => {
})
describe('when the validator score is non-zero', () => {
- const uptime = 0.99
+ const uptime = new BigNumber(0.99)
const adjustmentSpeed = fromFixed(validatorScoreParameters.adjustmentSpeed)
- const expectedScore = adjustmentSpeed.times(uptime)
+ // @ts-ignore
+ const expectedScore = adjustmentSpeed.times(uptime.pow(validatorScoreParameters.exponent))
const expectedTotalPayment = expectedScore.times(validatorEpochPayment)
+ const expectedGroupPayment = expectedTotalPayment
+ .times(fromFixed(commission))
+ .dp(0, BigNumber.ROUND_FLOOR)
+ const expectedValidatorPayment = expectedTotalPayment.minus(expectedGroupPayment)
beforeEach(async () => {
await validators.updateValidatorScore(validator, toFixed(uptime))
await validators.distributeEpochPayment(validator)
})
it('should pay the validator', async () => {
- const expectedPayment = expectedTotalPayment.times(
- new BigNumber(1).minus(fromFixed(commission))
- )
- assertEqualBN(await mockStableToken.balanceOf(validator), expectedPayment)
+ assertEqualBN(await mockStableToken.balanceOf(validator), expectedValidatorPayment)
})
it('should pay the group', async () => {
- const expectedPayment = expectedTotalPayment.times(fromFixed(commission))
- assertEqualBN(await mockStableToken.balanceOf(group), expectedPayment)
+ assertEqualBN(await mockStableToken.balanceOf(group), expectedGroupPayment)
})
})
})
diff --git a/yarn.lock b/yarn.lock
index ffa4029db39..a51bc235424 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2266,9 +2266,9 @@
qqjs "^0.3.10"
tslib "^1.9.3"
-"@celo/ganache-cli@git+https://github.com/celo-org/ganache-cli.git#816a475":
+"@celo/ganache-cli@git+https://github.com/celo-org/ganache-cli.git#4cf9664":
version "6.6.0"
- resolved "git+https://github.com/celo-org/ganache-cli.git#816a475d82535c05b59c4c43b3603c39dd31cd88"
+ resolved "git+https://github.com/celo-org/ganache-cli.git#4cf9664597fff315f7318e9376d079bd6d548624"
dependencies:
ethereumjs-util "6.1.0"
source-map-support "0.5.12"
From b4914537d5e43631ddd4bfacb5a9f9cf2be391c7 Mon Sep 17 00:00:00 2001
From: Asa Oines
Date: Mon, 7 Oct 2019 19:34:43 -0700
Subject: [PATCH 034/149] Revert "Feature #909 proxy delegatecall (#1152)"
This reverts commit 78afc930022894fd870f9eb70a3103899de348fa.
---
.../protocol/contracts/common/MultiSig.sol | 5 ----
packages/protocol/contracts/common/Proxy.sol | 12 +---------
.../common/libraries/AddressesHelper.sol | 21 -----------------
.../contracts/governance/Proposals.sol | 5 ----
packages/protocol/test/common/proxy.ts | 7 ------
.../protocol/test/governance/governance.ts | 23 -------------------
6 files changed, 1 insertion(+), 72 deletions(-)
delete mode 100644 packages/protocol/contracts/common/libraries/AddressesHelper.sol
diff --git a/packages/protocol/contracts/common/MultiSig.sol b/packages/protocol/contracts/common/MultiSig.sol
index ee6944ffd1a..c979e61e7e2 100644
--- a/packages/protocol/contracts/common/MultiSig.sol
+++ b/packages/protocol/contracts/common/MultiSig.sol
@@ -2,7 +2,6 @@ pragma solidity ^0.5.3;
/* solhint-disable no-inline-assembly, avoid-low-level-calls, func-name-mixedcase, func-order */
import "./Initializable.sol";
-import "./libraries/AddressesHelper.sol";
/// @title Multisignature wallet - Allows multiple parties to agree on transactions before
@@ -262,10 +261,6 @@ contract MultiSig is Initializable {
returns (bool)
{
bool result;
-
- if (dataLength > 0)
- require(AddressesHelper.isContract(destination), "Invalid contract address");
-
/* solhint-disable max-line-length */
assembly {
let x := mload(0x40) // "Allocate" memory for output (0x40 is where "free memory" pointer is stored by convention)
diff --git a/packages/protocol/contracts/common/Proxy.sol b/packages/protocol/contracts/common/Proxy.sol
index 13f085c66da..f5027a22f6e 100644
--- a/packages/protocol/contracts/common/Proxy.sol
+++ b/packages/protocol/contracts/common/Proxy.sol
@@ -1,7 +1,6 @@
pragma solidity ^0.5.3;
/* solhint-disable no-inline-assembly, no-complex-fallback, avoid-low-level-calls */
-import "./libraries/AddressesHelper.sol";
/**
* @title A Proxy utilizing the Unstructured Storage pattern.
@@ -33,15 +32,8 @@ contract Proxy {
function () external payable {
bytes32 implementationPosition = IMPLEMENTATION_POSITION;
- address implementationAddress;
-
- assembly {
- implementationAddress := sload(implementationPosition)
- }
-
- require(AddressesHelper.isContract(implementationAddress), "Invalid contract address");
-
assembly {
+ let implementationAddress := sload(implementationPosition)
let newCallDataPosition := mload(0x40)
mstore(0x40, add(newCallDataPosition, calldatasize))
@@ -122,8 +114,6 @@ contract Proxy {
function _setImplementation(address implementation) public onlyOwner {
bytes32 implementationPosition = IMPLEMENTATION_POSITION;
- require(AddressesHelper.isContract(implementation), "Invalid contract address");
-
assembly {
sstore(implementationPosition, implementation)
}
diff --git a/packages/protocol/contracts/common/libraries/AddressesHelper.sol b/packages/protocol/contracts/common/libraries/AddressesHelper.sol
deleted file mode 100644
index ee26b42d8a4..00000000000
--- a/packages/protocol/contracts/common/libraries/AddressesHelper.sol
+++ /dev/null
@@ -1,21 +0,0 @@
-pragma solidity ^0.5.3;
-
-/**
- * @title Library with support functions to deal with addresses
- */
-library AddressesHelper {
-
- /**
- * @dev isContract detect whether the address is
- * a contract address or externally owned account (EOA)
- * WARNING: Calling this function from a constructor will return false
- * independently if the address given as parameter is a contract or EOA
- * @return true if it is a contract address
- */
- function isContract(address addr) internal view returns (bool) {
- uint256 size;
- /* solium-disable-next-line security/no-inline-assembly */
- assembly { size := extcodesize(addr) }
- return size > 0;
- }
-}
diff --git a/packages/protocol/contracts/governance/Proposals.sol b/packages/protocol/contracts/governance/Proposals.sol
index 71e1a44e738..35602df088f 100644
--- a/packages/protocol/contracts/governance/Proposals.sol
+++ b/packages/protocol/contracts/governance/Proposals.sol
@@ -4,7 +4,6 @@ import "openzeppelin-solidity/contracts/math/SafeMath.sol";
import "solidity-bytes-utils/contracts/BytesLib.sol";
import "../common/FixidityLib.sol";
-import "../common/libraries/AddressesHelper.sol";
/**
* @title A library operating on Celo Governance proposals.
@@ -325,10 +324,6 @@ library Proposals {
returns (bool)
{
bool result;
-
- if (dataLength > 0)
- require(AddressesHelper.isContract(destination), "Invalid contract address");
-
/* solhint-disable no-inline-assembly */
assembly {
/* solhint-disable max-line-length */
diff --git a/packages/protocol/test/common/proxy.ts b/packages/protocol/test/common/proxy.ts
index 9a2a810fba5..a70c3e724d2 100644
--- a/packages/protocol/test/common/proxy.ts
+++ b/packages/protocol/test/common/proxy.ts
@@ -116,13 +116,6 @@ contract('Proxy', (accounts: string[]) => {
assert.equal(events[0].event, 'ImplementationSet')
})
- it('should not allow to call a non contract address', async () =>
- assertRevert(
- proxy._setAndInitializeImplementation(accounts[1], initializeData(42), {
- from: accounts[1],
- })
- ))
-
it('should not allow a non-owner to set an implementation', async () =>
assertRevert(
proxy._setAndInitializeImplementation(hasInitializer.address, initializeData(42), {
diff --git a/packages/protocol/test/governance/governance.ts b/packages/protocol/test/governance/governance.ts
index a72f1132eed..38f5f02d48e 100644
--- a/packages/protocol/test/governance/governance.ts
+++ b/packages/protocol/test/governance/governance.ts
@@ -1625,29 +1625,6 @@ contract('Governance', (accounts: string[]) => {
await assertRevert(governance.execute(proposalId, index))
})
})
-
- describe('when the proposal cannot execute because it is not a contract address', () => {
- beforeEach(async () => {
- await governance.propose(
- [transactionSuccess1.value],
- [accounts[1]],
- transactionSuccess1.data,
- [transactionSuccess1.data.length],
- // @ts-ignore: TODO(mcortesi) fix typings for TransactionDetails
- { value: minDeposit }
- )
- await timeTravel(dequeueFrequency, web3)
- await governance.approve(proposalId, index)
- await timeTravel(approvalStageDuration, web3)
- await mockLockedGold.setWeight(account, weight)
- await governance.vote(proposalId, index, value)
- await timeTravel(referendumStageDuration, web3)
- })
-
- it('should revert', async () => {
- await assertRevert(governance.execute(proposalId, index))
- })
- })
})
describe('when executing a proposal with two transactions', () => {
From 96f7a7aac239e4be8a772586168266a05f26bb7d Mon Sep 17 00:00:00 2001
From: Asa Oines
Date: Mon, 7 Oct 2019 22:32:13 -0700
Subject: [PATCH 035/149] Governance end-to-end tests working again
---
.../celotool/src/e2e-tests/governance_tests.ts | 16 +++++++++++++++-
.../protocol/contracts/governance/Election.sol | 8 ++++----
2 files changed, 19 insertions(+), 5 deletions(-)
diff --git a/packages/celotool/src/e2e-tests/governance_tests.ts b/packages/celotool/src/e2e-tests/governance_tests.ts
index f478f344e4f..daf8b7c4151 100644
--- a/packages/celotool/src/e2e-tests/governance_tests.ts
+++ b/packages/celotool/src/e2e-tests/governance_tests.ts
@@ -76,6 +76,17 @@ describe('governance tests', () => {
return [groupAddress, decryptedKeystore.privateKey]
}
+ const activate = async (web3: any, account: string, txOptions: any = {}) => {
+ await unlockAccount(account, web3)
+ const [group] = await validators.methods.getRegisteredValidatorGroups().call()
+ const tx = election.methods.activate(group)
+ let gas = txOptions.gas
+ if (!gas) {
+ gas = await tx.estimateGas({ ...txOptions })
+ }
+ return tx.send({ from: account, ...txOptions, gas })
+ }
+
const removeMember = async (
groupWeb3: any,
group: string,
@@ -106,7 +117,7 @@ describe('governance tests', () => {
}
describe('when the validator set is changing', () => {
- const epoch = 10
+ let epoch: number
const blockNumbers: number[] = []
let allValidators: string[]
before(async function(this: any) {
@@ -126,9 +137,12 @@ describe('governance tests', () => {
await initAndStartGeth(context.hooks.gethBinaryPath, groupInstance)
allValidators = await getValidatorGroupMembers()
assert.equal(allValidators.length, 5)
+ epoch = new BigNumber(await validators.methods.getEpochSize().call()).toNumber()
+ assert.equal(epoch, 10)
// Give the node time to sync.
await sleep(15)
+ await activate(web3, allValidators[0])
const groupWeb3 = new Web3('ws://localhost:8567')
const groupKit = newKitFromWeb3(groupWeb3)
validators = await groupKit._web3Contracts.getValidators()
diff --git a/packages/protocol/contracts/governance/Election.sol b/packages/protocol/contracts/governance/Election.sol
index 36f25479943..c82c198d648 100644
--- a/packages/protocol/contracts/governance/Election.sol
+++ b/packages/protocol/contracts/governance/Election.sol
@@ -442,6 +442,10 @@ contract Election is Ownable, ReentrancyGuard, Initializable, UsingRegistry, Usi
}
function getGroupEpochRewards(address group, uint256 totalEpochRewards) external view returns (uint256) {
+ // TODO(asa): Is this right?
+ if (votes.active.total[group] == 0 || votes.total.active == 0) {
+ return 0;
+ }
return totalEpochRewards.mul(votes.active.total[group]).div(votes.total.active);
}
@@ -451,10 +455,6 @@ contract Election is Ownable, ReentrancyGuard, Initializable, UsingRegistry, Usi
}
function _distributeEpochRewards(address group, uint256 value, address lesser, address greater) internal {
- // TODO(asa): What do here?
- if (votes.active.total[group] == 0) {
- }
-
if (votes.total.eligible.contains(group)) {
uint256 newVoteTotal = votes.total.eligible.getValue(group).add(value);
votes.total.eligible.update(group, newVoteTotal, lesser, greater);
From e40eaf0f90ae313da2b1f27aa27918edefae754d Mon Sep 17 00:00:00 2001
From: Asa Oines
Date: Tue, 8 Oct 2019 16:58:24 -0700
Subject: [PATCH 036/149] Address comments
---
packages/cli/package.json | 2 +-
packages/cli/src/commands/election/vote.ts | 2 +-
.../cli/src/commands/lockedgold/authorize.ts | 3 +
packages/cli/src/commands/lockedgold/lock.ts | 12 +-
packages/cli/src/commands/lockedgold/show.ts | 20 +-
.../cli/src/commands/lockedgold/unlock.ts | 6 +-
.../cli/src/commands/lockedgold/withdraw.ts | 27 +-
.../cli/src/commands/validatorgroup/member.ts | 6 +-
.../src/commands/validatorgroup/register.ts | 9 +-
packages/cli/src/utils/lockedgold.ts | 8 +-
packages/contractkit/src/wrappers/Election.ts | 51 +--
.../contractkit/src/wrappers/LockedGold.ts | 35 +-
.../contractkit/src/wrappers/Validators.ts | 22 +-
.../contracts/common/UsingRegistry.sol | 8 +-
.../common/linkedlists/AddressLinkedList.sol | 1 +
.../common/linkedlists/LinkedList.sol | 1 +
.../common/linkedlists/SortedLinkedList.sol | 1 +
.../contracts/governance/Election.sol | 341 ++++++++++--------
.../contracts/governance/LockedGold.sol | 15 +-
.../contracts/governance/Validators.sol | 15 +-
packages/protocol/migrationsConfig.js | 2 +-
packages/protocol/test/governance/election.ts | 202 +++++------
.../protocol/test/governance/validators.ts | 7 +-
23 files changed, 448 insertions(+), 348 deletions(-)
diff --git a/packages/cli/package.json b/packages/cli/package.json
index ee2ecec4124..8a8b7ea7fd5 100644
--- a/packages/cli/package.json
+++ b/packages/cli/package.json
@@ -84,7 +84,7 @@
"description": "Configure CLI options which persist across commands"
},
"lockedgold": {
- "description": "View and manage locked celo gold"
+ "description": "View and manage locked Celo Gold"
},
"node": {
"description": "Manage your full node"
diff --git a/packages/cli/src/commands/election/vote.ts b/packages/cli/src/commands/election/vote.ts
index 9b466094a1a..ae5f7590454 100644
--- a/packages/cli/src/commands/election/vote.ts
+++ b/packages/cli/src/commands/election/vote.ts
@@ -14,7 +14,7 @@ export default class ElectionVote extends BaseCommand {
description: "Set vote for ValidatorGroup's address",
required: true,
}),
- value: flags.string({ description: 'Amount of gold used to vote for group', required: true }),
+ value: flags.string({ description: 'Amount of Gold used to vote for group', required: true }),
}
static examples = [
diff --git a/packages/cli/src/commands/lockedgold/authorize.ts b/packages/cli/src/commands/lockedgold/authorize.ts
index 50ba89edd63..6a66475ee23 100644
--- a/packages/cli/src/commands/lockedgold/authorize.ts
+++ b/packages/cli/src/commands/lockedgold/authorize.ts
@@ -43,6 +43,9 @@ export default class Authorize extends BaseCommand {
tx = await lockedGold.authorizeVoter(res.flags.from, res.flags.to)
} else if (res.flags.role == 'validator') {
tx = await lockedGold.authorizeValidator(res.flags.from, res.flags.to)
+ } else {
+ this.error(`Invalid role provided`)
+ return
}
await displaySendTx('authorizeTx', tx)
}
diff --git a/packages/cli/src/commands/lockedgold/lock.ts b/packages/cli/src/commands/lockedgold/lock.ts
index ce086359e7e..4961d382cbd 100644
--- a/packages/cli/src/commands/lockedgold/lock.ts
+++ b/packages/cli/src/commands/lockedgold/lock.ts
@@ -12,13 +12,13 @@ export default class Lock extends BaseCommand {
static flags = {
...BaseCommand.flags,
from: flags.string({ ...Flags.address, required: true }),
- goldAmount: flags.string({ ...LockedGoldArgs.goldAmountArg, required: true }),
+ value: flags.string({ ...LockedGoldArgs.valueArg, required: true }),
}
static args = []
static examples = [
- 'lock --from 0x47e172F6CfB6c7D01C1574fa3E2Be7CC73269D95 --goldAmount 1000000000000000000',
+ 'lock --from 0x47e172F6CfB6c7D01C1574fa3E2Be7CC73269D95 --value 1000000000000000000',
]
async run() {
@@ -28,13 +28,13 @@ export default class Lock extends BaseCommand {
this.kit.defaultAccount = address
const lockedGold = await this.kit.contracts.getLockedGold()
- const goldAmount = new BigNumber(res.flags.goldAmount)
+ const value = new BigNumber(res.flags.value)
- if (!goldAmount.gt(new BigNumber(0))) {
- failWith(`require(goldAmount > 0) => [${goldAmount}]`)
+ if (!value.gt(new BigNumber(0))) {
+ failWith(`Provided value must be greater than zero => [${value}]`)
}
const tx = lockedGold.lock()
- await displaySendTx('lock', tx, { value: goldAmount.toString() })
+ await displaySendTx('lock', tx, { value: value.toString() })
}
}
diff --git a/packages/cli/src/commands/lockedgold/show.ts b/packages/cli/src/commands/lockedgold/show.ts
index a249a0f0172..712d5760f52 100644
--- a/packages/cli/src/commands/lockedgold/show.ts
+++ b/packages/cli/src/commands/lockedgold/show.ts
@@ -4,7 +4,7 @@ import { Args } from '../../utils/command'
import { eqAddress } from '@celo/utils/lib/address'
export default class Show extends BaseCommand {
- static description = 'Show locked gold information for a given account'
+ static description = 'Show Locked Gold information for a given account'
static flags = {
...BaseCommand.flags,
@@ -19,22 +19,6 @@ export default class Show extends BaseCommand {
const { args } = this.parse(Show)
const lockedGold = await this.kit.contracts.getLockedGold()
- const nonvoting = (await lockedGold.getAccountNonvotingLockedGold(args.account)).toString()
- const total = (await lockedGold.getAccountTotalLockedGold(args.account)).toString()
- const voter = await lockedGold.getVoterFromAccount(args.account)
- const validator = await lockedGold.getValidatorFromAccount(args.account)
- const pendingWithdrawals = await lockedGold.getPendingWithdrawals(args.account)
- const info = {
- lockedGold: {
- total,
- nonvoting,
- },
- authorizations: {
- voter: eqAddress(voter, args.account) ? 'None' : voter,
- validator: eqAddress(validator, args.account) ? 'None' : validator,
- },
- pendingWithdrawals: pendingWithdrawals.length > 0 ? pendingWithdrawals : '[]',
- }
- printValueMapRecursive(info)
+ printValueMapRecursive(await lockedGold.geAccountSummary(args.account))
}
}
diff --git a/packages/cli/src/commands/lockedgold/unlock.ts b/packages/cli/src/commands/lockedgold/unlock.ts
index 23d535fdf20..1b04e9980c1 100644
--- a/packages/cli/src/commands/lockedgold/unlock.ts
+++ b/packages/cli/src/commands/lockedgold/unlock.ts
@@ -10,17 +10,17 @@ export default class Unlock extends BaseCommand {
static flags = {
...BaseCommand.flags,
from: Flags.address({ required: true }),
- goldAmount: flags.string({ ...LockedGoldArgs.goldAmountArg, required: true }),
+ value: flags.string({ ...LockedGoldArgs.valueArg, required: true }),
}
static args = []
- static examples = ['unlock --goldAmount 500000000']
+ static examples = ['unlock --from 0x47e172F6CfB6c7D01C1574fa3E2Be7CC73269D95 --value 500000000']
async run() {
const res = this.parse(Unlock)
this.kit.defaultAccount = res.flags.from
const lockedgold = await this.kit.contracts.getLockedGold()
- await displaySendTx('unlock', lockedgold.unlock(res.flags.goldAmount))
+ await displaySendTx('unlock', lockedgold.unlock(res.flags.value))
}
}
diff --git a/packages/cli/src/commands/lockedgold/withdraw.ts b/packages/cli/src/commands/lockedgold/withdraw.ts
index cbeb0288890..067f24febbd 100644
--- a/packages/cli/src/commands/lockedgold/withdraw.ts
+++ b/packages/cli/src/commands/lockedgold/withdraw.ts
@@ -17,14 +17,29 @@ export default class Withdraw extends BaseCommand {
const { flags } = this.parse(Withdraw)
this.kit.defaultAccount = flags.from
const lockedgold = await this.kit.contracts.getLockedGold()
- const pendingWithdrawals = await lockedgold.getPendingWithdrawals(flags.from)
const currentTime = Math.round(new Date().getTime() / 1000)
- let withdrawals = 0
- for (let i = 0; i < pendingWithdrawals.length; i++) {
- if (pendingWithdrawals[i].time.isLessThan(currentTime)) {
- await displaySendTx('withdraw', lockedgold.withdraw(i - withdrawals))
- withdrawals += 1
+
+ while (true) {
+ let madeWithdrawal = false
+ const pendingWithdrawals = await lockedgold.getPendingWithdrawals(flags.from)
+ for (const pendingWithdrawal of pendingWithdrawals) {
+ if (pendingWithdrawal.time.isLessThan(currentTime)) {
+ console.log(
+ `Found available pending withdrawal of value ${pendingWithdrawal.value.toString()}, withdrawing`
+ )
+ await displaySendTx('withdraw', lockedgold.withdraw(i))
+ madeWithdrawal = true
+ break
+ }
}
}
+ const pendingWithdrawals = await lockedgold.getPendingWithdrawals(flags.from)
+ for (const pendingWithdrawal of pendingWithdrawals) {
+ console.log(
+ `Pending withdrawal of value ${pendingWithdrawal.value.toString()} available for withdrawal in ${pendingWithdrawal.time
+ .minus(currentTime)
+ .toString()} seconds.`
+ )
+ }
}
}
diff --git a/packages/cli/src/commands/validatorgroup/member.ts b/packages/cli/src/commands/validatorgroup/member.ts
index f09291f42d5..86d126c790d 100644
--- a/packages/cli/src/commands/validatorgroup/member.ts
+++ b/packages/cli/src/commands/validatorgroup/member.ts
@@ -4,7 +4,7 @@ import { BaseCommand } from '../../base'
import { displaySendTx } from '../../utils/cli'
import { Args, Flags } from '../../utils/command'
-export default class ValidatorGroupRegister extends BaseCommand {
+export default class ValidatorGroupMembers extends BaseCommand {
static description = 'Add or remove members from a Validator Group'
static flags = {
@@ -28,7 +28,7 @@ export default class ValidatorGroupRegister extends BaseCommand {
]
async run() {
- const res = this.parse(ValidatorGroupRegister)
+ const res = this.parse(ValidatorGroupMembers)
if (!(res.flags.accept || res.flags.remove)) {
this.error(`Specify action: --accept or --remove`)
@@ -41,7 +41,7 @@ export default class ValidatorGroupRegister extends BaseCommand {
if (res.flags.accept) {
await displaySendTx('addMember', validators.addMember((res.args as any).validatorAddress))
- if ((await validators.getGroupNumMembers(res.flags.from)) === '1') {
+ if ((await validators.getGroupNumMembers(res.flags.from)).isEqualTo(1)) {
const tx = await election.markGroupEligible(res.flags.from)
await displaySendTx('markGroupEligible', tx)
}
diff --git a/packages/cli/src/commands/validatorgroup/register.ts b/packages/cli/src/commands/validatorgroup/register.ts
index d3645c7fb6a..a805fa40d3d 100644
--- a/packages/cli/src/commands/validatorgroup/register.ts
+++ b/packages/cli/src/commands/validatorgroup/register.ts
@@ -3,7 +3,6 @@ import { flags } from '@oclif/command'
import { BaseCommand } from '../../base'
import { displaySendTx } from '../../utils/cli'
import { Flags } from '../../utils/command'
-import { toFixed } from '@celo/utils/lib/fixidity'
export default class ValidatorGroupRegister extends BaseCommand {
static description = 'Register a new Validator Group'
@@ -25,11 +24,13 @@ export default class ValidatorGroupRegister extends BaseCommand {
this.kit.defaultAccount = res.flags.from
const validators = await this.kit.contracts.getValidators()
- const commission = toFixed(new BigNumber(res.flags.commission)).toFixed()
-
await displaySendTx(
'registerValidatorGroup',
- validators.registerValidatorGroup(res.flags.name, res.flags.url, commission)
+ validators.registerValidatorGroup(
+ res.flags.name,
+ res.flags.url,
+ new BigNumber(res.flags.commission)
+ )
)
}
}
diff --git a/packages/cli/src/utils/lockedgold.ts b/packages/cli/src/utils/lockedgold.ts
index 64120283e25..9e9aee7645e 100644
--- a/packages/cli/src/utils/lockedgold.ts
+++ b/packages/cli/src/utils/lockedgold.ts
@@ -1,10 +1,10 @@
export const LockedGoldArgs = {
pendingWithdrawalIndexArg: {
- name: 'pendingWithdrawalINdex',
+ name: 'pendingWithdrawalIndex',
description: 'index of pending withdrawal whose unlocking period has passed',
},
- goldAmountArg: {
- name: 'goldAmount',
- description: 'unit amount of gold token (cGLD)',
+ valueArg: {
+ name: 'value',
+ description: 'unit amount of Celo Gold (cGLD)',
},
}
diff --git a/packages/contractkit/src/wrappers/Election.ts b/packages/contractkit/src/wrappers/Election.ts
index f8a9a875dbf..240edae9884 100644
--- a/packages/contractkit/src/wrappers/Election.ts
+++ b/packages/contractkit/src/wrappers/Election.ts
@@ -15,16 +15,14 @@ import {
export interface Validator {
address: Address
- id: string
name: string
url: string
publicKey: string
- affiliation: string | null
+ affiliation: Address | null
}
export interface ValidatorGroup {
address: Address
- id: string
name: string
url: string
members: Address[]
@@ -118,17 +116,19 @@ export class ElectionWrapper extends BaseWrapper {
async getValidatorGroupsVotes(): Promise {
const validators = await this.kit.contracts.getValidators()
- const vgAddresses = (await validators.getRegisteredValidatorGroups()).map((g) => g.address)
- const vgVotes = await Promise.all(
- vgAddresses.map((g) => this.contract.methods.getGroupTotalVotes(g).call())
+ const validatorGroupAddresses = (await validators.getRegisteredValidatorGroups()).map(
+ (g) => g.address
)
- const vgEligible = await Promise.all(
- vgAddresses.map((g) => this.contract.methods.getGroupEligibility(g).call())
+ const validatorGroupVotes = await Promise.all(
+ validatorGroupAddresses.map((g) => this.contract.methods.getGroupTotalVotes(g).call())
)
- return vgAddresses.map((a, i) => ({
+ const validatorGroupEligible = await Promise.all(
+ validatorGroupAddresses.map((g) => this.contract.methods.getGroupEligibility(g).call())
+ )
+ return validatorGroupAddresses.map((a, i) => ({
address: a,
- votes: toBigNumber(vgVotes[i]),
- eligible: vgEligible[i],
+ votes: toBigNumber(validatorGroupVotes[i]),
+ eligible: validatorGroupEligible[i],
}))
}
@@ -137,29 +137,6 @@ export class ElectionWrapper extends BaseWrapper {
return zip((a, b) => ({ address: a, votes: new BigNumber(b), eligible: true }), res[0], res[1])
}
- /*
- async revokeVote(): Promise> {
- if (this.kit.defaultAccount == null) {
- throw new Error(`missing from at new ValdidatorUtils()`)
- }
-
- const lockedGold = await this.kit.contracts.getLockedGold()
- const votingDetails = await lockedGold.getVotingDetails(this.kit.defaultAccount)
- const votedGroup = await this.getVoteFrom(votingDetails.accountAddress)
-
- if (votedGroup == null) {
- throw new Error(`Not current vote for ${this.kit.defaultAccount}`)
- }
-
- const { lesser, greater } = await this.findLesserAndGreaterAfterVote(
- votedGroup,
- votingDetails.weight.negated()
- )
-
- return wrapSend(this.kit, this.contract.methods.revokeVote(lesser, greater))
- }
- */
-
async markGroupEligible(validatorGroup: Address): Promise> {
if (this.kit.defaultAccount == null) {
throw new Error(`missing from at new ValdidatorUtils()`)
@@ -187,13 +164,13 @@ export class ElectionWrapper extends BaseWrapper {
)
}
- private async findLesserAndGreaterAfterVote(
+ async findLesserAndGreaterAfterVote(
votedGroup: Address,
voteWeight: BigNumber
): Promise<{ lesser: Address; greater: Address }> {
const currentVotes = await this.getEligibleValidatorGroupsVotes()
- const selectedGroup = currentVotes.find((cv) => eqAddress(cv.address, votedGroup))
+ const selectedGroup = currentVotes.find((votes) => eqAddress(votes.address, votedGroup))
// modify the list
if (selectedGroup) {
@@ -210,7 +187,7 @@ export class ElectionWrapper extends BaseWrapper {
currentVotes.sort((a, b) => a.votes.comparedTo(b.votes))
// find new index
- const newIdx = currentVotes.findIndex((cv) => eqAddress(cv.address, votedGroup))
+ const newIdx = currentVotes.findIndex((votes) => eqAddress(votes.address, votedGroup))
return {
lesser: newIdx === 0 ? NULL_ADDRESS : currentVotes[newIdx - 1].address,
diff --git a/packages/contractkit/src/wrappers/LockedGold.ts b/packages/contractkit/src/wrappers/LockedGold.ts
index c8f7dfbc29d..740efa111d1 100644
--- a/packages/contractkit/src/wrappers/LockedGold.ts
+++ b/packages/contractkit/src/wrappers/LockedGold.ts
@@ -1,3 +1,4 @@
+import { eqAddress } from '@celo/utils/lib/address'
import { zip } from '@celo/utils/lib/collections'
import BigNumber from 'bignumber.js'
import Web3 from 'web3'
@@ -19,6 +20,18 @@ export interface VotingDetails {
weight: BigNumber
}
+interface AccountSummary {
+ lockedGold: {
+ total: BigNumber
+ nonvoting: BigNumber
+ }
+ authorizations: {
+ voter: string
+ validator: string
+ }
+ pendingWithdrawals: PendingWithdrawal[]
+}
+
interface PendingWithdrawal {
time: BigNumber
value: BigNumber
@@ -64,6 +77,25 @@ export class LockedGoldWrapper extends BaseWrapper {
}
}
+ async getAccountSummary(account: string): Promise {
+ const nonvoting = await this.getAccountNonvotingLockedGold(args.account)
+ const total = await this.getAccountTotalLockedGold(args.account)
+ const voter = await this.getVoterFromAccount(args.account)
+ const validator = await this.getValidatorFromAccount(args.account)
+ const pendingWithdrawals = await this.getPendingWithdrawals(args.account)
+ return (info = {
+ lockedGold: {
+ total,
+ nonvoting,
+ },
+ authorizations: {
+ voter: eqAddress(voter, account) ? 'None' : voter,
+ validator: eqAddress(validator, account) ? 'None' : validator,
+ },
+ pendingWithdrawals,
+ })
+ }
+
/**
* Authorize voting on behalf of this account to another address.
* @param account Address of the active account.
@@ -72,6 +104,7 @@ export class LockedGoldWrapper extends BaseWrapper {
*/
async authorizeVoter(account: Address, voter: Address): Promise> {
const sig = await this.getParsedSignatureOfAddress(account, voter)
+ // TODO(asa): Pass default tx "from" argument.
return wrapSend(this.kit, this.contract.methods.authorizeVoter(voter, sig.v, sig.r, sig.s))
}
@@ -95,8 +128,8 @@ export class LockedGoldWrapper extends BaseWrapper {
async getPendingWithdrawals(account: string) {
const withdrawals = await this.contract.methods.getPendingWithdrawals(account).call()
return zip(
- // tslint:disable-next-line: no-object-literal-type-assertion
(time, value) =>
+ // tslint:disable-next-line: no-object-literal-type-assertion
({ time: toBigNumber(time), value: toBigNumber(value) } as PendingWithdrawal),
withdrawals[1],
withdrawals[0]
diff --git a/packages/contractkit/src/wrappers/Validators.ts b/packages/contractkit/src/wrappers/Validators.ts
index 4103279bc8f..51c0d410caf 100644
--- a/packages/contractkit/src/wrappers/Validators.ts
+++ b/packages/contractkit/src/wrappers/Validators.ts
@@ -2,7 +2,7 @@ import BigNumber from 'bignumber.js'
import { Address } from '../base'
import { Validators } from '../generated/types/Validators'
import { BaseWrapper, proxyCall, proxySend, toBigNumber } from './BaseWrapper'
-import { fromFixed } from '@celo/utils/lib/fixidity'
+import { fromFixed, toFixed } from '@celo/utils/lib/fixidity'
export interface Validator {
address: Address
@@ -46,6 +46,20 @@ export class ValidatorsWrapper extends BaseWrapper {
removeMember = proxySend(this.kit, this.contract.methods.removeMember)
registerValidator = proxySend(this.kit, this.contract.methods.registerValidator)
registerValidatorGroup = proxySend(this.kit, this.contract.methods.registerValidatorGroup)
+
+ async registerValidatorGroup(
+ name: string,
+ url: string,
+ commission: BigNumber
+ ): Promise> {
+ if (this.kit.defaultAccount == null) {
+ throw new Error(`missing from at new ValdidatorUtils()`)
+ }
+ return wrapSend(
+ this.kit,
+ this.contract.methods.registerValidatorGroup(name, url, toFixed(commission))
+ )
+ }
/**
* Returns the current registration requirements.
* @returns Group and validator registration requirements.
@@ -88,8 +102,10 @@ export class ValidatorsWrapper extends BaseWrapper {
return Promise.all(vgAddresses.map((addr) => this.getValidator(addr)))
}
- getGroupNumMembers: (group: Address) => Promise = proxyCall(
- this.contract.methods.getGroupNumMembers
+ getGroupNumMembers: (group: Address) => Promise = proxyCall(
+ this.contract.methods.getGroupNumMembers,
+ undefined,
+ toBigNumber
)
async getValidator(address: Address): Promise {
diff --git a/packages/protocol/contracts/common/UsingRegistry.sol b/packages/protocol/contracts/common/UsingRegistry.sol
index aa742dedcf5..a0dbf5ce45b 100644
--- a/packages/protocol/contracts/common/UsingRegistry.sol
+++ b/packages/protocol/contracts/common/UsingRegistry.sol
@@ -54,19 +54,19 @@ contract UsingRegistry is Ownable {
return IElection(registry.getAddressForOrDie(ELECTION_REGISTRY_ID));
}
- function getGoldToken() internal view returns(IERC20Token) {
+ function getGoldToken() internal view returns (IERC20Token) {
return IERC20Token(registry.getAddressForOrDie(GOLD_TOKEN_REGISTRY_ID));
}
- function getLockedGold() internal view returns(ILockedGold) {
+ function getLockedGold() internal view returns (ILockedGold) {
return ILockedGold(registry.getAddressForOrDie(LOCKED_GOLD_REGISTRY_ID));
}
- function getRandom() internal view returns(IRandom) {
+ function getRandom() internal view returns (IRandom) {
return IRandom(registry.getAddressForOrDie(RANDOM_REGISTRY_ID));
}
- function getValidators() internal view returns(IValidators) {
+ function getValidators() internal view returns (IValidators) {
return IValidators(registry.getAddressForOrDie(VALIDATORS_REGISTRY_ID));
}
}
diff --git a/packages/protocol/contracts/common/linkedlists/AddressLinkedList.sol b/packages/protocol/contracts/common/linkedlists/AddressLinkedList.sol
index aea287c5114..c0ee2fa07f7 100644
--- a/packages/protocol/contracts/common/linkedlists/AddressLinkedList.sol
+++ b/packages/protocol/contracts/common/linkedlists/AddressLinkedList.sol
@@ -82,6 +82,7 @@ library AddressLinkedList {
* @notice Returns the N greatest elements of the list.
* @param n The number of elements to return.
* @return The keys of the greatest elements.
+ * @dev Reverts if n is greater than the number of elements in the list.
*/
function headN(LinkedList.List storage list, uint256 n) public view returns (address[] memory) {
bytes32[] memory byteKeys = list.headN(n);
diff --git a/packages/protocol/contracts/common/linkedlists/LinkedList.sol b/packages/protocol/contracts/common/linkedlists/LinkedList.sol
index e1e514668dc..e3e80bdfc1f 100644
--- a/packages/protocol/contracts/common/linkedlists/LinkedList.sol
+++ b/packages/protocol/contracts/common/linkedlists/LinkedList.sol
@@ -153,6 +153,7 @@ library LinkedList {
* @notice Returns the keys of the N elements at the head of the list.
* @param n The number of elements to return.
* @return The keys of the N elements at the head of the list.
+ * @dev Reverts if n is greater than the number of elements in the list.
*/
function headN(List storage list, uint256 n) public view returns (bytes32[] memory) {
require(n <= list.numElements);
diff --git a/packages/protocol/contracts/common/linkedlists/SortedLinkedList.sol b/packages/protocol/contracts/common/linkedlists/SortedLinkedList.sol
index a8151ed5f49..9489f00802c 100644
--- a/packages/protocol/contracts/common/linkedlists/SortedLinkedList.sol
+++ b/packages/protocol/contracts/common/linkedlists/SortedLinkedList.sol
@@ -148,6 +148,7 @@ library SortedLinkedList {
* @notice Returns first N greatest elements of the list.
* @param n The number of elements to return.
* @return The keys of the first n elements.
+ * @dev Reverts if n is greater than the number of elements in the list.
*/
function headN(List storage list, uint256 n) public view returns (bytes32[] memory) {
return list.list.headN(n);
diff --git a/packages/protocol/contracts/governance/Election.sol b/packages/protocol/contracts/governance/Election.sol
index d5124b5d679..7532cea72e6 100644
--- a/packages/protocol/contracts/governance/Election.sol
+++ b/packages/protocol/contracts/governance/Election.sol
@@ -18,34 +18,51 @@ contract Election is Ownable, ReentrancyGuard, Initializable, UsingRegistry {
using FixidityLib for FixidityLib.Fraction;
using SafeMath for uint256;
+ struct TimestampedVote {
+ // The value of the vote, in gold.
+ uint256 value;
+ // The latest block number at which the vote was cast.
+ uint256 blockNumber;
+ }
+
+ struct GroupPendingVotes {
+ // The total number of pending votes that have been cast for this group.
+ uint256 total;
+ // Pending votes cast per voter.
+ mapping(address => TimestampedVote) byAccount;
+ }
+
// Pending votes are those for which no following elections have been held.
// These votes have yet to contribute to the election of validators and thus do not accrue
// rewards.
struct PendingVotes {
- // Maps groups to total pending voting balance.
- mapping(address => uint256) total;
- // Maps groups to accounts to pending voting balance.
- mapping(address => mapping(address => uint256)) balances;
- // Maps groups to accounts to timestamp of the account's most recent vote for the group.
- mapping(address => mapping(address => uint256)) timestamps;
+ // The total number of pending votes cast across all groups.
+ uint256 total;
+ mapping(address => GroupPendingVotes) forGroup;
+ }
+
+ struct GroupActiveVotes {
+ // The total number of active votes that have been cast for this group.
+ uint256 total;
+ // The total number of active votes by a voter is equal to the number of active vote units for
+ // that voter times the total number of active votes divided by the total number of active
+ // vote units.
+ uint256 totalUnits;
+ mapping(address => uint256) unitsByAccount;
}
// Active votes are those for which at least one following election has been held.
// These votes have contributed to the election of validators and thus accrue rewards.
struct ActiveVotes {
- // Maps groups to total active voting balance.
- mapping(address => uint256) total;
- // Maps groups to accounts to the numerator of the account's fraction of the group's
- // total active votes.
- mapping(address => mapping(address => uint256)) numerators;
- // Maps groups to the denominator of all accounts' fraction of the group's total active votes.
- mapping(address => uint256) denominators;
+ // The total number of active votes cast across all groups.
+ uint256 total;
+ mapping(address => GroupActiveVotes) forGroup;
}
struct TotalVotes {
- // The total number of votes cast.
- uint256 total;
- // A list of eligible ValidatorGroups sorted by total votes.
+ // A list of eligible ValidatorGroups sorted by total (pending+active) votes.
+ // Note that this list will omit ineligible ValidatorGroups, including those that may have > 0
+ // total votes.
SortedLinkedList.List eligible;
}
@@ -57,18 +74,24 @@ contract Election is Ownable, ReentrancyGuard, Initializable, UsingRegistry {
mapping(address => address[]) groupsVotedFor;
}
+ struct ElectableValidators {
+ uint256 min;
+ uint256 max;
+ }
+
Votes private votes;
- uint256 public minElectableValidators;
- uint256 public maxElectableValidators;
+ // Governs the minimum and maximum number of validators that can be elected.
+ ElectableValidators public electableValidators;
+ // Governs how many validator groups a single account can vote for.
uint256 public maxNumGroupsVotedFor;
+ // Groups must receive at least this fraction of the total votes in order to be considered in
+ // elections.
+ // TODO(asa): Implement this constraint.
FixidityLib.Fraction public electabilityThreshold;
- event MinElectableValidatorsSet(
- uint256 minElectableValidators
- );
-
- event MaxElectableValidatorsSet(
- uint256 maxElectableValidators
+ event ElectableValidatorsSet(
+ uint256 min,
+ uint256 max
);
event MaxNumGroupsVotedForSet(
@@ -108,7 +131,7 @@ contract Election is Ownable, ReentrancyGuard, Initializable, UsingRegistry {
/**
* @notice Initializes critical variables.
* @param registryAddress The address of the registry contract.
- * @param _minElectableValidators The minimum number of validators that can be elected.
+ * @param minElectableValidators The minimum number of validators that can be elected.
* @param _maxNumGroupsVotedFor The maximum number of groups that an acconut can vote for at once.
* @param _electabilityThreshold The minimum ratio of votes a group needs before its members can
* be elected.
@@ -116,63 +139,46 @@ contract Election is Ownable, ReentrancyGuard, Initializable, UsingRegistry {
*/
function initialize(
address registryAddress,
- uint256 _minElectableValidators,
- uint256 _maxElectableValidators,
+ uint256 minElectableValidators,
+ uint256 maxElectableValidators,
uint256 _maxNumGroupsVotedFor,
uint256 _electabilityThreshold
)
external
initializer
{
- require(_minElectableValidators > 0 && _maxElectableValidators >= _minElectableValidators);
_transferOwnership(msg.sender);
setRegistry(registryAddress);
- minElectableValidators = _minElectableValidators;
- maxElectableValidators = _maxElectableValidators;
- maxNumGroupsVotedFor = _maxNumGroupsVotedFor;
- electabilityThreshold = FixidityLib.wrap(_electabilityThreshold);
+ _setElectableValidators(minElectableValidators, maxElectableValidators);
+ _setMaxNumGroupsVotedFor(_maxNumGroupsVotedFor);
+ _setElectabilityThreshold(_electabilityThreshold);
}
/**
- * @notice Updates the minimum number of validators that can be elected.
- * @param _minElectableValidators The minimum number of validators that can be elected.
+ * @notice Updates the minimum and maximum number of validators that can be elected.
+ * @param min The minimum number of validators that can be elected.
+ * @param max The maximum number of validators that can be elected.
* @return True upon success.
*/
- function setMinElectableValidators(
- uint256 _minElectableValidators
- )
- external
- onlyOwner
- returns (bool)
- {
- require(
- _minElectableValidators > 0 &&
- _minElectableValidators != minElectableValidators &&
- _minElectableValidators <= maxElectableValidators
- );
- minElectableValidators = _minElectableValidators;
- emit MinElectableValidatorsSet(_minElectableValidators);
- return true;
+ function setElectableValidators(uint256 min, uint256 max) external onlyOwner returns (bool) {
+ return _setElectableValidators(min, max);
+ }
+
+ function getElectableValidators() external view returns (uint256, uint256) {
+ return (electableValidators.min, electableValidators.max);
}
/**
- * @notice Updates the maximum number of validators that can be elected.
- * @param _maxElectableValidators The maximum number of validators that can be elected.
+ * @notice Updates the minimum and maximum number of validators that can be elected.
+ * @param min The minimum number of validators that can be elected.
+ * @param max The maximum number of validators that can be elected.
* @return True upon success.
*/
- function setMaxElectableValidators(
- uint256 _maxElectableValidators
- )
- external
- onlyOwner
- returns (bool)
- {
- require(
- _maxElectableValidators != maxElectableValidators &&
- _maxElectableValidators >= minElectableValidators
- );
- maxElectableValidators = _maxElectableValidators;
- emit MaxElectableValidatorsSet(_maxElectableValidators);
+ function _setElectableValidators(uint256 min, uint256 max) private returns (bool) {
+ require(0 < min && min <= max);
+ require(min != electableValidators.min || max != electableValidators.max);
+ electableValidators = ElectableValidators(min, max);
+ emit ElectableValidatorsSet(min, max);
return true;
}
@@ -188,6 +194,15 @@ contract Election is Ownable, ReentrancyGuard, Initializable, UsingRegistry {
onlyOwner
returns (bool)
{
+ return _setMaxNumGroupsVotedFor(_maxNumGroupsVotedFor);
+ }
+
+ /**
+ * @notice Updates the maximum number of groups an account can be voting for at once.
+ * @param _maxNumGroupsVotedFor The maximum number of groups an account can vote for.
+ * @return True upon success.
+ */
+ function _setMaxNumGroupsVotedFor(uint256 _maxNumGroupsVotedFor) private returns (bool) {
require(_maxNumGroupsVotedFor != maxNumGroupsVotedFor);
maxNumGroupsVotedFor = _maxNumGroupsVotedFor;
emit MaxNumGroupsVotedForSet(_maxNumGroupsVotedFor);
@@ -199,13 +214,16 @@ contract Election is Ownable, ReentrancyGuard, Initializable, UsingRegistry {
* @param threshold Electability threshold as unwrapped Fraction.
* @return True upon success.
*/
- function setElectabilityThreshold(
- uint256 threshold
- )
- public
- onlyOwner
- returns (bool)
- {
+ function setElectabilityThreshold(uint256 threshold) public onlyOwner returns (bool) {
+ return _setElectabilityThreshold(threshold);
+ }
+
+ /**
+ * @notice Sets the electability threshold.
+ * @param threshold Electability threshold as unwrapped Fraction.
+ * @return True upon success.
+ */
+ function _setElectabilityThreshold(uint256 threshold) private returns (bool) {
electabilityThreshold = FixidityLib.wrap(threshold);
require(
electabilityThreshold.lt(FixidityLib.fixed1()),
@@ -245,13 +263,17 @@ contract Election is Ownable, ReentrancyGuard, Initializable, UsingRegistry {
returns (bool)
{
require(votes.total.eligible.contains(group));
- require(0 < value && value <= getNumVotesReceivable(group));
+ require(0 < value);
+ require(canReceiveVotes(group, value));
address account = getLockedGold().getAccountFromVoter(msg.sender);
+
+ // Add group to the groups voted for by the account.
address[] storage groups = votes.groupsVotedFor[account];
require(groups.length < maxNumGroupsVotedFor);
for (uint256 i = 0; i < groups.length; i = i.add(1)) {
require(groups[i] != group);
}
+
groups.push(group);
incrementPendingVotes(group, account, value);
incrementTotalVotes(group, value, lesser, greater);
@@ -264,11 +286,13 @@ contract Election is Ownable, ReentrancyGuard, Initializable, UsingRegistry {
* @notice Converts `account`'s pending votes for `group` to active votes.
* @param group The validator group to vote for.
* @return True upon success.
+ * @dev Pending votes cannot be activated until an election has been held.
*/
+ // TODO(asa): Prevent users from activating pending votes until an election has been held.
function activate(address group) external nonReentrant returns (bool) {
address account = getLockedGold().getAccountFromVoter(msg.sender);
PendingVotes storage pending = votes.pending;
- uint256 value = pending.balances[group][account];
+ uint256 value = pending.forGroup[group].byAccount[account].value;
require(value > 0);
decrementPendingVotes(group, account, value);
incrementActiveVotes(group, account, value);
@@ -300,11 +324,11 @@ contract Election is Ownable, ReentrancyGuard, Initializable, UsingRegistry {
{
require(group != address(0));
address account = getLockedGold().getAccountFromVoter(msg.sender);
- require(0 < value && value <= getAccountPendingVotesForGroup(group, account));
+ require(0 < value && value <= getPendingVotesForGroupByAccount(group, account));
decrementPendingVotes(group, account, value);
decrementTotalVotes(group, value, lesser, greater);
getLockedGold().incrementNonvotingAccountBalance(account, value);
- if (getAccountTotalVotesForGroup(group, account) == 0) {
+ if (getTotalVotesForGroupByAccount(group, account) == 0) {
deleteElement(votes.groupsVotedFor[account], group, index);
}
emit ValidatorGroupVoteRevoked(account, group, value);
@@ -334,44 +358,36 @@ contract Election is Ownable, ReentrancyGuard, Initializable, UsingRegistry {
nonReentrant
returns (bool)
{
+ // TODO(asa): Dedup with revokePending.
require(group != address(0));
address account = getLockedGold().getAccountFromVoter(msg.sender);
- require(0 < value && value <= getAccountActiveVotesForGroup(group, account));
+ require(0 < value && value <= getActiveVotesForGroupByAccount(group, account));
decrementActiveVotes(group, account, value);
decrementTotalVotes(group, value, lesser, greater);
getLockedGold().incrementNonvotingAccountBalance(account, value);
- if (getAccountTotalVotesForGroup(group, account) == 0) {
+ if (getTotalVotesForGroupByAccount(group, account) == 0) {
deleteElement(votes.groupsVotedFor[account], group, index);
}
emit ValidatorGroupVoteRevoked(account, group, value);
return true;
}
- function getAccountTotalVotes(address account) external view returns (uint256) {
+ function getTotalVotesByAccount(address account) external view returns (uint256) {
uint256 total = 0;
address[] memory groups = votes.groupsVotedFor[account];
for (uint256 i = 0; i < groups.length; i = i.add(1)) {
- total = total.add(getAccountTotalVotesForGroup(groups[i], account));
+ total = total.add(getTotalVotesForGroupByAccount(groups[i], account));
}
return total;
}
- /**
- * @notice Returns the groups voted for by a particular account.
- * @param account The address of the account.
- * @return The groups voted for by a particular account.
- */
- function getAccountGroupsVotedFor(address account) external view returns (address[] memory) {
- return votes.groupsVotedFor[account];
- }
-
/**
* @notice Returns the pending votes for `group` made by `account`.
* @param group The address of the validator group.
* @param account The address of the voting account.
* @return The pending votes for `group` made by `account`.
*/
- function getAccountPendingVotesForGroup(
+ function getPendingVotesForGroupByAccount(
address group,
address account
)
@@ -379,7 +395,7 @@ contract Election is Ownable, ReentrancyGuard, Initializable, UsingRegistry {
view
returns (uint256)
{
- return votes.pending.balances[group][account];
+ return votes.pending.forGroup[group].byAccount[account].value;
}
/**
@@ -388,7 +404,7 @@ contract Election is Ownable, ReentrancyGuard, Initializable, UsingRegistry {
* @param account The address of the voting account.
* @return The active votes for `group` made by `account`.
*/
- function getAccountActiveVotesForGroup(
+ function getActiveVotesForGroupByAccount(
address group,
address account
)
@@ -396,11 +412,12 @@ contract Election is Ownable, ReentrancyGuard, Initializable, UsingRegistry {
view
returns (uint256)
{
- uint256 numerator = votes.active.numerators[group][account].mul(votes.active.total[group]);
+ GroupActiveVotes storage groupActiveVotes = votes.active.forGroup[group];
+ uint256 numerator = groupActiveVotes.unitsByAccount[account].mul(groupActiveVotes.total);
if (numerator == 0) {
return 0;
}
- uint256 denominator = votes.active.denominators[group];
+ uint256 denominator = groupActiveVotes.totalUnits;
return numerator.div(denominator);
}
@@ -410,7 +427,7 @@ contract Election is Ownable, ReentrancyGuard, Initializable, UsingRegistry {
* @param account The address of the voting account.
* @return The total votes for `group` made by `account`.
*/
- function getAccountTotalVotesForGroup(
+ function getTotalVotesForGroupByAccount(
address group,
address account
)
@@ -418,8 +435,8 @@ contract Election is Ownable, ReentrancyGuard, Initializable, UsingRegistry {
view
returns (uint256)
{
- uint256 pending = getAccountPendingVotesForGroup(group, account);
- uint256 active = getAccountActiveVotesForGroup(group, account);
+ uint256 pending = getPendingVotesForGroupByAccount(group, account);
+ uint256 active = getActiveVotesForGroupByAccount(group, account);
return pending.add(active);
}
@@ -428,8 +445,8 @@ contract Election is Ownable, ReentrancyGuard, Initializable, UsingRegistry {
* @param group The address of the validator group.
* @return The total votes made for `group`.
*/
- function getGroupTotalVotes(address group) external view returns (uint256) {
- return votes.pending.total[group].add(votes.active.total[group]);
+ function getTotalVotesForGroup(address group) public view returns (uint256) {
+ return votes.pending.forGroup[group].total.add(votes.active.forGroup[group].total);
}
function getGroupEligibility(address group) external view returns (bool) {
@@ -453,10 +470,8 @@ contract Election is Ownable, ReentrancyGuard, Initializable, UsingRegistry {
)
private
{
- require(votes.total.eligible.contains(group));
uint256 newVoteTotal = votes.total.eligible.getValue(group).add(value);
votes.total.eligible.update(group, newVoteTotal, lesser, greater);
- votes.total.total = votes.total.total.add(value);
}
/**
@@ -480,7 +495,6 @@ contract Election is Ownable, ReentrancyGuard, Initializable, UsingRegistry {
uint256 newVoteTotal = votes.total.eligible.getValue(group).sub(value);
votes.total.eligible.update(group, newVoteTotal, lesser, greater);
}
- votes.total.total = votes.total.total.sub(value);
}
/**
@@ -500,21 +514,21 @@ contract Election is Ownable, ReentrancyGuard, Initializable, UsingRegistry {
/**
* @notice Marks a group eligible for electing validators.
- * @param group The address of the validator group.
* @param lesser The address of the group that has received fewer votes than this group.
* @param greater The address of the group that has received more votes than this group.
*/
function markGroupEligible(
- address group,
address lesser,
address greater
)
external
+ nonReentrant
returns (bool)
{
+ address group = getLockedGold().getAccountFromValidator(msg.sender);
require(!votes.total.eligible.contains(group));
require(getValidators().getGroupNumMembers(group) > 0);
- uint256 value = votes.pending.total[group].add(votes.active.total[group]);
+ uint256 value = getTotalVotesForGroup(group);
votes.total.eligible.insert(group, value, lesser, greater);
emit ValidatorGroupMarkedEligible(group);
return true;
@@ -528,9 +542,14 @@ contract Election is Ownable, ReentrancyGuard, Initializable, UsingRegistry {
*/
function incrementPendingVotes(address group, address account, uint256 value) private {
PendingVotes storage pending = votes.pending;
- pending.balances[group][account] = pending.balances[group][account].add(value);
- pending.timestamps[group][account] = now;
- pending.total[group] = pending.total[group].add(value);
+ pending.total = pending.total.add(value);
+
+ GroupPendingVotes storage groupPending = pending.forGroup[group];
+ groupPending.total = groupPending.total.add(value);
+
+ TimestampedVote storage timestampedVote = groupPending.byAccount[account];
+ timestampedVote.value = timestampedVote.value.add(value);
+ timestampedVote.blockNumber = block.number;
}
/**
@@ -541,12 +560,16 @@ contract Election is Ownable, ReentrancyGuard, Initializable, UsingRegistry {
*/
function decrementPendingVotes(address group, address account, uint256 value) private {
PendingVotes storage pending = votes.pending;
- uint256 newValue = pending.balances[group][account].sub(value);
- pending.balances[group][account] = newValue;
- if (newValue == 0) {
- pending.timestamps[group][account] = 0;
+ pending.total = pending.total.sub(value);
+
+ GroupPendingVotes storage groupPending = pending.forGroup[group];
+ groupPending.total = groupPending.total.sub(value);
+
+ TimestampedVote storage timestampedVote = groupPending.byAccount[account];
+ timestampedVote.value = timestampedVote.value.sub(value);
+ if (timestampedVote.value == 0) {
+ timestampedVote.blockNumber = 0;
}
- pending.total[group] = pending.total[group].sub(value);
}
/**
@@ -556,11 +579,16 @@ contract Election is Ownable, ReentrancyGuard, Initializable, UsingRegistry {
* @param value The number of votes.
*/
function incrementActiveVotes(address group, address account, uint256 value) private {
- uint256 delta = getActiveVotesDelta(group, value);
ActiveVotes storage active = votes.active;
- active.numerators[group][account] = active.numerators[group][account].add(delta);
- active.denominators[group] = active.denominators[group].add(delta);
- active.total[group] = active.total[group].add(value);
+ active.total = active.total.add(value);
+
+ uint256 unitsDelta = getActiveVotesUnitsDelta(group, value);
+
+ GroupActiveVotes storage groupActive = active.forGroup[group];
+ groupActive.total = groupActive.total.add(value);
+
+ groupActive.totalUnits = groupActive.totalUnits.add(unitsDelta);
+ groupActive.unitsByAccount[account] = groupActive.unitsByAccount[account].add(unitsDelta);
}
/**
@@ -570,27 +598,37 @@ contract Election is Ownable, ReentrancyGuard, Initializable, UsingRegistry {
* @param value The number of votes.
*/
function decrementActiveVotes(address group, address account, uint256 value) private {
- uint256 delta = getActiveVotesDelta(group, value);
ActiveVotes storage active = votes.active;
- active.numerators[group][account] = active.numerators[group][account].sub(delta);
- active.denominators[group] = active.denominators[group].sub(delta);
- active.total[group] = active.total[group].sub(value);
+ active.total = active.total.sub(value);
+
+ uint256 unitsDelta = getActiveVotesUnitsDelta(group, value);
+
+ GroupActiveVotes storage groupActive = active.forGroup[group];
+ groupActive.total = groupActive.total.sub(value);
+
+ groupActive.totalUnits = groupActive.totalUnits.sub(unitsDelta);
+ groupActive.unitsByAccount[account] = groupActive.unitsByAccount[account].sub(unitsDelta);
}
/**
* @notice Returns the delta in active vote denominator for `group`.
* @param group The address of the validator group.
- * @param value The numebr of active votes being added.
+ * @param value The number of active votes being added.
* @return The delta in active vote denominator for `group`.
*/
- function getActiveVotesDelta(address group, uint256 value) private view returns (uint256) {
- // Preserve delta * total = value * denominator
- return value.mul(votes.active.denominators[group].add(1)).div(
- votes.active.total[group].add(1)
+ function getActiveVotesUnitsDelta(address group, uint256 value) private view returns (uint256) {
+ // Preserve unitsDelta * total = value * totalUnits
+ return value.mul(votes.active.forGroup[group].totalUnits.add(1)).div(
+ votes.active.forGroup[group].total.add(1)
);
}
- function getGroupsVotedFor(address account) external view returns (address[] memory) {
+ /**
+ * @notice 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.
+ */
+ function getGroupsVotedForByAccount(address account) external view returns (address[] memory) {
return votes.groupsVotedFor[account];
}
@@ -601,23 +639,47 @@ contract Election is Ownable, ReentrancyGuard, Initializable, UsingRegistry {
* @param index The index of `element` in the list.
*/
function deleteElement(address[] storage list, address element, uint256 index) private {
+ // TODO(asa): Move this to a library to be shared.
require(index < list.length && list[index] == element);
uint256 lastIndex = list.length.sub(1);
list[index] = list[lastIndex];
- list[lastIndex] = address(0);
+ delete list[lastIndex];
list.length = lastIndex;
}
+ /**
+ * @notice Returns whether or not a group can receive the specified number of votes.
+ * @param group The address of the group.
+ * @param value The number of votes.
+ * @return Whether or not a group can receive the specified number of votes.
+ * @dev Votes are not allowed to be cast that would increase a group's proportion of locked gold
+ * voting for it to greater than
+ * (numGroupMembers + 1) / min(maxElectableValidators, numRegisteredValidators)
+ */
+ function canReceiveVotes(address group, uint256 value) public view returns (bool) {
+ uint256 totalVotesForGroup = getTotalVotesForGroup(group).add(value);
+ uint256 left = totalVotesForGroup.mul(
+ Math.min(
+ electableValidators.max,
+ getValidators().getNumRegisteredValidators()
+ )
+ );
+ uint256 right = getValidators().getGroupNumMembers(group).add(1).mul(getLockedGold().getTotalLockedGold());
+ return left <= right;
+ }
+
/**
* @notice Returns the number of votes that a group can receive.
* @param group The address of the group.
* @return The number of votes that a group can receive.
+ * @dev Votes are not allowed to be cast that would increase a group's proportion of locked gold
+ * voting for it to greater than
+ * (numGroupMembers + 1) / min(maxElectableValidators, numRegisteredValidators)
*/
- function getNumVotesReceivable(address group) public view returns (uint256) {
- uint256 totalLockedGold = getLockedGold().getTotalLockedGold();
- uint256 numerator = getValidators().getGroupNumMembers(group).add(1).mul(totalLockedGold);
+ function getNumVotesReceivable(address group) external view returns (uint256) {
+ uint256 numerator = getValidators().getGroupNumMembers(group).add(1).mul(getLockedGold().getTotalLockedGold());
uint256 denominator = Math.min(
- maxElectableValidators,
+ electableValidators.max,
getValidators().getNumRegisteredValidators()
);
return numerator.div(denominator);
@@ -627,8 +689,8 @@ contract Election is Ownable, ReentrancyGuard, Initializable, UsingRegistry {
* @notice Returns the total votes received across all groups.
* @return The total votes received across all groups.
*/
- function getTotalVotes() external view returns (uint256) {
- return votes.total.total;
+ function getTotalVotes() public view returns (uint256) {
+ return votes.active.total.add(votes.pending.total);
}
/**
@@ -643,7 +705,7 @@ contract Election is Ownable, ReentrancyGuard, Initializable, UsingRegistry {
* @notice Returns lists of all validator groups and the number of votes they've received.
* @return Lists of all validator groups and the number of votes they've received.
*/
- function getEligibleValidatorGroupsVoteTotals()
+ function getTotalVotesForEligibleValidatorGroups()
external
view
returns (address[] memory, uint256[] memory)
@@ -685,14 +747,9 @@ contract Election is Ownable, ReentrancyGuard, Initializable, UsingRegistry {
function electValidators() external view returns (address[] memory) {
// Only members of these validator groups are eligible for election.
uint256 maxNumElectionGroups = Math.min(
- maxElectableValidators,
+ electableValidators.max,
votes.total.eligible.list.numElements
);
- /*
- uint256 requiredVotes = electabilityThreshold.multiply(
- FixidityLib.newFixed(votes.total.total)
- ).fromFixed();
- */
// TODO(asa): Filter by > requiredVotes
address[] memory electionGroups = votes.total.eligible.headN(maxNumElectionGroups);
uint256[] memory numMembers = getValidators().getGroupsNumMembers(electionGroups);
@@ -700,7 +757,7 @@ contract Election is Ownable, ReentrancyGuard, Initializable, UsingRegistry {
uint256[] memory numMembersElected = new uint256[](electionGroups.length);
uint256 totalNumMembersElected = 0;
// Assign a number of seats to each validator group.
- while (totalNumMembersElected < maxElectableValidators) {
+ while (totalNumMembersElected < electableValidators.max) {
uint256 groupIndex = 0;
bool memberElected = false;
(groupIndex, memberElected) = dHondt(electionGroups, numMembers, numMembersElected);
@@ -712,7 +769,7 @@ contract Election is Ownable, ReentrancyGuard, Initializable, UsingRegistry {
break;
}
}
- require(totalNumMembersElected >= minElectableValidators);
+ require(totalNumMembersElected >= electableValidators.min);
// Grab the top validators from each group that won seats.
address[] memory electedValidators = new address[](totalNumMembersElected);
totalNumMembersElected = 0;
diff --git a/packages/protocol/contracts/governance/LockedGold.sol b/packages/protocol/contracts/governance/LockedGold.sol
index 6f89b30453c..11e6d156a88 100644
--- a/packages/protocol/contracts/governance/LockedGold.sol
+++ b/packages/protocol/contracts/governance/LockedGold.sol
@@ -15,17 +15,23 @@ contract LockedGold is ILockedGold, ReentrancyGuard, Initializable, UsingRegistr
using SafeMath for uint256;
struct MustMaintain {
+ // The Locked Gold balance that the account must maintain.
uint256 value;
+ // The timestamp at which the account is no longer subject to these constraints.
uint256 timestamp;
}
struct Authorizations {
+ // The address that is authorized to vote on behalf of the account.
address voting;
+ // The address that is authorized to validate on behalf of the account.
address validating;
}
struct PendingWithdrawal {
+ // The value of the pending withdrawal.
uint256 value;
+ // The timestamp at which the pending withdrawal becomes available.
uint256 timestamp;
}
@@ -33,7 +39,9 @@ contract LockedGold is ILockedGold, ReentrancyGuard, Initializable, UsingRegistr
// This contract does not store an account's locked gold that is being used in electing
// validators.
uint256 nonvoting;
+ // Gold that has been unlocked and will become available for withdrawal.
PendingWithdrawal[] pendingWithdrawals;
+ // Balance requirements imposed on this account.
MustMaintain requirements;
}
@@ -189,7 +197,7 @@ contract LockedGold is ILockedGold, ReentrancyGuard, Initializable, UsingRegistr
function unlock(uint256 value) external nonReentrant {
require(isAccount(msg.sender));
Account storage account = accounts[msg.sender];
- MustMaintain memory requirement = account.balances.requirements;
+ MustMaintain storage requirement = account.balances.requirements;
require(
now >= requirement.timestamp ||
getAccountTotalLockedGold(msg.sender).sub(value) >= requirement.value
@@ -223,7 +231,7 @@ contract LockedGold is ILockedGold, ReentrancyGuard, Initializable, UsingRegistr
require(isAccount(msg.sender));
Account storage account = accounts[msg.sender];
require(index < account.balances.pendingWithdrawals.length);
- PendingWithdrawal memory pendingWithdrawal = account.balances.pendingWithdrawals[index];
+ PendingWithdrawal storage pendingWithdrawal = account.balances.pendingWithdrawals[index];
require(now >= pendingWithdrawal.timestamp);
uint256 value = pendingWithdrawal.value;
deletePendingWithdrawal(account.balances.pendingWithdrawals, index);
@@ -271,7 +279,8 @@ contract LockedGold is ILockedGold, ReentrancyGuard, Initializable, UsingRegistr
}
/**
- * @notice Returns the total amount of locked gold in the system.
+ * @notice 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.
* @return The total amount of locked gold in the system.
*/
function getTotalLockedGold() external view returns (uint256) {
diff --git a/packages/protocol/contracts/governance/Validators.sol b/packages/protocol/contracts/governance/Validators.sol
index bd363e8e3b2..d4bdec86fe5 100644
--- a/packages/protocol/contracts/governance/Validators.sol
+++ b/packages/protocol/contracts/governance/Validators.sol
@@ -41,6 +41,7 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
struct ValidatorGroup {
string name;
string url;
+ // TODO(asa): Add a function that allows groups to update their commission.
FixidityLib.Fraction commission;
LinkedList.List members;
}
@@ -194,8 +195,8 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
/**
* @notice Updates the duration for which gold remains locked after deregistration.
- * @param groupLockup The duration for groups.
- * @param validatorLockup The duration for validators.
+ * @param groupLockup The duration for groups in seconds.
+ * @param validatorLockup The duration for validators in seconds.
* @return True upon success.
* @dev The new requirement is only enforced for future validator or group deregistrations.
*/
@@ -252,8 +253,7 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
require(!isValidator(account) && !isValidatorGroup(account));
require(meetsValidatorRegistrationRequirement(account));
- Validator memory validator = Validator(name, url, publicKeysData, address(0));
- validators[account] = validator;
+ validators[account] = Validator(name, url, publicKeysData, address(0));
_validators.push(account);
getLockedGold().setAccountMustMaintain(account, registrationRequirements.validator, MAX_INT);
emit ValidatorRegistered(account, name, url, publicKeysData);
@@ -321,7 +321,7 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
*/
function affiliate(address group) external nonReentrant returns (bool) {
address account = getLockedGold().getAccountFromValidator(msg.sender);
- require(isValidator(account) && isValidatorGroup(group), "blah");
+ require(isValidator(account) && isValidatorGroup(group));
Validator storage validator = validators[account];
if (validator.affiliation != address(0)) {
_deaffiliate(validator, account);
@@ -420,7 +420,7 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
*/
function _addMember(address group, address validator) private returns (bool) {
ValidatorGroup storage _group = groups[group];
- require(_group.members.numElements < maxGroupSize);
+ require(_group.members.numElements < maxGroupSize, "group would exceed maximum size");
require(validators[validator].affiliation == group && !_group.members.contains(validator));
_group.members.push(validator);
emit ValidatorGroupMemberAdded(group, validator);
@@ -517,6 +517,7 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
* @return The number of members in a validator group.
*/
function getGroupNumMembers(address account) public view returns (uint256) {
+ require(isValidatorGroup(account));
return groups[account].members.numElements;
}
@@ -629,7 +630,7 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
require(index < list.length && list[index] == element);
uint256 lastIndex = list.length.sub(1);
list[index] = list[lastIndex];
- list[lastIndex] = address(0);
+ delete list[lastIndex];
list.length = lastIndex;
}
diff --git a/packages/protocol/migrationsConfig.js b/packages/protocol/migrationsConfig.js
index a270504a09f..c8d354bbb08 100644
--- a/packages/protocol/migrationsConfig.js
+++ b/packages/protocol/migrationsConfig.js
@@ -18,7 +18,7 @@ const DefaultConfig = {
reportExpiry: 60 * 60, // 1 hour
},
election: {
- minElectableValidators: '10',
+ minElectableValidators: '22',
maxElectableValidators: '100',
maxVotesPerAccount: 3,
electabilityThreshold: '0', // no threshold
diff --git a/packages/protocol/test/governance/election.ts b/packages/protocol/test/governance/election.ts
index c3647fd622f..61d55ba9234 100644
--- a/packages/protocol/test/governance/election.ts
+++ b/packages/protocol/test/governance/election.ts
@@ -37,8 +37,10 @@ contract('Election', (accounts: string[]) => {
let mockValidators: MockValidatorsInstance
const nonOwner = accounts[1]
- const minElectableValidators = new BigNumber(4)
- const maxElectableValidators = new BigNumber(6)
+ const electableValidators = {
+ min: new BigNumber(4),
+ max: new BigNumber(6),
+ }
const maxNumGroupsVotedFor = new BigNumber(3)
const electabilityThreshold = new BigNumber(0)
@@ -51,8 +53,8 @@ contract('Election', (accounts: string[]) => {
await registry.setAddressFor(CeloContractName.Validators, mockValidators.address)
await election.initialize(
registry.address,
- minElectableValidators,
- maxElectableValidators,
+ electableValidators.min,
+ electableValidators.max,
maxNumGroupsVotedFor,
electabilityThreshold
)
@@ -64,14 +66,10 @@ contract('Election', (accounts: string[]) => {
assert.equal(owner, accounts[0])
})
- it('should have set minElectableValidators', async () => {
- const actualMinElectableValidators = await election.minElectableValidators()
- assertEqualBN(actualMinElectableValidators, minElectableValidators)
- })
-
- it('should have set maxElectableValidators', async () => {
- const actualMaxElectableValidators = await election.maxElectableValidators()
- assertEqualBN(actualMaxElectableValidators, maxElectableValidators)
+ it('should have set electableValidators', async () => {
+ const [min, max] = await election.getElectableValidators()
+ assertEqualBN(min, electableValidators.min)
+ assertEqualBN(max, electableValidators.max)
})
it('should have set maxNumGroupsVotedFor', async () => {
@@ -79,12 +77,17 @@ contract('Election', (accounts: string[]) => {
assertEqualBN(actualMaxNumGroupsVotedFor, maxNumGroupsVotedFor)
})
+ it('should have set electabilityThreshold', async () => {
+ const actualElectabilityThreshold = await election.getElectabilityThreshold()
+ assertEqualBN(actualElectabilityThreshold, electabilityThreshold)
+ })
+
it('should not be callable again', async () => {
await assertRevert(
election.initialize(
registry.address,
- minElectableValidators,
- maxElectableValidators,
+ electableValidators.min,
+ electableValidators.max,
maxNumGroupsVotedFor,
electabilityThreshold
)
@@ -106,74 +109,59 @@ contract('Election', (accounts: string[]) => {
})
})
- describe('#setMinElectableValidators', () => {
- const newMinElectableValidators = minElectableValidators.plus(1)
+ describe('#setElectableValidators', () => {
+ const newElectableValidators = {
+ min: electableValidators.min.plus(1),
+ max: electableValidators.max.plus(1),
+ }
+
it('should set the minimum electable valdiators', async () => {
- await election.setMinElectableValidators(newMinElectableValidators)
- assertEqualBN(await election.minElectableValidators(), newMinElectableValidators)
+ await election.setElectableValidators(newElectableValidators.min, newElectableValidators.max)
+ const [min, max] = await election.getElectableValidators()
+ assertEqualBN(min, newElectableValidators.min)
+ assertEqualBN(max, newElectableValidators.max)
})
- it('should emit the MinElectableValidatorsSet event', async () => {
- const resp = await election.setMinElectableValidators(newMinElectableValidators)
+ it('should emit the ElectableValidatorsSet event', async () => {
+ const resp = await election.setElectableValidators(
+ newElectableValidators.min,
+ newElectableValidators.max
+ )
assert.equal(resp.logs.length, 1)
const log = resp.logs[0]
assertContainSubset(log, {
- event: 'MinElectableValidatorsSet',
+ event: 'ElectableValidatorsSet',
args: {
- minElectableValidators: new BigNumber(newMinElectableValidators),
+ min: newElectableValidators.min,
+ max: newElectableValidators.max,
},
})
})
it('should revert when the minElectableValidators is zero', async () => {
- await assertRevert(election.setMinElectableValidators(0))
+ await assertRevert(election.setElectableValidators(0, newElectableValidators.max))
})
- it('should revert when the minElectableValidators is greater than maxElectableValidators', async () => {
- await assertRevert(election.setMinElectableValidators(maxElectableValidators.plus(1)))
- })
-
- it('should revert when the minElectableValidators is unchanged', async () => {
- await assertRevert(election.setMinElectableValidators(minElectableValidators))
- })
-
- it('should revert when called by anyone other than the owner', async () => {
+ it('should revert when the min is greater than max', async () => {
await assertRevert(
- election.setMinElectableValidators(newMinElectableValidators, { from: nonOwner })
+ election.setElectableValidators(
+ newElectableValidators.max.plus(1),
+ newElectableValidators.max
+ )
)
})
- })
- describe('#setMaxElectableValidators', () => {
- const newMaxElectableValidators = maxElectableValidators.plus(1)
- it('should set the max electable validators', async () => {
- await election.setMaxElectableValidators(newMaxElectableValidators)
- assertEqualBN(await election.maxElectableValidators(), newMaxElectableValidators)
- })
-
- it('should emit the MaxElectableValidatorsSet event', async () => {
- const resp = await election.setMaxElectableValidators(newMaxElectableValidators)
- assert.equal(resp.logs.length, 1)
- const log = resp.logs[0]
- assertContainSubset(log, {
- event: 'MaxElectableValidatorsSet',
- args: {
- maxElectableValidators: new BigNumber(newMaxElectableValidators),
- },
- })
- })
-
- it('should revert when the maxElectableValidators is less than minElectableValidators', async () => {
- await assertRevert(election.setMaxElectableValidators(minElectableValidators.minus(1)))
- })
-
- it('should revert when the maxElectableValidators is unchanged', async () => {
- await assertRevert(election.setMaxElectableValidators(maxElectableValidators))
+ it('should revert when the values are unchanged', async () => {
+ await assertRevert(
+ election.setElectableValidators(electableValidators.min, electableValidators.max)
+ )
})
it('should revert when called by anyone other than the owner', async () => {
await assertRevert(
- election.setMaxElectableValidators(newMaxElectableValidators, { from: nonOwner })
+ election.setElectableValidators(newElectableValidators.min, newElectableValidators.max, {
+ from: nonOwner,
+ })
)
})
})
@@ -218,7 +206,7 @@ contract('Election', (accounts: string[]) => {
describe('when the group has no votes', () => {
let resp: any
beforeEach(async () => {
- resp = await election.markGroupEligible(group, NULL_ADDRESS, NULL_ADDRESS)
+ resp = await election.markGroupEligible(NULL_ADDRESS, NULL_ADDRESS, { from: group })
})
it('should add the group to the list of eligible groups', async () => {
@@ -238,7 +226,9 @@ contract('Election', (accounts: string[]) => {
describe('when the group has already been marked eligible', () => {
it('should revert', async () => {
- await assertRevert(election.markGroupEligible(group, NULL_ADDRESS, NULL_ADDRESS))
+ await assertRevert(
+ election.markGroupEligible(NULL_ADDRESS, NULL_ADDRESS, { from: group })
+ )
})
})
})
@@ -246,7 +236,7 @@ contract('Election', (accounts: string[]) => {
describe('when the group has no members', () => {
it('should revert', async () => {
- await assertRevert(election.markGroupEligible(group, NULL_ADDRESS, NULL_ADDRESS))
+ await assertRevert(election.markGroupEligible(NULL_ADDRESS, NULL_ADDRESS, { from: group }))
})
})
})
@@ -256,7 +246,7 @@ contract('Election', (accounts: string[]) => {
describe('when the group is eligible', () => {
beforeEach(async () => {
await mockValidators.setMembers(group, [accounts[9]])
- await election.markGroupEligible(group, NULL_ADDRESS, NULL_ADDRESS)
+ await election.markGroupEligible(NULL_ADDRESS, NULL_ADDRESS, { from: group })
})
describe('when called by the registered Validators contract', () => {
@@ -307,11 +297,11 @@ contract('Election', (accounts: string[]) => {
describe('#vote', () => {
const voter = accounts[0]
const group = accounts[1]
- const value = 1000
+ const value = new BigNumber(1000)
describe('when the group is eligible', () => {
beforeEach(async () => {
await mockValidators.setMembers(group, [accounts[9]])
- await election.markGroupEligible(group, NULL_ADDRESS, NULL_ADDRESS)
+ await election.markGroupEligible(NULL_ADDRESS, NULL_ADDRESS, { from: group })
})
describe('when the group can receive votes', () => {
@@ -329,23 +319,23 @@ contract('Election', (accounts: string[]) => {
})
it('should add the group to the list of groups the account has voted for', async () => {
- assert.deepEqual(await election.getAccountGroupsVotedFor(voter), [group])
+ assert.deepEqual(await election.getGroupsVotedForByAccount(voter), [group])
})
it("should increment the account's pending votes for the group", async () => {
- assertEqualBN(await election.getAccountPendingVotesForGroup(group, voter), value)
+ assertEqualBN(await election.getPendingVotesForGroupByAccount(group, voter), value)
})
it("should increment the account's total votes for the group", async () => {
- assertEqualBN(await election.getAccountTotalVotesForGroup(group, voter), value)
+ assertEqualBN(await election.getTotalVotesForGroupByAccount(group, voter), value)
})
it("should increment the account's total votes", async () => {
- assertEqualBN(await election.getAccountTotalVotes(voter), value)
+ assertEqualBN(await election.getTotalVotesByAccount(voter), value)
})
it('should increment the total votes for the group', async () => {
- assertEqualBN(await election.getGroupTotalVotes(group), value)
+ assertEqualBN(await election.getTotalVotesForGroup(group), value)
})
it('should increment the total votes', async () => {
@@ -372,7 +362,7 @@ contract('Election', (accounts: string[]) => {
describe('when the voter does not have sufficient non-voting balance', () => {
beforeEach(async () => {
- await mockLockedGold.incrementNonvotingAccountBalance(voter, value - 1)
+ await mockLockedGold.incrementNonvotingAccountBalance(voter, value.minus(1))
})
it('should revert', async () => {
@@ -388,20 +378,26 @@ contract('Election', (accounts: string[]) => {
for (let i = 0; i < maxNumGroupsVotedFor.toNumber(); i++) {
newGroup = accounts[i + 2]
await mockValidators.setMembers(newGroup, [accounts[9]])
- await election.markGroupEligible(newGroup, group, NULL_ADDRESS)
+ await election.markGroupEligible(group, NULL_ADDRESS, { from: newGroup })
await election.vote(newGroup, 1, group, NULL_ADDRESS)
}
})
it('should revert', async () => {
await assertRevert(
- election.vote(group, value - maxNumGroupsVotedFor.toNumber(), newGroup, NULL_ADDRESS)
+ election.vote(group, value.minus(maxNumGroupsVotedFor), newGroup, NULL_ADDRESS)
)
})
})
})
describe('when the group cannot receive votes', () => {
+ beforeEach(async () => {
+ await mockLockedGold.setTotalLockedGold(value.div(2).minus(1))
+ await mockValidators.setNumRegisteredValidators(1)
+ assertEqualBN(await election.getNumVotesReceivable(group), value.minus(2))
+ })
+
it('should revert', async () => {
await assertRevert(election.vote(group, value, NULL_ADDRESS, NULL_ADDRESS))
})
@@ -421,7 +417,7 @@ contract('Election', (accounts: string[]) => {
const value = 1000
beforeEach(async () => {
await mockValidators.setMembers(group, [accounts[9]])
- await election.markGroupEligible(group, NULL_ADDRESS, NULL_ADDRESS)
+ await election.markGroupEligible(NULL_ADDRESS, NULL_ADDRESS, { from: group })
await mockLockedGold.setTotalLockedGold(value)
await mockValidators.setNumRegisteredValidators(1)
await mockLockedGold.incrementNonvotingAccountBalance(voter, value)
@@ -435,23 +431,23 @@ contract('Election', (accounts: string[]) => {
})
it("should decrement the account's pending votes for the group", async () => {
- assertEqualBN(await election.getAccountPendingVotesForGroup(group, voter), 0)
+ assertEqualBN(await election.getPendingVotesForGroupByAccount(group, voter), 0)
})
it("should increment the account's active votes for the group", async () => {
- assertEqualBN(await election.getAccountActiveVotesForGroup(group, voter), value)
+ assertEqualBN(await election.getActiveVotesForGroupByAccount(group, voter), value)
})
it("should not modify the account's total votes for the group", async () => {
- assertEqualBN(await election.getAccountTotalVotesForGroup(group, voter), value)
+ assertEqualBN(await election.getTotalVotesForGroupByAccount(group, voter), value)
})
it("should not modify the account's total votes", async () => {
- assertEqualBN(await election.getAccountTotalVotes(voter), value)
+ assertEqualBN(await election.getTotalVotesByAccount(voter), value)
})
it('should not modify the total votes for the group', async () => {
- assertEqualBN(await election.getGroupTotalVotes(group), value)
+ assertEqualBN(await election.getTotalVotesForGroup(group), value)
})
it('should not modify the total votes', async () => {
@@ -481,35 +477,35 @@ contract('Election', (accounts: string[]) => {
})
it("should not modify the first account's active votes for the group", async () => {
- assertEqualBN(await election.getAccountActiveVotesForGroup(group, voter), value)
+ assertEqualBN(await election.getActiveVotesForGroupByAccount(group, voter), value)
})
it("should not modify the first account's total votes for the group", async () => {
- assertEqualBN(await election.getAccountTotalVotesForGroup(group, voter), value)
+ assertEqualBN(await election.getTotalVotesForGroupByAccount(group, voter), value)
})
it("should not modify the first account's total votes", async () => {
- assertEqualBN(await election.getAccountTotalVotes(voter), value)
+ assertEqualBN(await election.getTotalVotesByAccount(voter), value)
})
it("should decrement the second account's pending votes for the group", async () => {
- assertEqualBN(await election.getAccountPendingVotesForGroup(group, voter2), 0)
+ assertEqualBN(await election.getPendingVotesForGroupByAccount(group, voter2), 0)
})
it("should increment the second account's active votes for the group", async () => {
- assertEqualBN(await election.getAccountActiveVotesForGroup(group, voter2), value2)
+ assertEqualBN(await election.getActiveVotesForGroupByAccount(group, voter2), value2)
})
it("should not modify the second account's total votes for the group", async () => {
- assertEqualBN(await election.getAccountTotalVotesForGroup(group, voter2), value2)
+ assertEqualBN(await election.getTotalVotesForGroupByAccount(group, voter2), value2)
})
it("should not modify the second account's total votes", async () => {
- assertEqualBN(await election.getAccountTotalVotes(voter2), value2)
+ assertEqualBN(await election.getTotalVotesByAccount(voter2), value2)
})
it('should not modify the total votes for the group', async () => {
- assertEqualBN(await election.getGroupTotalVotes(group), value + value2)
+ assertEqualBN(await election.getTotalVotesForGroup(group), value + value2)
})
it('should not modify the total votes', async () => {
@@ -532,7 +528,7 @@ contract('Election', (accounts: string[]) => {
describe('when the voter has pending votes', () => {
beforeEach(async () => {
await mockValidators.setMembers(group, [accounts[9]])
- await election.markGroupEligible(group, NULL_ADDRESS, NULL_ADDRESS)
+ await election.markGroupEligible(NULL_ADDRESS, NULL_ADDRESS, { from: group })
await mockLockedGold.setTotalLockedGold(value)
await mockValidators.setNumRegisteredValidators(1)
await mockLockedGold.incrementNonvotingAccountBalance(voter, value)
@@ -555,19 +551,19 @@ contract('Election', (accounts: string[]) => {
})
it("should decrement the account's pending votes for the group", async () => {
- assertEqualBN(await election.getAccountPendingVotesForGroup(group, voter), remaining)
+ assertEqualBN(await election.getPendingVotesForGroupByAccount(group, voter), remaining)
})
it("should decrement the account's total votes for the group", async () => {
- assertEqualBN(await election.getAccountTotalVotesForGroup(group, voter), remaining)
+ assertEqualBN(await election.getTotalVotesForGroupByAccount(group, voter), remaining)
})
it("should decrement the account's total votes", async () => {
- assertEqualBN(await election.getAccountTotalVotes(voter), remaining)
+ assertEqualBN(await election.getTotalVotesByAccount(voter), remaining)
})
it('should decrement the total votes for the group', async () => {
- assertEqualBN(await election.getGroupTotalVotes(group), remaining)
+ assertEqualBN(await election.getTotalVotesForGroup(group), remaining)
})
it('should decrement the total votes', async () => {
@@ -600,7 +596,7 @@ contract('Election', (accounts: string[]) => {
})
it('should remove the group to the list of groups the account has voted for', async () => {
- assert.deepEqual(await election.getAccountGroupsVotedFor(voter), [])
+ assert.deepEqual(await election.getGroupsVotedForByAccount(voter), [])
})
})
@@ -632,7 +628,7 @@ contract('Election', (accounts: string[]) => {
describe('when the voter has active votes', () => {
beforeEach(async () => {
await mockValidators.setMembers(group, [accounts[9]])
- await election.markGroupEligible(group, NULL_ADDRESS, NULL_ADDRESS)
+ await election.markGroupEligible(NULL_ADDRESS, NULL_ADDRESS, { from: group })
await mockLockedGold.setTotalLockedGold(value)
await mockValidators.setNumRegisteredValidators(1)
await mockLockedGold.incrementNonvotingAccountBalance(voter, value)
@@ -650,19 +646,19 @@ contract('Election', (accounts: string[]) => {
})
it("should decrement the account's active votes for the group", async () => {
- assertEqualBN(await election.getAccountActiveVotesForGroup(group, voter), remaining)
+ assertEqualBN(await election.getActiveVotesForGroupByAccount(group, voter), remaining)
})
it("should decrement the account's total votes for the group", async () => {
- assertEqualBN(await election.getAccountTotalVotesForGroup(group, voter), remaining)
+ assertEqualBN(await election.getTotalVotesForGroupByAccount(group, voter), remaining)
})
it("should decrement the account's total votes", async () => {
- assertEqualBN(await election.getAccountTotalVotes(voter), remaining)
+ assertEqualBN(await election.getTotalVotesByAccount(voter), remaining)
})
it('should decrement the total votes for the group', async () => {
- assertEqualBN(await election.getGroupTotalVotes(group), remaining)
+ assertEqualBN(await election.getTotalVotesForGroup(group), remaining)
})
it('should decrement the total votes', async () => {
@@ -695,7 +691,7 @@ contract('Election', (accounts: string[]) => {
})
it('should remove the group to the list of groups the account has voted for', async () => {
- assert.deepEqual(await election.getAccountGroupsVotedFor(voter), [])
+ assert.deepEqual(await election.getGroupsVotedForByAccount(voter), [])
})
})
@@ -755,9 +751,9 @@ contract('Election', (accounts: string[]) => {
await mockValidators.setMembers(group2, [validator5, validator6])
await mockValidators.setMembers(group3, [validator7])
- await election.markGroupEligible(group1, NULL_ADDRESS, NULL_ADDRESS)
- await election.markGroupEligible(group2, NULL_ADDRESS, group1)
- await election.markGroupEligible(group3, NULL_ADDRESS, group2)
+ await election.markGroupEligible(NULL_ADDRESS, NULL_ADDRESS, { from: group1 })
+ await election.markGroupEligible(NULL_ADDRESS, group1, { from: group2 })
+ await election.markGroupEligible(NULL_ADDRESS, group2, { from: group3 })
for (const voter of [voter1, voter2, voter3]) {
await mockLockedGold.incrementNonvotingAccountBalance(voter.address, voter.weight)
diff --git a/packages/protocol/test/governance/validators.ts b/packages/protocol/test/governance/validators.ts
index 2251fca668c..df5d6d23970 100644
--- a/packages/protocol/test/governance/validators.ts
+++ b/packages/protocol/test/governance/validators.ts
@@ -133,6 +133,11 @@ contract('Validators', (accounts: string[]) => {
assertEqualBN(validator, deregistrationLockups.validator)
})
+ it('should have set the max group size', async () => {
+ const actualMaxGroupSize = await validators.getMaxGroupSize()
+ assertEqualBN(actualMaxGroupSize, maxGroupSize)
+ })
+
it('should not be callable again', async () => {
await assertRevert(
validators.initialize(
@@ -419,7 +424,7 @@ contract('Validators', (accounts: string[]) => {
const validator = accounts[0]
const index = 0
let resp: any
- describe('when the account is not a registered validator', () => {
+ describe('when the account is a registered validator', () => {
beforeEach(async () => {
await registerValidator(validator)
resp = await validators.deregisterValidator(index)
From adcad0b3f2b5bae96ac78d3364005ec5936f4dc1 Mon Sep 17 00:00:00 2001
From: Asa Oines
Date: Tue, 8 Oct 2019 17:08:33 -0700
Subject: [PATCH 037/149] Fix linting issues
---
packages/protocol/contracts/governance/Election.sol | 8 ++++++--
1 file changed, 6 insertions(+), 2 deletions(-)
diff --git a/packages/protocol/contracts/governance/Election.sol b/packages/protocol/contracts/governance/Election.sol
index 7532cea72e6..7e8c3ecd37d 100644
--- a/packages/protocol/contracts/governance/Election.sol
+++ b/packages/protocol/contracts/governance/Election.sol
@@ -664,7 +664,9 @@ contract Election is Ownable, ReentrancyGuard, Initializable, UsingRegistry {
getValidators().getNumRegisteredValidators()
)
);
- uint256 right = getValidators().getGroupNumMembers(group).add(1).mul(getLockedGold().getTotalLockedGold());
+ uint256 right = getValidators().getGroupNumMembers(group).add(1).mul(
+ getLockedGold().getTotalLockedGold()
+ );
return left <= right;
}
@@ -677,7 +679,9 @@ contract Election is Ownable, ReentrancyGuard, Initializable, UsingRegistry {
* (numGroupMembers + 1) / min(maxElectableValidators, numRegisteredValidators)
*/
function getNumVotesReceivable(address group) external view returns (uint256) {
- uint256 numerator = getValidators().getGroupNumMembers(group).add(1).mul(getLockedGold().getTotalLockedGold());
+ uint256 numerator = getValidators().getGroupNumMembers(group).add(1).mul(
+ getLockedGold().getTotalLockedGold()
+ );
uint256 denominator = Math.min(
electableValidators.max,
getValidators().getNumRegisteredValidators()
From 53e4977d65f1ec0e9fbb54391e963fab340e2515 Mon Sep 17 00:00:00 2001
From: Asa Oines
Date: Tue, 8 Oct 2019 17:45:26 -0700
Subject: [PATCH 038/149] Make things build, tests pass
---
packages/cli/src/commands/lockedgold/show.ts | 3 +-
.../cli/src/commands/lockedgold/withdraw.ts | 6 +-
.../src/commands/validatorgroup/register.ts | 12 ++--
packages/contractkit/src/wrappers/Election.ts | 57 ++++++++-----------
.../contractkit/src/wrappers/LockedGold.ts | 14 ++---
.../contractkit/src/wrappers/Validators.ts | 13 +++--
.../contracts/governance/Election.sol | 4 +-
.../contracts/governance/Governance.sol | 7 ++-
.../contracts/governance/LockedGold.sol | 2 +-
.../governance/interfaces/IElection.sol | 2 +-
10 files changed, 59 insertions(+), 61 deletions(-)
diff --git a/packages/cli/src/commands/lockedgold/show.ts b/packages/cli/src/commands/lockedgold/show.ts
index 712d5760f52..9c9c90c089c 100644
--- a/packages/cli/src/commands/lockedgold/show.ts
+++ b/packages/cli/src/commands/lockedgold/show.ts
@@ -1,7 +1,6 @@
import { BaseCommand } from '../../base'
import { printValueMapRecursive } from '../../utils/cli'
import { Args } from '../../utils/command'
-import { eqAddress } from '@celo/utils/lib/address'
export default class Show extends BaseCommand {
static description = 'Show Locked Gold information for a given account'
@@ -19,6 +18,6 @@ export default class Show extends BaseCommand {
const { args } = this.parse(Show)
const lockedGold = await this.kit.contracts.getLockedGold()
- printValueMapRecursive(await lockedGold.geAccountSummary(args.account))
+ printValueMapRecursive(await lockedGold.getAccountSummary(args.account))
}
}
diff --git a/packages/cli/src/commands/lockedgold/withdraw.ts b/packages/cli/src/commands/lockedgold/withdraw.ts
index 067f24febbd..3691d715dc2 100644
--- a/packages/cli/src/commands/lockedgold/withdraw.ts
+++ b/packages/cli/src/commands/lockedgold/withdraw.ts
@@ -22,7 +22,8 @@ export default class Withdraw extends BaseCommand {
while (true) {
let madeWithdrawal = false
const pendingWithdrawals = await lockedgold.getPendingWithdrawals(flags.from)
- for (const pendingWithdrawal of pendingWithdrawals) {
+ for (let i = 0; i < pendingWithdrawals.length; i++) {
+ const pendingWithdrawal = pendingWithdrawals[i]
if (pendingWithdrawal.time.isLessThan(currentTime)) {
console.log(
`Found available pending withdrawal of value ${pendingWithdrawal.value.toString()}, withdrawing`
@@ -32,6 +33,9 @@ export default class Withdraw extends BaseCommand {
break
}
}
+ if (!madeWithdrawal) {
+ break
+ }
}
const pendingWithdrawals = await lockedgold.getPendingWithdrawals(flags.from)
for (const pendingWithdrawal of pendingWithdrawals) {
diff --git a/packages/cli/src/commands/validatorgroup/register.ts b/packages/cli/src/commands/validatorgroup/register.ts
index a805fa40d3d..a8650e81fee 100644
--- a/packages/cli/src/commands/validatorgroup/register.ts
+++ b/packages/cli/src/commands/validatorgroup/register.ts
@@ -24,13 +24,11 @@ export default class ValidatorGroupRegister extends BaseCommand {
this.kit.defaultAccount = res.flags.from
const validators = await this.kit.contracts.getValidators()
- await displaySendTx(
- 'registerValidatorGroup',
- validators.registerValidatorGroup(
- res.flags.name,
- res.flags.url,
- new BigNumber(res.flags.commission)
- )
+ const tx = await validators.registerValidatorGroup(
+ res.flags.name,
+ res.flags.url,
+ new BigNumber(res.flags.commission)
)
+ await displaySendTx('registerValidatorGroup', tx)
}
}
diff --git a/packages/contractkit/src/wrappers/Election.ts b/packages/contractkit/src/wrappers/Election.ts
index 240edae9884..89403022f49 100644
--- a/packages/contractkit/src/wrappers/Election.ts
+++ b/packages/contractkit/src/wrappers/Election.ts
@@ -34,9 +34,13 @@ export interface ValidatorGroupVote {
eligible: boolean
}
+export interface ElectableValidators {
+ min: BigNumber
+ max: BigNumber
+}
+
export interface ElectionConfig {
- minElectableValidators: BigNumber
- maxElectableValidators: BigNumber
+ electableValidators: ElectableValidators
electabilityThreshold: BigNumber
maxNumGroupsVotedFor: BigNumber
}
@@ -47,23 +51,13 @@ export interface ElectionConfig {
export class ElectionWrapper extends BaseWrapper {
activate = proxySend(this.kit, this.contract.methods.activate)
/**
- * Returns the minimum number of validators that can be elected.
- * @returns The minimum number of validators that can be elected.
- */
- minElectableValidators = proxyCall(
- this.contract.methods.minElectableValidators,
- undefined,
- toBigNumber
- )
- /**
- * Returns the maximum number of validators that can be elected.
- * @returns The maximum number of validators that can be elected.
+ * Returns the minimum and maximum number of validators that can be elected.
+ * @returns The minimum and maximum number of validators that can be elected.
*/
- maxElectableValidators = proxyCall(
- this.contract.methods.maxElectableValidators,
- undefined,
- toBigNumber
- )
+ async electableValidators(): Promise {
+ const { min, max } = await this.contract.methods.electableValidators().call()
+ return { min: toBigNumber(min), max: toBigNumber(max) }
+ }
/**
* Returns the current election threshold.
* @returns Election threshold.
@@ -80,8 +74,8 @@ export class ElectionWrapper extends BaseWrapper {
toNumber
)
- getGroupsVotedFor: (account: Address) => Promise = proxyCall(
- this.contract.methods.getGroupsVotedFor
+ getGroupsVotedForByAccount: (account: Address) => Promise = proxyCall(
+ this.contract.methods.getGroupsVotedForByAccount
)
/**
@@ -89,16 +83,14 @@ export class ElectionWrapper extends BaseWrapper {
*/
async getConfig(): Promise {
const res = await Promise.all([
- this.minElectableValidators(),
- this.maxElectableValidators(),
+ this.electableValidators(),
this.electabilityThreshold(),
this.contract.methods.maxNumGroupsVotedFor().call(),
])
return {
- minElectableValidators: res[0],
- maxElectableValidators: res[1],
- electabilityThreshold: res[2],
- maxNumGroupsVotedFor: toBigNumber(res[3]),
+ electableValidators: res[0],
+ electabilityThreshold: res[1],
+ maxNumGroupsVotedFor: toBigNumber(res[2]),
}
}
@@ -120,7 +112,7 @@ export class ElectionWrapper extends BaseWrapper {
(g) => g.address
)
const validatorGroupVotes = await Promise.all(
- validatorGroupAddresses.map((g) => this.contract.methods.getGroupTotalVotes(g).call())
+ validatorGroupAddresses.map((g) => this.contract.methods.getTotalVotesForGroup(g).call())
)
const validatorGroupEligible = await Promise.all(
validatorGroupAddresses.map((g) => this.contract.methods.getGroupEligibility(g).call())
@@ -133,7 +125,7 @@ export class ElectionWrapper extends BaseWrapper {
}
async getEligibleValidatorGroupsVotes(): Promise {
- const res = await this.contract.methods.getEligibleValidatorGroupsVoteTotals().call()
+ const res = await this.contract.methods.getTotalVotesForEligibleValidatorGroups().call()
return zip((a, b) => ({ address: a, votes: new BigNumber(b), eligible: true }), res[0], res[1])
}
@@ -142,13 +134,12 @@ export class ElectionWrapper extends BaseWrapper {
throw new Error(`missing from at new ValdidatorUtils()`)
}
- const value = toBigNumber(await this.contract.methods.getGroupTotalVotes(validatorGroup).call())
+ const value = toBigNumber(
+ await this.contract.methods.getTotalVotesForGroup(validatorGroup).call()
+ )
const { lesser, greater } = await this.findLesserAndGreaterAfterVote(validatorGroup, value)
- return wrapSend(
- this.kit,
- this.contract.methods.markGroupEligible(validatorGroup, lesser, greater)
- )
+ return wrapSend(this.kit, this.contract.methods.markGroupEligible(lesser, greater))
}
async vote(validatorGroup: Address, value: BigNumber): Promise> {
diff --git a/packages/contractkit/src/wrappers/LockedGold.ts b/packages/contractkit/src/wrappers/LockedGold.ts
index 740efa111d1..62a53657862 100644
--- a/packages/contractkit/src/wrappers/LockedGold.ts
+++ b/packages/contractkit/src/wrappers/LockedGold.ts
@@ -78,12 +78,12 @@ export class LockedGoldWrapper extends BaseWrapper {
}
async getAccountSummary(account: string): Promise {
- const nonvoting = await this.getAccountNonvotingLockedGold(args.account)
- const total = await this.getAccountTotalLockedGold(args.account)
- const voter = await this.getVoterFromAccount(args.account)
- const validator = await this.getValidatorFromAccount(args.account)
- const pendingWithdrawals = await this.getPendingWithdrawals(args.account)
- return (info = {
+ const nonvoting = await this.getAccountNonvotingLockedGold(account)
+ const total = await this.getAccountTotalLockedGold(account)
+ const voter = await this.getVoterFromAccount(account)
+ const validator = await this.getValidatorFromAccount(account)
+ const pendingWithdrawals = await this.getPendingWithdrawals(account)
+ return {
lockedGold: {
total,
nonvoting,
@@ -93,7 +93,7 @@ export class LockedGoldWrapper extends BaseWrapper {
validator: eqAddress(validator, account) ? 'None' : validator,
},
pendingWithdrawals,
- })
+ }
}
/**
diff --git a/packages/contractkit/src/wrappers/Validators.ts b/packages/contractkit/src/wrappers/Validators.ts
index 51c0d410caf..0241d35987b 100644
--- a/packages/contractkit/src/wrappers/Validators.ts
+++ b/packages/contractkit/src/wrappers/Validators.ts
@@ -1,7 +1,14 @@
import BigNumber from 'bignumber.js'
import { Address } from '../base'
import { Validators } from '../generated/types/Validators'
-import { BaseWrapper, proxyCall, proxySend, toBigNumber } from './BaseWrapper'
+import {
+ BaseWrapper,
+ CeloTransactionObject,
+ proxyCall,
+ proxySend,
+ toBigNumber,
+ wrapSend,
+} from './BaseWrapper'
import { fromFixed, toFixed } from '@celo/utils/lib/fixidity'
export interface Validator {
@@ -45,8 +52,6 @@ export class ValidatorsWrapper extends BaseWrapper {
addMember = proxySend(this.kit, this.contract.methods.addMember)
removeMember = proxySend(this.kit, this.contract.methods.removeMember)
registerValidator = proxySend(this.kit, this.contract.methods.registerValidator)
- registerValidatorGroup = proxySend(this.kit, this.contract.methods.registerValidatorGroup)
-
async registerValidatorGroup(
name: string,
url: string,
@@ -57,7 +62,7 @@ export class ValidatorsWrapper extends BaseWrapper {
}
return wrapSend(
this.kit,
- this.contract.methods.registerValidatorGroup(name, url, toFixed(commission))
+ this.contract.methods.registerValidatorGroup(name, url, toFixed(commission).toFixed())
)
}
/**
diff --git a/packages/protocol/contracts/governance/Election.sol b/packages/protocol/contracts/governance/Election.sol
index 7e8c3ecd37d..88b27f88028 100644
--- a/packages/protocol/contracts/governance/Election.sol
+++ b/packages/protocol/contracts/governance/Election.sol
@@ -5,14 +5,14 @@ import "openzeppelin-solidity/contracts/math/SafeMath.sol";
import "openzeppelin-solidity/contracts/ownership/Ownable.sol";
import "openzeppelin-solidity/contracts/utils/ReentrancyGuard.sol";
-import "./interfaces/IValidators.sol";
+import "./interfaces/IElection.sol";
import "../common/Initializable.sol";
import "../common/FixidityLib.sol";
import "../common/linkedlists/AddressSortedLinkedList.sol";
import "../common/UsingRegistry.sol";
-contract Election is Ownable, ReentrancyGuard, Initializable, UsingRegistry {
+contract Election is IElection, Ownable, ReentrancyGuard, Initializable, UsingRegistry {
using AddressSortedLinkedList for SortedLinkedList.List;
using FixidityLib for FixidityLib.Fraction;
diff --git a/packages/protocol/contracts/governance/Governance.sol b/packages/protocol/contracts/governance/Governance.sol
index dfe1a2634df..2d863af40fb 100644
--- a/packages/protocol/contracts/governance/Governance.sol
+++ b/packages/protocol/contracts/governance/Governance.sol
@@ -499,10 +499,11 @@ contract Governance is IGovernance, Ownable, Initializable, ReentrancyGuard, Usi
Voter storage voter = voters[account];
// We can upvote a proposal in the queue if we're not already upvoting a proposal in the queue.
uint256 weight = getLockedGold().getAccountTotalLockedGold(account);
+ require(weight > 0, "cannot upvote without locking gold");
+ require(isQueued(proposalId), "cannot upvote a proposal not in the queue");
require(
- isQueued(proposalId) &&
- (voter.upvote.proposalId == 0 || !queue.contains(voter.upvote.proposalId)) &&
- weight > 0
+ voter.upvote.proposalId == 0 || !queue.contains(voter.upvote.proposalId),
+ "cannot upvote more than one queued proposal"
);
uint256 upvotes = queue.getValue(proposalId).add(weight);
queue.update(proposalId, upvotes, lesser, greater);
diff --git a/packages/protocol/contracts/governance/LockedGold.sol b/packages/protocol/contracts/governance/LockedGold.sol
index 11e6d156a88..7ec0cdbe6b6 100644
--- a/packages/protocol/contracts/governance/LockedGold.sol
+++ b/packages/protocol/contracts/governance/LockedGold.sol
@@ -302,7 +302,7 @@ contract LockedGold is ILockedGold, ReentrancyGuard, Initializable, UsingRegistr
*/
function getAccountTotalLockedGold(address account) public view returns (uint256) {
uint256 total = accounts[account].balances.nonvoting;
- return total.add(getElection().getAccountTotalVotes(account));
+ return total.add(getElection().getTotalVotesByAccount(account));
}
/**
diff --git a/packages/protocol/contracts/governance/interfaces/IElection.sol b/packages/protocol/contracts/governance/interfaces/IElection.sol
index 03c68fca96c..24226b3f7a9 100644
--- a/packages/protocol/contracts/governance/interfaces/IElection.sol
+++ b/packages/protocol/contracts/governance/interfaces/IElection.sol
@@ -3,7 +3,7 @@ pragma solidity ^0.5.3;
interface IElection {
function getTotalVotes() external view returns (uint256);
- function getAccountTotalVotes(address) external view returns (uint256);
+ function getTotalVotesByAccount(address) external view returns (uint256);
function markGroupIneligible(address) external;
function electValidators() external view returns (address[] memory);
}
From a5aa266638de1bed45a7e010ea866d6b4cc74f60 Mon Sep 17 00:00:00 2001
From: Asa Oines
Date: Wed, 9 Oct 2019 12:47:57 -0700
Subject: [PATCH 039/149] Fix unit tests
---
.../contracts/governance/Election.sol | 8 +--
.../governance/test/MockElection.sol | 2 +-
.../governance/test/MockLockedGold.sol | 6 +-
packages/protocol/test/governance/election.ts | 58 +++++++++----------
4 files changed, 37 insertions(+), 37 deletions(-)
diff --git a/packages/protocol/contracts/governance/Election.sol b/packages/protocol/contracts/governance/Election.sol
index 77233ced99a..3dd5835e6e6 100644
--- a/packages/protocol/contracts/governance/Election.sol
+++ b/packages/protocol/contracts/governance/Election.sol
@@ -456,10 +456,10 @@ contract Election is IElection, Ownable, ReentrancyGuard, Initializable, UsingRe
function getGroupEpochRewards(address group, uint256 totalEpochRewards) external view returns (uint256) {
// TODO(asa): Is this right?
- if (votes.active.total[group] == 0 || votes.total.active == 0) {
+ if (votes.active.total == 0) {
return 0;
}
- return totalEpochRewards.mul(votes.active.total[group]).div(votes.total.active);
+ return totalEpochRewards.mul(votes.active.forGroup[group].total).div(votes.active.total);
}
function distributeEpochRewards(address group, uint256 value, address lesser, address greater) external {
@@ -473,8 +473,8 @@ contract Election is IElection, Ownable, ReentrancyGuard, Initializable, UsingRe
votes.total.eligible.update(group, newVoteTotal, lesser, greater);
}
- votes.active.total[group] = votes.active.total[group].add(value);
- votes.total.active = votes.total.active.add(value);
+ votes.active.forGroup[group].total = votes.active.forGroup[group].total.add(value);
+ votes.active.total = votes.active.total.add(value);
}
/**
diff --git a/packages/protocol/contracts/governance/test/MockElection.sol b/packages/protocol/contracts/governance/test/MockElection.sol
index da4a5e34e18..212133020b1 100644
--- a/packages/protocol/contracts/governance/test/MockElection.sol
+++ b/packages/protocol/contracts/governance/test/MockElection.sol
@@ -18,7 +18,7 @@ contract MockElection is IElection {
return 0;
}
- function getAccountTotalVotes(address) external view returns (uint256) {
+ function getTotalVotesByAccount(address) external view returns (uint256) {
return 0;
}
diff --git a/packages/protocol/contracts/governance/test/MockLockedGold.sol b/packages/protocol/contracts/governance/test/MockLockedGold.sol
index d0846c7973a..ca6320133ea 100644
--- a/packages/protocol/contracts/governance/test/MockLockedGold.sol
+++ b/packages/protocol/contracts/governance/test/MockLockedGold.sol
@@ -34,15 +34,15 @@ contract MockLockedGold {
authorizedValidators[account] = validator;
}
- function getAccountFromValidator(address accountOrValidator) external view returns (address) {
+ function getAccountFromValidator(address accountOrValidator) external pure returns (address) {
return accountOrValidator;
}
- function getAccountFromActiveValidator(address accountOrValidator) external view returns (address) {
+ function getAccountFromActiveValidator(address accountOrValidator) external pure returns (address) {
return accountOrValidator;
}
- function getAccountFromActiveVoter(address accountOrVoter) external view returns (address) {
+ function getAccountFromActiveVoter(address accountOrVoter) external pure returns (address) {
return accountOrVoter;
}
diff --git a/packages/protocol/test/governance/election.ts b/packages/protocol/test/governance/election.ts
index 765b99ea035..5ec5ba423cf 100644
--- a/packages/protocol/test/governance/election.ts
+++ b/packages/protocol/test/governance/election.ts
@@ -440,23 +440,23 @@ contract('Election', (accounts: string[]) => {
})
it("should decrement the account's pending votes for the group", async () => {
- assertEqualBN(await election.getAccountPendingVotesForGroup(group, voter), 0)
+ assertEqualBN(await election.getPendingVotesForGroupByAccount(group, voter), 0)
})
it("should increment the account's active votes for the group", async () => {
- assertEqualBN(await election.getAccountActiveVotesForGroup(group, voter), value)
+ assertEqualBN(await election.getActiveVotesForGroupByAccount(group, voter), value)
})
it("should not modify the account's total votes for the group", async () => {
- assertEqualBN(await election.getAccountTotalVotesForGroup(group, voter), value)
+ assertEqualBN(await election.getTotalVotesForGroupByAccount(group, voter), value)
})
it("should not modify the account's total votes", async () => {
- assertEqualBN(await election.getAccountTotalVotes(voter), value)
+ assertEqualBN(await election.getTotalVotesByAccount(voter), value)
})
it('should not modify the total votes for the group', async () => {
- assertEqualBN(await election.getGroupTotalVotes(group), value)
+ assertEqualBN(await election.getTotalVotesForGroup(group), value)
})
it('should not modify the total votes', async () => {
@@ -487,35 +487,35 @@ contract('Election', (accounts: string[]) => {
})
it("should not modify the first account's active votes for the group", async () => {
- assertEqualBN(await election.getAccountActiveVotesForGroup(group, voter), value)
+ assertEqualBN(await election.getActiveVotesForGroupByAccount(group, voter), value)
})
it("should not modify the first account's total votes for the group", async () => {
- assertEqualBN(await election.getAccountTotalVotesForGroup(group, voter), value)
+ assertEqualBN(await election.getTotalVotesForGroupByAccount(group, voter), value)
})
it("should not modify the first account's total votes", async () => {
- assertEqualBN(await election.getAccountTotalVotes(voter), value)
+ assertEqualBN(await election.getTotalVotesByAccount(voter), value)
})
it("should decrement the second account's pending votes for the group", async () => {
- assertEqualBN(await election.getAccountPendingVotesForGroup(group, voter2), 0)
+ assertEqualBN(await election.getPendingVotesForGroupByAccount(group, voter2), 0)
})
it("should increment the second account's active votes for the group", async () => {
- assertEqualBN(await election.getAccountActiveVotesForGroup(group, voter2), value2)
+ assertEqualBN(await election.getActiveVotesForGroupByAccount(group, voter2), value2)
})
it("should not modify the second account's total votes for the group", async () => {
- assertEqualBN(await election.getAccountTotalVotesForGroup(group, voter2), value2)
+ assertEqualBN(await election.getTotalVotesForGroupByAccount(group, voter2), value2)
})
it("should not modify the second account's total votes", async () => {
- assertEqualBN(await election.getAccountTotalVotes(voter2), value2)
+ assertEqualBN(await election.getTotalVotesByAccount(voter2), value2)
})
it('should not modify the total votes for the group', async () => {
- assertEqualBN(await election.getGroupTotalVotes(group), value + value2)
+ assertEqualBN(await election.getTotalVotesForGroup(group), value + value2)
})
it('should not modify the total votes', async () => {
@@ -877,7 +877,7 @@ contract('Election', (accounts: string[]) => {
const rewardValue = new BigNumber(1000000)
beforeEach(async () => {
await mockValidators.setMembers(group, [accounts[9]])
- await election.markGroupEligible(group, NULL_ADDRESS, NULL_ADDRESS)
+ await election.markGroupEligible(NULL_ADDRESS, NULL_ADDRESS, { from: group })
await mockLockedGold.setTotalLockedGold(voteValue)
await mockValidators.setNumRegisteredValidators(1)
await mockLockedGold.incrementNonvotingAccountBalance(voter, voteValue)
@@ -894,24 +894,24 @@ contract('Election', (accounts: string[]) => {
it("should increment the account's active votes for the group", async () => {
assertEqualBN(
- await election.getAccountActiveVotesForGroup(group, voter),
+ await election.getActiveVotesForGroupByAccount(group, voter),
voteValue.plus(rewardValue)
)
})
it("should increment the account's total votes for the group", async () => {
assertEqualBN(
- await election.getAccountTotalVotesForGroup(group, voter),
+ await election.getTotalVotesForGroupByAccount(group, voter),
voteValue.plus(rewardValue)
)
})
it("should increment account's total votes", async () => {
- assertEqualBN(await election.getAccountTotalVotes(voter), voteValue.plus(rewardValue))
+ assertEqualBN(await election.getTotalVotesByAccount(voter), voteValue.plus(rewardValue))
})
it('should increment the total votes for the group', async () => {
- assertEqualBN(await election.getGroupTotalVotes(group), voteValue.plus(rewardValue))
+ assertEqualBN(await election.getTotalVotesForGroup(group), voteValue.plus(rewardValue))
})
it('should increment the total votes', async () => {
@@ -927,7 +927,7 @@ contract('Election', (accounts: string[]) => {
const rewardValue2 = new BigNumber(10000000)
beforeEach(async () => {
await mockValidators.setMembers(group2, [accounts[8]])
- await election.markGroupEligible(group2, NULL_ADDRESS, group)
+ await election.markGroupEligible(NULL_ADDRESS, group, { from: group2 })
await mockLockedGold.setTotalLockedGold(voteValue.plus(voteValue2))
await mockValidators.setNumRegisteredValidators(2)
await mockLockedGold.incrementNonvotingAccountBalance(voter2, voteValue2)
@@ -956,49 +956,49 @@ contract('Election', (accounts: string[]) => {
it("should increment the accounts' active votes for both groups", async () => {
assertEqualBN(
- await election.getAccountActiveVotesForGroup(group, voter),
+ await election.getActiveVotesForGroupByAccount(group, voter),
expectedVoterActiveVotesForGroup
)
assertEqualBN(
- await election.getAccountActiveVotesForGroup(group, voter2),
+ await election.getActiveVotesForGroupByAccount(group, voter2),
expectedVoter2ActiveVotesForGroup
)
assertEqualBN(
- await election.getAccountActiveVotesForGroup(group2, voter2),
+ await election.getActiveVotesForGroupByAccount(group2, voter2),
expectedVoter2ActiveVotesForGroup2
)
})
it("should increment the accounts' total votes for both groups", async () => {
assertEqualBN(
- await election.getAccountTotalVotesForGroup(group, voter),
+ await election.getTotalVotesForGroupByAccount(group, voter),
expectedVoterActiveVotesForGroup
)
assertEqualBN(
- await election.getAccountTotalVotesForGroup(group, voter2),
+ await election.getTotalVotesForGroupByAccount(group, voter2),
expectedVoter2ActiveVotesForGroup
)
assertEqualBN(
- await election.getAccountTotalVotesForGroup(group2, voter2),
+ await election.getTotalVotesForGroupByAccount(group2, voter2),
expectedVoter2ActiveVotesForGroup2
)
})
it("should increment the accounts' total votes", async () => {
assertEqualBN(
- await election.getAccountTotalVotes(voter),
+ await election.getTotalVotesByAccount(voter),
expectedVoterActiveVotesForGroup
)
assertEqualBN(
- await election.getAccountTotalVotes(voter2),
+ await election.getTotalVotesByAccount(voter2),
expectedVoter2ActiveVotesForGroup.plus(expectedVoter2ActiveVotesForGroup2)
)
})
it('should increment the total votes for the groups', async () => {
- assertEqualBN(await election.getGroupTotalVotes(group), expectedGroupTotalActiveVotes)
+ assertEqualBN(await election.getTotalVotesForGroup(group), expectedGroupTotalActiveVotes)
assertEqualBN(
- await election.getGroupTotalVotes(group2),
+ await election.getTotalVotesForGroup(group2),
expectedVoter2ActiveVotesForGroup2
)
})
From 115a45734d0f3841c2b037fc57ddc47f6df0dc70 Mon Sep 17 00:00:00 2001
From: Asa Oines
Date: Wed, 9 Oct 2019 13:38:24 -0700
Subject: [PATCH 040/149] Fix migration
---
packages/protocol/migrations/17_elect_validators.ts | 6 +-----
1 file changed, 1 insertion(+), 5 deletions(-)
diff --git a/packages/protocol/migrations/17_elect_validators.ts b/packages/protocol/migrations/17_elect_validators.ts
index 4a9d7838907..4d39ba9fb19 100644
--- a/packages/protocol/migrations/17_elect_validators.ts
+++ b/packages/protocol/migrations/17_elect_validators.ts
@@ -172,11 +172,7 @@ module.exports = async (_deployer: any) => {
console.info(' Marking Validator Group as eligible for election ...')
// @ts-ignore
- const markTx = election.contract.methods.markGroupEligible(
- account.address,
- NULL_ADDRESS,
- NULL_ADDRESS
- )
+ const markTx = election.contract.methods.markGroupEligible(NULL_ADDRESS, NULL_ADDRESS)
await sendTransactionWithPrivateKey(web3, markTx, account.privateKey, {
to: election.address,
})
From 884773bbd39472d9e08cef593cf335613310b107 Mon Sep 17 00:00:00 2001
From: Asa Oines
Date: Wed, 9 Oct 2019 15:37:00 -0700
Subject: [PATCH 041/149] Add addFirstMember function
---
.../src/e2e-tests/governance_tests.ts | 6 +-
packages/celotool/src/e2e-tests/utils.ts | 4 +-
.../contracts/governance/Election.sol | 9 +--
.../contracts/governance/Validators.sol | 39 +++++++++--
.../governance/interfaces/IElection.sol | 1 +
.../governance/test/MockElection.sol | 5 ++
packages/protocol/test/governance/election.ts | 64 +++++++++++--------
.../protocol/test/governance/validators.ts | 32 ++++++----
8 files changed, 108 insertions(+), 52 deletions(-)
diff --git a/packages/celotool/src/e2e-tests/governance_tests.ts b/packages/celotool/src/e2e-tests/governance_tests.ts
index daf8b7c4151..4682d2196f5 100644
--- a/packages/celotool/src/e2e-tests/governance_tests.ts
+++ b/packages/celotool/src/e2e-tests/governance_tests.ts
@@ -27,7 +27,7 @@ describe('governance tests', () => {
before(async function(this: any) {
this.timeout(0)
- await context.hooks.before()
+ // await context.hooks.before()
})
after(context.hooks.after)
@@ -350,10 +350,10 @@ describe('governance tests', () => {
expected: BigNumber
) => {
const currentVotes = new BigNumber(
- await election.methods.getGroupTotalVotes(group).call({}, blockNumber)
+ await election.methods.getTotalVotesForGroup(group).call({}, blockNumber)
)
const previousVotes = new BigNumber(
- await election.methods.getGroupTotalVotes(group).call({}, blockNumber - 1)
+ await election.methods.getTotalVotesForGroup(group).call({}, blockNumber - 1)
)
assert.equal(expected.toFixed(), currentVotes.minus(previousVotes).toFixed())
}
diff --git a/packages/celotool/src/e2e-tests/utils.ts b/packages/celotool/src/e2e-tests/utils.ts
index 98e2b119688..aae32ca88ac 100644
--- a/packages/celotool/src/e2e-tests/utils.ts
+++ b/packages/celotool/src/e2e-tests/utils.ts
@@ -234,8 +234,8 @@ async function waitForPortOpen(host: string, port: number, seconds: number) {
return false
}
-export async function sleep(seconds: number) {
- await execCmd('sleep', [seconds.toString()])
+export function sleep(seconds: number) {
+ return new Promise((resolve) => setTimeout(resolve, seconds * 1000))
}
export async function getEnode(port: number, ws: boolean = false) {
diff --git a/packages/protocol/contracts/governance/Election.sol b/packages/protocol/contracts/governance/Election.sol
index 3dd5835e6e6..3ef758fb56f 100644
--- a/packages/protocol/contracts/governance/Election.sol
+++ b/packages/protocol/contracts/governance/Election.sol
@@ -538,24 +538,21 @@ contract Election is IElection, Ownable, ReentrancyGuard, Initializable, UsingRe
/**
* @notice Marks a group eligible for electing validators.
+ * @param group The address of the validator group.
* @param lesser The address of the group that has received fewer votes than this group.
* @param greater The address of the group that has received more votes than this group.
*/
function markGroupEligible(
+ address group,
address lesser,
address greater
)
external
- nonReentrant
- returns (bool)
+ onlyRegisteredContract(VALIDATORS_REGISTRY_ID)
{
- address group = getLockedGold().getAccountFromValidator(msg.sender);
- require(!votes.total.eligible.contains(group));
- require(getValidators().getGroupNumMembers(group) > 0);
uint256 value = getTotalVotesForGroup(group);
votes.total.eligible.insert(group, value, lesser, greater);
emit ValidatorGroupMarkedEligible(group);
- return true;
}
/**
diff --git a/packages/protocol/contracts/governance/Validators.sol b/packages/protocol/contracts/governance/Validators.sol
index eb65b44969a..5635fb67092 100644
--- a/packages/protocol/contracts/governance/Validators.sol
+++ b/packages/protocol/contracts/governance/Validators.sol
@@ -570,24 +570,55 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
* @param validator The validator to add to the group
* @return True upon success.
* @dev Fails if `validator` has not set their affiliation to this account.
+ * @dev Fails if the group has zero members.
*/
function addMember(address validator) external nonReentrant returns (bool) {
address account = getLockedGold().getAccountFromActiveValidator(msg.sender);
- require(isValidatorGroup(account) && isValidator(validator));
- return _addMember(account, validator);
+ require(groups[account].members.numElements > 0);
+ return _addMember(account, validator, address(0), address(0));
}
/**
- * @notice Adds a member to the end of a validator group's list of members.
+ * @notice Adds the first member to a group's list of members and marks it eligible for election.
* @param validator The validator to add to the group
+ * @param lesser The address of the group that has received fewer votes than this group.
+ * @param greater The address of the group that has received more votes than this group.
* @return True upon success.
* @dev Fails if `validator` has not set their affiliation to this account.
+ * @dev Fails if the group has > 0 members.
*/
- function _addMember(address group, address validator) private returns (bool) {
+ function addFirstMember(
+ address validator,
+ address lesser,
+ address greater
+ )
+ external
+ nonReentrant
+ returns (bool)
+ {
+ address account = getLockedGold().getAccountFromActiveValidator(msg.sender);
+ require(groups[account].members.numElements == 0);
+ return _addMember(account, validator, lesser, greater);
+ }
+
+ /**
+ * @notice Adds a member to the end of a validator group's list of members.
+ * @param group The address of the validator group.
+ * @param validator The validator to add to the group.
+ * @param lesser The address of the group that has received fewer votes than this group.
+ * @param greater The address of the group that has received more votes than this group.
+ * @return True upon success.
+ * @dev Fails if `validator` has not set their affiliation to this account.
+ */
+ function _addMember(address group, address validator, address lesser, address greater) private returns (bool) {
+ require(isValidatorGroup(group) && isValidator(validator));
ValidatorGroup storage _group = groups[group];
require(_group.members.numElements < maxGroupSize, "group would exceed maximum size");
require(validators[validator].affiliation == group && !_group.members.contains(validator));
_group.members.push(validator);
+ if (_group.members.numElements == 1) {
+ getElection().markGroupEligible(group, lesser, greater);
+ }
updateMembershipHistory(validator, group);
emit ValidatorGroupMemberAdded(group, validator);
return true;
diff --git a/packages/protocol/contracts/governance/interfaces/IElection.sol b/packages/protocol/contracts/governance/interfaces/IElection.sol
index 24226b3f7a9..caa2df9a882 100644
--- a/packages/protocol/contracts/governance/interfaces/IElection.sol
+++ b/packages/protocol/contracts/governance/interfaces/IElection.sol
@@ -5,5 +5,6 @@ interface IElection {
function getTotalVotes() external view returns (uint256);
function getTotalVotesByAccount(address) external view returns (uint256);
function markGroupIneligible(address) external;
+ function markGroupEligible(address,address,address) external;
function electValidators() external view returns (address[] memory);
}
diff --git a/packages/protocol/contracts/governance/test/MockElection.sol b/packages/protocol/contracts/governance/test/MockElection.sol
index 212133020b1..47f46d42ca3 100644
--- a/packages/protocol/contracts/governance/test/MockElection.sol
+++ b/packages/protocol/contracts/governance/test/MockElection.sol
@@ -8,12 +8,17 @@ import "../interfaces/IElection.sol";
contract MockElection is IElection {
mapping(address => bool) public isIneligible;
+ mapping(address => bool) public isEligible;
address[] public electedValidators;
function markGroupIneligible(address account) external {
isIneligible[account] = true;
}
+ function markGroupEligible(address account, address, address) external {
+ isEligible[account] = true;
+ }
+
function getTotalVotes() external view returns (uint256) {
return 0;
}
diff --git a/packages/protocol/test/governance/election.ts b/packages/protocol/test/governance/election.ts
index 5ec5ba423cf..4e4bc7a4bfd 100644
--- a/packages/protocol/test/governance/election.ts
+++ b/packages/protocol/test/governance/election.ts
@@ -202,15 +202,15 @@ contract('Election', (accounts: string[]) => {
describe('#markGroupEligible', () => {
const group = accounts[1]
- describe('when the group has members', () => {
+ describe('when called by the registered validators contract', () => {
beforeEach(async () => {
- await mockValidators.setMembers(group, [accounts[9]])
+ await registry.setAddressFor(CeloContractName.Validators, accounts[0])
})
describe('when the group has no votes', () => {
let resp: any
beforeEach(async () => {
- resp = await election.markGroupEligible(NULL_ADDRESS, NULL_ADDRESS, { from: group })
+ resp = await election.markGroupEligible(group, NULL_ADDRESS, NULL_ADDRESS)
})
it('should add the group to the list of eligible groups', async () => {
@@ -230,17 +230,15 @@ contract('Election', (accounts: string[]) => {
describe('when the group has already been marked eligible', () => {
it('should revert', async () => {
- await assertRevert(
- election.markGroupEligible(NULL_ADDRESS, NULL_ADDRESS, { from: group })
- )
+ await assertRevert(election.markGroupEligible(group, NULL_ADDRESS, NULL_ADDRESS))
})
})
})
})
- describe('when the group has no members', () => {
+ describe('not called by the registered validators contract', () => {
it('should revert', async () => {
- await assertRevert(election.markGroupEligible(NULL_ADDRESS, NULL_ADDRESS, { from: group }))
+ await assertRevert(election.markGroupEligible(group, NULL_ADDRESS, NULL_ADDRESS))
})
})
})
@@ -250,7 +248,9 @@ contract('Election', (accounts: string[]) => {
describe('when the group is eligible', () => {
beforeEach(async () => {
await mockValidators.setMembers(group, [accounts[9]])
- await election.markGroupEligible(NULL_ADDRESS, NULL_ADDRESS, { from: group })
+ await registry.setAddressFor(CeloContractName.Validators, accounts[0])
+ await election.markGroupEligible(group, NULL_ADDRESS, NULL_ADDRESS)
+ await registry.setAddressFor(CeloContractName.Validators, mockValidators.address)
})
describe('when called by the registered Validators contract', () => {
@@ -304,8 +304,9 @@ contract('Election', (accounts: string[]) => {
const value = new BigNumber(1000)
describe('when the group is eligible', () => {
beforeEach(async () => {
- await mockValidators.setMembers(group, [accounts[9]])
- await election.markGroupEligible(NULL_ADDRESS, NULL_ADDRESS, { from: group })
+ await registry.setAddressFor(CeloContractName.Validators, accounts[0])
+ await election.markGroupEligible(group, NULL_ADDRESS, NULL_ADDRESS)
+ await registry.setAddressFor(CeloContractName.Validators, mockValidators.address)
})
describe('when the group can receive votes', () => {
@@ -381,8 +382,9 @@ contract('Election', (accounts: string[]) => {
await mockLockedGold.incrementNonvotingAccountBalance(voter, value)
for (let i = 0; i < maxNumGroupsVotedFor.toNumber(); i++) {
newGroup = accounts[i + 2]
- await mockValidators.setMembers(newGroup, [accounts[9]])
- await election.markGroupEligible(group, NULL_ADDRESS, { from: newGroup })
+ await registry.setAddressFor(CeloContractName.Validators, accounts[0])
+ await election.markGroupEligible(newGroup, group, NULL_ADDRESS)
+ await registry.setAddressFor(CeloContractName.Validators, mockValidators.address)
await election.vote(newGroup, 1, group, NULL_ADDRESS)
}
})
@@ -398,6 +400,7 @@ contract('Election', (accounts: string[]) => {
describe('when the group cannot receive votes', () => {
beforeEach(async () => {
await mockLockedGold.setTotalLockedGold(value.div(2).minus(1))
+ await mockValidators.setMembers(group, [accounts[9]])
await mockValidators.setNumRegisteredValidators(1)
assertEqualBN(await election.getNumVotesReceivable(group), value.minus(2))
})
@@ -420,9 +423,11 @@ contract('Election', (accounts: string[]) => {
const group = accounts[1]
const value = 1000
beforeEach(async () => {
- await mockValidators.setMembers(group, [accounts[9]])
- await election.markGroupEligible(NULL_ADDRESS, NULL_ADDRESS, { from: group })
+ await registry.setAddressFor(CeloContractName.Validators, accounts[0])
+ await election.markGroupEligible(group, NULL_ADDRESS, NULL_ADDRESS)
+ await registry.setAddressFor(CeloContractName.Validators, mockValidators.address)
await mockLockedGold.setTotalLockedGold(value)
+ await mockValidators.setMembers(group, [accounts[9]])
await mockValidators.setNumRegisteredValidators(1)
await mockLockedGold.incrementNonvotingAccountBalance(voter, value)
})
@@ -544,8 +549,9 @@ contract('Election', (accounts: string[]) => {
const value = 1000
describe('when the voter has pending votes', () => {
beforeEach(async () => {
- await mockValidators.setMembers(group, [accounts[9]])
- await election.markGroupEligible(NULL_ADDRESS, NULL_ADDRESS, { from: group })
+ await registry.setAddressFor(CeloContractName.Validators, accounts[0])
+ await election.markGroupEligible(group, NULL_ADDRESS, NULL_ADDRESS)
+ await registry.setAddressFor(CeloContractName.Validators, mockValidators.address)
await mockLockedGold.setTotalLockedGold(value)
await mockValidators.setNumRegisteredValidators(1)
await mockLockedGold.incrementNonvotingAccountBalance(voter, value)
@@ -644,8 +650,9 @@ contract('Election', (accounts: string[]) => {
const value = 1000
describe('when the voter has active votes', () => {
beforeEach(async () => {
- await mockValidators.setMembers(group, [accounts[9]])
- await election.markGroupEligible(NULL_ADDRESS, NULL_ADDRESS, { from: group })
+ await registry.setAddressFor(CeloContractName.Validators, accounts[0])
+ await election.markGroupEligible(group, NULL_ADDRESS, NULL_ADDRESS)
+ await registry.setAddressFor(CeloContractName.Validators, mockValidators.address)
await mockLockedGold.setTotalLockedGold(value)
await mockValidators.setNumRegisteredValidators(1)
await mockLockedGold.incrementNonvotingAccountBalance(voter, value)
@@ -769,9 +776,11 @@ contract('Election', (accounts: string[]) => {
await mockValidators.setMembers(group2, [validator5, validator6])
await mockValidators.setMembers(group3, [validator7])
- await election.markGroupEligible(NULL_ADDRESS, NULL_ADDRESS, { from: group1 })
- await election.markGroupEligible(NULL_ADDRESS, group1, { from: group2 })
- await election.markGroupEligible(NULL_ADDRESS, group2, { from: group3 })
+ await registry.setAddressFor(CeloContractName.Validators, accounts[0])
+ await election.markGroupEligible(group1, NULL_ADDRESS, NULL_ADDRESS)
+ await election.markGroupEligible(group2, NULL_ADDRESS, group1)
+ await election.markGroupEligible(group3, NULL_ADDRESS, group2)
+ await registry.setAddressFor(CeloContractName.Validators, mockValidators.address)
for (const voter of [voter1, voter2, voter3]) {
await mockLockedGold.incrementNonvotingAccountBalance(voter.address, voter.weight)
@@ -876,9 +885,11 @@ contract('Election', (accounts: string[]) => {
const voteValue = new BigNumber(1000000)
const rewardValue = new BigNumber(1000000)
beforeEach(async () => {
- await mockValidators.setMembers(group, [accounts[9]])
- await election.markGroupEligible(NULL_ADDRESS, NULL_ADDRESS, { from: group })
+ await registry.setAddressFor(CeloContractName.Validators, accounts[0])
+ await election.markGroupEligible(group, NULL_ADDRESS, NULL_ADDRESS)
+ await registry.setAddressFor(CeloContractName.Validators, mockValidators.address)
await mockLockedGold.setTotalLockedGold(voteValue)
+ await mockValidators.setMembers(group, [accounts[9]])
await mockValidators.setNumRegisteredValidators(1)
await mockLockedGold.incrementNonvotingAccountBalance(voter, voteValue)
await election.vote(group, voteValue, NULL_ADDRESS, NULL_ADDRESS)
@@ -926,8 +937,9 @@ contract('Election', (accounts: string[]) => {
const voteValue2 = new BigNumber(1000000)
const rewardValue2 = new BigNumber(10000000)
beforeEach(async () => {
- await mockValidators.setMembers(group2, [accounts[8]])
- await election.markGroupEligible(NULL_ADDRESS, group, { from: group2 })
+ await registry.setAddressFor(CeloContractName.Validators, accounts[0])
+ await election.markGroupEligible(group2, NULL_ADDRESS, group)
+ await registry.setAddressFor(CeloContractName.Validators, mockValidators.address)
await mockLockedGold.setTotalLockedGold(voteValue.plus(voteValue2))
await mockValidators.setNumRegisteredValidators(2)
await mockLockedGold.incrementNonvotingAccountBalance(voter2, voteValue2)
diff --git a/packages/protocol/test/governance/validators.ts b/packages/protocol/test/governance/validators.ts
index b5768e021d2..94ddc7295e5 100644
--- a/packages/protocol/test/governance/validators.ts
+++ b/packages/protocol/test/governance/validators.ts
@@ -131,7 +131,11 @@ contract('Validators', (accounts: string[]) => {
for (const validator of members) {
await registerValidator(validator)
await validators.affiliate(group, { from: validator })
- await validators.addMember(validator, { from: group })
+ if (validator == members[0]) {
+ await validators.addFirstMember(validator, NULL_ADDRESS, NULL_ADDRESS, { from: group })
+ } else {
+ await validators.addMember(validator, { from: group })
+ }
}
}
@@ -676,7 +680,7 @@ contract('Validators', (accounts: string[]) => {
describe('when the validator is a member of that group', () => {
beforeEach(async () => {
- await validators.addMember(validator, { from: group })
+ await validators.addFirstMember(validator, NULL_ADDRESS, NULL_ADDRESS, { from: group })
})
it('should remove the validator from the group membership list', async () => {
@@ -784,7 +788,7 @@ contract('Validators', (accounts: string[]) => {
describe('when the validator is a member of that group', () => {
beforeEach(async () => {
- await validators.addMember(validator, { from: group })
+ await validators.addFirstMember(validator, NULL_ADDRESS, NULL_ADDRESS, { from: group })
})
it('should remove the validator from the group membership list', async () => {
@@ -854,7 +858,7 @@ contract('Validators', (accounts: string[]) => {
describe('when the validator is a member of the affiliated group', () => {
beforeEach(async () => {
- await validators.addMember(validator, { from: group })
+ await validators.addFirstMember(validator, NULL_ADDRESS, NULL_ADDRESS, { from: group })
})
it('should remove the validator from the group membership list', async () => {
@@ -1029,7 +1033,7 @@ contract('Validators', (accounts: string[]) => {
await registerValidatorGroup(group)
await registerValidator(validator)
await validators.affiliate(group, { from: validator })
- await validators.addMember(validator)
+ await validators.addFirstMember(validator, NULL_ADDRESS, NULL_ADDRESS)
})
it('should revert', async () => {
@@ -1046,7 +1050,7 @@ contract('Validators', (accounts: string[]) => {
await registerValidator(validator)
await registerValidatorGroup(group)
await validators.affiliate(group, { from: validator })
- resp = await validators.addMember(validator)
+ resp = await validators.addFirstMember(validator, NULL_ADDRESS, NULL_ADDRESS)
})
it('should add the member to the list of members', async () => {
@@ -1078,11 +1082,13 @@ contract('Validators', (accounts: string[]) => {
})
it('should revert when the account is not a registered validator group', async () => {
- await assertRevert(validators.addMember(validator, { from: accounts[2] }))
+ await assertRevert(
+ validators.addFirstMember(validator, NULL_ADDRESS, NULL_ADDRESS, { from: accounts[2] })
+ )
})
it('should revert when the member is not a registered validator', async () => {
- await assertRevert(validators.addMember(accounts[2]))
+ await assertRevert(validators.addFirstMember(accounts[2], NULL_ADDRESS, NULL_ADDRESS))
})
it('should revert when trying to add too many members to group', async () => {
@@ -1098,7 +1104,7 @@ contract('Validators', (accounts: string[]) => {
})
it('should revert', async () => {
- await assertRevert(validators.addMember(validator))
+ await assertRevert(validators.addFirstMember(validator, NULL_ADDRESS, NULL_ADDRESS))
})
})
@@ -1293,7 +1299,9 @@ contract('Validators', (accounts: string[]) => {
const currentEpoch = new BigNumber(
Math.floor((await web3.eth.getBlock('latest')).number / EPOCH)
)
- await validators.addMember(validator, { from: groups[i] })
+ await validators.addFirstMember(validator, NULL_ADDRESS, NULL_ADDRESS, {
+ from: groups[i],
+ })
await mineBlocks(EPOCH, web3)
const membershipHistory = await validators.getMembershipHistory(validator)
@@ -1332,7 +1340,9 @@ contract('Validators', (accounts: string[]) => {
it('should always return the correct membership for the last epoch', async () => {
for (let i = 0; i < membershipHistoryLength.plus(1).toNumber(); i++) {
await validators.affiliate(groups[i])
- await validators.addMember(validator, { from: groups[i] })
+ await validators.addFirstMember(validator, NULL_ADDRESS, NULL_ADDRESS, {
+ from: groups[i],
+ })
if (i > 0) {
assert.equal(await validators.getMembershipInLastEpoch(validator), groups[i - 1])
}
From b2858d07035318dfac4f4fe93a14150353d0f043 Mon Sep 17 00:00:00 2001
From: Asa Oines
Date: Wed, 9 Oct 2019 18:05:13 -0700
Subject: [PATCH 042/149] Rework balance requirements
---
packages/contractkit/src/wrappers/Election.ts | 6 +-
.../contractkit/src/wrappers/LockedGold.ts | 16 ++-
.../contracts/governance/Election.sol | 40 +++---
.../contracts/governance/LockedGold.sol | 38 +-----
.../contracts/governance/Validators.sol | 94 +++++++++----
.../governance/interfaces/ILockedGold.sol | 1 -
.../governance/interfaces/IValidators.sol | 1 +
.../governance/test/MockElection.sol | 2 +-
.../governance/test/MockLockedGold.sol | 24 ----
.../governance/test/MockValidators.sol | 9 ++
.../protocol/test/governance/lockedgold.ts | 35 +++--
.../protocol/test/governance/validators.ts | 127 +++++++++---------
12 files changed, 197 insertions(+), 196 deletions(-)
diff --git a/packages/contractkit/src/wrappers/Election.ts b/packages/contractkit/src/wrappers/Election.ts
index 89403022f49..860164238d5 100644
--- a/packages/contractkit/src/wrappers/Election.ts
+++ b/packages/contractkit/src/wrappers/Election.ts
@@ -67,7 +67,11 @@ export class ElectionWrapper extends BaseWrapper {
undefined,
toBigNumber
)
- validatorAddressFromCurrentSet = proxyCall(this.contract.methods.validatorAddressFromCurrentSet)
+ validatorAddressFromCurrentSet: (index: number) => Promise = proxyCall(
+ this.contract.methods.validatorAddressFromCurrentSet,
+ tupleParser(identity)
+ )
+
numberValidatorsInCurrentSet = proxyCall(
this.contract.methods.numberValidatorsInCurrentSet,
undefined,
diff --git a/packages/contractkit/src/wrappers/LockedGold.ts b/packages/contractkit/src/wrappers/LockedGold.ts
index 62a53657862..d636d276649 100644
--- a/packages/contractkit/src/wrappers/LockedGold.ts
+++ b/packages/contractkit/src/wrappers/LockedGold.ts
@@ -45,11 +45,21 @@ export interface LockedGoldConfig {
* Contract for handling deposits needed for voting.
*/
export class LockedGoldWrapper extends BaseWrapper {
- unlock = proxySend(this.kit, this.contract.methods.unlock)
+ unlock: (value: NumberLike) => CeloTransactionObject = proxySend(
+ this.kit,
+ this.contract.methods.unlock,
+ tupleParser(parseNumber)
+ )
createAccount = proxySend(this.kit, this.contract.methods.createAccount)
- withdraw = proxySend(this.kit, this.contract.methods.withdraw)
+ withdraw: (index: number) => CeloTransactionObject = proxySend(
+ this.kit,
+ this.contract.methods.withdraw
+ )
lock = proxySend(this.kit, this.contract.methods.lock)
- relock = proxySend(this.kit, this.contract.methods.relock)
+ relock: (index: number) => CeloTransactionObject = proxySend(
+ this.kit,
+ this.contract.methods.relock
+ )
getAccountTotalLockedGold = proxyCall(
this.contract.methods.getAccountTotalLockedGold,
diff --git a/packages/protocol/contracts/governance/Election.sol b/packages/protocol/contracts/governance/Election.sol
index 88b27f88028..b222873d852 100644
--- a/packages/protocol/contracts/governance/Election.sol
+++ b/packages/protocol/contracts/governance/Election.sol
@@ -18,7 +18,7 @@ contract Election is IElection, Ownable, ReentrancyGuard, Initializable, UsingRe
using FixidityLib for FixidityLib.Fraction;
using SafeMath for uint256;
- struct TimestampedVote {
+ struct PendingVote {
// The value of the vote, in gold.
uint256 value;
// The latest block number at which the vote was cast.
@@ -29,7 +29,7 @@ contract Election is IElection, Ownable, ReentrancyGuard, Initializable, UsingRe
// The total number of pending votes that have been cast for this group.
uint256 total;
// Pending votes cast per voter.
- mapping(address => TimestampedVote) byAccount;
+ mapping(address => PendingVote) byAccount;
}
// Pending votes are those for which no following elections have been held.
@@ -547,9 +547,9 @@ contract Election is IElection, Ownable, ReentrancyGuard, Initializable, UsingRe
GroupPendingVotes storage groupPending = pending.forGroup[group];
groupPending.total = groupPending.total.add(value);
- TimestampedVote storage timestampedVote = groupPending.byAccount[account];
- timestampedVote.value = timestampedVote.value.add(value);
- timestampedVote.blockNumber = block.number;
+ PendingVote storage pendingVote = groupPending.byAccount[account];
+ pendingVote.value = pendingVote.value.add(value);
+ pendingVote.blockNumber = block.number;
}
/**
@@ -565,10 +565,10 @@ contract Election is IElection, Ownable, ReentrancyGuard, Initializable, UsingRe
GroupPendingVotes storage groupPending = pending.forGroup[group];
groupPending.total = groupPending.total.sub(value);
- TimestampedVote storage timestampedVote = groupPending.byAccount[account];
- timestampedVote.value = timestampedVote.value.sub(value);
- if (timestampedVote.value == 0) {
- timestampedVote.blockNumber = 0;
+ PendingVote storage pendingVote = groupPending.byAccount[account];
+ pendingVote.value = pendingVote.value.sub(value);
+ if (pendingVote.value == 0) {
+ pendingVote.blockNumber = 0;
}
}
@@ -655,6 +655,8 @@ contract Election is IElection, Ownable, ReentrancyGuard, Initializable, UsingRe
* @dev Votes are not allowed to be cast that would increase a group's proportion of locked gold
* voting for it to greater than
* (numGroupMembers + 1) / min(maxElectableValidators, numRegisteredValidators)
+ * @dev Note that groups may still receive additional votes via rewards even if this function
+ * returns false.
*/
function canReceiveVotes(address group, uint256 value) public view returns (bool) {
uint256 totalVotesForGroup = getTotalVotesForGroup(group).add(value);
@@ -677,6 +679,7 @@ contract Election is IElection, Ownable, ReentrancyGuard, Initializable, UsingRe
* @dev Votes are not allowed to be cast that would increase a group's proportion of locked gold
* voting for it to greater than
* (numGroupMembers + 1) / min(maxElectableValidators, numRegisteredValidators)
+ * @dev Note that a group's vote total may exceed this number through rewards or config changes.
*/
function getNumVotesReceivable(address group) external view returns (uint256) {
uint256 numerator = getValidators().getGroupNumMembers(group).add(1).mul(
@@ -789,14 +792,7 @@ contract Election is IElection, Ownable, ReentrancyGuard, Initializable, UsingRe
}
}
// Shuffle the validator set using validator-supplied entropy
- bytes32 r = getRandom().random();
- for (uint256 i = electedValidators.length - 1; i > 0; i = i.sub(1)) {
- uint256 j = uint256(r) % (i + 1);
- (electedValidators[i], electedValidators[j]) = (electedValidators[j], electedValidators[i]);
- r = keccak256(abi.encodePacked(r));
- }
-
- return electedValidators;
+ return shuffleArray(electedValidators);
}
/**
@@ -837,4 +833,14 @@ contract Election is IElection, Ownable, ReentrancyGuard, Initializable, UsingRe
}
return (groupIndex, memberElected);
}
+
+ function shuffleArray(address[] memory array) private view returns (address[] memory) {
+ bytes32 r = getRandom().random();
+ for (uint256 i = array.length - 1; i > 0; i = i.sub(1)) {
+ uint256 j = uint256(r) % (i + 1);
+ (array[i], array[j]) = (array[j], array[i]);
+ r = keccak256(abi.encodePacked(r));
+ }
+ return array;
+ }
}
diff --git a/packages/protocol/contracts/governance/LockedGold.sol b/packages/protocol/contracts/governance/LockedGold.sol
index 7ec0cdbe6b6..fcc81a120dc 100644
--- a/packages/protocol/contracts/governance/LockedGold.sol
+++ b/packages/protocol/contracts/governance/LockedGold.sol
@@ -14,13 +14,6 @@ contract LockedGold is ILockedGold, ReentrancyGuard, Initializable, UsingRegistr
using SafeMath for uint256;
- struct MustMaintain {
- // The Locked Gold balance that the account must maintain.
- uint256 value;
- // The timestamp at which the account is no longer subject to these constraints.
- uint256 timestamp;
- }
-
struct Authorizations {
// The address that is authorized to vote on behalf of the account.
address voting;
@@ -41,8 +34,6 @@ contract LockedGold is ILockedGold, ReentrancyGuard, Initializable, UsingRegistr
uint256 nonvoting;
// Gold that has been unlocked and will become available for withdrawal.
PendingWithdrawal[] pendingWithdrawals;
- // Balance requirements imposed on this account.
- MustMaintain requirements;
}
struct Account {
@@ -65,7 +56,6 @@ contract LockedGold is ILockedGold, ReentrancyGuard, Initializable, UsingRegistr
event GoldLocked(address indexed account, uint256 value);
event GoldUnlocked(address indexed account, uint256 value, uint256 available);
event GoldWithdrawn(address indexed account, uint256 value);
- event AccountMustMaintainSet(address indexed account, uint256 value, uint256 timestamp);
function initialize(address registryAddress, uint256 _unlockingPeriod) external initializer {
_transferOwnership(msg.sender);
@@ -197,11 +187,8 @@ contract LockedGold is ILockedGold, ReentrancyGuard, Initializable, UsingRegistr
function unlock(uint256 value) external nonReentrant {
require(isAccount(msg.sender));
Account storage account = accounts[msg.sender];
- MustMaintain storage requirement = account.balances.requirements;
- require(
- now >= requirement.timestamp ||
- getAccountTotalLockedGold(msg.sender).sub(value) >= requirement.value
- );
+ uint256 balanceRequirement = getValidators().getAccountBalanceRequirement(msg.sender);
+ require(balanceRequirement <= getAccountTotalLockedGold(msg.sender).sub(value));
_decrementNonvotingAccountBalance(msg.sender, value);
uint256 available = now.add(unlockingPeriod);
account.balances.pendingWithdrawals.push(PendingWithdrawal(value, available));
@@ -239,27 +226,6 @@ contract LockedGold is ILockedGold, ReentrancyGuard, Initializable, UsingRegistr
emit GoldWithdrawn(msg.sender, value);
}
- /**
- * @notice Sets account locked gold balance requirements.
- * @param account The account for which to set balance requirements.
- * @param value The value that the account must maintain.
- * @param timestamp The timestamp after which the account no longer must maintain this balance.
- * @dev Can only be called by the registered "Validators" smart contract.
- */
- function setAccountMustMaintain(
- address account,
- uint256 value,
- uint256 timestamp
- )
- public
- onlyRegisteredContract(VALIDATORS_REGISTRY_ID)
- nonReentrant
- returns (bool)
- {
- accounts[account].balances.requirements = MustMaintain(value, timestamp);
- emit AccountMustMaintainSet(account, value, timestamp);
- }
-
// TODO(asa): Dedup
/**
* @notice Returns the account associated with `accountOrVoter`.
diff --git a/packages/protocol/contracts/governance/Validators.sol b/packages/protocol/contracts/governance/Validators.sol
index d4bdec86fe5..7ef9cccfb51 100644
--- a/packages/protocol/contracts/governance/Validators.sol
+++ b/packages/protocol/contracts/governance/Validators.sol
@@ -28,16 +28,33 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
address constant PROOF_OF_POSSESSION = address(0xff - 4);
uint256 constant MAX_INT = 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff;
- struct RegistrationRequirements {
+ // If an account has not registered a validator or group, these values represent the minimum
+ // amount of Locked Gold required to do so.
+ // If an account has a registered a validator or validator group, these values represent the
+ // minimum amount of Locked Gold required in order to earn epoch rewards. Furthermore, the
+ // account will not be able to unlock Gold if it would cause the account to fall below
+ // these values.
+ // If an account has deregistered a validator or validator group and is still subject to the
+ // `DeregistrationLockup`, the account will not be able to unlock Gold if it would cause the
+ // account to fall below these values.
+ struct BalanceRequirements {
uint256 group;
uint256 validator;
}
+ // After deregistering a validator or validator group, the account will remain subject to the
+ // current balance requirements for this long (in seconds).
struct DeregistrationLockups {
uint256 group;
uint256 validator;
}
+ // Stores the timestamps at which deregistration of a validator or validator group occurred.
+ struct DeregistrationTimestamps {
+ uint256 group;
+ uint256 validator;
+ }
+
struct ValidatorGroup {
string name;
string url;
@@ -55,9 +72,10 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
mapping(address => ValidatorGroup) private groups;
mapping(address => Validator) private validators;
+ mapping(address => DeregistrationTimestamps) private deregistrationTimestamps;
address[] private _groups;
address[] private _validators;
- RegistrationRequirements public registrationRequirements;
+ BalanceRequirements public balanceRequirements;
DeregistrationLockups public deregistrationLockups;
uint256 public maxGroupSize;
@@ -65,7 +83,7 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
uint256 size
);
- event RegistrationRequirementsSet(
+ event BalanceRequirementsSet(
uint256 group,
uint256 validator
);
@@ -144,7 +162,7 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
{
_transferOwnership(msg.sender);
setRegistry(registryAddress);
- registrationRequirements = RegistrationRequirements(groupRequirement, validatorRequirement);
+ balanceRequirements = BalanceRequirements(groupRequirement, validatorRequirement);
deregistrationLockups = DeregistrationLockups(groupLockup, validatorLockup);
maxGroupSize = _maxGroupSize;
}
@@ -176,7 +194,9 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
* @return True upon success.
* @dev The new requirement is only enforced for future validator or group registrations.
*/
- function setRegistrationRequirements(
+ // TODO(asa): Allow validators to adjust their LockedGold MustMaintain if the registration
+ // requirements fall.
+ function setBalanceRequirements(
uint256 groupRequirement,
uint256 validatorRequirement
)
@@ -185,11 +205,11 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
returns (bool)
{
require(
- groupRequirement != registrationRequirements.group ||
- validatorRequirement != registrationRequirements.validator
+ groupRequirement != balanceRequirements.group ||
+ validatorRequirement != balanceRequirements.validator
);
- registrationRequirements = RegistrationRequirements(groupRequirement, validatorRequirement);
- emit RegistrationRequirementsSet(groupRequirement, validatorRequirement);
+ balanceRequirements = BalanceRequirements(groupRequirement, validatorRequirement);
+ emit BalanceRequirementsSet(groupRequirement, validatorRequirement);
return true;
}
@@ -251,11 +271,10 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
address account = getLockedGold().getAccountFromValidator(msg.sender);
require(!isValidator(account) && !isValidatorGroup(account));
- require(meetsValidatorRegistrationRequirement(account));
+ require(meetsValidatorBalanceRequirements(account));
validators[account] = Validator(name, url, publicKeysData, address(0));
_validators.push(account);
- getLockedGold().setAccountMustMaintain(account, registrationRequirements.validator, MAX_INT);
emit ValidatorRegistered(account, name, url, publicKeysData);
return true;
}
@@ -276,8 +295,8 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
* @param account The account.
* @return Whether an account meets the requirements to register a validator.
*/
- function meetsValidatorRegistrationRequirement(address account) public view returns (bool) {
- return getLockedGold().getAccountTotalLockedGold(account) >= registrationRequirements.validator;
+ function meetsValidatorBalanceRequirements(address account) public view returns (bool) {
+ return getLockedGold().getAccountTotalLockedGold(account) >= balanceRequirements.validator;
}
/**
@@ -285,8 +304,8 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
* @param account The account.
* @return Whether an account meets the requirements to register a group.
*/
- function meetsValidatorGroupRegistrationRequirement(address account) public view returns (bool) {
- return getLockedGold().getAccountTotalLockedGold(account) >= registrationRequirements.group;
+ function meetsValidatorGroupBalanceRequirements(address account) public view returns (bool) {
+ return getLockedGold().getAccountTotalLockedGold(account) >= balanceRequirements.group;
}
/**
@@ -304,11 +323,7 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
}
delete validators[account];
deleteElement(_validators, account, index);
- getLockedGold().setAccountMustMaintain(
- account,
- registrationRequirements.validator,
- now.add(deregistrationLockups.validator)
- );
+ deregistrationTimestamps[account].validator = now;
emit ValidatorDeregistered(account);
return true;
}
@@ -367,14 +382,13 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
require(commission <= FixidityLib.fixed1().unwrap(), "Commission can't be greater than 100%");
address account = getLockedGold().getAccountFromValidator(msg.sender);
require(!isValidator(account) && !isValidatorGroup(account));
- require(meetsValidatorGroupRegistrationRequirement(account));
+ require(meetsValidatorGroupBalanceRequirements(account));
ValidatorGroup storage group = groups[account];
group.name = name;
group.url = url;
group.commission = FixidityLib.wrap(commission);
_groups.push(account);
- getLockedGold().setAccountMustMaintain(account, registrationRequirements.group, MAX_INT);
emit ValidatorGroupRegistered(account, name, url);
return true;
}
@@ -391,11 +405,7 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
require(isValidatorGroup(account) && groups[account].members.numElements == 0);
delete groups[account];
deleteElement(_groups, account, index);
- getLockedGold().setAccountMustMaintain(
- account,
- registrationRequirements.group,
- now.add(deregistrationLockups.group)
- );
+ deregistrationTimestamps[account].group = now;
emit ValidatorGroupDeregistered(account);
return true;
}
@@ -467,6 +477,32 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
return true;
}
+ /**
+ * @notice Returns the locked gold balance requirement for the supplied account.
+ * @param account The account that may have to meet locked gold balance requirements.
+ * @return The locked gold balance requirement for the supplied account.
+ */
+ function getAccountBalanceRequirement(address account) external view returns (uint256) {
+ DeregistrationTimestamps storage timestamps = deregistrationTimestamps[account];
+ if (
+ isValidator(account) ||
+ (timestamps.validator > 0 && now < timestamps.validator.add(deregistrationLockups.validator))
+ ) {
+ return balanceRequirements.validator;
+ }
+ if (
+ isValidatorGroup(account) ||
+ (timestamps.group > 0 && now < timestamps.group.add(deregistrationLockups.group))
+ ) {
+ return balanceRequirements.group;
+ }
+ return 0;
+ }
+
+ function getDeregistrationTimestamps(address account) external view returns (uint256, uint256) {
+ return (deregistrationTimestamps[account].group, deregistrationTimestamps[account].validator);
+ }
+
/**
* @notice Returns validator information.
* @param account The account that registered the validator.
@@ -574,8 +610,8 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
* @notice Returns the Locked Gold requirements to register a validator or group.
* @return The locked gold requirements to register a validator or group.
*/
- function getRegistrationRequirements() external view returns (uint256, uint256) {
- return (registrationRequirements.group, registrationRequirements.validator);
+ function getBalanceRequirements() external view returns (uint256, uint256) {
+ return (balanceRequirements.group, balanceRequirements.validator);
}
/**
diff --git a/packages/protocol/contracts/governance/interfaces/ILockedGold.sol b/packages/protocol/contracts/governance/interfaces/ILockedGold.sol
index 1f961197356..d9a25b9ab64 100644
--- a/packages/protocol/contracts/governance/interfaces/ILockedGold.sol
+++ b/packages/protocol/contracts/governance/interfaces/ILockedGold.sol
@@ -9,5 +9,4 @@ interface ILockedGold {
function decrementNonvotingAccountBalance(address, uint256) external;
function getAccountTotalLockedGold(address) external view returns (uint256);
function getTotalLockedGold() external view returns (uint256);
- function setAccountMustMaintain(address, uint256, uint256) external returns (bool);
}
diff --git a/packages/protocol/contracts/governance/interfaces/IValidators.sol b/packages/protocol/contracts/governance/interfaces/IValidators.sol
index 5bc937ee60f..fa931072877 100644
--- a/packages/protocol/contracts/governance/interfaces/IValidators.sol
+++ b/packages/protocol/contracts/governance/interfaces/IValidators.sol
@@ -2,6 +2,7 @@ pragma solidity ^0.5.3;
interface IValidators {
+ function getAccountBalanceRequirement(address) external view returns (uint256);
function getGroupNumMembers(address) external view returns (uint256);
function getGroupsNumMembers(address[] calldata) external view returns (uint256[] memory);
function getNumRegisteredValidators() external view returns (uint256);
diff --git a/packages/protocol/contracts/governance/test/MockElection.sol b/packages/protocol/contracts/governance/test/MockElection.sol
index da4a5e34e18..212133020b1 100644
--- a/packages/protocol/contracts/governance/test/MockElection.sol
+++ b/packages/protocol/contracts/governance/test/MockElection.sol
@@ -18,7 +18,7 @@ contract MockElection is IElection {
return 0;
}
- function getAccountTotalVotes(address) external view returns (uint256) {
+ function getTotalVotesByAccount(address) external view returns (uint256) {
return 0;
}
diff --git a/packages/protocol/contracts/governance/test/MockLockedGold.sol b/packages/protocol/contracts/governance/test/MockLockedGold.sol
index 970e8079d33..66970442359 100644
--- a/packages/protocol/contracts/governance/test/MockLockedGold.sol
+++ b/packages/protocol/contracts/governance/test/MockLockedGold.sol
@@ -12,19 +12,12 @@ contract MockLockedGold is ILockedGold {
using SafeMath for uint256;
- struct MustMaintain {
- uint256 value;
- uint256 timestamp;
- }
-
struct Authorizations {
address validator;
address voter;
}
mapping(address => uint256) public accountTotalLockedGold;
- // TODO(asa): Rename to minimumBalance
- mapping(address => MustMaintain) public mustMaintain;
mapping(address => uint256) public nonvotingAccountBalance;
mapping(address => address) public authorizedValidators;
uint256 private totalLockedGold;
@@ -54,23 +47,6 @@ contract MockLockedGold is ILockedGold {
nonvotingAccountBalance[account] = nonvotingAccountBalance[account].sub(value);
}
- function setAccountMustMaintain(
- address account,
- uint256 value,
- uint256 timestamp
- )
- external
- returns (bool)
- {
- mustMaintain[account] = MustMaintain(value, timestamp);
- return true;
- }
-
- function getAccountMustMaintain(address account) external view returns (uint256, uint256) {
- MustMaintain storage m = mustMaintain[account];
- return (m.value, m.timestamp);
- }
-
function setAccountTotalLockedGold(address account, uint256 value) external {
accountTotalLockedGold[account] = value;
}
diff --git a/packages/protocol/contracts/governance/test/MockValidators.sol b/packages/protocol/contracts/governance/test/MockValidators.sol
index 68514b13a1d..2f642113fe8 100644
--- a/packages/protocol/contracts/governance/test/MockValidators.sol
+++ b/packages/protocol/contracts/governance/test/MockValidators.sol
@@ -10,6 +10,7 @@ contract MockValidators is IValidators {
mapping(address => bool) private _isValidating;
mapping(address => bool) private _isVoting;
mapping(address => uint256) private numGroupMembers;
+ mapping(address => uint256) private balanceRequirements;
mapping(address => address[]) private members;
uint256 private numRegisteredValidators;
@@ -45,6 +46,14 @@ contract MockValidators is IValidators {
members[group] = _members;
}
+ function setAccountBalanceRequirement(address account, uint256 value) external {
+ balanceRequirements[account] = value;
+ }
+
+ function getAccountBalanceRequirement(address account) external view returns (uint256) {
+ return balanceRequirements[account];
+ }
+
function getTopValidatorsFromGroup(
address group,
uint256 n
diff --git a/packages/protocol/test/governance/lockedgold.ts b/packages/protocol/test/governance/lockedgold.ts
index 6b25af9714f..befc8827ba0 100644
--- a/packages/protocol/test/governance/lockedgold.ts
+++ b/packages/protocol/test/governance/lockedgold.ts
@@ -10,18 +10,21 @@ import BigNumber from 'bignumber.js'
import {
LockedGoldContract,
LockedGoldInstance,
- MockGoldTokenContract,
- MockGoldTokenInstance,
MockElectionContract,
MockElectionInstance,
+ MockGoldTokenContract,
+ MockGoldTokenInstance,
+ MockValidatorsContract,
+ MockValidatorsInstance,
RegistryContract,
RegistryInstance,
} from 'types'
const LockedGold: LockedGoldContract = artifacts.require('LockedGold')
-const Registry: RegistryContract = artifacts.require('Registry')
-const MockGoldToken: MockGoldTokenContract = artifacts.require('MockGoldToken')
const MockElection: MockElectionContract = artifacts.require('MockElection')
+const MockGoldToken: MockGoldTokenContract = artifacts.require('MockGoldToken')
+const MockValidators: MockValidatorsContract = artifacts.require('MockValidators')
+const Registry: RegistryContract = artifacts.require('Registry')
// @ts-ignore
// TODO(mcortesi): Use BN
@@ -36,8 +39,9 @@ contract('LockedGold', (accounts: string[]) => {
const nonOwner = accounts[1]
const unlockingPeriod = 3 * DAY
let lockedGold: LockedGoldInstance
- let registry: RegistryInstance
let mockElection: MockElectionInstance
+ let mockValidators: MockValidatorsInstance
+ let registry: RegistryInstance
const capitalize = (s: string) => {
return s.charAt(0).toUpperCase() + s.slice(1)
@@ -56,11 +60,13 @@ contract('LockedGold', (accounts: string[]) => {
beforeEach(async () => {
const mockGoldToken: MockGoldTokenInstance = await MockGoldToken.new()
- mockElection = await MockElection.new()
lockedGold = await LockedGold.new()
+ mockElection = await MockElection.new()
+ mockValidators = await MockValidators.new()
registry = await Registry.new()
await registry.setAddressFor(CeloContractName.GoldToken, mockGoldToken.address)
await registry.setAddressFor(CeloContractName.Election, mockElection.address)
+ await registry.setAddressFor(CeloContractName.Validators, mockValidators.address)
await lockedGold.initialize(registry.address, unlockingPeriod)
await lockedGold.createAccount()
@@ -338,15 +344,11 @@ contract('LockedGold', (accounts: string[]) => {
})
describe('when there are balance requirements', () => {
- let mustMaintain: any
+ const balanceRequirement = 10
beforeEach(async () => {
// @ts-ignore: TODO(mcortesi) fix typings for TransactionDetails
await lockedGold.lock({ value })
- // Allow ourselves to call `setAccountMustMaintain()`
- await registry.setAddressFor(CeloContractName.Validators, account)
- const timestamp = (await web3.eth.getBlock('latest')).timestamp
- mustMaintain = { value: 100, timestamp: timestamp + DAY }
- await lockedGold.setAccountMustMaintain(account, mustMaintain.value, mustMaintain.timestamp)
+ await mockValidators.setAccountBalanceRequirement(account, balanceRequirement)
})
describe('when unlocking would yield a locked gold balance less than the required value', () => {
@@ -355,18 +357,11 @@ contract('LockedGold', (accounts: string[]) => {
await assertRevert(lockedGold.unlock(value))
})
})
-
- describe('when the the current time is later than the requirement time', () => {
- it('should succeed', async () => {
- await timeTravel(DAY, web3)
- await lockedGold.unlock(value)
- })
- })
})
describe('when unlocking would yield a locked gold balance equal to the required value', () => {
it('should succeed', async () => {
- await lockedGold.unlock(value - mustMaintain.value)
+ await lockedGold.unlock(value - balanceRequirement)
})
})
})
diff --git a/packages/protocol/test/governance/validators.ts b/packages/protocol/test/governance/validators.ts
index df5d6d23970..e11ce9ee871 100644
--- a/packages/protocol/test/governance/validators.ts
+++ b/packages/protocol/test/governance/validators.ts
@@ -46,7 +46,6 @@ const parseValidatorGroupParams = (groupParams: any) => {
const HOUR = 60 * 60
const DAY = 24 * HOUR
-const MAX_UINT256 = new BigNumber(2).pow(256).minus(1)
contract('Validators', (accounts: string[]) => {
let validators: ValidatorsInstance
@@ -64,7 +63,7 @@ contract('Validators', (accounts: string[]) => {
const publicKeysData = '0x' + publicKey + blsPublicKey + blsPoP
const nonOwner = accounts[1]
- const registrationRequirements = { group: new BigNumber(1000), validator: new BigNumber(100) }
+ const balanceRequirements = { group: new BigNumber(1000), validator: new BigNumber(100) }
const deregistrationLockups = {
group: new BigNumber(100 * DAY),
validator: new BigNumber(60 * DAY),
@@ -82,8 +81,8 @@ contract('Validators', (accounts: string[]) => {
await registry.setAddressFor(CeloContractName.Election, mockElection.address)
await validators.initialize(
registry.address,
- registrationRequirements.group,
- registrationRequirements.validator,
+ balanceRequirements.group,
+ balanceRequirements.validator,
deregistrationLockups.group,
deregistrationLockups.validator,
maxGroupSize
@@ -91,7 +90,7 @@ contract('Validators', (accounts: string[]) => {
})
const registerValidator = async (validator: string) => {
- await mockLockedGold.setAccountTotalLockedGold(validator, registrationRequirements.validator)
+ await mockLockedGold.setAccountTotalLockedGold(validator, balanceRequirements.validator)
await validators.registerValidator(
name,
url,
@@ -102,7 +101,7 @@ contract('Validators', (accounts: string[]) => {
}
const registerValidatorGroup = async (group: string) => {
- await mockLockedGold.setAccountTotalLockedGold(group, registrationRequirements.group)
+ await mockLockedGold.setAccountTotalLockedGold(group, balanceRequirements.group)
await validators.registerValidatorGroup(name, url, commission, { from: group })
}
@@ -121,10 +120,10 @@ contract('Validators', (accounts: string[]) => {
assert.equal(owner, accounts[0])
})
- it('should have set the registration requirements', async () => {
- const [group, validator] = await validators.getRegistrationRequirements()
- assertEqualBN(group, registrationRequirements.group)
- assertEqualBN(validator, registrationRequirements.validator)
+ it('should have set the balance requirements', async () => {
+ const [group, validator] = await validators.getBalanceRequirements()
+ assertEqualBN(group, balanceRequirements.group)
+ assertEqualBN(validator, balanceRequirements.validator)
})
it('should have set the deregistration lockups', async () => {
@@ -142,8 +141,8 @@ contract('Validators', (accounts: string[]) => {
await assertRevert(
validators.initialize(
registry.address,
- registrationRequirements.group,
- registrationRequirements.validator,
+ balanceRequirements.group,
+ balanceRequirements.validator,
deregistrationLockups.group,
deregistrationLockups.validator,
maxGroupSize
@@ -152,34 +151,34 @@ contract('Validators', (accounts: string[]) => {
})
})
- describe('#setRegistrationRequirements()', () => {
+ describe('#setBalanceRequirements()', () => {
describe('when the requirements are different', () => {
const newRequirements = {
- group: registrationRequirements.group.plus(1),
- validator: registrationRequirements.validator.plus(1),
+ group: balanceRequirements.group.plus(1),
+ validator: balanceRequirements.validator.plus(1),
}
describe('when called by the owner', () => {
let resp: any
beforeEach(async () => {
- resp = await validators.setRegistrationRequirements(
+ resp = await validators.setBalanceRequirements(
newRequirements.group,
newRequirements.validator
)
})
it('should set the group and validator requirements', async () => {
- const [group, validator] = await validators.getRegistrationRequirements()
+ const [group, validator] = await validators.getBalanceRequirements()
assertEqualBN(group, newRequirements.group)
assertEqualBN(validator, newRequirements.validator)
})
- it('should emit the RegistrationRequirementsSet event', async () => {
+ it('should emit the BalanceRequirementsSet event', async () => {
assert.equal(resp.logs.length, 1)
const log = resp.logs[0]
assertContainSubset(log, {
- event: 'RegistrationRequirementsSet',
+ event: 'BalanceRequirementsSet',
args: {
group: new BigNumber(newRequirements.group),
validator: new BigNumber(newRequirements.validator),
@@ -190,13 +189,9 @@ contract('Validators', (accounts: string[]) => {
describe('when called by a non-owner', () => {
it('should revert', async () => {
await assertRevert(
- validators.setRegistrationRequirements(
- newRequirements.group,
- newRequirements.validator,
- {
- from: nonOwner,
- }
- )
+ validators.setBalanceRequirements(newRequirements.group, newRequirements.validator, {
+ from: nonOwner,
+ })
)
})
})
@@ -205,9 +200,9 @@ contract('Validators', (accounts: string[]) => {
describe('when the requirements are the same', () => {
it('should revert', async () => {
await assertRevert(
- validators.setRegistrationRequirements(
- registrationRequirements.group,
- registrationRequirements.validator
+ validators.setBalanceRequirements(
+ balanceRequirements.group,
+ balanceRequirements.validator
)
)
})
@@ -317,10 +312,7 @@ contract('Validators', (accounts: string[]) => {
let resp: any
describe('when the account is not a registered validator', () => {
beforeEach(async () => {
- await mockLockedGold.setAccountTotalLockedGold(
- validator,
- registrationRequirements.validator
- )
+ await mockLockedGold.setAccountTotalLockedGold(validator, balanceRequirements.validator)
resp = await validators.registerValidator(
name,
url,
@@ -344,10 +336,9 @@ contract('Validators', (accounts: string[]) => {
assert.equal(parsedValidator.publicKeysData, publicKeysData)
})
- it('should set account balance requirements on locked gold', async () => {
- const [value, timestamp] = await mockLockedGold.getAccountMustMaintain(validator)
- assertEqualBN(value, registrationRequirements.validator)
- assertEqualBN(timestamp, MAX_UINT256)
+ it('should set account balance requirements', async () => {
+ const requirement = await validators.getAccountBalanceRequirement(validator)
+ assertEqualBN(requirement, balanceRequirements.validator)
})
it('should emit the ValidatorRegistered event', async () => {
@@ -367,10 +358,7 @@ contract('Validators', (accounts: string[]) => {
describe('when the account is already a registered validator', () => {
beforeEach(async () => {
- await mockLockedGold.setAccountTotalLockedGold(
- validator,
- registrationRequirements.validator
- )
+ await mockLockedGold.setAccountTotalLockedGold(validator, balanceRequirements.validator)
await validators.registerValidator(
name,
url,
@@ -383,7 +371,7 @@ contract('Validators', (accounts: string[]) => {
describe('when the account is already a registered validator', () => {
beforeEach(async () => {
- await mockLockedGold.setAccountTotalLockedGold(validator, registrationRequirements.group)
+ await mockLockedGold.setAccountTotalLockedGold(validator, balanceRequirements.group)
await validators.registerValidatorGroup(name, url, commission)
})
@@ -399,11 +387,11 @@ contract('Validators', (accounts: string[]) => {
})
})
- describe('when the account does not meet the registration requirements', () => {
+ describe('when the account does not meet the balance requirements', () => {
beforeEach(async () => {
await mockLockedGold.setAccountTotalLockedGold(
validator,
- registrationRequirements.validator.minus(1)
+ balanceRequirements.validator.minus(1)
)
})
@@ -438,14 +426,18 @@ contract('Validators', (accounts: string[]) => {
assert.deepEqual(await validators.getRegisteredValidators(), [])
})
- it('should set account balance requirements on locked gold', async () => {
+ it('should preserve account balance requirements', async () => {
+ const requirement = await validators.getAccountBalanceRequirement(validator)
+ assertEqualBN(requirement, balanceRequirements.validator)
+ })
+
+ it('should set the validator deregistration timestamp', async () => {
const latestTimestamp = (await web3.eth.getBlock('latest')).timestamp
- const [value, timestamp] = await mockLockedGold.getAccountMustMaintain(validator)
- assertEqualBN(value, registrationRequirements.validator)
- assertEqualBN(
- timestamp,
- new BigNumber(latestTimestamp).plus(deregistrationLockups.validator)
+ const [groupTimestamp, validatorTimestamp] = await validators.getDeregistrationTimestamps(
+ validator
)
+ assertEqualBN(groupTimestamp, 0)
+ assertEqualBN(validatorTimestamp, latestTimestamp)
})
it('should emit the ValidatorDeregistered event', async () => {
@@ -706,7 +698,7 @@ contract('Validators', (accounts: string[]) => {
let resp: any
describe('when the account is not a registered validator group', () => {
beforeEach(async () => {
- await mockLockedGold.setAccountTotalLockedGold(group, registrationRequirements.group)
+ await mockLockedGold.setAccountTotalLockedGold(group, balanceRequirements.group)
resp = await validators.registerValidatorGroup(name, url, commission)
})
@@ -724,6 +716,11 @@ contract('Validators', (accounts: string[]) => {
assert.equal(parsedGroup.url, url)
})
+ it('should set account balance requirements', async () => {
+ const requirement = await validators.getAccountBalanceRequirement(group)
+ assertEqualBN(requirement, balanceRequirements.group)
+ })
+
it('should emit the ValidatorGroupRegistered event', async () => {
assert.equal(resp.logs.length, 1)
const log = resp.logs[0]
@@ -744,15 +741,13 @@ contract('Validators', (accounts: string[]) => {
})
it('should revert', async () => {
- await assertRevert(
- validators.registerValidatorGroup(name, url, registrationRequirements.group)
- )
+ await assertRevert(validators.registerValidatorGroup(name, url, balanceRequirements.group))
})
})
describe('when the account is already a registered validator group', () => {
beforeEach(async () => {
- await mockLockedGold.setAccountTotalLockedGold(group, registrationRequirements.group)
+ await mockLockedGold.setAccountTotalLockedGold(group, balanceRequirements.group)
await validators.registerValidatorGroup(name, url, commission)
})
@@ -761,12 +756,9 @@ contract('Validators', (accounts: string[]) => {
})
})
- describe('when the account does not meet the registration requirements', () => {
+ describe('when the account does not meet the balance requirements', () => {
beforeEach(async () => {
- await mockLockedGold.setAccountTotalLockedGold(
- group,
- registrationRequirements.group.minus(1)
- )
+ await mockLockedGold.setAccountTotalLockedGold(group, balanceRequirements.group.minus(1))
})
it('should revert', async () => {
@@ -792,11 +784,18 @@ contract('Validators', (accounts: string[]) => {
assert.deepEqual(await validators.getRegisteredValidatorGroups(), [])
})
- it('should set account balance requirements on locked gold', async () => {
+ it('should preserve account balance requirements', async () => {
+ const requirement = await validators.getAccountBalanceRequirement(group)
+ assertEqualBN(requirement, balanceRequirements.group)
+ })
+
+ it('should set the group deregistration timestamp', async () => {
const latestTimestamp = (await web3.eth.getBlock('latest')).timestamp
- const [value, timestamp] = await mockLockedGold.getAccountMustMaintain(group)
- assertEqualBN(value, registrationRequirements.group)
- assertEqualBN(timestamp, new BigNumber(latestTimestamp).plus(deregistrationLockups.group))
+ const [groupTimestamp, validatorTimestamp] = await validators.getDeregistrationTimestamps(
+ group
+ )
+ assertEqualBN(groupTimestamp, latestTimestamp)
+ assertEqualBN(validatorTimestamp, 0)
})
it('should emit the ValidatorGroupDeregistered event', async () => {
From 0492b15d27768fe3863cbd605e6ec7128bc31ea1 Mon Sep 17 00:00:00 2001
From: Asa Oines
Date: Wed, 9 Oct 2019 18:14:20 -0700
Subject: [PATCH 043/149] Fix build issues in contractkit
---
packages/contractkit/src/wrappers/Election.ts | 2 ++
packages/contractkit/src/wrappers/LockedGold.ts | 3 +++
packages/contractkit/src/wrappers/Validators.ts | 12 ++++++------
3 files changed, 11 insertions(+), 6 deletions(-)
diff --git a/packages/contractkit/src/wrappers/Election.ts b/packages/contractkit/src/wrappers/Election.ts
index 860164238d5..24a5dd25d77 100644
--- a/packages/contractkit/src/wrappers/Election.ts
+++ b/packages/contractkit/src/wrappers/Election.ts
@@ -6,10 +6,12 @@ import { Election } from '../generated/types/Election'
import {
BaseWrapper,
CeloTransactionObject,
+ identity,
proxyCall,
proxySend,
toBigNumber,
toNumber,
+ tupleParser,
wrapSend,
} from './BaseWrapper'
diff --git a/packages/contractkit/src/wrappers/LockedGold.ts b/packages/contractkit/src/wrappers/LockedGold.ts
index d636d276649..75c96d77b8d 100644
--- a/packages/contractkit/src/wrappers/LockedGold.ts
+++ b/packages/contractkit/src/wrappers/LockedGold.ts
@@ -7,9 +7,12 @@ import { LockedGold } from '../generated/types/LockedGold'
import {
BaseWrapper,
CeloTransactionObject,
+ NumberLike,
+ parseNumber,
proxyCall,
proxySend,
toBigNumber,
+ tupleParser,
wrapSend,
} from '../wrappers/BaseWrapper'
diff --git a/packages/contractkit/src/wrappers/Validators.ts b/packages/contractkit/src/wrappers/Validators.ts
index 0241d35987b..f75de231747 100644
--- a/packages/contractkit/src/wrappers/Validators.ts
+++ b/packages/contractkit/src/wrappers/Validators.ts
@@ -27,7 +27,7 @@ export interface ValidatorGroup {
commission: BigNumber
}
-export interface RegistrationRequirements {
+export interface BalanceRequirements {
group: BigNumber
validator: BigNumber
}
@@ -38,7 +38,7 @@ export interface DeregistrationLockups {
}
export interface ValidatorsConfig {
- registrationRequirements: RegistrationRequirements
+ balanceRequirements: BalanceRequirements
deregistrationLockups: DeregistrationLockups
maxGroupSize: BigNumber
}
@@ -69,8 +69,8 @@ export class ValidatorsWrapper extends BaseWrapper {
* Returns the current registration requirements.
* @returns Group and validator registration requirements.
*/
- async getRegistrationRequirements(): Promise {
- const res = await this.contract.methods.getRegistrationRequirements().call()
+ async getBalanceRequirements(): Promise {
+ const res = await this.contract.methods.getBalanceRequirements().call()
return {
group: toBigNumber(res[0]),
validator: toBigNumber(res[1]),
@@ -90,12 +90,12 @@ export class ValidatorsWrapper extends BaseWrapper {
*/
async getConfig(): Promise {
const res = await Promise.all([
- this.getRegistrationRequirements(),
+ this.getBalanceRequirements(),
this.getDeregistrationLockups(),
this.contract.methods.maxGroupSize().call(),
])
return {
- registrationRequirements: res[0],
+ balanceRequirements: res[0],
deregistrationLockups: res[1],
maxGroupSize: toBigNumber(res[2]),
}
From 264a7f5cbf2b583fc3a418eadc35d8ff4c9793a0 Mon Sep 17 00:00:00 2001
From: Asa Oines
Date: Wed, 9 Oct 2019 18:58:12 -0700
Subject: [PATCH 044/149] Fix
---
packages/celotool/src/e2e-tests/governance_tests.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/packages/celotool/src/e2e-tests/governance_tests.ts b/packages/celotool/src/e2e-tests/governance_tests.ts
index 4682d2196f5..e2b081352ca 100644
--- a/packages/celotool/src/e2e-tests/governance_tests.ts
+++ b/packages/celotool/src/e2e-tests/governance_tests.ts
@@ -27,7 +27,7 @@ describe('governance tests', () => {
before(async function(this: any) {
this.timeout(0)
- // await context.hooks.before()
+ await context.hooks.before()
})
after(context.hooks.after)
From 99d3fcc860a4490708abe4df2cb6ef42684e3f88 Mon Sep 17 00:00:00 2001
From: Asa Oines
Date: Wed, 9 Oct 2019 18:59:03 -0700
Subject: [PATCH 045/149] Remove registry from governance test
---
.../src/e2e-tests/governance_tests.ts | 24 -------------------
1 file changed, 24 deletions(-)
diff --git a/packages/celotool/src/e2e-tests/governance_tests.ts b/packages/celotool/src/e2e-tests/governance_tests.ts
index ad6dbc5cc86..a4a97f87cb0 100644
--- a/packages/celotool/src/e2e-tests/governance_tests.ts
+++ b/packages/celotool/src/e2e-tests/governance_tests.ts
@@ -50,28 +50,6 @@ const electionAbi = [
},
]
-const registryAbi = [
- {
- constant: true,
- inputs: [
- {
- name: 'identifier',
- type: 'string',
- },
- ],
- name: 'getAddressForString',
- outputs: [
- {
- name: '',
- type: 'address',
- },
- ],
- payable: false,
- stateMutability: 'view',
- type: 'function',
- },
-]
-
const validatorsAbi = [
{
constant: true,
@@ -221,7 +199,6 @@ describe('governance tests', () => {
let election: any
let validators: any
let goldToken: any
- let registry: any
before(async function(this: any) {
this.timeout(0)
@@ -235,7 +212,6 @@ describe('governance tests', () => {
web3 = new Web3('http://localhost:8545')
goldToken = new web3.eth.Contract(erc20Abi, await getContractAddress('GoldTokenProxy'))
validators = new web3.eth.Contract(validatorsAbi, await getContractAddress('ValidatorsProxy'))
- registry = new web3.eth.Contract(registryAbi, '0x000000000000000000000000000000000000ce10')
election = new web3.eth.Contract(electionAbi, await getContractAddress('ElectionProxy'))
}
From 7d9c5cc814450b8440fc5b2a3fbffa02f679a015 Mon Sep 17 00:00:00 2001
From: Asa Oines
Date: Wed, 9 Oct 2019 19:16:16 -0700
Subject: [PATCH 046/149] Fix linting issues
---
packages/cli/src/commands/election/vote.ts | 2 +-
.../cli/src/commands/lockedgold/authorize.ts | 4 +-
.../cli/src/commands/lockedgold/withdraw.ts | 4 +-
.../src/commands/validatorgroup/register.ts | 2 +-
.../contractkit/src/wrappers/Validators.ts | 2 +-
.../docs/command-line-interface/lockedgold.md | 114 +++++-------------
.../docs/command-line-interface/validator.md | 11 +-
.../command-line-interface/validatorgroup.md | 35 +-----
8 files changed, 45 insertions(+), 129 deletions(-)
diff --git a/packages/cli/src/commands/election/vote.ts b/packages/cli/src/commands/election/vote.ts
index ae5f7590454..cf957d74755 100644
--- a/packages/cli/src/commands/election/vote.ts
+++ b/packages/cli/src/commands/election/vote.ts
@@ -1,5 +1,5 @@
-import BigNumber from 'bignumber.js'
import { flags } from '@oclif/command'
+import BigNumber from 'bignumber.js'
import { BaseCommand } from '../../base'
import { displaySendTx } from '../../utils/cli'
import { Flags } from '../../utils/command'
diff --git a/packages/cli/src/commands/lockedgold/authorize.ts b/packages/cli/src/commands/lockedgold/authorize.ts
index 6a66475ee23..aae72011553 100644
--- a/packages/cli/src/commands/lockedgold/authorize.ts
+++ b/packages/cli/src/commands/lockedgold/authorize.ts
@@ -39,9 +39,9 @@ export default class Authorize extends BaseCommand {
this.kit.defaultAccount = res.flags.from
const lockedGold = await this.kit.contracts.getLockedGold()
let tx: any
- if (res.flags.role == 'voter') {
+ if (res.flags.role === 'voter') {
tx = await lockedGold.authorizeVoter(res.flags.from, res.flags.to)
- } else if (res.flags.role == 'validator') {
+ } else if (res.flags.role === 'validator') {
tx = await lockedGold.authorizeValidator(res.flags.from, res.flags.to)
} else {
this.error(`Invalid role provided`)
diff --git a/packages/cli/src/commands/lockedgold/withdraw.ts b/packages/cli/src/commands/lockedgold/withdraw.ts
index 3691d715dc2..06383dea399 100644
--- a/packages/cli/src/commands/lockedgold/withdraw.ts
+++ b/packages/cli/src/commands/lockedgold/withdraw.ts
@@ -37,8 +37,8 @@ export default class Withdraw extends BaseCommand {
break
}
}
- const pendingWithdrawals = await lockedgold.getPendingWithdrawals(flags.from)
- for (const pendingWithdrawal of pendingWithdrawals) {
+ const remainingPendingWithdrawals = await lockedgold.getPendingWithdrawals(flags.from)
+ for (const pendingWithdrawal of remainingPendingWithdrawals) {
console.log(
`Pending withdrawal of value ${pendingWithdrawal.value.toString()} available for withdrawal in ${pendingWithdrawal.time
.minus(currentTime)
diff --git a/packages/cli/src/commands/validatorgroup/register.ts b/packages/cli/src/commands/validatorgroup/register.ts
index a8650e81fee..365bc9cf7f6 100644
--- a/packages/cli/src/commands/validatorgroup/register.ts
+++ b/packages/cli/src/commands/validatorgroup/register.ts
@@ -1,5 +1,5 @@
-import BigNumber from 'bignumber.js'
import { flags } from '@oclif/command'
+import BigNumber from 'bignumber.js'
import { BaseCommand } from '../../base'
import { displaySendTx } from '../../utils/cli'
import { Flags } from '../../utils/command'
diff --git a/packages/contractkit/src/wrappers/Validators.ts b/packages/contractkit/src/wrappers/Validators.ts
index f75de231747..35494ab25fd 100644
--- a/packages/contractkit/src/wrappers/Validators.ts
+++ b/packages/contractkit/src/wrappers/Validators.ts
@@ -1,3 +1,4 @@
+import { fromFixed, toFixed } from '@celo/utils/lib/fixidity'
import BigNumber from 'bignumber.js'
import { Address } from '../base'
import { Validators } from '../generated/types/Validators'
@@ -9,7 +10,6 @@ import {
toBigNumber,
wrapSend,
} from './BaseWrapper'
-import { fromFixed, toFixed } from '@celo/utils/lib/fixidity'
export interface Validator {
address: Address
diff --git a/packages/docs/command-line-interface/lockedgold.md b/packages/docs/command-line-interface/lockedgold.md
index 52b2f762242..ef067a6e750 100644
--- a/packages/docs/command-line-interface/lockedgold.md
+++ b/packages/docs/command-line-interface/lockedgold.md
@@ -1,84 +1,46 @@
---
-description: Manage Locked Gold to participate in governance and earn rewards
+description: View and manage locked Celo Gold
---
## Commands
-### Delegate
+### Authorize
-Delegate validating, voting and reward roles for Locked Gold account
+Authorize validating or voting address for a Locked Gold account
```
USAGE
- $ celocli lockedgold:delegate
+ $ celocli lockedgold:authorize
OPTIONS
- -r, --role=Validating|Voting|Rewards Role to delegate
+ -r, --role=voter|validator Role to delegate
--from=0xc1912fEE45d61C87Cc5EA59DaE31190FFFFf232d (required) Account Address
--to=0xc1912fEE45d61C87Cc5EA59DaE31190FFFFf232d (required) Account Address
EXAMPLE
- delegate --from=0x5409ED021D9299bf6814279A6A1411A7e866A631 --role Voting
- --to=0xc1912fEE45d61C87Cc5EA59DaE31190FFFFf232d
+ authorize --from 0x5409ED021D9299bf6814279A6A1411A7e866A631 --role voter --to
+ 0xc1912fEE45d61C87Cc5EA59DaE31190FFFFf232d
```
-_See code: [packages/cli/src/commands/lockedgold/delegate.ts](https://github.com/celo-org/celo-monorepo/tree/master/packages/cli/src/commands/lockedgold/delegate.ts)_
+_See code: [packages/cli/src/commands/lockedgold/authorize.ts](https://github.com/celo-org/celo-monorepo/tree/master/packages/cli/src/commands/lockedgold/authorize.ts)_
-### List
+### Lock
-View information about all of the account's commitments
+Locks Celo Gold to be used in governance and validator elections.
```
USAGE
- $ celocli lockedgold:list ACCOUNT
-
-EXAMPLE
- list 0x5409ed021d9299bf6814279a6a1411a7e866a631
-```
-
-_See code: [packages/cli/src/commands/lockedgold/list.ts](https://github.com/celo-org/celo-monorepo/tree/master/packages/cli/src/commands/lockedgold/list.ts)_
-
-### Lockup
-
-Create a Locked Gold commitment given notice period and gold amount
-
-```
-USAGE
- $ celocli lockedgold:lockup
-
-OPTIONS
- --from=from (required)
- --goldAmount=goldAmount (required) unit amount of gold token (cGLD)
-
- --noticePeriod=noticePeriod (required) duration (seconds) from notice to withdrawable; doubles as ID of a Locked Gold
- commitment;
-
-EXAMPLE
- lockup --from 0x47e172F6CfB6c7D01C1574fa3E2Be7CC73269D95 --noticePeriod 8640 --goldAmount 1000000000000000000
-```
-
-_See code: [packages/cli/src/commands/lockedgold/lockup.ts](https://github.com/celo-org/celo-monorepo/tree/master/packages/cli/src/commands/lockedgold/lockup.ts)_
-
-### Notify
-
-Notify a Locked Gold commitment given notice period and gold amount
-
-```
-USAGE
- $ celocli lockedgold:notify
+ $ celocli lockedgold:lock
OPTIONS
- --from=0xc1912fEE45d61C87Cc5EA59DaE31190FFFFf232d (required) Account Address
- --goldAmount=goldAmount (required) unit amount of gold token (cGLD)
-
- --noticePeriod=noticePeriod (required) duration (seconds) from notice to withdrawable; doubles
- as ID of a Locked Gold commitment;
+ --from=from (required)
+ --value=value (required) unit amount of Celo Gold (cGLD)
EXAMPLE
- notify --noticePeriod=3600 --goldAmount=500
+ lock --from 0x47e172F6CfB6c7D01C1574fa3E2Be7CC73269D95 --value 1000000000000000000
```
-_See code: [packages/cli/src/commands/lockedgold/notify.ts](https://github.com/celo-org/celo-monorepo/tree/master/packages/cli/src/commands/lockedgold/notify.ts)_
+_See code: [packages/cli/src/commands/lockedgold/lock.ts](https://github.com/celo-org/celo-monorepo/tree/master/packages/cli/src/commands/lockedgold/lock.ts)_
### Register
@@ -97,63 +59,51 @@ EXAMPLE
_See code: [packages/cli/src/commands/lockedgold/register.ts](https://github.com/celo-org/celo-monorepo/tree/master/packages/cli/src/commands/lockedgold/register.ts)_
-### Rewards
+### Show
-Manage rewards for Locked Gold account
+Show Locked Gold information for a given account
```
USAGE
- $ celocli lockedgold:rewards
-
-OPTIONS
- -d, --delegate=0xc1912fEE45d61C87Cc5EA59DaE31190FFFFf232d Delegate rewards to provided account
- -r, --redeem Redeem accrued rewards from Locked Gold
- --from=0xc1912fEE45d61C87Cc5EA59DaE31190FFFFf232d (required) Account Address
+ $ celocli lockedgold:show ACCOUNT
-EXAMPLES
- rewards --redeem
- rewards --delegate=0x56e172F6CfB6c7D01C1574fa3E2Be7CC73269D95
+EXAMPLE
+ show 0x5409ed021d9299bf6814279a6a1411a7e866a631
```
-_See code: [packages/cli/src/commands/lockedgold/rewards.ts](https://github.com/celo-org/celo-monorepo/tree/master/packages/cli/src/commands/lockedgold/rewards.ts)_
+_See code: [packages/cli/src/commands/lockedgold/show.ts](https://github.com/celo-org/celo-monorepo/tree/master/packages/cli/src/commands/lockedgold/show.ts)_
-### Show
+### Unlock
-Show Locked Gold and corresponding account weight of a commitment given ID
+Unlocks Celo Gold, which can be withdrawn after the unlocking period.
```
USAGE
- $ celocli lockedgold:show ACCOUNT
+ $ celocli lockedgold:unlock
OPTIONS
- --availabilityTime=availabilityTime unix timestamp at which withdrawable; doubles as ID of a notified commitment
-
- --noticePeriod=noticePeriod duration (seconds) from notice to withdrawable; doubles as ID of a Locked Gold
- commitment;
+ --from=0xc1912fEE45d61C87Cc5EA59DaE31190FFFFf232d (required) Account Address
+ --value=value (required) unit amount of Celo Gold (cGLD)
-EXAMPLES
- show 0x5409ed021d9299bf6814279a6a1411a7e866a631 --noticePeriod=3600
- show 0x5409ed021d9299bf6814279a6a1411a7e866a631 --availabilityTime=1562206887
+EXAMPLE
+ unlock --from 0x47e172F6CfB6c7D01C1574fa3E2Be7CC73269D95 --value 500000000
```
-_See code: [packages/cli/src/commands/lockedgold/show.ts](https://github.com/celo-org/celo-monorepo/tree/master/packages/cli/src/commands/lockedgold/show.ts)_
+_See code: [packages/cli/src/commands/lockedgold/unlock.ts](https://github.com/celo-org/celo-monorepo/tree/master/packages/cli/src/commands/lockedgold/unlock.ts)_
### Withdraw
-Withdraw notified commitment given availability time
+Withdraw unlocked gold whose unlocking period has passed.
```
USAGE
- $ celocli lockedgold:withdraw AVAILABILITYTIME
-
-ARGUMENTS
- AVAILABILITYTIME unix timestamp at which withdrawable; doubles as ID of a notified commitment
+ $ celocli lockedgold:withdraw
OPTIONS
--from=0xc1912fEE45d61C87Cc5EA59DaE31190FFFFf232d (required) Account Address
EXAMPLE
- withdraw 3600
+ withdraw --from 0x47e172F6CfB6c7D01C1574fa3E2Be7CC73269D95
```
_See code: [packages/cli/src/commands/lockedgold/withdraw.ts](https://github.com/celo-org/celo-monorepo/tree/master/packages/cli/src/commands/lockedgold/withdraw.ts)_
diff --git a/packages/docs/command-line-interface/validator.md b/packages/docs/command-line-interface/validator.md
index 826206472f2..4c72974b8fd 100644
--- a/packages/docs/command-line-interface/validator.md
+++ b/packages/docs/command-line-interface/validator.md
@@ -1,5 +1,5 @@
---
-description: View validator information and register your own
+description: View and manage validators
---
## Commands
@@ -48,19 +48,12 @@ USAGE
OPTIONS
--from=0xc1912fEE45d61C87Cc5EA59DaE31190FFFFf232d (required) Address for the Validator
- --id=id (required)
--name=name (required)
-
- --noticePeriod=noticePeriod (required) Notice period of the Locked Gold commitment. Specify
- multiple notice periods to use the sum of the commitments.
-
--publicKey=0x (required) Public Key
-
--url=url (required)
EXAMPLE
- register --from 0x47e172F6CfB6c7D01C1574fa3E2Be7CC73269D95 --id myID --name myName --noticePeriod 5184000
- --noticePeriod 5184001 --url "http://validator.com" --publicKey
+ register --from 0x47e172F6CfB6c7D01C1574fa3E2Be7CC73269D95 --name myName --url "http://validator.com" --publicKey
0xc52f3fab06e22a54915a8765c4f6826090cfac5e40282b43844bf1c0df83aaa632e55b67869758f2291d1aabe0ebecc7cbf4236aaa45e3e0cfbf
997eda082ae19d3e1d8f49f6b0d8e9a03d80ca07b1d24cf1cc0557bdcc04f5e17a46e35d02d0d411d956dbd5d2d2464eebd7b74ae30005d223780d
785d2abc5644fac7ac29fb0e302bdc80c81a5d45018b68b1045068a4b3a4861c93037685fd0d252d7405011220a66a6257562d0c26dabf64485a1d
diff --git a/packages/docs/command-line-interface/validatorgroup.md b/packages/docs/command-line-interface/validatorgroup.md
index f8e3eda5b7b..eb3596f76d6 100644
--- a/packages/docs/command-line-interface/validatorgroup.md
+++ b/packages/docs/command-line-interface/validatorgroup.md
@@ -1,5 +1,5 @@
---
-description: View validator group information and cast votes
+description: View and manage validator groups
---
## Commands
@@ -20,7 +20,7 @@ _See code: [packages/cli/src/commands/validatorgroup/list.ts](https://github.com
### Member
-Manage members of a Validator Group
+Add or remove members from a Validator Group
```
USAGE
@@ -50,18 +50,13 @@ USAGE
$ celocli validatorgroup:register
OPTIONS
+ --commission=commission (required)
--from=0xc1912fEE45d61C87Cc5EA59DaE31190FFFFf232d (required) Address for the Validator Group
- --id=id (required)
--name=name (required)
-
- --noticePeriod=noticePeriod (required) Notice period of the Locked Gold commitment. Specify
- multiple notice periods to use the sum of the commitments.
-
--url=url (required)
EXAMPLE
- register --from 0x47e172F6CfB6c7D01C1574fa3E2Be7CC73269D95 --id myID --name myName --noticePeriod 5184000
- --noticePeriod 5184001 --url "http://vgroup.com"
+ register --from 0x47e172F6CfB6c7D01C1574fa3E2Be7CC73269D95 --name myName --url "http://vgroup.com" --commission 0.1
```
_See code: [packages/cli/src/commands/validatorgroup/register.ts](https://github.com/celo-org/celo-monorepo/tree/master/packages/cli/src/commands/validatorgroup/register.ts)_
@@ -82,25 +77,3 @@ EXAMPLE
```
_See code: [packages/cli/src/commands/validatorgroup/show.ts](https://github.com/celo-org/celo-monorepo/tree/master/packages/cli/src/commands/validatorgroup/show.ts)_
-
-### Vote
-
-Vote for a Validator Group
-
-```
-USAGE
- $ celocli validatorgroup:vote
-
-OPTIONS
- --current Show voter's current vote
- --for=0xc1912fEE45d61C87Cc5EA59DaE31190FFFFf232d Set vote for ValidatorGroup's address
- --from=0xc1912fEE45d61C87Cc5EA59DaE31190FFFFf232d (required) Voter's address
- --revoke Revoke voter's current vote
-
-EXAMPLES
- vote --from 0x4443d0349e8b3075cba511a0a87796597602a0f1 --for 0x932fee04521f5fcb21949041bf161917da3f588b
- vote --from 0x4443d0349e8b3075cba511a0a87796597602a0f1 --revoke
- vote --from 0x4443d0349e8b3075cba511a0a87796597602a0f1 --current
-```
-
-_See code: [packages/cli/src/commands/validatorgroup/vote.ts](https://github.com/celo-org/celo-monorepo/tree/master/packages/cli/src/commands/validatorgroup/vote.ts)_
From f15e844aee4015b92602d319ab83a6a1f1aac6b2 Mon Sep 17 00:00:00 2001
From: Asa Oines
Date: Wed, 9 Oct 2019 19:16:29 -0700
Subject: [PATCH 047/149] Add missing cli doc
---
.../docs/command-line-interface/election.md | 39 +++++++++++++++++++
1 file changed, 39 insertions(+)
create mode 100644 packages/docs/command-line-interface/election.md
diff --git a/packages/docs/command-line-interface/election.md b/packages/docs/command-line-interface/election.md
new file mode 100644
index 00000000000..6ed0013466e
--- /dev/null
+++ b/packages/docs/command-line-interface/election.md
@@ -0,0 +1,39 @@
+---
+description: View and manage validator elections
+---
+
+## Commands
+
+### Validatorset
+
+Outputs the current validator set
+
+```
+USAGE
+ $ celocli election:validatorset
+
+EXAMPLE
+ validatorset
+```
+
+_See code: [packages/cli/src/commands/election/validatorset.ts](https://github.com/celo-org/celo-monorepo/tree/master/packages/cli/src/commands/election/validatorset.ts)_
+
+### Vote
+
+Vote for a Validator Group in validator elections.
+
+```
+USAGE
+ $ celocli election:vote
+
+OPTIONS
+ --for=0xc1912fEE45d61C87Cc5EA59DaE31190FFFFf232d (required) Set vote for ValidatorGroup's address
+ --from=0xc1912fEE45d61C87Cc5EA59DaE31190FFFFf232d (required) Voter's address
+ --value=value (required) Amount of Gold used to vote for group
+
+EXAMPLE
+ vote --from 0x4443d0349e8b3075cba511a0a87796597602a0f1 --for 0x932fee04521f5fcb21949041bf161917da3f588b, --value
+ 1000000
+```
+
+_See code: [packages/cli/src/commands/election/vote.ts](https://github.com/celo-org/celo-monorepo/tree/master/packages/cli/src/commands/election/vote.ts)_
From 993339ce168c454105a5a685423b331dabea6072 Mon Sep 17 00:00:00 2001
From: Asa Oines
Date: Wed, 9 Oct 2019 19:39:34 -0700
Subject: [PATCH 048/149] Governance end-to-end test passing
---
packages/contractkit/src/wrappers/Election.ts | 13 ------------
.../migrations/17_elect_validators.ts | 20 +++++++++----------
2 files changed, 10 insertions(+), 23 deletions(-)
diff --git a/packages/contractkit/src/wrappers/Election.ts b/packages/contractkit/src/wrappers/Election.ts
index 24a5dd25d77..db0f775c4cd 100644
--- a/packages/contractkit/src/wrappers/Election.ts
+++ b/packages/contractkit/src/wrappers/Election.ts
@@ -135,19 +135,6 @@ export class ElectionWrapper extends BaseWrapper {
return zip((a, b) => ({ address: a, votes: new BigNumber(b), eligible: true }), res[0], res[1])
}
- async markGroupEligible(validatorGroup: Address): Promise> {
- if (this.kit.defaultAccount == null) {
- throw new Error(`missing from at new ValdidatorUtils()`)
- }
-
- const value = toBigNumber(
- await this.contract.methods.getTotalVotesForGroup(validatorGroup).call()
- )
- const { lesser, greater } = await this.findLesserAndGreaterAfterVote(validatorGroup, value)
-
- return wrapSend(this.kit, this.contract.methods.markGroupEligible(lesser, greater))
- }
-
async vote(validatorGroup: Address, value: BigNumber): Promise> {
if (this.kit.defaultAccount == null) {
throw new Error(`missing from at new ValdidatorUtils()`)
diff --git a/packages/protocol/migrations/17_elect_validators.ts b/packages/protocol/migrations/17_elect_validators.ts
index 4d39ba9fb19..4d354c6e4f7 100644
--- a/packages/protocol/migrations/17_elect_validators.ts
+++ b/packages/protocol/migrations/17_elect_validators.ts
@@ -161,22 +161,22 @@ module.exports = async (_deployer: any) => {
)
console.info(' Adding Validators to Validator Group ...')
- for (const key of valKeys) {
+ for (let i = 0; i < valKeys.length; i++) {
+ const key = valKeys[i]
const address = generateAccountAddressFromPrivateKey(key.slice(2))
- // @ts-ignore
- const addTx = validators.contract.methods.addMember(address)
+ let addTx: any
+ if (i == 0) {
+ // @ts-ignore
+ addTx = validators.contract.methods.addFirstMember(address, NULL_ADDRESS, NULL_ADDRESS)
+ } else {
+ // @ts-ignore
+ addTx = validators.contract.methods.addMember(address)
+ }
await sendTransactionWithPrivateKey(web3, addTx, account.privateKey, {
to: validators.address,
})
}
- console.info(' Marking Validator Group as eligible for election ...')
- // @ts-ignore
- const markTx = election.contract.methods.markGroupEligible(NULL_ADDRESS, NULL_ADDRESS)
- await sendTransactionWithPrivateKey(web3, markTx, account.privateKey, {
- to: election.address,
- })
-
console.info(' Voting for Validator Group ...')
// Make another deposit so our vote has more weight.
const minLockedGoldVotePerValidator = 10000
From 44f9a434a4dda6bbac7e057055e6eb84237b8a35 Mon Sep 17 00:00:00 2001
From: Asa Oines
Date: Wed, 9 Oct 2019 19:47:59 -0700
Subject: [PATCH 049/149] Fix migration
---
packages/protocol/migrations/17_elect_validators.ts | 6 +-----
1 file changed, 1 insertion(+), 5 deletions(-)
diff --git a/packages/protocol/migrations/17_elect_validators.ts b/packages/protocol/migrations/17_elect_validators.ts
index 4a9d7838907..4d39ba9fb19 100644
--- a/packages/protocol/migrations/17_elect_validators.ts
+++ b/packages/protocol/migrations/17_elect_validators.ts
@@ -172,11 +172,7 @@ module.exports = async (_deployer: any) => {
console.info(' Marking Validator Group as eligible for election ...')
// @ts-ignore
- const markTx = election.contract.methods.markGroupEligible(
- account.address,
- NULL_ADDRESS,
- NULL_ADDRESS
- )
+ const markTx = election.contract.methods.markGroupEligible(NULL_ADDRESS, NULL_ADDRESS)
await sendTransactionWithPrivateKey(web3, markTx, account.privateKey, {
to: election.address,
})
From f59ca01d01c899797de55ac9d488205695b67ca7 Mon Sep 17 00:00:00 2001
From: Asa Oines
Date: Thu, 10 Oct 2019 12:06:16 -0700
Subject: [PATCH 050/149] Fix CLI build
---
.../cli/src/commands/validatorgroup/member.ts | 9 ++-------
packages/contractkit/src/wrappers/Election.ts | 6 ++++++
.../contractkit/src/wrappers/Validators.ts | 18 +++++++++++++++++-
3 files changed, 25 insertions(+), 8 deletions(-)
diff --git a/packages/cli/src/commands/validatorgroup/member.ts b/packages/cli/src/commands/validatorgroup/member.ts
index 86d126c790d..9df2958813e 100644
--- a/packages/cli/src/commands/validatorgroup/member.ts
+++ b/packages/cli/src/commands/validatorgroup/member.ts
@@ -37,14 +37,9 @@ export default class ValidatorGroupMembers extends BaseCommand {
this.kit.defaultAccount = res.flags.from
const validators = await this.kit.contracts.getValidators()
- const election = await this.kit.contracts.getElection()
-
if (res.flags.accept) {
- await displaySendTx('addMember', validators.addMember((res.args as any).validatorAddress))
- if ((await validators.getGroupNumMembers(res.flags.from)).isEqualTo(1)) {
- const tx = await election.markGroupEligible(res.flags.from)
- await displaySendTx('markGroupEligible', tx)
- }
+ const tx = await validators.addMember((res.args as any).validatorAddress)
+ await displaySendTx('addMember', tx)
} else {
await displaySendTx(
'removeMember',
diff --git a/packages/contractkit/src/wrappers/Election.ts b/packages/contractkit/src/wrappers/Election.ts
index db0f775c4cd..d51b14d27f6 100644
--- a/packages/contractkit/src/wrappers/Election.ts
+++ b/packages/contractkit/src/wrappers/Election.ts
@@ -80,6 +80,12 @@ export class ElectionWrapper extends BaseWrapper {
toNumber
)
+ getTotalVotesForGroup = proxyCall(
+ this.contract.methods.getTotalVotesForGroup,
+ undefined,
+ toBigNumber
+ )
+
getGroupsVotedForByAccount: (account: Address) => Promise = proxyCall(
this.contract.methods.getGroupsVotedForByAccount
)
diff --git a/packages/contractkit/src/wrappers/Validators.ts b/packages/contractkit/src/wrappers/Validators.ts
index 35494ab25fd..becc6a76ed5 100644
--- a/packages/contractkit/src/wrappers/Validators.ts
+++ b/packages/contractkit/src/wrappers/Validators.ts
@@ -49,7 +49,6 @@ export interface ValidatorsConfig {
export class ValidatorsWrapper extends BaseWrapper {
affiliate = proxySend(this.kit, this.contract.methods.affiliate)
deaffiliate = proxySend(this.kit, this.contract.methods.deaffiliate)
- addMember = proxySend(this.kit, this.contract.methods.addMember)
removeMember = proxySend(this.kit, this.contract.methods.removeMember)
registerValidator = proxySend(this.kit, this.contract.methods.registerValidator)
async registerValidatorGroup(
@@ -65,6 +64,23 @@ export class ValidatorsWrapper extends BaseWrapper {
this.contract.methods.registerValidatorGroup(name, url, toFixed(commission).toFixed())
)
}
+ async addMember(member: string): Promise> {
+ if (this.kit.defaultAccount == null) {
+ throw new Error(`missing from at new ValdidatorUtils()`)
+ }
+ // TODO(asa): Support authorized validators
+ const group = this.kit.defaultAccount
+ const numMembers = await this.getGroupNumMembers(group)
+ if (numMembers.isZero()) {
+ const election = await this.kit.contracts.getElection()
+ const voteWeight = await election.getTotalVotesForGroup(group)
+ const { lesser, greater } = await election.findLesserAndGreaterAfterVote(group, voteWeight)
+
+ return wrapSend(this.kit, this.contract.methods.addFirstMember(member, lesser, greater))
+ } else {
+ return wrapSend(this.kit, this.contract.methods.addMember(member))
+ }
+ }
/**
* Returns the current registration requirements.
* @returns Group and validator registration requirements.
From 00f806175083de61c2a2134df8a67e9ffdaff919 Mon Sep 17 00:00:00 2001
From: Asa Oines
Date: Thu, 10 Oct 2019 13:37:57 -0700
Subject: [PATCH 051/149] Add electabilityThreshold enforcement
---
.../contracts/common/UsingPrecompiles.sol | 25 +++++++
.../linkedlists/AddressSortedLinkedList.sol | 27 +++++++
.../contracts/governance/Election.sol | 72 ++++++++++---------
.../contracts/governance/Validators.sol | 47 +++++++++---
.../governance/test/ElectionTest.sol | 9 ++-
.../governance/test/MockLockedGold.sol | 8 ++-
packages/protocol/migrations/12_election.ts | 3 +-
.../migrations/17_elect_validators.ts | 13 ++--
packages/protocol/migrationsConfig.js | 2 +-
packages/protocol/test/governance/election.ts | 33 ++++++++-
10 files changed, 180 insertions(+), 59 deletions(-)
diff --git a/packages/protocol/contracts/common/UsingPrecompiles.sol b/packages/protocol/contracts/common/UsingPrecompiles.sol
index 412a2470697..eca7ee67461 100644
--- a/packages/protocol/contracts/common/UsingPrecompiles.sol
+++ b/packages/protocol/contracts/common/UsingPrecompiles.sol
@@ -88,4 +88,29 @@ contract UsingPrecompiles {
}
return epochNumber;
}
+
+ function validatorAddressFromCurrentSet(uint256 index) external view returns (address) {
+ address validatorAddress;
+ assembly {
+ let newCallDataPosition := mload(0x40)
+ mstore(newCallDataPosition, index)
+ let success := staticcall(5000, 0xfa, newCallDataPosition, 32, 0, 0)
+ returndatacopy(add(newCallDataPosition, 64), 0, 32)
+ validatorAddress := mload(add(newCallDataPosition, 64))
+ }
+
+ return validatorAddress;
+ }
+
+ function numberValidatorsInCurrentSet() external view returns (uint256) {
+ uint256 numberValidators;
+ assembly {
+ let success := staticcall(5000, 0xf9, 0, 0, 0, 0)
+ let returnData := mload(0x40)
+ returndatacopy(returnData, 0, 32)
+ numberValidators := mload(returnData)
+ }
+
+ return numberValidators;
+ }
}
diff --git a/packages/protocol/contracts/common/linkedlists/AddressSortedLinkedList.sol b/packages/protocol/contracts/common/linkedlists/AddressSortedLinkedList.sol
index 61ea7d0fdef..0938c1b486e 100644
--- a/packages/protocol/contracts/common/linkedlists/AddressSortedLinkedList.sol
+++ b/packages/protocol/contracts/common/linkedlists/AddressSortedLinkedList.sol
@@ -1,5 +1,6 @@
pragma solidity ^0.5.3;
+import "openzeppelin-solidity/contracts/math/Math.sol";
import "openzeppelin-solidity/contracts/math/SafeMath.sol";
import "./AddressLinkedList.sol";
import "./SortedLinkedList.sol";
@@ -106,6 +107,32 @@ library AddressSortedLinkedList {
return (keys, values);
}
+ /**
+ * @notice Returns the minimum of `max` and the number of elements in the list > threshold.
+ * @param threshold The number that the element must exceed to be included.
+ * @param max The maximum number returned by this function.
+ * @return The minimum of `max` and the number of elements in the list > threshold.
+ */
+ function numElementsGreaterThan(
+ SortedLinkedList.List storage list,
+ uint256 threshold,
+ uint256 max
+ )
+ public
+ view
+ returns (uint256)
+ {
+ uint256 revisedMax = Math.min(max, list.list.numElements);
+ bytes32 key = list.list.head;
+ for (uint256 i = 0; i < revisedMax; i++) {
+ if (list.getValue(key) < threshold) {
+ return i;
+ }
+ key = list.list.elements[key].previousKey;
+ }
+ return revisedMax;
+ }
+
/**
* @notice Returns the N greatest elements of the list.
* @param n The number of elements to return.
diff --git a/packages/protocol/contracts/governance/Election.sol b/packages/protocol/contracts/governance/Election.sol
index cc90c4d9bb5..5edfc83ff0f 100644
--- a/packages/protocol/contracts/governance/Election.sol
+++ b/packages/protocol/contracts/governance/Election.sol
@@ -13,7 +13,8 @@ import "../common/UsingPrecompiles.sol";
import "../common/UsingRegistry.sol";
-contract Election is IElection, Ownable, ReentrancyGuard, Initializable, UsingRegistry, UsingPrecompiles {
+contract Election is
+ IElection, Ownable, ReentrancyGuard, Initializable, UsingRegistry, UsingPrecompiles {
using AddressSortedLinkedList for SortedLinkedList.List;
using FixidityLib for FixidityLib.Fraction;
@@ -454,7 +455,14 @@ contract Election is IElection, Ownable, ReentrancyGuard, Initializable, UsingRe
return votes.total.eligible.contains(group);
}
- function getGroupEpochRewards(address group, uint256 totalEpochRewards) external view returns (uint256) {
+ function getGroupEpochRewards(
+ address group,
+ uint256 totalEpochRewards
+ )
+ external
+ view
+ returns (uint256)
+ {
// TODO(asa): Is this right?
if (votes.active.total == 0) {
return 0;
@@ -462,12 +470,26 @@ contract Election is IElection, Ownable, ReentrancyGuard, Initializable, UsingRe
return totalEpochRewards.mul(votes.active.forGroup[group].total).div(votes.active.total);
}
- function distributeEpochRewards(address group, uint256 value, address lesser, address greater) external {
+ function distributeEpochRewards(
+ address group,
+ uint256 value,
+ address lesser,
+ address greater
+ )
+ external
+ {
require(msg.sender == address(0));
_distributeEpochRewards(group, value, lesser, greater);
}
- function _distributeEpochRewards(address group, uint256 value, address lesser, address greater) internal {
+ function _distributeEpochRewards(
+ address group,
+ uint256 value,
+ address lesser,
+ address greater
+ )
+ internal
+ {
if (votes.total.eligible.contains(group)) {
uint256 newVoteTotal = votes.total.eligible.getValue(group).add(value);
votes.total.eligible.update(group, newVoteTotal, lesser, greater);
@@ -741,31 +763,6 @@ contract Election is IElection, Ownable, ReentrancyGuard, Initializable, UsingRe
return votes.total.eligible.getElements();
}
- function validatorAddressFromCurrentSet(uint256 index) external view returns (address) {
- address validatorAddress;
- assembly {
- let newCallDataPosition := mload(0x40)
- mstore(newCallDataPosition, index)
- let success := staticcall(5000, 0xfa, newCallDataPosition, 32, 0, 0)
- returndatacopy(add(newCallDataPosition, 64), 0, 32)
- validatorAddress := mload(add(newCallDataPosition, 64))
- }
-
- return validatorAddress;
- }
-
- function numberValidatorsInCurrentSet() external view returns (uint256) {
- uint256 numberValidators;
- assembly {
- let success := staticcall(5000, 0xf9, 0, 0, 0, 0)
- let returnData := mload(0x40)
- returndatacopy(returnData, 0, 32)
- numberValidators := mload(returnData)
- }
-
- return numberValidators;
- }
-
/**
* @notice Returns a list of elected validators with seats allocated to groups via the D'Hondt
* method.
@@ -773,13 +770,18 @@ contract Election is IElection, Ownable, ReentrancyGuard, Initializable, UsingRe
* @dev See https://en.wikipedia.org/wiki/D%27Hondt_method#Allocation for more information.
*/
function electValidators() external view returns (address[] memory) {
- // Only members of these validator groups are eligible for election.
- uint256 maxNumElectionGroups = Math.min(
- electableValidators.max,
- votes.total.eligible.list.numElements
+ // Groups must have at least `electabilityThreshold` proportion of the total votes to be
+ // considered for the election.
+ uint256 requiredVotes = electabilityThreshold.multiply(
+ FixidityLib.newFixed(getTotalVotes())
+ ).fromFixed();
+ // Only consider groups with at least `requiredVotes` but do not consider more groups than the
+ // max number of electable validators.
+ uint256 numElectionGroups = votes.total.eligible.numElementsGreaterThan(
+ requiredVotes,
+ electableValidators.max
);
- // TODO(asa): Filter by > requiredVotes
- address[] memory electionGroups = votes.total.eligible.headN(maxNumElectionGroups);
+ address[] memory electionGroups = votes.total.eligible.headN(numElectionGroups);
uint256[] memory numMembers = getValidators().getGroupsNumMembers(electionGroups);
// Holds the number of members elected for each of the eligible validator groups.
uint256[] memory numMembersElected = new uint256[](electionGroups.length);
diff --git a/packages/protocol/contracts/governance/Validators.sol b/packages/protocol/contracts/governance/Validators.sol
index 24de392bfa6..58b742bf359 100644
--- a/packages/protocol/contracts/governance/Validators.sol
+++ b/packages/protocol/contracts/governance/Validators.sol
@@ -20,7 +20,8 @@ import "../common/UsingPrecompiles.sol";
/**
* @title A contract for registering and electing Validator Groups and Validators.
*/
-contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, UsingRegistry, UsingPrecompiles {
+contract Validators is
+ IValidators, Ownable, ReentrancyGuard, Initializable, UsingRegistry, UsingPrecompiles {
using FixidityLib for FixidityLib.Fraction;
using AddressLinkedList for LinkedList.List;
@@ -186,7 +187,7 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
* @param validatorScoreExponent The exponent used in calculating validator scores.
* @param validatorScoreAdjustmentSpeed The speed at which validator scores are adjusted.
* @param _validatorEpochPayment The duration the above gold remains locked after deregistration.
- * @param _membershipHistoryLength The maximum number of entries for validator membership history.
+ * @param _membershipHistoryLength The max number of entries for validator membership history.
* @param _maxGroupSize The maximum group size.
* @dev Should be called only once.
*/
@@ -249,13 +250,23 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
* @param adjustmentSpeed The speed at which the score is adjusted.
* @return True upon success.
*/
- function setValidatorScoreParameters(uint256 exponent, uint256 adjustmentSpeed) external onlyOwner returns (bool) {
+ function setValidatorScoreParameters(
+ uint256 exponent,
+ uint256 adjustmentSpeed
+ )
+ external
+ onlyOwner
+ returns (bool)
+ {
require(adjustmentSpeed <= FixidityLib.fixed1().unwrap());
require(
exponent != validatorScoreParameters.exponent ||
!FixidityLib.wrap(adjustmentSpeed).equals(validatorScoreParameters.adjustmentSpeed)
);
- validatorScoreParameters = ValidatorScoreParameters(exponent, FixidityLib.wrap(adjustmentSpeed));
+ validatorScoreParameters = ValidatorScoreParameters(
+ exponent,
+ FixidityLib.wrap(adjustmentSpeed)
+ );
emit ValidatorScoreParametersSet(exponent, adjustmentSpeed);
return true;
}
@@ -396,7 +407,13 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
return (validatorScoreParameters.exponent, validatorScoreParameters.adjustmentSpeed.unwrap());
}
- function getMembershipHistory(address account) external view returns (uint256[] memory, address[] memory) {
+ function getMembershipHistory(
+ address account
+ )
+ external
+ view
+ returns (uint256[] memory, address[] memory)
+ {
MembershipHistoryEntry[] memory entries = validators[account].membershipHistory.entries;
uint256[] memory epochs = new uint256[](entries.length);
address[] memory membershipGroups = new address[](entries.length);
@@ -439,7 +456,9 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
18
);
- FixidityLib.Fraction memory epochScore = FixidityLib.wrap(numerator).divide(FixidityLib.wrap(denominator));
+ FixidityLib.Fraction memory epochScore = FixidityLib.wrap(numerator).divide(
+ FixidityLib.wrap(denominator)
+ );
FixidityLib.Fraction memory newComponent = validatorScoreParameters.adjustmentSpeed.multiply(
epochScore
);
@@ -463,12 +482,12 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
function _distributeEpochPayment(address validator) internal {
address account = getLockedGold().getAccountFromValidator(validator);
require(isValidator(account));
- FixidityLib.Fraction memory totalPayment = FixidityLib.newFixed(validatorEpochPayment).multiply(validators[account].score);
+ FixidityLib.Fraction memory totalPayment = FixidityLib.newFixed(
+ validatorEpochPayment
+ ).multiply(validators[account].score);
address group = getMembershipInLastEpoch(account);
uint256 groupPayment = totalPayment.multiply(groups[group].commission).fromFixed();
uint256 validatorPayment = totalPayment.fromFixed().sub(groupPayment);
- // For some reason, one validator seems to be getting the full payment (not less commission)
- // Perhaps, getMembershipInLastEpoch is returning 0? Probably what's happening...
getStableToken().mint(group, groupPayment);
getStableToken().mint(account, validatorPayment);
}
@@ -620,7 +639,15 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
* @return True upon success.
* @dev Fails if `validator` has not set their affiliation to this account.
*/
- function _addMember(address group, address validator, address lesser, address greater) private returns (bool) {
+ function _addMember(
+ address group,
+ address validator,
+ address lesser,
+ address greater
+ )
+ private
+ returns (bool)
+ {
require(isValidatorGroup(group) && isValidator(validator));
ValidatorGroup storage _group = groups[group];
require(_group.members.numElements < maxGroupSize, "group would exceed maximum size");
diff --git a/packages/protocol/contracts/governance/test/ElectionTest.sol b/packages/protocol/contracts/governance/test/ElectionTest.sol
index 383e915ad11..37e1bb4dcb6 100644
--- a/packages/protocol/contracts/governance/test/ElectionTest.sol
+++ b/packages/protocol/contracts/governance/test/ElectionTest.sol
@@ -8,7 +8,14 @@ import "../../common/FixidityLib.sol";
*/
contract ElectionTest is Election {
- function distributeEpochRewards(address group, uint256 value, address lesser, address greater) external {
+ function distributeEpochRewards(
+ address group,
+ uint256 value,
+ address lesser,
+ address greater
+ )
+ external
+ {
return _distributeEpochRewards(group, value, lesser, greater);
}
}
diff --git a/packages/protocol/contracts/governance/test/MockLockedGold.sol b/packages/protocol/contracts/governance/test/MockLockedGold.sol
index e92dd7b4a45..848338a9326 100644
--- a/packages/protocol/contracts/governance/test/MockLockedGold.sol
+++ b/packages/protocol/contracts/governance/test/MockLockedGold.sol
@@ -31,7 +31,13 @@ contract MockLockedGold {
return accountOrValidator;
}
- function getAccountFromActiveValidator(address accountOrValidator) external pure returns (address) {
+ function getAccountFromActiveValidator(
+ address accountOrValidator
+ )
+ external
+ pure
+ returns (address)
+ {
return accountOrValidator;
}
diff --git a/packages/protocol/migrations/12_election.ts b/packages/protocol/migrations/12_election.ts
index 84c89838fa4..dedc26a72b9 100644
--- a/packages/protocol/migrations/12_election.ts
+++ b/packages/protocol/migrations/12_election.ts
@@ -1,3 +1,4 @@
+import { toFixed } from '@celo/utils/lib/fixidity'
import { CeloContractName } from '@celo/protocol/lib/registry-utils'
import { deploymentForCoreContract } from '@celo/protocol/lib/web3-utils'
import { config } from '@celo/protocol/migrationsConfig'
@@ -9,7 +10,7 @@ const initializeArgs = async (): Promise => {
config.election.minElectableValidators,
config.election.maxElectableValidators,
config.election.maxVotesPerAccount,
- config.election.electabilityThreshold,
+ toFixed(config.election.electabilityThreshold).toFixed(),
]
}
diff --git a/packages/protocol/migrations/17_elect_validators.ts b/packages/protocol/migrations/17_elect_validators.ts
index 4d354c6e4f7..38c6bf9f163 100644
--- a/packages/protocol/migrations/17_elect_validators.ts
+++ b/packages/protocol/migrations/17_elect_validators.ts
@@ -164,14 +164,11 @@ module.exports = async (_deployer: any) => {
for (let i = 0; i < valKeys.length; i++) {
const key = valKeys[i]
const address = generateAccountAddressFromPrivateKey(key.slice(2))
- let addTx: any
- if (i == 0) {
- // @ts-ignore
- addTx = validators.contract.methods.addFirstMember(address, NULL_ADDRESS, NULL_ADDRESS)
- } else {
- // @ts-ignore
- addTx = validators.contract.methods.addMember(address)
- }
+ // @ts-ignore
+ const addTx =
+ i === 0
+ ? validators.contract.methods.addFirstMember(address, NULL_ADDRESS, NULL_ADDRESS)
+ : validators.contract.methods.addMember(address)
await sendTransactionWithPrivateKey(web3, addTx, account.privateKey, {
to: validators.address,
})
diff --git a/packages/protocol/migrationsConfig.js b/packages/protocol/migrationsConfig.js
index a4006e9a971..94c84141777 100644
--- a/packages/protocol/migrationsConfig.js
+++ b/packages/protocol/migrationsConfig.js
@@ -21,7 +21,7 @@ const DefaultConfig = {
minElectableValidators: '22',
maxElectableValidators: '100',
maxVotesPerAccount: 3,
- electabilityThreshold: '0', // no threshold
+ electabilityThreshold: 1 / 100,
},
exchange: {
spread: 5 / 1000,
diff --git a/packages/protocol/test/governance/election.ts b/packages/protocol/test/governance/election.ts
index 4e4bc7a4bfd..a2a0c43c16c 100644
--- a/packages/protocol/test/governance/election.ts
+++ b/packages/protocol/test/governance/election.ts
@@ -6,7 +6,7 @@ import {
NULL_ADDRESS,
mineBlocks,
} from '@celo/protocol/lib/test-utils'
-import { toFixed } from '@celo/utils/lib/fixidity'
+import { fromFixed, toFixed } from '@celo/utils/lib/fixidity'
import BigNumber from 'bignumber.js'
import {
MockLockedGoldContract,
@@ -46,7 +46,7 @@ contract('Election', (accounts: string[]) => {
max: new BigNumber(6),
}
const maxNumGroupsVotedFor = new BigNumber(3)
- const electabilityThreshold = new BigNumber(0)
+ const electabilityThreshold = toFixed(1 / 100)
beforeEach(async () => {
election = await ElectionTest.new()
@@ -867,6 +867,35 @@ contract('Election', (accounts: string[]) => {
})
})
+ describe('when a group does not receive `electabilityThresholdVotes', () => {
+ beforeEach(async () => {
+ const thresholdExcludingGroup3 = (voter3.weight + 1) / totalLockedGold
+ await election.setElectabilityThreshold(toFixed(thresholdExcludingGroup3))
+ await election.vote(group1, voter1.weight, group2, NULL_ADDRESS, { from: voter1.address })
+ await election.vote(group2, voter2.weight, NULL_ADDRESS, group1, { from: voter2.address })
+ await election.vote(group3, voter3.weight, NULL_ADDRESS, group2, { from: voter3.address })
+ const totalVotes = await election.getTotalVotesForEligibleValidatorGroups()
+ console.log(totalVotes[1].map((x) => x.toFixed()))
+ console.log(fromFixed(await election.getElectabilityThreshold()).toFixed())
+ console.log((await election.getTotalVotes()).toFixed())
+ console.log((await election.getRequiredVotes()).toFixed())
+ console.log((await election.getNumElectionGroups()).toFixed())
+ console.log(await election.getElectionGroups())
+ console.log(group1, group2, group3)
+ })
+
+ it('should not elect any members from that group', async () => {
+ assertSameAddresses(await election.electValidators(), [
+ validator1,
+ validator2,
+ validator3,
+ validator4,
+ validator5,
+ validator6,
+ ])
+ })
+ })
+
describe('when there are not enough electable validators', () => {
beforeEach(async () => {
await election.vote(group2, voter2.weight, group1, NULL_ADDRESS, { from: voter2.address })
From aba85529225bd21f74dee5e84a85f4044288429b Mon Sep 17 00:00:00 2001
From: Asa Oines
Date: Thu, 10 Oct 2019 13:56:21 -0700
Subject: [PATCH 052/149] Don't pay out epoch payments unless validator and
group meet balance requirements
---
.../contracts/governance/Election.sol | 2 +-
.../contracts/governance/Validators.sol | 24 ++++++---
.../migrations/17_elect_validators.ts | 7 +--
.../protocol/test/governance/validators.ts | 50 ++++++++++++++++---
4 files changed, 65 insertions(+), 18 deletions(-)
diff --git a/packages/protocol/contracts/governance/Election.sol b/packages/protocol/contracts/governance/Election.sol
index 5edfc83ff0f..aafe0cc039c 100644
--- a/packages/protocol/contracts/governance/Election.sol
+++ b/packages/protocol/contracts/governance/Election.sol
@@ -463,10 +463,10 @@ contract Election is
view
returns (uint256)
{
- // TODO(asa): Is this right?
if (votes.active.total == 0) {
return 0;
}
+
return totalEpochRewards.mul(votes.active.forGroup[group].total).div(votes.active.total);
}
diff --git a/packages/protocol/contracts/governance/Validators.sol b/packages/protocol/contracts/governance/Validators.sol
index 58b742bf359..c1232c7c1e7 100644
--- a/packages/protocol/contracts/governance/Validators.sol
+++ b/packages/protocol/contracts/governance/Validators.sol
@@ -482,14 +482,22 @@ contract Validators is
function _distributeEpochPayment(address validator) internal {
address account = getLockedGold().getAccountFromValidator(validator);
require(isValidator(account));
- FixidityLib.Fraction memory totalPayment = FixidityLib.newFixed(
- validatorEpochPayment
- ).multiply(validators[account].score);
address group = getMembershipInLastEpoch(account);
- uint256 groupPayment = totalPayment.multiply(groups[group].commission).fromFixed();
- uint256 validatorPayment = totalPayment.fromFixed().sub(groupPayment);
- getStableToken().mint(group, groupPayment);
- getStableToken().mint(account, validatorPayment);
+ // Both the validator and the group must maintain the minimum locked gold balance in order to
+ // receive epoch payments.
+ bool meetsBalanceRequirements = (
+ getLockedGold().getAccountTotalLockedGold(group) >= getAccountBalanceRequirement(group) &&
+ getLockedGold().getAccountTotalLockedGold(account) >= getAccountBalanceRequirement(account)
+ );
+ if (meetsBalanceRequirements) {
+ FixidityLib.Fraction memory totalPayment = FixidityLib.newFixed(
+ validatorEpochPayment
+ ).multiply(validators[account].score);
+ uint256 groupPayment = totalPayment.multiply(groups[group].commission).fromFixed();
+ uint256 validatorPayment = totalPayment.fromFixed().sub(groupPayment);
+ getStableToken().mint(group, groupPayment);
+ getStableToken().mint(account, validatorPayment);
+ }
}
/**
@@ -706,7 +714,7 @@ contract Validators is
* @param account The account that may have to meet locked gold balance requirements.
* @return The locked gold balance requirement for the supplied account.
*/
- function getAccountBalanceRequirement(address account) external view returns (uint256) {
+ function getAccountBalanceRequirement(address account) public view returns (uint256) {
DeregistrationTimestamps storage timestamps = deregistrationTimestamps[account];
if (
isValidator(account) ||
diff --git a/packages/protocol/migrations/17_elect_validators.ts b/packages/protocol/migrations/17_elect_validators.ts
index 38c6bf9f163..40a28f47452 100644
--- a/packages/protocol/migrations/17_elect_validators.ts
+++ b/packages/protocol/migrations/17_elect_validators.ts
@@ -164,11 +164,12 @@ module.exports = async (_deployer: any) => {
for (let i = 0; i < valKeys.length; i++) {
const key = valKeys[i]
const address = generateAccountAddressFromPrivateKey(key.slice(2))
- // @ts-ignore
const addTx =
i === 0
- ? validators.contract.methods.addFirstMember(address, NULL_ADDRESS, NULL_ADDRESS)
- : validators.contract.methods.addMember(address)
+ ? // @ts-ignore
+ validators.contract.methods.addFirstMember(address, NULL_ADDRESS, NULL_ADDRESS)
+ : // @ts-ignore
+ validators.contract.methods.addMember(address)
await sendTransactionWithPrivateKey(web3, addTx, account.privateKey, {
to: validators.address,
})
diff --git a/packages/protocol/test/governance/validators.ts b/packages/protocol/test/governance/validators.ts
index 96ff46eef48..d034dc03dd5 100644
--- a/packages/protocol/test/governance/validators.ts
+++ b/packages/protocol/test/governance/validators.ts
@@ -1363,7 +1363,7 @@ contract('Validators', (accounts: string[]) => {
})
})
- describe('#distributeEpochPayment', () => {
+ describe.only('#distributeEpochPayment', () => {
const validator = accounts[0]
const group = accounts[1]
let mockStableToken: MockStableTokenInstance
@@ -1385,15 +1385,53 @@ contract('Validators', (accounts: string[]) => {
const expectedValidatorPayment = expectedTotalPayment.minus(expectedGroupPayment)
beforeEach(async () => {
await validators.updateValidatorScore(validator, toFixed(uptime))
- await validators.distributeEpochPayment(validator)
})
- it('should pay the validator', async () => {
- assertEqualBN(await mockStableToken.balanceOf(validator), expectedValidatorPayment)
+ describe('when the validator and group meet the balance requirements', () => {
+ beforeEach(async () => {
+ await validators.distributeEpochPayment(validator)
+ })
+
+ it('should pay the validator', async () => {
+ assertEqualBN(await mockStableToken.balanceOf(validator), expectedValidatorPayment)
+ })
+
+ it('should pay the group', async () => {
+ assertEqualBN(await mockStableToken.balanceOf(group), expectedGroupPayment)
+ })
+ })
+
+ describe('when the validator does not meet the balance requirements', () => {
+ beforeEach(async () => {
+ await mockLockedGold.setAccountTotalLockedGold(
+ validator,
+ balanceRequirements.validator.minus(1)
+ )
+ await validators.distributeEpochPayment(validator)
+ })
+
+ it('should not pay the validator', async () => {
+ assertEqualBN(await mockStableToken.balanceOf(validator), 0)
+ })
+
+ it('should not pay the group', async () => {
+ assertEqualBN(await mockStableToken.balanceOf(group), 0)
+ })
})
- it('should pay the group', async () => {
- assertEqualBN(await mockStableToken.balanceOf(group), expectedGroupPayment)
+ describe('when the validator does not meet the balance requirements', () => {
+ beforeEach(async () => {
+ await mockLockedGold.setAccountTotalLockedGold(group, balanceRequirements.group.minus(1))
+ await validators.distributeEpochPayment(validator)
+ })
+
+ it('should not pay the validator', async () => {
+ assertEqualBN(await mockStableToken.balanceOf(validator), 0)
+ })
+
+ it('should not pay the group', async () => {
+ assertEqualBN(await mockStableToken.balanceOf(group), 0)
+ })
})
})
})
From 16f476c85cec6e3b5a041e3e087f0032af97ed49 Mon Sep 17 00:00:00 2001
From: Asa Oines
Date: Thu, 10 Oct 2019 14:54:55 -0700
Subject: [PATCH 053/149] Don't pay out epoch rewards unless the group meets
balance requirements
---
.../contracts/governance/Election.sol | 11 +-
packages/protocol/test/governance/election.ts | 107 ++++++++++++++++--
2 files changed, 106 insertions(+), 12 deletions(-)
diff --git a/packages/protocol/contracts/governance/Election.sol b/packages/protocol/contracts/governance/Election.sol
index aafe0cc039c..08f300721e5 100644
--- a/packages/protocol/contracts/governance/Election.sol
+++ b/packages/protocol/contracts/governance/Election.sol
@@ -463,11 +463,16 @@ contract Election is
view
returns (uint256)
{
- if (votes.active.total == 0) {
+ bool meetsBalanceRequirements = (
+ getLockedGold().getAccountTotalLockedGold(group) >=
+ getValidators().getAccountBalanceRequirement(group)
+ );
+
+ if (meetsBalanceRequirements && votes.active.total > 0) {
+ return totalEpochRewards.mul(votes.active.forGroup[group].total).div(votes.active.total);
+ } else {
return 0;
}
-
- return totalEpochRewards.mul(votes.active.forGroup[group].total).div(votes.active.total);
}
function distributeEpochRewards(
diff --git a/packages/protocol/test/governance/election.ts b/packages/protocol/test/governance/election.ts
index a2a0c43c16c..46dfce00a89 100644
--- a/packages/protocol/test/governance/election.ts
+++ b/packages/protocol/test/governance/election.ts
@@ -6,7 +6,7 @@ import {
NULL_ADDRESS,
mineBlocks,
} from '@celo/protocol/lib/test-utils'
-import { fromFixed, toFixed } from '@celo/utils/lib/fixidity'
+import { toFixed } from '@celo/utils/lib/fixidity'
import BigNumber from 'bignumber.js'
import {
MockLockedGoldContract,
@@ -874,14 +874,6 @@ contract('Election', (accounts: string[]) => {
await election.vote(group1, voter1.weight, group2, NULL_ADDRESS, { from: voter1.address })
await election.vote(group2, voter2.weight, NULL_ADDRESS, group1, { from: voter2.address })
await election.vote(group3, voter3.weight, NULL_ADDRESS, group2, { from: voter3.address })
- const totalVotes = await election.getTotalVotesForEligibleValidatorGroups()
- console.log(totalVotes[1].map((x) => x.toFixed()))
- console.log(fromFixed(await election.getElectabilityThreshold()).toFixed())
- console.log((await election.getTotalVotes()).toFixed())
- console.log((await election.getRequiredVotes()).toFixed())
- console.log((await election.getNumElectionGroups()).toFixed())
- console.log(await election.getElectionGroups())
- console.log(group1, group2, group3)
})
it('should not elect any members from that group', async () => {
@@ -908,6 +900,103 @@ contract('Election', (accounts: string[]) => {
})
})
+ describe('#getGroupEpochRewards', () => {
+ const voter = accounts[0]
+ const group1 = accounts[1]
+ const group2 = accounts[2]
+ const voteValue1 = new BigNumber(2000000)
+ const voteValue2 = new BigNumber(1000000)
+ const totalRewardValue = new BigNumber(3000000)
+ const balanceRequirement = new BigNumber(1000000)
+ beforeEach(async () => {
+ await registry.setAddressFor(CeloContractName.Validators, accounts[0])
+ await election.markGroupEligible(group1, NULL_ADDRESS, NULL_ADDRESS)
+ await election.markGroupEligible(group2, NULL_ADDRESS, group1)
+ await registry.setAddressFor(CeloContractName.Validators, mockValidators.address)
+ await mockLockedGold.setTotalLockedGold(voteValue1.plus(voteValue2))
+ await mockValidators.setMembers(group1, [accounts[8]])
+ await mockValidators.setMembers(group2, [accounts[9]])
+ await mockValidators.setNumRegisteredValidators(2)
+ await mockLockedGold.incrementNonvotingAccountBalance(voter, voteValue1.plus(voteValue2))
+ await election.vote(group1, voteValue1, group2, NULL_ADDRESS)
+ await election.vote(group2, voteValue2, NULL_ADDRESS, group1)
+ await mockValidators.setAccountBalanceRequirement(group1, balanceRequirement)
+ await mockValidators.setAccountBalanceRequirement(group2, balanceRequirement)
+ })
+
+ describe('when one group has active votes', () => {
+ beforeEach(async () => {
+ await mineBlocks(EPOCH, web3)
+ await election.activate(group1)
+ })
+
+ describe('when the group meets the balance requirements ', () => {
+ beforeEach(async () => {
+ await mockLockedGold.setAccountTotalLockedGold(group1, balanceRequirement)
+ })
+
+ it('should return the total reward value', async () => {
+ assertEqualBN(
+ await election.getGroupEpochRewards(group1, totalRewardValue),
+ totalRewardValue
+ )
+ })
+ })
+
+ describe('when the group does not meet the balance requirements ', () => {
+ beforeEach(async () => {
+ await mockLockedGold.setAccountTotalLockedGold(group1, balanceRequirement.minus(1))
+ })
+
+ it('should return zero', async () => {
+ assertEqualBN(await election.getGroupEpochRewards(group1, totalRewardValue), 0)
+ })
+ })
+ })
+
+ describe('when two groups have active votes', () => {
+ const balanceRequirement = new BigNumber(1000000)
+ const expectedGroup1EpochRewards = voteValue1
+ .div(voteValue1.plus(voteValue2))
+ .times(totalRewardValue)
+ .dp(0)
+ beforeEach(async () => {
+ await mineBlocks(EPOCH, web3)
+ await election.activate(group1)
+ await election.activate(group2)
+ })
+
+ describe('when one group meets the balance requirements ', () => {
+ beforeEach(async () => {
+ await mockLockedGold.setAccountTotalLockedGold(group1, balanceRequirement)
+ })
+
+ it('should return the proportional reward value for that group', async () => {
+ assertEqualBN(
+ await election.getGroupEpochRewards(group1, totalRewardValue),
+ expectedGroup1EpochRewards
+ )
+ })
+
+ it('should return zero for the other group', async () => {
+ assertEqualBN(await election.getGroupEpochRewards(group2, totalRewardValue), 0)
+ })
+ })
+ })
+
+ describe('when the group does not have active votes', () => {
+ describe('when the group meets the balance requirements ', () => {
+ beforeEach(async () => {
+ await mockLockedGold.setAccountTotalLockedGold(group1, balanceRequirement)
+ })
+
+ it('should return zero', async () => {
+ assertEqualBN(await election.getGroupEpochRewards(group1, totalRewardValue), 0)
+ })
+ })
+ })
+ })
+
describe('#distributeEpochRewards', () => {
const voter = accounts[0]
const group = accounts[1]
From 3616cc689db05d8338e7735be5c56a06ad7f80a9 Mon Sep 17 00:00:00 2001
From: Asa Oines
Date: Thu, 10 Oct 2019 15:10:27 -0700
Subject: [PATCH 054/149] Fix migrations
---
packages/protocol/migrations/12_election.ts | 2 +-
.../migrations/17_elect_validators.ts | 22 +++++++++++--------
2 files changed, 14 insertions(+), 10 deletions(-)
diff --git a/packages/protocol/migrations/12_election.ts b/packages/protocol/migrations/12_election.ts
index dedc26a72b9..94bc55add3a 100644
--- a/packages/protocol/migrations/12_election.ts
+++ b/packages/protocol/migrations/12_election.ts
@@ -1,7 +1,7 @@
-import { toFixed } from '@celo/utils/lib/fixidity'
import { CeloContractName } from '@celo/protocol/lib/registry-utils'
import { deploymentForCoreContract } from '@celo/protocol/lib/web3-utils'
import { config } from '@celo/protocol/migrationsConfig'
+import { toFixed } from '@celo/utils/lib/fixidity'
import { ElectionInstance } from 'types'
const initializeArgs = async (): Promise => {
diff --git a/packages/protocol/migrations/17_elect_validators.ts b/packages/protocol/migrations/17_elect_validators.ts
index 40a28f47452..61fa8fc3d02 100644
--- a/packages/protocol/migrations/17_elect_validators.ts
+++ b/packages/protocol/migrations/17_elect_validators.ts
@@ -164,15 +164,19 @@ module.exports = async (_deployer: any) => {
for (let i = 0; i < valKeys.length; i++) {
const key = valKeys[i]
const address = generateAccountAddressFromPrivateKey(key.slice(2))
- const addTx =
- i === 0
- ? // @ts-ignore
- validators.contract.methods.addFirstMember(address, NULL_ADDRESS, NULL_ADDRESS)
- : // @ts-ignore
- validators.contract.methods.addMember(address)
- await sendTransactionWithPrivateKey(web3, addTx, account.privateKey, {
- to: validators.address,
- })
+ if (i === 0) {
+ // @ts-ignore
+ const addTx = validators.contract.methods.addFirstMember(address, NULL_ADDRESS, NULL_ADDRESS)
+ await sendTransactionWithPrivateKey(web3, addTx, account.privateKey, {
+ to: validators.address,
+ })
+ } else {
+ // @ts-ignore
+ const addTx = validators.contract.methods.addMember(address)
+ await sendTransactionWithPrivateKey(web3, addTx, account.privateKey, {
+ to: validators.address,
+ })
+ }
}
console.info(' Voting for Validator Group ...')
From 64016a72558b4030630a7ae5dd8408e5f7192daa Mon Sep 17 00:00:00 2001
From: Asa Oines
Date: Fri, 11 Oct 2019 12:10:53 -0700
Subject: [PATCH 055/149] Beef up documentation
---
packages/celotool/src/e2e-tests/utils.ts | 4 +-
packages/contractkit/src/wrappers/Election.ts | 24 ++++++++++
.../contractkit/src/wrappers/LockedGold.ts | 44 +++++++++++++++++++
.../contractkit/src/wrappers/Validators.ts | 4 ++
.../contracts/governance/Election.sol | 19 ++++++++
.../contracts/governance/Validators.sol | 5 +++
6 files changed, 98 insertions(+), 2 deletions(-)
diff --git a/packages/celotool/src/e2e-tests/utils.ts b/packages/celotool/src/e2e-tests/utils.ts
index 98e2b119688..aae32ca88ac 100644
--- a/packages/celotool/src/e2e-tests/utils.ts
+++ b/packages/celotool/src/e2e-tests/utils.ts
@@ -234,8 +234,8 @@ async function waitForPortOpen(host: string, port: number, seconds: number) {
return false
}
-export async function sleep(seconds: number) {
- await execCmd('sleep', [seconds.toString()])
+export function sleep(seconds: number) {
+ return new Promise((resolve) => setTimeout(resolve, seconds * 1000))
}
export async function getEnode(port: number, ws: boolean = false) {
diff --git a/packages/contractkit/src/wrappers/Election.ts b/packages/contractkit/src/wrappers/Election.ts
index 24a5dd25d77..4da80f58f8b 100644
--- a/packages/contractkit/src/wrappers/Election.ts
+++ b/packages/contractkit/src/wrappers/Election.ts
@@ -80,6 +80,11 @@ export class ElectionWrapper extends BaseWrapper {
toNumber
)
+ /**
+ * 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.
+ */
getGroupsVotedForByAccount: (account: Address) => Promise = proxyCall(
this.contract.methods.getGroupsVotedForByAccount
)
@@ -100,6 +105,9 @@ export class ElectionWrapper extends BaseWrapper {
}
}
+ /**
+ * Returns the addresses in the current validator set.
+ */
async getValidatorSetAddresses(): Promise {
const numberValidators = await this.numberValidatorsInCurrentSet()
@@ -112,6 +120,9 @@ export class ElectionWrapper extends BaseWrapper {
return Promise.all(validatorAddressPromises)
}
+ /**
+ * Returns the current registered validator groups and their total votes and eligibility.
+ */
async getValidatorGroupsVotes(): Promise {
const validators = await this.kit.contracts.getValidators()
const validatorGroupAddresses = (await validators.getRegisteredValidatorGroups()).map(
@@ -130,11 +141,19 @@ export class ElectionWrapper extends BaseWrapper {
}))
}
+ /**
+ * Returns the current eligible validator groups and their total votes.
+ */
async getEligibleValidatorGroupsVotes(): Promise {
const res = await this.contract.methods.getTotalVotesForEligibleValidatorGroups().call()
return zip((a, b) => ({ address: a, votes: new BigNumber(b), eligible: true }), res[0], res[1])
}
+ /**
+ * Marks a group eligible for electing validators.
+ * @param lesser The address of the group that has received fewer votes than this group.
+ * @param greater The address of the group that has received more votes than this group.
+ */
async markGroupEligible(validatorGroup: Address): Promise> {
if (this.kit.defaultAccount == null) {
throw new Error(`missing from at new ValdidatorUtils()`)
@@ -148,6 +167,11 @@ export class ElectionWrapper extends BaseWrapper {
return wrapSend(this.kit, this.contract.methods.markGroupEligible(lesser, greater))
}
+ /**
+ * 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.
+ */
async vote(validatorGroup: Address, value: BigNumber): Promise> {
if (this.kit.defaultAccount == null) {
throw new Error(`missing from at new ValdidatorUtils()`)
diff --git a/packages/contractkit/src/wrappers/LockedGold.ts b/packages/contractkit/src/wrappers/LockedGold.ts
index 75c96d77b8d..979289a51dc 100644
--- a/packages/contractkit/src/wrappers/LockedGold.ts
+++ b/packages/contractkit/src/wrappers/LockedGold.ts
@@ -48,35 +48,73 @@ export interface LockedGoldConfig {
* Contract for handling deposits needed for voting.
*/
export class LockedGoldWrapper extends BaseWrapper {
+ /**
+ * Unlocks gold that becomes withdrawable after the unlocking period.
+ * @param value The amount of gold to unlock.
+ */
unlock: (value: NumberLike) => CeloTransactionObject = proxySend(
this.kit,
this.contract.methods.unlock,
tupleParser(parseNumber)
)
+ /**
+ * Creates an account.
+ */
createAccount = proxySend(this.kit, this.contract.methods.createAccount)
+ /**
+ * Withdraws a gold that has been unlocked after the unlocking period has passed.
+ * @param index The index of the pending withdrawal to withdraw.
+ */
withdraw: (index: number) => CeloTransactionObject = proxySend(
this.kit,
this.contract.methods.withdraw
)
+ /**
+ * @notice Locks gold to be used for voting.
+ */
lock = proxySend(this.kit, this.contract.methods.lock)
+ /**
+ * Relocks gold that has been unlocked but not withdrawn.
+ * @param index The index of the pending withdrawal to relock.
+ */
relock: (index: number) => CeloTransactionObject = proxySend(
this.kit,
this.contract.methods.relock
)
+ /**
+ * Returns the total amount of locked gold for an account.
+ * @param account The account.
+ * @return The total amount of locked gold for an account.
+ */
getAccountTotalLockedGold = proxyCall(
this.contract.methods.getAccountTotalLockedGold,
undefined,
toBigNumber
)
+ /**
+ * 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.
+ */
getAccountNonvotingLockedGold = proxyCall(
this.contract.methods.getAccountNonvotingLockedGold,
undefined,
toBigNumber
)
+ /**
+ * Returns the voter for the specified account.
+ * @param account The address of the account.
+ * @return The address with which the account can vote.
+ */
getVoterFromAccount: (account: string) => Promise = proxyCall(
this.contract.methods.getVoterFromAccount
)
+ /**
+ * Returns the validator for the specified account.
+ * @param account The address of the account.
+ * @return The address with which the account can register a validator or group.
+ */
getValidatorFromAccount: (account: string) => Promise = proxyCall(
this.contract.methods.getValidatorFromAccount
)
@@ -138,6 +176,11 @@ export class LockedGoldWrapper extends BaseWrapper {
)
}
+ /**
+ * 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.
+ */
async getPendingWithdrawals(account: string) {
const withdrawals = await this.contract.methods.getPendingWithdrawals(account).call()
return zip(
@@ -148,6 +191,7 @@ export class LockedGoldWrapper extends BaseWrapper {
withdrawals[0]
)
}
+
private async getParsedSignatureOfAddress(address: Address, signer: string) {
const hash = Web3.utils.soliditySha3({ type: 'address', value: address })
const signature = (await this.kit.web3.eth.sign(hash, signer)).slice(2)
diff --git a/packages/contractkit/src/wrappers/Validators.ts b/packages/contractkit/src/wrappers/Validators.ts
index 35494ab25fd..4ecc8e9ceb2 100644
--- a/packages/contractkit/src/wrappers/Validators.ts
+++ b/packages/contractkit/src/wrappers/Validators.ts
@@ -77,6 +77,10 @@ export class ValidatorsWrapper extends BaseWrapper {
}
}
+ /**
+ * Returns the lockup periods after deregistering groups and validators.
+ * @return The lockup periods after deregistering groups and validators.
+ */
async getDeregistrationLockups(): Promise {
const res = await this.contract.methods.getDeregistrationLockups().call()
return {
diff --git a/packages/protocol/contracts/governance/Election.sol b/packages/protocol/contracts/governance/Election.sol
index b222873d852..3f1c7919503 100644
--- a/packages/protocol/contracts/governance/Election.sol
+++ b/packages/protocol/contracts/governance/Election.sol
@@ -164,6 +164,10 @@ contract Election is IElection, Ownable, ReentrancyGuard, Initializable, UsingRe
return _setElectableValidators(min, max);
}
+ /**
+ * @notice Returns the minimum and maximum number of validators that can be elected.
+ * @return The minimum and maximum number of validators that can be elected.
+ */
function getElectableValidators() external view returns (uint256, uint256) {
return (electableValidators.min, electableValidators.max);
}
@@ -372,6 +376,11 @@ contract Election is IElection, Ownable, ReentrancyGuard, Initializable, UsingRe
return true;
}
+ /**
+ * @notice Returns the total number of votes cast by an account.
+ * @param account The address of the account.
+ * @return The total number of votes cast by an account.
+ */
function getTotalVotesByAccount(address account) external view returns (uint256) {
uint256 total = 0;
address[] memory groups = votes.groupsVotedFor[account];
@@ -449,6 +458,11 @@ contract Election is IElection, Ownable, ReentrancyGuard, Initializable, UsingRe
return votes.pending.forGroup[group].total.add(votes.active.forGroup[group].total);
}
+ /**
+ * @notice Returns whether or not a group is eligible to receive votes.
+ * @return Whether or not a group is eligible to receive votes.
+ * @dev Eligible groups that have received their maximum number of votes cannot receive more.
+ */
function getGroupEligibility(address group) external view returns (bool) {
return votes.total.eligible.contains(group);
}
@@ -834,6 +848,11 @@ contract Election is IElection, Ownable, ReentrancyGuard, Initializable, UsingRe
return (groupIndex, memberElected);
}
+ /**
+ * @notice Randomly permutes an array of addresses.
+ * @param array The array to permute.
+ * @return The permuted array.
+ */
function shuffleArray(address[] memory array) private view returns (address[] memory) {
bytes32 r = getRandom().random();
for (uint256 i = array.length - 1; i > 0; i = i.sub(1)) {
diff --git a/packages/protocol/contracts/governance/Validators.sol b/packages/protocol/contracts/governance/Validators.sol
index 7ef9cccfb51..1756a78d372 100644
--- a/packages/protocol/contracts/governance/Validators.sol
+++ b/packages/protocol/contracts/governance/Validators.sol
@@ -499,6 +499,11 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
return 0;
}
+ /**
+ * @notice Returns the timestamp of the last time this account deregistered a validator or group.
+ * @param account The account to query.
+ * @return The timestamp of the last time this account deregistered a validator or group.
+ */
function getDeregistrationTimestamps(address account) external view returns (uint256, uint256) {
return (deregistrationTimestamps[account].group, deregistrationTimestamps[account].validator);
}
From fb5e8b4a86f573e3e026c1043b538ab19fc23a29 Mon Sep 17 00:00:00 2001
From: Asa Oines
Date: Fri, 11 Oct 2019 12:19:44 -0700
Subject: [PATCH 056/149] Fix linting issues
---
.../src/e2e-tests/governance_tests.ts | 26 +++++++------------
1 file changed, 9 insertions(+), 17 deletions(-)
diff --git a/packages/celotool/src/e2e-tests/governance_tests.ts b/packages/celotool/src/e2e-tests/governance_tests.ts
index e2b081352ca..da77abe07f8 100644
--- a/packages/celotool/src/e2e-tests/governance_tests.ts
+++ b/packages/celotool/src/e2e-tests/governance_tests.ts
@@ -1,6 +1,6 @@
-import BigNumber from 'bignumber.js'
import { ContractKit, newKitFromWeb3 } from '@celo/contractkit'
import { fromFixed, toFixed } from '@celo/utils/lib/fixidity'
+import BigNumber from 'bignumber.js'
import { assert } from 'chai'
import Web3 from 'web3'
import { getContext, getEnode, importGenesis, initAndStartGeth, sleep } from './utils'
@@ -76,7 +76,7 @@ describe('governance tests', () => {
return [groupAddress, decryptedKeystore.privateKey]
}
- const activate = async (web3: any, account: string, txOptions: any = {}) => {
+ const activate = async (account: string, txOptions: any = {}) => {
await unlockAccount(account, web3)
const [group] = await validators.methods.getRegisteredValidatorGroups().call()
const tx = election.methods.activate(group)
@@ -113,7 +113,7 @@ describe('governance tests', () => {
}
const isLastBlockOfEpoch = (blockNumber: number, epochSize: number) => {
- return blockNumber % epochSize == 0
+ return blockNumber % epochSize === 0
}
describe('when the validator set is changing', () => {
@@ -142,7 +142,7 @@ describe('governance tests', () => {
// Give the node time to sync.
await sleep(15)
- await activate(web3, allValidators[0])
+ await activate(allValidators[0])
const groupWeb3 = new Web3('ws://localhost:8567')
const groupKit = newKitFromWeb3(groupWeb3)
validators = await groupKit._web3Contracts.getValidators()
@@ -221,7 +221,6 @@ describe('governance tests', () => {
})
it('should update the validator scores at the end of each epoch', async () => {
- const validators = await kit._web3Contracts.getValidators()
const adjustmentSpeed = fromFixed(
new BigNumber((await validators.methods.getValidatorScoreParameters().call())[1])
)
@@ -272,7 +271,6 @@ describe('governance tests', () => {
})
it('should distribute epoch payments at the end of each epoch', async () => {
- const validators = await kit._web3Contracts.getValidators()
const stableToken = await kit._web3Contracts.getStableToken()
const commission = 0.1
const validatorEpochPayment = new BigNumber(
@@ -336,19 +334,13 @@ describe('governance tests', () => {
})
it('should distribute epoch rewards at the end of each epoch', async () => {
- const validators = await kit._web3Contracts.getValidators()
- const election = await kit._web3Contracts.getElection()
const lockedGold = await kit._web3Contracts.getLockedGold()
const governance = await kit._web3Contracts.getGovernance()
const epochReward = new BigNumber(10).pow(18)
const infraReward = new BigNumber(10).pow(18)
const [group] = await validators.methods.getRegisteredValidatorGroups().call()
- const assertVotesChanged = async (
- group: string,
- blockNumber: number,
- expected: BigNumber
- ) => {
+ const assertVotesChanged = async (blockNumber: number, expected: BigNumber) => {
const currentVotes = new BigNumber(
await election.methods.getTotalVotesForGroup(group).call({}, blockNumber)
)
@@ -393,8 +385,8 @@ describe('governance tests', () => {
await assertBalanceChanged(governance.options.address, blockNumber, expected)
}
- const assertVotesUnchanged = async (group: string, blockNumber: number) => {
- await assertVotesChanged(group, blockNumber, new BigNumber(0))
+ const assertVotesUnchanged = async (blockNumber: number) => {
+ await assertVotesChanged(blockNumber, new BigNumber(0))
}
const assertGoldTokenTotalSupplyUnchanged = async (blockNumber: number) => {
@@ -411,12 +403,12 @@ describe('governance tests', () => {
for (const blockNumber of blockNumbers) {
if (isLastBlockOfEpoch(blockNumber, epoch)) {
- await assertVotesChanged(group, blockNumber, epochReward)
+ await assertVotesChanged(blockNumber, epochReward)
await assertGoldTokenTotalSupplyChanged(blockNumber, epochReward.plus(infraReward))
await assertLockedGoldBalanceChanged(blockNumber, epochReward)
await assertGovernanceBalanceChanged(blockNumber, infraReward)
} else {
- await assertVotesUnchanged(group, blockNumber)
+ await assertVotesUnchanged(blockNumber)
await assertGoldTokenTotalSupplyUnchanged(blockNumber)
await assertLockedGoldBalanceUnchanged(blockNumber)
await assertGovernanceBalanceUnchanged(blockNumber)
From 449e1ede24b0774bd2602d7952d0578e890e0174 Mon Sep 17 00:00:00 2001
From: Asa Oines
Date: Fri, 11 Oct 2019 12:44:06 -0700
Subject: [PATCH 057/149] Add documentation
---
.../contracts/governance/Election.sol | 21 ++++++++++++
.../contracts/governance/Validators.sol | 33 +++++++++++++++++++
.../governance/test/MockLockedGold.sol | 3 +-
3 files changed, 55 insertions(+), 2 deletions(-)
diff --git a/packages/protocol/contracts/governance/Election.sol b/packages/protocol/contracts/governance/Election.sol
index 270b6a6cca0..3b9651a8ba0 100644
--- a/packages/protocol/contracts/governance/Election.sol
+++ b/packages/protocol/contracts/governance/Election.sol
@@ -469,6 +469,13 @@ contract Election is
return votes.total.eligible.contains(group);
}
+ /**
+ * @notice Returns the amount of rewards that voters for `group` are due at the end of an epoch.
+ * @param group The group to calculate epoch rewards for.
+ * @param totalEpochRewards The total amount of rewards going to all voters.
+ * @return The amount of rewards that voters for `group` are due at the end of an epoch.
+ * @dev Eligible groups that have received their maximum number of votes cannot receive more.
+ */
function getGroupEpochRewards(
address group,
uint256 totalEpochRewards
@@ -489,6 +496,13 @@ contract Election is
}
}
+ /**
+ * @notice Distributes epoch rewards to voters for `group` in the form of active votes.
+ * @param group The group whose voters will receive rewards.
+ * @param value The amount of rewards to distribute to voters for the group.
+ * @param lesser The group receiving fewer votes than `group` after the rewards are added.
+ * @param greater The group receiving more votes than `group` after the rewards are added.
+ */
function distributeEpochRewards(
address group,
uint256 value,
@@ -501,6 +515,13 @@ contract Election is
_distributeEpochRewards(group, value, lesser, greater);
}
+ /**
+ * @notice Distributes epoch rewards to voters for `group` in the form of active votes.
+ * @param group The group whose voters will receive rewards.
+ * @param value The amount of rewards to distribute to voters for the group.
+ * @param lesser The group receiving fewer votes than `group` after the rewards are added.
+ * @param greater The group receiving more votes than `group` after the rewards are added.
+ */
function _distributeEpochRewards(
address group,
uint256 value,
diff --git a/packages/protocol/contracts/governance/Validators.sol b/packages/protocol/contracts/governance/Validators.sol
index d4ef0de070d..1b98061c3ed 100644
--- a/packages/protocol/contracts/governance/Validators.sol
+++ b/packages/protocol/contracts/governance/Validators.sol
@@ -66,11 +66,13 @@ contract Validators is
FixidityLib.Fraction commission;
}
+ // Stores the epoch number at which a validator joined a particular group.
struct MembershipHistoryEntry {
uint256 epochNumber;
address group;
}
+ // A circular buffer storing the membership history of a validator.
struct MembershipHistory {
uint256 head;
MembershipHistoryEntry[] entries;
@@ -85,6 +87,7 @@ contract Validators is
MembershipHistory membershipHistory;
}
+ // Parameters that govern the calculation of validator's score.
struct ValidatorScoreParameters {
uint256 exponent;
FixidityLib.Fraction adjustmentSpeed;
@@ -403,10 +406,19 @@ contract Validators is
return getLockedGold().getAccountTotalLockedGold(account) >= balanceRequirements.group;
}
+ /**
+ * @notice Returns the parameters that goven how a validator's score is calculated.
+ * @return The parameters that goven how a validator's score is calculated.
+ */
function getValidatorScoreParameters() external view returns (uint256, uint256) {
return (validatorScoreParameters.exponent, validatorScoreParameters.adjustmentSpeed.unwrap());
}
+ /**
+ * @notice Returns the group membership history of a validator.
+ * @param account The validator whose membership history to return.
+ * @return The group membership history of a validator.
+ */
function getMembershipHistory(
address account
)
@@ -475,13 +487,21 @@ contract Validators is
);
}
+ /**
+ * @notice Distributes epoch payments to `validator` and its group.
+ */
function distributeEpochPayment(address validator) external onlyVm() {
_distributeEpochPayment(validator);
}
+ /**
+ * @notice Distributes epoch payments to `validator` and its group.
+ */
function _distributeEpochPayment(address validator) internal {
address account = getLockedGold().getAccountFromValidator(validator);
require(isValidator(account));
+ // The group that should be paid is the group that the validator was a member of at the
+ // time it was elected.
address group = getMembershipInLastEpoch(account);
// Both the validator and the group must maintain the minimum locked gold balance in order to
// receive epoch payments.
@@ -931,6 +951,14 @@ contract Validators is
return true;
}
+ /**
+ * @notice Updates the group membership history of a particular account.
+ * @param account The account whose group membership has changed.
+ * @param group The group that the account is now a member of.
+ * @return True upon success.
+ * @dev Note that this is used to determine a validator's membership at the time of an election,
+ * and so group changes within an epoch will overwrite eachother.
+ */
function updateMembershipHistory(address account, address group) private returns (bool) {
MembershipHistory storage history = validators[account].membershipHistory;
uint256 epochNumber = getEpochNumber();
@@ -951,6 +979,11 @@ contract Validators is
}
}
+ /**
+ * @notice Returns the group that `account` was a member of at the end of the last epoch.
+ * @param account The account whose group membership should be returned.
+ * @return The group that `account` was a member of at the end of the last epoch.
+ */
function getMembershipInLastEpoch(address account) public view returns (address) {
uint256 epochNumber = getEpochNumber();
MembershipHistory storage history = validators[account].membershipHistory;
diff --git a/packages/protocol/contracts/governance/test/MockLockedGold.sol b/packages/protocol/contracts/governance/test/MockLockedGold.sol
index 848338a9326..532604b112c 100644
--- a/packages/protocol/contracts/governance/test/MockLockedGold.sol
+++ b/packages/protocol/contracts/governance/test/MockLockedGold.sol
@@ -8,8 +8,7 @@ import "../interfaces/ILockedGold.sol";
/**
* @title A mock LockedGold for testing.
*/
-// TODO(asa): Use ILockedGold interface.
-contract MockLockedGold {
+contract MockLockedGold is ILockedGold {
using SafeMath for uint256;
From 143a69cd43e1875203a2de066e840fb07247e6d7 Mon Sep 17 00:00:00 2001
From: Asa Oines
Date: Fri, 11 Oct 2019 12:52:22 -0700
Subject: [PATCH 058/149] Fix interface
---
.../protocol/contracts/governance/test/MockLockedGold.sol | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/packages/protocol/contracts/governance/test/MockLockedGold.sol b/packages/protocol/contracts/governance/test/MockLockedGold.sol
index 532604b112c..5e3ab2e68d8 100644
--- a/packages/protocol/contracts/governance/test/MockLockedGold.sol
+++ b/packages/protocol/contracts/governance/test/MockLockedGold.sol
@@ -26,7 +26,7 @@ contract MockLockedGold is ILockedGold {
authorizedValidators[account] = validator;
}
- function getAccountFromValidator(address accountOrValidator) external pure returns (address) {
+ function getAccountFromValidator(address accountOrValidator) external view returns (address) {
return accountOrValidator;
}
@@ -34,13 +34,13 @@ contract MockLockedGold is ILockedGold {
address accountOrValidator
)
external
- pure
+ view
returns (address)
{
return accountOrValidator;
}
- function getAccountFromActiveVoter(address accountOrVoter) external pure returns (address) {
+ function getAccountFromActiveVoter(address accountOrVoter) external view returns (address) {
return accountOrVoter;
}
From 1f18108bc11cc92cab034daa8e308eb54df19a62 Mon Sep 17 00:00:00 2001
From: Asa Oines
Date: Fri, 11 Oct 2019 14:07:01 -0700
Subject: [PATCH 059/149] Expire previously upvoted proposals
---
.../contracts/governance/Governance.sol | 9 +++
.../protocol/test/governance/governance.ts | 61 +++++++++++++++++++
2 files changed, 70 insertions(+)
diff --git a/packages/protocol/contracts/governance/Governance.sol b/packages/protocol/contracts/governance/Governance.sol
index 2d863af40fb..c32e9f10991 100644
--- a/packages/protocol/contracts/governance/Governance.sol
+++ b/packages/protocol/contracts/governance/Governance.sol
@@ -497,6 +497,15 @@ contract Governance is IGovernance, Ownable, Initializable, ReentrancyGuard, Usi
return false;
}
Voter storage voter = voters[account];
+ // If the previously upvoted proposal is still in the queue but has expired, expire the
+ // proposal from the queue.
+ if (
+ queue.contains(voter.upvote.proposalId) &&
+ now >= proposals[voter.upvote.proposalId].timestamp.add(queueExpiry)
+ ) {
+ queue.remove(voter.upvote.proposalId);
+ emit ProposalExpired(voter.upvote.proposalId);
+ }
// We can upvote a proposal in the queue if we're not already upvoting a proposal in the queue.
uint256 weight = getLockedGold().getAccountTotalLockedGold(account);
require(weight > 0, "cannot upvote without locking gold");
diff --git a/packages/protocol/test/governance/governance.ts b/packages/protocol/test/governance/governance.ts
index 78dade7ba70..43c95b96652 100644
--- a/packages/protocol/test/governance/governance.ts
+++ b/packages/protocol/test/governance/governance.ts
@@ -1060,6 +1060,67 @@ contract('Governance', (accounts: string[]) => {
await assertRevert(governance.upvote(proposalId, 0, 0))
})
})
+
+ describe('when the previously upvoted proposal is in the queue and expired', () => {
+ const upvotedProposalId = 2
+ // Expire the upvoted proposal without dequeueing it.
+ const queueExpiry = 60
+ beforeEach(async () => {
+ await governance.setQueueExpiry(60)
+ await governance.upvote(proposalId, 0, 0)
+ await timeTravel(queueExpiry, web3)
+ await governance.propose(
+ [transactionSuccess1.value],
+ [transactionSuccess1.destination],
+ transactionSuccess1.data,
+ [transactionSuccess1.data.length],
+ // @ts-ignore: TODO(mcortesi) fix typings for TransactionDetails
+ { value: minDeposit }
+ )
+ })
+
+ it('should increase the number of upvotes for the proposal', async () => {
+ await governance.upvote(upvotedProposalId, 0, 0)
+ assertEqualBN(await governance.getUpvotes(upvotedProposalId), weight)
+ })
+
+ it('should mark the account as having upvoted the proposal', async () => {
+ await governance.upvote(upvotedProposalId, 0, 0)
+ const [recordId, recordWeight] = await governance.getUpvoteRecord(account)
+ assertEqualBN(recordId, upvotedProposalId)
+ assertEqualBN(recordWeight, weight)
+ })
+
+ it('should return true', async () => {
+ const success = await governance.upvote.call(upvotedProposalId, 0, 0)
+ assert.isTrue(success)
+ })
+
+ it('should emit the ProposalExpired event', async () => {
+ const resp = await governance.upvote(upvotedProposalId, 0, 0)
+ assert.equal(resp.logs.length, 2)
+ const log = resp.logs[0]
+ assertLogMatches2(log, {
+ event: 'ProposalExpired',
+ args: {
+ proposalId: new BigNumber(proposalId),
+ },
+ })
+ })
+ it('should emit the ProposalUpvoted event', async () => {
+ const resp = await governance.upvote(upvotedProposalId, 0, 0)
+ assert.equal(resp.logs.length, 2)
+ const log = resp.logs[1]
+ assertLogMatches2(log, {
+ event: 'ProposalUpvoted',
+ args: {
+ proposalId: new BigNumber(upvotedProposalId),
+ account,
+ upvotes: new BigNumber(weight),
+ },
+ })
+ })
+ })
})
describe('#revokeUpvote()', () => {
From 506a77bca97b91de54183ed3abab8d6a54da0029 Mon Sep 17 00:00:00 2001
From: Asa Oines
Date: Fri, 11 Oct 2019 15:20:52 -0700
Subject: [PATCH 060/149] Address comments
---
.../contracts/governance/LockedGold.sol | 36 +++++++++++++++----
.../protocol/test/governance/lockedgold.ts | 29 +++++++++++++++
2 files changed, 59 insertions(+), 6 deletions(-)
diff --git a/packages/protocol/contracts/governance/LockedGold.sol b/packages/protocol/contracts/governance/LockedGold.sol
index fcc81a120dc..1fbc1665b7b 100644
--- a/packages/protocol/contracts/governance/LockedGold.sol
+++ b/packages/protocol/contracts/governance/LockedGold.sol
@@ -16,8 +16,12 @@ contract LockedGold is ILockedGold, ReentrancyGuard, Initializable, UsingRegistr
struct Authorizations {
// The address that is authorized to vote on behalf of the account.
+ // The account can vote as well, whether or not an authorized voter has been specified.
address voting;
// The address that is authorized to validate on behalf of the account.
+ // The account can manage the validator, whether or not an authorized validator has been
+ // specified. However if an authorized validator has been specified, only that key may actually
+ // participate in consensus.
address validating;
}
@@ -28,9 +32,11 @@ contract LockedGold is ILockedGold, ReentrancyGuard, Initializable, UsingRegistr
uint256 timestamp;
}
+ // NOTE: This contract does not store an account's locked gold that is being used in electing
+ // validators.
struct Balances {
- // This contract does not store an account's locked gold that is being used in electing
- // validators.
+ // The amount of locked gold that this account has that is not currently participating in
+ // validator elections.
uint256 nonvoting;
// Gold that has been unlocked and will become available for withdrawal.
PendingWithdrawal[] pendingWithdrawals;
@@ -51,6 +57,7 @@ contract LockedGold is ILockedGold, ReentrancyGuard, Initializable, UsingRegistr
uint256 public totalNonvoting;
uint256 public unlockingPeriod;
+ event UnlockingPeriodSet(uint256 period);
event VoterAuthorized(address indexed account, address voter);
event ValidatorAuthorized(address indexed account, address validator);
event GoldLocked(address indexed account, uint256 value);
@@ -80,6 +87,7 @@ contract LockedGold is ILockedGold, ReentrancyGuard, Initializable, UsingRegistr
* @param v The recovery id of the incoming ECDSA signature.
* @param r Output value r of the ECDSA signature.
* @param s Output value s of the ECDSA signature.
+ * @dev v, r, s constitute `voter`'s signature on `msg.sender`.
*/
function authorizeVoter(
address voter,
@@ -102,6 +110,7 @@ contract LockedGold is ILockedGold, ReentrancyGuard, Initializable, UsingRegistr
* @param v The recovery id of the incoming ECDSA signature.
* @param r Output value r of the ECDSA signature.
* @param s Output value s of the ECDSA signature.
+ * @dev v, r, s constitute `validator`'s signature on `msg.sender`.
*/
function authorizeValidator(
address validator,
@@ -118,6 +127,16 @@ contract LockedGold is ILockedGold, ReentrancyGuard, Initializable, UsingRegistr
emit ValidatorAuthorized(msg.sender, validator);
}
+ /**
+ * @notice Sets the duration in seconds users must wait before withdrawing gold after unlocking.
+ * @param value The unlocking period in seconds.
+ */
+ function setUnlockingPeriod(uint256 value) external onlyOwner {
+ require(value != unlockingPeriod);
+ unlockingPeriod = value;
+ emit UnlockingPeriodSet(value);
+ }
+
/**
* @notice Locks gold to be used for voting.
*/
@@ -132,7 +151,7 @@ contract LockedGold is ILockedGold, ReentrancyGuard, Initializable, UsingRegistr
* @notice Increments the non-voting balance for an account.
* @param account The account whose non-voting balance should be incremented.
* @param value The amount by which to increment.
- * @dev Can only be called by the registered "Election" smart contract.
+ * @dev Can only be called by the registered Election smart contract.
*/
function incrementNonvotingAccountBalance(
address account,
@@ -211,7 +230,7 @@ contract LockedGold is ILockedGold, ReentrancyGuard, Initializable, UsingRegistr
}
/**
- * @notice Withdraws a gold that has been unlocked after the unlocking period has passed.
+ * @notice Withdraws gold that has been unlocked after the unlocking period has passed.
* @param index The index of the pending withdrawal to withdraw.
*/
function withdraw(uint256 index) external nonReentrant {
@@ -327,7 +346,7 @@ contract LockedGold is ILockedGold, ReentrancyGuard, Initializable, UsingRegistr
function getPendingWithdrawals(
address account
)
- public
+ external
view
returns (uint256[] memory, uint256[] memory)
{
@@ -353,7 +372,7 @@ contract LockedGold is ILockedGold, ReentrancyGuard, Initializable, UsingRegistr
* @param r Output value r of the ECDSA signature.
* @param s Output value s of the ECDSA signature.
* @dev Fails if the address is already authorized or is an account.
- * @dev v, r, s constitute `authorize`'s signature on `msg.sender`.
+ * @dev v, r, s constitute `current`'s signature on `msg.sender`.
*/
function authorize(
address current,
@@ -409,6 +428,11 @@ contract LockedGold is ILockedGold, ReentrancyGuard, Initializable, UsingRegistr
return (authorizedBy[account] == address(0));
}
+ /**
+ * @notice Deletes a pending withdrawal.
+ * @param list The list of pending withdrawals from which to delete.
+ * @param index The index of the pending withdrawal to delete.
+ */
function deletePendingWithdrawal(PendingWithdrawal[] storage list, uint256 index) private {
uint256 lastIndex = list.length.sub(1);
list[index] = list[lastIndex];
diff --git a/packages/protocol/test/governance/lockedgold.ts b/packages/protocol/test/governance/lockedgold.ts
index befc8827ba0..31ec2fbd9ab 100644
--- a/packages/protocol/test/governance/lockedgold.ts
+++ b/packages/protocol/test/governance/lockedgold.ts
@@ -2,6 +2,7 @@ import { CeloContractName } from '@celo/protocol/lib/registry-utils'
import {
assertEqualBN,
assertLogMatches,
+ assertLogMatches2,
assertRevert,
NULL_ADDRESS,
timeTravel,
@@ -116,6 +117,34 @@ contract('LockedGold', (accounts: string[]) => {
})
})
+ describe('#setUnlockingPeriod', () => {
+ const newUnlockingPeriod = unlockingPeriod + 1
+ it('should set the unlockingPeriod', async () => {
+ await lockedGold.setUnlockingPeriod(newUnlockingPeriod)
+ assertEqualBN(await lockedGold.unlockingPeriod(), newUnlockingPeriod)
+ })
+
+ it('should emit the UnlockingPeriodSet event', async () => {
+ const resp = await lockedGold.setUnlockingPeriod(newUnlockingPeriod)
+ assert.equal(resp.logs.length, 1)
+ const log = resp.logs[0]
+ assertLogMatches2(log, {
+ event: 'UnlockingPeriodSet',
+ args: {
+ period: newUnlockingPeriod,
+ },
+ })
+ })
+
+ it('should revert when the unlockingPeriod is unchanged', async () => {
+ await assertRevert(lockedGold.setUnlockingPeriod(unlockingPeriod))
+ })
+
+ it('should revert when called by anyone other than the owner', async () => {
+ await assertRevert(lockedGold.setUnlockingPeriod(newUnlockingPeriod, { from: nonOwner }))
+ })
+ })
+
Object.keys(authorizationTests).forEach((key) => {
describe('authorization tests:', () => {
let authorizationTest: any
From 0e207d0320758ae1c18127510489832dca882791 Mon Sep 17 00:00:00 2001
From: Asa Oines
Date: Fri, 11 Oct 2019 16:01:18 -0700
Subject: [PATCH 061/149] Point to asaj/pos-2
---
.circleci/config.yml | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/.circleci/config.yml b/.circleci/config.yml
index bd72f4dd705..b23544ca01e 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -516,7 +516,7 @@ jobs:
go version
cd packages/celotool
mkdir ~/.ssh/ && echo -e "Host github.com\n\tStrictHostKeyChecking no\n" > ~/.ssh/config
- ./ci_test_transfers.sh checkout master
+ ./ci_test_transfers.sh checkout asaj/pos-2
end-to-end-geth-exit-test:
<<: *defaults
@@ -555,7 +555,7 @@ jobs:
go version
cd packages/celotool
mkdir ~/.ssh/ && echo -e "Host github.com\n\tStrictHostKeyChecking no\n" > ~/.ssh/config
- ./ci_test_exit.sh checkout master
+ ./ci_test_exit.sh checkout asaj/pos-2
end-to-end-geth-governance-test:
<<: *defaults
@@ -596,7 +596,7 @@ jobs:
go version
cd packages/celotool
mkdir ~/.ssh/ && echo -e "Host github.com\n\tStrictHostKeyChecking no\n" > ~/.ssh/config
- ./ci_test_governance.sh checkout asaj/pos
+ ./ci_test_governance.sh checkout asaj/pos-2
end-to-end-geth-sync-test:
<<: *defaults
@@ -636,7 +636,7 @@ jobs:
go version
cd packages/celotool
mkdir ~/.ssh/ && echo -e "Host github.com\n\tStrictHostKeyChecking no\n" > ~/.ssh/config
- ./ci_test_sync.sh checkout asaj/pos
+ ./ci_test_sync.sh checkout asaj/pos-2
end-to-end-geth-integration-sync-test:
<<: *defaults
@@ -669,7 +669,7 @@ jobs:
go version
cd packages/celotool
mkdir ~/.ssh/ && echo -e "Host github.com\n\tStrictHostKeyChecking no\n" > ~/.ssh/config
- ./ci_test_sync_with_network.sh checkout asaj/pos
+ ./ci_test_sync_with_network.sh checkout asaj/pos-2
web:
working_directory: ~/app
From 90e8b58cc5765fb423403be7b4bcf51de14f13b6 Mon Sep 17 00:00:00 2001
From: Asa Oines
Date: Mon, 14 Oct 2019 12:10:49 -0700
Subject: [PATCH 062/149] Address comments
---
.../contracts/governance/Validators.sol | 54 ++++++++-----------
packages/protocol/test/common/integration.ts | 3 +-
packages/protocol/test/governance/election.ts | 9 ++--
3 files changed, 28 insertions(+), 38 deletions(-)
diff --git a/packages/protocol/contracts/governance/Validators.sol b/packages/protocol/contracts/governance/Validators.sol
index 1756a78d372..d13e03a892a 100644
--- a/packages/protocol/contracts/governance/Validators.sol
+++ b/packages/protocol/contracts/governance/Validators.sol
@@ -162,9 +162,9 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
{
_transferOwnership(msg.sender);
setRegistry(registryAddress);
- balanceRequirements = BalanceRequirements(groupRequirement, validatorRequirement);
- deregistrationLockups = DeregistrationLockups(groupLockup, validatorLockup);
- maxGroupSize = _maxGroupSize;
+ setBalanceRequirements(groupRequirement, validatorRequirement);
+ setDeregistrationLockups(groupLockup, validatorLockup);
+ setMaxGroupSize(_maxGroupSize);
}
/**
@@ -172,7 +172,7 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
* @param size The maximum group size.
* @return True upon success.
*/
- function setMaxGroupSize(uint256 size) external onlyOwner returns (bool) {
+ function setMaxGroupSize(uint256 size) public onlyOwner returns (bool) {
require(0 < size && size != maxGroupSize);
maxGroupSize = size;
emit MaxGroupSizeSet(size);
@@ -188,52 +188,42 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
}
/**
- * @notice Updates the minimum gold requirements to register a validator group or validator.
- * @param groupRequirement The minimum locked gold needed to register a group.
- * @param validatorRequirement The minimum locked gold needed to register a validator.
+ * @notice Updates the minimum gold requirements to register a group/validator and earn rewards.
+ * @param group The minimum locked gold needed to register a group and earn rewards.
+ * @param validator The minimum locked gold needed to register a validator and earn rewards.
* @return True upon success.
- * @dev The new requirement is only enforced for future validator or group registrations.
*/
- // TODO(asa): Allow validators to adjust their LockedGold MustMaintain if the registration
- // requirements fall.
function setBalanceRequirements(
- uint256 groupRequirement,
- uint256 validatorRequirement
+ uint256 group,
+ uint256 validator
)
- external
+ public
onlyOwner
returns (bool)
{
- require(
- groupRequirement != balanceRequirements.group ||
- validatorRequirement != balanceRequirements.validator
- );
- balanceRequirements = BalanceRequirements(groupRequirement, validatorRequirement);
- emit BalanceRequirementsSet(groupRequirement, validatorRequirement);
+ require(group != balanceRequirements.group || validator != balanceRequirements.validator);
+ balanceRequirements = BalanceRequirements(group, validator);
+ emit BalanceRequirementsSet(group, validator);
return true;
}
/**
* @notice Updates the duration for which gold remains locked after deregistration.
- * @param groupLockup The duration for groups in seconds.
- * @param validatorLockup The duration for validators in seconds.
+ * @param group The lockup duration for groups in seconds.
+ * @param validator The lockup duration for validators in seconds.
* @return True upon success.
- * @dev The new requirement is only enforced for future validator or group deregistrations.
*/
function setDeregistrationLockups(
- uint256 groupLockup,
- uint256 validatorLockup
+ uint256 group,
+ uint256 validator
)
- external
+ public
onlyOwner
returns (bool)
{
- require(
- groupLockup != deregistrationLockups.group ||
- validatorLockup != deregistrationLockups.validator
- );
- deregistrationLockups = DeregistrationLockups(groupLockup, validatorLockup);
- emit DeregistrationLockupsSet(groupLockup, validatorLockup);
+ require(group != deregistrationLockups.group || validator != deregistrationLockups.validator);
+ deregistrationLockups = DeregistrationLockups(group, validator);
+ emit DeregistrationLockupsSet(group, validator);
return true;
}
@@ -364,6 +354,8 @@ contract Validators is IValidators, Ownable, ReentrancyGuard, Initializable, Usi
* @notice Registers a validator group with no member validators.
* @param name A name for the validator group.
* @param url A URL for the validator group.
+ * @param commission Fixidity representation of the commission this group receives on epoch
+ * payments made to its members.
* @return True upon success.
* @dev Fails if the account is already a validator or validator group.
* @dev Fails if the account does not have sufficient weight.
diff --git a/packages/protocol/test/common/integration.ts b/packages/protocol/test/common/integration.ts
index 68996a8334e..cdcbca11e4f 100644
--- a/packages/protocol/test/common/integration.ts
+++ b/packages/protocol/test/common/integration.ts
@@ -31,7 +31,7 @@ contract('Integration: Governance', (accounts: string[]) => {
let governance: GovernanceInstance
let registry: RegistryInstance
let proposalTransactions: any
- let value: BigNumber
+ const value = new BigNumber('1000000000000000000')
before(async () => {
lockedGold = await getDeployedProxiedContract('LockedGold', artifacts)
@@ -39,7 +39,6 @@ contract('Integration: Governance', (accounts: string[]) => {
registry = await getDeployedProxiedContract('Registry', artifacts)
// Set up a LockedGold account with which we can vote.
await lockedGold.createAccount()
- value = new BigNumber('1000000000000000000')
// @ts-ignore
await lockedGold.lock({ value })
proposalTransactions = [
diff --git a/packages/protocol/test/governance/election.ts b/packages/protocol/test/governance/election.ts
index 61d55ba9234..9763611f5c2 100644
--- a/packages/protocol/test/governance/election.ts
+++ b/packages/protocol/test/governance/election.ts
@@ -256,8 +256,6 @@ contract('Election', (accounts: string[]) => {
resp = await election.markGroupIneligible(group)
})
- describe('when the group has votes', () => {})
-
it('should remove the group from the list of eligible groups', async () => {
assert.deepEqual(await election.getEligibleValidatorGroups(), [])
})
@@ -767,10 +765,11 @@ contract('Election', (accounts: string[]) => {
})
describe('when a single group has >= minElectableValidators as members and received votes', () => {
- beforeEach(async () => {})
+ beforeEach(async () => {
+ await election.vote(group1, voter1.weight, group2, NULL_ADDRESS, { from: voter1.address })
+ })
it("should return that group's member list", async () => {
- await election.vote(group1, voter1.weight, group2, NULL_ADDRESS, { from: voter1.address })
assertSameAddresses(await election.electValidators(), [
validator1,
validator2,
@@ -780,7 +779,7 @@ contract('Election', (accounts: string[]) => {
})
})
- describe("when > maxElectableValidators members's groups receive votes", () => {
+ describe("when > maxElectableValidators members' groups receive votes", () => {
beforeEach(async () => {
await election.vote(group1, voter1.weight, group2, NULL_ADDRESS, { from: voter1.address })
await election.vote(group2, voter2.weight, NULL_ADDRESS, group1, { from: voter2.address })
From 610cfe34c3adabb9b9353e2f4827b9212a7e7734 Mon Sep 17 00:00:00 2001
From: Asa Oines
Date: Tue, 15 Oct 2019 17:10:53 -0700
Subject: [PATCH 063/149] Base group locked gold requirement on number of
members
---
.../contracts/common/UsingPrecompiles.sol | 12 +
.../contracts/governance/Election.sol | 7 +-
.../contracts/governance/LockedGold.sol | 2 +-
.../contracts/governance/Validators.sol | 705 +++++------
.../governance/interfaces/IValidators.sol | 3 +-
.../protocol/test/governance/validators.ts | 1096 +++++++++++------
6 files changed, 1043 insertions(+), 782 deletions(-)
diff --git a/packages/protocol/contracts/common/UsingPrecompiles.sol b/packages/protocol/contracts/common/UsingPrecompiles.sol
index eca7ee67461..1ca4bb606f7 100644
--- a/packages/protocol/contracts/common/UsingPrecompiles.sol
+++ b/packages/protocol/contracts/common/UsingPrecompiles.sol
@@ -1,6 +1,7 @@
pragma solidity ^0.5.3;
contract UsingPrecompiles {
+ address constant PROOF_OF_POSSESSION = address(0xff - 4);
/**
* @notice calculate a * b^x for fractions a, b to `decimals` precision
@@ -113,4 +114,15 @@ contract UsingPrecompiles {
return numberValidators;
}
+
+ /**
+ * @notice Checks a BLS proof of possession.
+ * @param proofOfPossessionBytes The public key and signature of the proof of possession.
+ * @return True upon success.
+ */
+ function checkProofOfPossession(bytes memory proofOfPossessionBytes) internal returns (bool) {
+ bool success;
+ (success, ) = PROOF_OF_POSSESSION.call.value(0).gas(gasleft())(proofOfPossessionBytes);
+ return success;
+ }
}
diff --git a/packages/protocol/contracts/governance/Election.sol b/packages/protocol/contracts/governance/Election.sol
index 3b9651a8ba0..b9e2caa511f 100644
--- a/packages/protocol/contracts/governance/Election.sol
+++ b/packages/protocol/contracts/governance/Election.sol
@@ -484,12 +484,7 @@ contract Election is
view
returns (uint256)
{
- bool meetsBalanceRequirements = (
- getLockedGold().getAccountTotalLockedGold(group) >=
- getValidators().getAccountBalanceRequirement(group)
- );
-
- if (meetsBalanceRequirements && votes.active.total > 0) {
+ if (getValidators().meetsAccountLockedGoldRequirements(group) && votes.active.total > 0) {
return totalEpochRewards.mul(votes.active.forGroup[group].total).div(votes.active.total);
} else {
return 0;
diff --git a/packages/protocol/contracts/governance/LockedGold.sol b/packages/protocol/contracts/governance/LockedGold.sol
index d45b35e2c69..c2844d43b6d 100644
--- a/packages/protocol/contracts/governance/LockedGold.sol
+++ b/packages/protocol/contracts/governance/LockedGold.sol
@@ -193,7 +193,7 @@ contract LockedGold is ILockedGold, ReentrancyGuard, Initializable, UsingRegistr
function unlock(uint256 value) external nonReentrant {
require(isAccount(msg.sender));
Account storage account = accounts[msg.sender];
- uint256 balanceRequirement = getValidators().getAccountBalanceRequirement(msg.sender);
+ uint256 balanceRequirement = getValidators().getAccountLockedGoldRequirement(msg.sender);
require(balanceRequirement <= getAccountTotalLockedGold(msg.sender).sub(value));
_decrementNonvotingAccountBalance(msg.sender, value);
uint256 available = now.add(unlockingPeriod);
diff --git a/packages/protocol/contracts/governance/Validators.sol b/packages/protocol/contracts/governance/Validators.sol
index 1b98061c3ed..2791ba80454 100644
--- a/packages/protocol/contracts/governance/Validators.sol
+++ b/packages/protocol/contracts/governance/Validators.sol
@@ -28,35 +28,30 @@ contract Validators is
using SafeMath for uint256;
using BytesLib for bytes;
- address constant PROOF_OF_POSSESSION = address(0xff - 4);
- uint256 constant MAX_INT = 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff;
-
- // If an account has not registered a validator or group, these values represent the minimum
- // amount of Locked Gold required to do so.
- // If an account has a registered a validator or validator group, these values represent the
- // minimum amount of Locked Gold required in order to earn epoch rewards. Furthermore, the
- // account will not be able to unlock Gold if it would cause the account to fall below
- // these values.
- // If an account has deregistered a validator or validator group and is still subject to the
- // `DeregistrationLockup`, the account will not be able to unlock Gold if it would cause the
- // account to fall below these values.
- struct BalanceRequirements {
- uint256 group;
- uint256 validator;
- }
-
- // After deregistering a validator or validator group, the account will remain subject to the
- // current balance requirements for this long (in seconds).
- struct DeregistrationLockups {
- uint256 group;
- uint256 validator;
- }
-
- // Stores the timestamps at which deregistration of a validator or validator group occurred.
- struct DeregistrationTimestamps {
- uint256 group;
- uint256 validator;
- }
+ // For Validators, these requirements must be met in order to:
+ // 1. Register a validator
+ // 2. Affiliate with and be added to a group
+ // 3. Receive epoch payments (note that the group must meet the group requirements as well)
+ // Accounts may de-register their Validator `duration` seconds after they were last a member of a
+ // group, after which no restrictions on Locked Gold will apply to the account.
+ //
+ // For Validator Groups, these requirements must be met in order to:
+ // 1. Register a group
+ // 2. Add a member to a group
+ // 3. Receive epoch payments
+ // Note that for groups, the requirement value is multiplied by the number of members, and is
+ // enforced for `duration` seconds after the group last had that number of members.
+ // Accounts may de-register their Group `duration` seconds after they were last non-empty, after
+ // which no restrictions on Locked Gold will apply to the account.
+ struct LockedGoldRequirements {
+ uint256 value;
+ // In seconds.
+ uint256 duration;
+ }
+
+ // If we knew what time the validator was last in a group, we could enforce that to deregister a
+ // group, you need to have had 0 members for `duration`, and to deregister a validator, you need
+ // to have been out of a group for `duration`...
struct ValidatorGroup {
string name;
@@ -64,6 +59,8 @@ contract Validators is
LinkedList.List members;
// TODO(asa): Add a function that allows groups to update their commission.
FixidityLib.Fraction commission;
+ // sizeHistory[i] contains the last time the group contained i members.
+ uint256[] sizeHistory;
}
// Stores the epoch number at which a validator joined a particular group.
@@ -72,10 +69,14 @@ contract Validators is
address group;
}
- // A circular buffer storing the membership history of a validator.
+ // Stores a circular buffer storing the per-epoch membership history of a validator, used to
+ // determine which group commission should be paid to.
+ // Stores a timestamp of the last time the validator was removed from a group, used to determine
+ // whether or not a group can de-register.
struct MembershipHistory {
uint256 head;
MembershipHistoryEntry[] entries;
+ uint256 lastRemovedFromGroupTimestamp;
}
struct Validator {
@@ -95,85 +96,34 @@ contract Validators is
mapping(address => ValidatorGroup) private groups;
mapping(address => Validator) private validators;
- mapping(address => DeregistrationTimestamps) private deregistrationTimestamps;
- address[] private _groups;
- address[] private _validators;
- BalanceRequirements public balanceRequirements;
- DeregistrationLockups public deregistrationLockups;
+ address[] private registeredGroups;
+ address[] private registeredValidators;
+ LockedGoldRequirements public validatorLockedGoldRequirements;
+ LockedGoldRequirements public groupLockedGoldRequirements;
ValidatorScoreParameters private validatorScoreParameters;
uint256 public validatorEpochPayment;
uint256 public membershipHistoryLength;
uint256 public maxGroupSize;
- event Debug(uint256 value);
- event MaxGroupSizeSet(
- uint256 size
- );
-
- event ValidatorEpochPaymentSet(
- uint256 value
- );
-
- event ValidatorScoreParametersSet(
- uint256 exponent,
- uint256 adjustmentSpeed
- );
-
- event BalanceRequirementsSet(
- uint256 group,
- uint256 validator
- );
-
- event DeregistrationLockupsSet(
- uint256 group,
- uint256 validator
- );
-
+ event MaxGroupSizeSet(uint256 size);
+ event ValidatorEpochPaymentSet(uint256 value);
+ event ValidatorScoreParametersSet(uint256 exponent, uint256 adjustmentSpeed);
+ event GroupLockedGoldRequirementsSet(uint256 value, uint256 duration);
+ event ValidatorLockedGoldRequirementsSet(uint256 value, uint256 duration);
event ValidatorRegistered(
address indexed validator,
string name,
string url,
bytes publicKeysData
);
-
- event ValidatorDeregistered(
- address indexed validator
- );
-
- event ValidatorAffiliated(
- address indexed validator,
- address indexed group
- );
-
- event ValidatorDeaffiliated(
- address indexed validator,
- address indexed group
- );
-
- event ValidatorGroupRegistered(
- address indexed group,
- string name,
- string url
- );
-
- event ValidatorGroupDeregistered(
- address indexed group
- );
-
- event ValidatorGroupMemberAdded(
- address indexed group,
- address indexed validator
- );
-
- event ValidatorGroupMemberRemoved(
- address indexed group,
- address indexed validator
- );
-
- event ValidatorGroupMemberReordered(
- address indexed group,
- address indexed validator
- );
+ event ValidatorDeregistered(address indexed validator);
+ event ValidatorAffiliated(address indexed validator, address indexed group);
+ event ValidatorDeaffiliated(address indexed validator, address indexed group);
+ event ValidatorGroupRegistered(address indexed group, string name, string url);
+ event ValidatorGroupDeregistered(address indexed group);
+ event ValidatorGroupMemberAdded(address indexed group, address indexed validator);
+ event ValidatorGroupMemberRemoved(address indexed group, address indexed validator);
+ event ValidatorGroupMemberReordered(address indexed group, address indexed validator);
modifier onlyVm() {
require(msg.sender == address(0));
@@ -183,10 +133,10 @@ contract Validators is
/**
* @notice Initializes critical variables.
* @param registryAddress The address of the registry contract.
- * @param groupRequirement The minimum locked gold needed to register a group.
- * @param validatorRequirement The minimum locked gold needed to register a validator.
- * @param groupLockup The duration the above gold remains locked after deregistration.
- * @param validatorLockup The duration the above gold remains locked after deregistration.
+ * @param groupRequirementValue The Locked Gold requirement amount for groups.
+ * @param groupRequirementDuration The Locked Gold requirement duration for groups.
+ * @param validatorRequirementValue The Locked Gold requirement amount for validators.
+ * @param validatorRequirementDuration The Locked Gold requirement duration for validators.
* @param validatorScoreExponent The exponent used in calculating validator scores.
* @param validatorScoreAdjustmentSpeed The speed at which validator scores are adjusted.
* @param _validatorEpochPayment The duration the above gold remains locked after deregistration.
@@ -196,10 +146,10 @@ contract Validators is
*/
function initialize(
address registryAddress,
- uint256 groupRequirement,
- uint256 validatorRequirement,
- uint256 groupLockup,
- uint256 validatorLockup,
+ uint256 groupRequirementValue,
+ uint256 groupRequirementDuration,
+ uint256 validatorRequirementValue,
+ uint256 validatorRequirementDuration,
uint256 validatorScoreExponent,
uint256 validatorScoreAdjustmentSpeed,
uint256 _validatorEpochPayment,
@@ -209,18 +159,14 @@ contract Validators is
external
initializer
{
- require(validatorScoreAdjustmentSpeed <= FixidityLib.fixed1().unwrap());
_transferOwnership(msg.sender);
setRegistry(registryAddress);
- balanceRequirements = BalanceRequirements(groupRequirement, validatorRequirement);
- deregistrationLockups = DeregistrationLockups(groupLockup, validatorLockup);
- validatorScoreParameters = ValidatorScoreParameters(
- validatorScoreExponent,
- FixidityLib.wrap(validatorScoreAdjustmentSpeed)
- );
- validatorEpochPayment = _validatorEpochPayment;
+ setGroupLockedGoldRequirements(groupRequirementValue, groupRequirementDuration);
+ setValidatorLockedGoldRequirements(validatorRequirementValue, validatorRequirementDuration);
+ setValidatorScoreParameters(validatorScoreExponent, validatorScoreAdjustmentSpeed);
+ setMaxGroupSize(_maxGroupSize);
+ setValidatorEpochPayment(_validatorEpochPayment);
membershipHistoryLength = _membershipHistoryLength;
- maxGroupSize = _maxGroupSize;
}
/**
@@ -228,7 +174,7 @@ contract Validators is
* @param size The maximum group size.
* @return True upon success.
*/
- function setMaxGroupSize(uint256 size) external onlyOwner returns (bool) {
+ function setMaxGroupSize(uint256 size) public onlyOwner returns (bool) {
require(0 < size && size != maxGroupSize);
maxGroupSize = size;
emit MaxGroupSizeSet(size);
@@ -240,7 +186,7 @@ contract Validators is
* @param value The value in Celo Dollars.
* @return True upon success.
*/
- function setValidatorEpochPayment(uint256 value) external onlyOwner returns (bool) {
+ function setValidatorEpochPayment(uint256 value) public onlyOwner returns (bool) {
require(value != validatorEpochPayment);
validatorEpochPayment = value;
emit ValidatorEpochPaymentSet(value);
@@ -257,7 +203,7 @@ contract Validators is
uint256 exponent,
uint256 adjustmentSpeed
)
- external
+ public
onlyOwner
returns (bool)
{
@@ -275,60 +221,44 @@ contract Validators is
}
/**
- * @notice Returns the maximum number of members a group can add.
- * @return The maximum number of members a group can add.
- */
- function getMaxGroupSize() external view returns (uint256) {
- return maxGroupSize;
- }
-
- /**
- * @notice Updates the minimum gold requirements to register a validator group or validator.
- * @param groupRequirement The minimum locked gold needed to register a group.
- * @param validatorRequirement The minimum locked gold needed to register a validator.
+ * @notice Updates the Locked Gold requirements for Validator Groups.
+ * @param value The per-member amount of Locked Gold required.
+ * @param duration The time (in seconds) that these requirements persist for.
* @return True upon success.
- * @dev The new requirement is only enforced for future validator or group registrations.
*/
- // TODO(asa): Allow validators to adjust their LockedGold MustMaintain if the registration
- // requirements fall.
- function setBalanceRequirements(
- uint256 groupRequirement,
- uint256 validatorRequirement
+ function setGroupLockedGoldRequirements(
+ uint256 value,
+ uint256 duration
)
- external
+ public
onlyOwner
returns (bool)
{
- require(
- groupRequirement != balanceRequirements.group ||
- validatorRequirement != balanceRequirements.validator
- );
- balanceRequirements = BalanceRequirements(groupRequirement, validatorRequirement);
- emit BalanceRequirementsSet(groupRequirement, validatorRequirement);
+ LockedGoldRequirements storage requirements = groupLockedGoldRequirements;
+ require(value != requirements.value || duration != requirements.duration);
+ groupLockedGoldRequirements = LockedGoldRequirements(value, duration);
+ emit GroupLockedGoldRequirementsSet(value, duration);
return true;
}
/**
- * @notice Updates the duration for which gold remains locked after deregistration.
- * @param groupLockup The duration for groups in seconds.
- * @param validatorLockup The duration for validators in seconds.
+ * @notice Updates the Locked Gold requirements for Validators.
+ * @param value The amount of Locked Gold required.
+ * @param duration The time (in seconds) that these requirements persist for.
* @return True upon success.
- * @dev The new requirement is only enforced for future validator or group deregistrations.
*/
- function setDeregistrationLockups(
- uint256 groupLockup,
- uint256 validatorLockup
+ function setValidatorLockedGoldRequirements(
+ uint256 value,
+ uint256 duration
)
- external
+ public
onlyOwner
returns (bool)
{
- require(
- groupLockup != deregistrationLockups.group ||
- validatorLockup != deregistrationLockups.validator
- );
- deregistrationLockups = DeregistrationLockups(groupLockup, validatorLockup);
- emit DeregistrationLockupsSet(groupLockup, validatorLockup);
+ LockedGoldRequirements storage requirements = validatorLockedGoldRequirements;
+ require(value != requirements.value || duration != requirements.duration);
+ validatorLockedGoldRequirements = LockedGoldRequirements(value, duration);
+ emit ValidatorLockedGoldRequirementsSet(value, duration);
return true;
}
@@ -344,7 +274,7 @@ contract Validators is
* - blsPoP - The BLS public key proof of possession. 96 bytes.
* @return True upon success.
* @dev Fails if the account is already a validator or validator group.
- * @dev Fails if the account does not have sufficient weight.
+ * @dev Fails if the account does not have sufficient Locked Gold.
*/
function registerValidator(
string calldata name,
@@ -366,176 +296,42 @@ contract Validators is
address account = getLockedGold().getAccountFromActiveValidator(msg.sender);
require(!isValidator(account) && !isValidatorGroup(account));
- require(meetsValidatorBalanceRequirements(account));
-
- validators[account].name = name;
- validators[account].url = url;
- validators[account].publicKeysData = publicKeysData;
- _validators.push(account);
+ uint256 lockedGoldBalance = getLockedGold().getAccountTotalLockedGold(account);
+ require(lockedGoldBalance >= validatorLockedGoldRequirements.value);
+ Validator storage validator = validators[account];
+ validator.name = name;
+ validator.url = url;
+ validator.publicKeysData = publicKeysData;
+ registeredValidators.push(account);
updateMembershipHistory(account, address(0));
emit ValidatorRegistered(account, name, url, publicKeysData);
return true;
}
/**
- * @notice Checks a BLS proof of possession.
- * @param proofOfPossessionBytes The public key and signature of the proof of possession.
- * @return True upon success.
- */
- function checkProofOfPossession(bytes memory proofOfPossessionBytes) private returns (bool) {
- bool success;
- (success, ) = PROOF_OF_POSSESSION.call.value(0).gas(gasleft())(proofOfPossessionBytes);
- return success;
- }
-
- /**
- * @notice 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.
- */
- function meetsValidatorBalanceRequirements(address account) public view returns (bool) {
- return getLockedGold().getAccountTotalLockedGold(account) >= balanceRequirements.validator;
- }
-
- /**
- * @notice 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.
- */
- function meetsValidatorGroupBalanceRequirements(address account) public view returns (bool) {
- return getLockedGold().getAccountTotalLockedGold(account) >= balanceRequirements.group;
- }
-
- /**
- * @notice Returns the parameters that goven how a validator's score is calculated.
- * @return The parameters that goven how a validator's score is calculated.
- */
- function getValidatorScoreParameters() external view returns (uint256, uint256) {
- return (validatorScoreParameters.exponent, validatorScoreParameters.adjustmentSpeed.unwrap());
- }
-
- /**
- * @notice Returns the group membership history of a validator.
- * @param account The validator whose membership history to return.
- * @return The group membership history of a validator.
- */
- function getMembershipHistory(
- address account
- )
- external
- view
- returns (uint256[] memory, address[] memory)
- {
- MembershipHistoryEntry[] memory entries = validators[account].membershipHistory.entries;
- uint256[] memory epochs = new uint256[](entries.length);
- address[] memory membershipGroups = new address[](entries.length);
- for (uint256 i = 0; i < entries.length; i = i.add(1)) {
- epochs[i] = entries[i].epochNumber;
- membershipGroups[i] = entries[i].group;
- }
- return (epochs, membershipGroups);
- }
-
- /**
- * @notice Updates a validator's score based on its uptime for the epoch.
- * @param validator The address of the validator.
- * @param uptime The Fixidity representation of the validator's uptime, between 0 and 1.
- * @return True upon success.
- */
- function updateValidatorScore(address validator, uint256 uptime) external onlyVm() {
- _updateValidatorScore(validator, uptime);
- }
-
- /**
- * @notice Updates a validator's score based on its uptime for the epoch.
- * @param validator The address of the validator.
- * @param uptime The Fixidity representation of the validator's uptime, between 0 and 1.
- * @return True upon success.
- */
- function _updateValidatorScore(address validator, uint256 uptime) internal {
- address account = getLockedGold().getAccountFromValidator(validator);
- require(isValidator(account), "isvalidator");
- require(uptime <= FixidityLib.fixed1().unwrap(), "uptime");
-
- uint256 numerator;
- uint256 denominator;
- (numerator, denominator) = fractionMulExp(
- FixidityLib.fixed1().unwrap(),
- FixidityLib.fixed1().unwrap(),
- uptime,
- FixidityLib.fixed1().unwrap(),
- validatorScoreParameters.exponent,
- 18
- );
-
- FixidityLib.Fraction memory epochScore = FixidityLib.wrap(numerator).divide(
- FixidityLib.wrap(denominator)
- );
- FixidityLib.Fraction memory newComponent = validatorScoreParameters.adjustmentSpeed.multiply(
- epochScore
- );
-
- FixidityLib.Fraction memory currentComponent = FixidityLib.fixed1().subtract(
- validatorScoreParameters.adjustmentSpeed
- );
- currentComponent = currentComponent.multiply(validators[account].score);
- validators[account].score = FixidityLib.wrap(
- Math.min(
- epochScore.unwrap(),
- newComponent.add(currentComponent).unwrap()
- )
- );
- }
-
- /**
- * @notice Distributes epoch payments to `validator` and its group.
- */
- function distributeEpochPayment(address validator) external onlyVm() {
- _distributeEpochPayment(validator);
- }
-
- /**
- * @notice Distributes epoch payments to `validator` and its group.
- */
- function _distributeEpochPayment(address validator) internal {
- address account = getLockedGold().getAccountFromValidator(validator);
- require(isValidator(account));
- // The group that should be paid is the group that the validator was a member of at the
- // time it was elected.
- address group = getMembershipInLastEpoch(account);
- // Both the validator and the group must maintain the minimum locked gold balance in order to
- // receive epoch payments.
- bool meetsBalanceRequirements = (
- getLockedGold().getAccountTotalLockedGold(group) >= getAccountBalanceRequirement(group) &&
- getLockedGold().getAccountTotalLockedGold(account) >= getAccountBalanceRequirement(account)
- );
- if (meetsBalanceRequirements) {
- FixidityLib.Fraction memory totalPayment = FixidityLib.newFixed(
- validatorEpochPayment
- ).multiply(validators[account].score);
- uint256 groupPayment = totalPayment.multiply(groups[group].commission).fromFixed();
- uint256 validatorPayment = totalPayment.fromFixed().sub(groupPayment);
- getStableToken().mint(group, groupPayment);
- getStableToken().mint(account, validatorPayment);
- }
- }
-
- /**
- * @notice De-registers a validator, removing it from the group for which it is a member.
- * @param index The index of this validator in the list of all validators.
+ * @notice De-registers a validator.
+ * @param index The index of this validator in the list of all registered validators.
* @return True upon success.
* @dev Fails if the account is not a validator.
*/
function deregisterValidator(uint256 index) external nonReentrant returns (bool) {
address account = getLockedGold().getAccountFromActiveValidator(msg.sender);
require(isValidator(account));
+
+ // Require that the validator has not been a member of a validator group for
+ // `validatorLockedGoldRequirements.duration` seconds.
Validator storage validator = validators[account];
if (validator.affiliation != address(0)) {
- _deaffiliate(validator, account);
+ require(!groups[validator.affiliation].members.contains(account), 'two');
}
+ uint256 requirementEndTime = validator.membershipHistory.lastRemovedFromGroupTimestamp.add(
+ validatorLockedGoldRequirements.duration
+ );
+ require(requirementEndTime < now, 'one');
+
+ // Remove the validator.
+ deleteElement(registeredValidators, account, index);
delete validators[account];
- deleteElement(_validators, account, index);
- deregistrationTimestamps[account].validator = now;
emit ValidatorDeregistered(account);
return true;
}
@@ -549,6 +345,7 @@ contract Validators is
function affiliate(address group) external nonReentrant returns (bool) {
address account = getLockedGold().getAccountFromActiveValidator(msg.sender);
require(isValidator(account) && isValidatorGroup(group));
+ require(meetsAccountLockedGoldRequirements(account) && meetsAccountLockedGoldRequirements(group), "three");
Validator storage validator = validators[account];
if (validator.affiliation != address(0)) {
_deaffiliate(validator, account);
@@ -594,13 +391,13 @@ contract Validators is
require(commission <= FixidityLib.fixed1().unwrap(), "Commission can't be greater than 100%");
address account = getLockedGold().getAccountFromActiveValidator(msg.sender);
require(!isValidator(account) && !isValidatorGroup(account));
- require(meetsValidatorGroupBalanceRequirements(account));
-
+ uint256 lockedGoldBalance = getLockedGold().getAccountTotalLockedGold(account);
+ require(lockedGoldBalance >= groupLockedGoldRequirements.value);
ValidatorGroup storage group = groups[account];
group.name = name;
group.url = url;
group.commission = FixidityLib.wrap(commission);
- _groups.push(account);
+ registeredGroups.push(account);
emit ValidatorGroupRegistered(account, name, url);
return true;
}
@@ -613,11 +410,15 @@ contract Validators is
*/
function deregisterValidatorGroup(uint256 index) external nonReentrant returns (bool) {
address account = getLockedGold().getAccountFromActiveValidator(msg.sender);
- // Only empty Validator Groups can be deregistered.
+ // Only Validator Groups that have never had members or have been empty for at least
+ // `groupLockedGoldRequirements.duration` seconds can be deregistered.
require(isValidatorGroup(account) && groups[account].members.numElements == 0);
+ uint256[] storage sizeHistory = groups[account].sizeHistory;
+ if (sizeHistory.length > 1) {
+ require(sizeHistory[1].add(groupLockedGoldRequirements.duration) < now);
+ }
delete groups[account];
- deleteElement(_groups, account, index);
- deregistrationTimestamps[account].group = now;
+ deleteElement(registeredGroups, account, index);
emit ValidatorGroupDeregistered(account);
return true;
}
@@ -680,11 +481,14 @@ contract Validators is
ValidatorGroup storage _group = groups[group];
require(_group.members.numElements < maxGroupSize, "group would exceed maximum size");
require(validators[validator].affiliation == group && !_group.members.contains(validator));
+ uint256 numMembers = _group.members.numElements.add(1);
+ require(meetsAccountLockedGoldRequirements(group) && meetsAccountLockedGoldRequirements(validator));
_group.members.push(validator);
- if (_group.members.numElements == 1) {
+ if (numMembers == 1) {
getElection().markGroupEligible(group, lesser, greater);
}
updateMembershipHistory(validator, group);
+ updateSizeHistory(group, numMembers.sub(1));
emit ValidatorGroupMemberAdded(group, validator);
return true;
}
@@ -729,35 +533,173 @@ contract Validators is
return true;
}
+ /**
+ * @notice Updates a validator's score based on its uptime for the epoch.
+ * @param validator The address of the validator.
+ * @param uptime The Fixidity representation of the validator's uptime, between 0 and 1.
+ * @return True upon success.
+ */
+ function updateValidatorScore(address validator, uint256 uptime) external onlyVm() {
+ _updateValidatorScore(validator, uptime);
+ }
+
+ /**
+ * @notice Updates a validator's score based on its uptime for the epoch.
+ * @param validator The address of the validator.
+ * @param uptime The Fixidity representation of the validator's uptime, between 0 and 1.
+ * @return True upon success.
+ */
+ function _updateValidatorScore(address validator, uint256 uptime) internal {
+ address account = getLockedGold().getAccountFromValidator(validator);
+ require(isValidator(account));
+ require(uptime <= FixidityLib.fixed1().unwrap());
+
+ uint256 numerator;
+ uint256 denominator;
+ (numerator, denominator) = fractionMulExp(
+ FixidityLib.fixed1().unwrap(),
+ FixidityLib.fixed1().unwrap(),
+ uptime,
+ FixidityLib.fixed1().unwrap(),
+ validatorScoreParameters.exponent,
+ 18
+ );
+
+ FixidityLib.Fraction memory epochScore = FixidityLib.wrap(numerator).divide(
+ FixidityLib.wrap(denominator)
+ );
+ FixidityLib.Fraction memory newComponent = validatorScoreParameters.adjustmentSpeed.multiply(
+ epochScore
+ );
+
+ FixidityLib.Fraction memory currentComponent = FixidityLib.fixed1().subtract(
+ validatorScoreParameters.adjustmentSpeed
+ );
+ currentComponent = currentComponent.multiply(validators[account].score);
+ validators[account].score = FixidityLib.wrap(
+ Math.min(
+ epochScore.unwrap(),
+ newComponent.add(currentComponent).unwrap()
+ )
+ );
+ }
+
+ /**
+ * @notice Distributes epoch payments to `validator` and its group.
+ */
+ function distributeEpochPayment(address validator) external onlyVm() {
+ _distributeEpochPayment(validator);
+ }
+
+ /**
+ * @notice Distributes epoch payments to `validator` and its group.
+ */
+ function _distributeEpochPayment(address validator) internal {
+ address account = getLockedGold().getAccountFromValidator(validator);
+ require(isValidator(account));
+ // The group that should be paid is the group that the validator was a member of at the
+ // time it was elected.
+ address group = getMembershipInLastEpoch(account);
+ // Both the validator and the group must maintain the minimum locked gold balance in order to
+ // receive epoch payments.
+ if (meetsAccountLockedGoldRequirements(account) && meetsAccountLockedGoldRequirements(group)) {
+ FixidityLib.Fraction memory totalPayment = FixidityLib.newFixed(
+ validatorEpochPayment
+ ).multiply(validators[account].score);
+ uint256 groupPayment = totalPayment.multiply(groups[group].commission).fromFixed();
+ uint256 validatorPayment = totalPayment.fromFixed().sub(groupPayment);
+ getStableToken().mint(group, groupPayment);
+ getStableToken().mint(account, validatorPayment);
+ }
+ }
+
+ /**
+ * @notice Returns the parameters that goven how a validator's score is calculated.
+ * @return The parameters that goven how a validator's score is calculated.
+ */
+ function getValidatorScoreParameters() external view returns (uint256, uint256) {
+ return (validatorScoreParameters.exponent, validatorScoreParameters.adjustmentSpeed.unwrap());
+ }
+
+ /**
+ * @notice Returns the Locked Gold requirements for validators.
+ * @return The Locked Gold requirements for validators.
+ */
+ function getValidatorLockedGoldRequirements() external view returns (uint256, uint256) {
+ return (validatorLockedGoldRequirements.value, validatorLockedGoldRequirements.duration);
+ }
+
+ /**
+ * @notice Returns the Locked Gold requirements for validator groups.
+ * @return The Locked Gold requirements for validator groups.
+ */
+ function getGroupLockedGoldRequirements() external view returns (uint256, uint256) {
+ return (groupLockedGoldRequirements.value, groupLockedGoldRequirements.duration);
+ }
+
+ /**
+ * @notice Returns the maximum number of members a group can add.
+ * @return The maximum number of members a group can add.
+ */
+ function getMaxGroupSize() external view returns (uint256) {
+ return maxGroupSize;
+ }
+
+ /**
+ * @notice Returns the group membership history of a validator.
+ * @param account The validator whose membership history to return.
+ * @return The group membership history of a validator.
+ */
+ function getMembershipHistory(
+ address account
+ )
+ external
+ view
+ returns (uint256[] memory, address[] memory, uint256)
+ {
+ MembershipHistory storage history = validators[account].membershipHistory;
+ MembershipHistoryEntry[] memory entries = history.entries;
+ uint256[] memory epochs = new uint256[](entries.length);
+ address[] memory membershipGroups = new address[](entries.length);
+ for (uint256 i = 0; i < entries.length; i = i.add(1)) {
+ epochs[i] = entries[i].epochNumber;
+ membershipGroups[i] = entries[i].group;
+ }
+ return (epochs, membershipGroups, history.lastRemovedFromGroupTimestamp);
+ }
+
/**
* @notice Returns the locked gold balance requirement for the supplied account.
* @param account The account that may have to meet locked gold balance requirements.
* @return The locked gold balance requirement for the supplied account.
*/
- function getAccountBalanceRequirement(address account) public view returns (uint256) {
- DeregistrationTimestamps storage timestamps = deregistrationTimestamps[account];
- if (
- isValidator(account) ||
- (timestamps.validator > 0 && now < timestamps.validator.add(deregistrationLockups.validator))
- ) {
- return balanceRequirements.validator;
- }
- if (
- isValidatorGroup(account) ||
- (timestamps.group > 0 && now < timestamps.group.add(deregistrationLockups.group))
- ) {
- return balanceRequirements.group;
+ function getAccountLockedGoldRequirement(address account) public view returns (uint256) {
+ if (isValidator(account)) {
+ return validatorLockedGoldRequirements.value;
+ } else if (isValidatorGroup(account)) {
+ uint256 multiplier = Math.max(1, groups[account].members.numElements);
+ uint256[] storage sizeHistory = groups[account].sizeHistory;
+ if (sizeHistory.length > 0) {
+ for (uint256 i = sizeHistory.length.sub(1); i > 0; i = i.sub(1)) {
+ if (sizeHistory[i].add(groupLockedGoldRequirements.duration) >= now) {
+ multiplier = Math.max(i, multiplier);
+ break;
+ }
+ }
+ }
+ return groupLockedGoldRequirements.value.mul(multiplier);
}
return 0;
}
/**
- * @notice Returns the timestamp of the last time this account deregistered a validator or group.
- * @param account The account to query.
- * @return The timestamp of the last time this account deregistered a validator or group.
+ * @notice Returns whether or not an account meets its Locked Gold requirements.
+ * @param account The address of the account.
+ * @return Whether or not an account meets its Locked Gold requirements.
*/
- function getDeregistrationTimestamps(address account) external view returns (uint256, uint256) {
- return (deregistrationTimestamps[account].group, deregistrationTimestamps[account].validator);
+ function meetsAccountLockedGoldRequirements(address account) public view returns (bool) {
+ uint256 balance = getLockedGold().getAccountTotalLockedGold(account);
+ return balance >= getAccountLockedGoldRequirement(account);
}
/**
@@ -799,11 +741,17 @@ contract Validators is
)
external
view
- returns (string memory, string memory, address[] memory, uint256)
+ returns (string memory, string memory, address[] memory, uint256, uint256[] memory)
{
require(isValidatorGroup(account));
ValidatorGroup storage group = groups[account];
- return (group.name, group.url, group.members.getKeys(), group.commission.unwrap());
+ return (
+ group.name,
+ group.url,
+ group.members.getKeys(),
+ group.commission.unwrap(),
+ group.sizeHistory
+ );
}
/**
@@ -862,23 +810,7 @@ contract Validators is
* @return The number of registered validators.
*/
function getNumRegisteredValidators() external view returns (uint256) {
- return _validators.length;
- }
-
- /**
- * @notice Returns the Locked Gold requirements to register a validator or group.
- * @return The locked gold requirements to register a validator or group.
- */
- function getBalanceRequirements() external view returns (uint256, uint256) {
- return (balanceRequirements.group, balanceRequirements.validator);
- }
-
- /**
- * @notice Returns the lockup periods after deregistering groups and validators.
- * @return The lockup periods after deregistering groups and validators.
- */
- function getDeregistrationLockups() external view returns (uint256, uint256) {
- return (deregistrationLockups.group, deregistrationLockups.validator);
+ return registeredValidators.length;
}
/**
@@ -886,7 +818,7 @@ contract Validators is
* @return The list of registered validator accounts.
*/
function getRegisteredValidators() external view returns (address[] memory) {
- return _validators;
+ return registeredValidators;
}
/**
@@ -894,22 +826,22 @@ contract Validators is
* @return The list of registered validator group addresses.
*/
function getRegisteredValidatorGroups() external view returns (address[] memory) {
- return _groups;
+ return registeredGroups;
}
/**
- * @notice Returns whether a particular account has a registered validator group.
+ * @notice Returns whether a particular account has a validator group.
* @param account The account.
- * @return Whether a particular address is a registered validator group.
+ * @return Whether a particular address is a validator group.
*/
function isValidatorGroup(address account) public view returns (bool) {
return bytes(groups[account].name).length > 0;
}
/**
- * @notice Returns whether a particular account has a registered validator.
+ * @notice Returns whether a particular account has a validator.
* @param account The account.
- * @return Whether a particular address is a registered validator.
+ * @return Whether a particular address is a validator.
*/
function isValidator(address account) public view returns (bool) {
return bytes(validators[account].name).length > 0;
@@ -941,13 +873,14 @@ contract Validators is
ValidatorGroup storage _group = groups[group];
require(validators[validator].affiliation == group && _group.members.contains(validator));
_group.members.remove(validator);
- updateMembershipHistory(validator, address(0));
- emit ValidatorGroupMemberRemoved(group, validator);
-
+ uint256 numMembers = _group.members.numElements;
// Empty validator groups are not electable.
- if (groups[group].members.numElements == 0) {
+ if (numMembers == 0) {
getElection().markGroupIneligible(group);
}
+ updateMembershipHistory(validator, address(0));
+ updateSizeHistory(group, numMembers.add(1));
+ emit ValidatorGroupMemberRemoved(group, validator);
return true;
}
@@ -962,21 +895,39 @@ contract Validators is
function updateMembershipHistory(address account, address group) private returns (bool) {
MembershipHistory storage history = validators[account].membershipHistory;
uint256 epochNumber = getEpochNumber();
- if (history.entries.length > 0 && epochNumber == history.entries[history.head].epochNumber) {
- // There have been no elections since the validator last changed membership, overwrite the
- // previous entry.
- history.entries[history.head] = MembershipHistoryEntry(epochNumber, group);
- } else {
- if (history.entries.length > 0) {
- // MembershipHistoryEntries are a circular buffer.
- history.head = history.head.add(1) % membershipHistoryLength;
+ if (history.entries.length > 0) {
+ if (group == address(0)) {
+ history.lastRemovedFromGroupTimestamp = now;
}
- if (history.head >= history.entries.length) {
- history.entries.push(MembershipHistoryEntry(epochNumber, group));
- } else {
+
+ if (epochNumber == history.entries[history.head].epochNumber) {
+ // There have been no elections since the validator last changed membership, overwrite the
+ // previous entry.
history.entries[history.head] = MembershipHistoryEntry(epochNumber, group);
+ return true;
+ } else {
+ // MembershipHistoryEntries are a circular buffer.
+ history.head = history.head.add(1) % membershipHistoryLength;
}
}
+
+ if (history.head >= history.entries.length) {
+ history.entries.push(MembershipHistoryEntry(epochNumber, group));
+ } else {
+ history.entries[history.head] = MembershipHistoryEntry(epochNumber, group);
+ }
+ return true;
+ }
+
+ function updateSizeHistory(address group, uint256 size) private {
+ uint256[] storage sizeHistory = groups[group].sizeHistory;
+ if (size == sizeHistory.length) {
+ sizeHistory.push(now);
+ } else if (size < sizeHistory.length) {
+ sizeHistory[size] = now;
+ } else {
+ require(false, "Unable to update size history");
+ }
}
/**
@@ -1018,8 +969,8 @@ contract Validators is
if (group.members.contains(validatorAccount)) {
_removeMember(affiliation, validatorAccount);
}
- emit ValidatorDeaffiliated(validatorAccount, affiliation);
validator.affiliation = address(0);
+ emit ValidatorDeaffiliated(validatorAccount, affiliation);
return true;
}
}
diff --git a/packages/protocol/contracts/governance/interfaces/IValidators.sol b/packages/protocol/contracts/governance/interfaces/IValidators.sol
index 41a68b70859..6650c34686f 100644
--- a/packages/protocol/contracts/governance/interfaces/IValidators.sol
+++ b/packages/protocol/contracts/governance/interfaces/IValidators.sol
@@ -2,7 +2,8 @@ pragma solidity ^0.5.3;
interface IValidators {
- function getAccountBalanceRequirement(address) external view returns (uint256);
+ function getAccountLockedGoldRequirement(address) external view returns (uint256);
+ function meetsAccountLockedGoldRequirements(address) external view returns (bool);
function getGroupNumMembers(address) external view returns (uint256);
function getGroupsNumMembers(address[] calldata) external view returns (uint256[] memory);
function getNumRegisteredValidators() external view returns (uint256);
diff --git a/packages/protocol/test/governance/validators.ts b/packages/protocol/test/governance/validators.ts
index d034dc03dd5..6fd79470b93 100644
--- a/packages/protocol/test/governance/validators.ts
+++ b/packages/protocol/test/governance/validators.ts
@@ -6,6 +6,7 @@ import {
assertSameAddress,
NULL_ADDRESS,
mineBlocks,
+ timeTravel,
} from '@celo/protocol/lib/test-utils'
import BigNumber from 'bignumber.js'
import {
@@ -48,6 +49,15 @@ const parseValidatorGroupParams = (groupParams: any) => {
url: groupParams[1],
members: groupParams[2],
commission: groupParams[3],
+ sizeHistory: groupParams[4],
+ }
+}
+
+const parseMembershipHistory = (membershipHistory: any) => {
+ return {
+ epochs: membershipHistory[0],
+ groups: membershipHistory[1],
+ lastRemovedFromGroupTimestamp: membershipHistory[2],
}
}
@@ -64,17 +74,20 @@ contract('Validators', (accounts: string[]) => {
let mockElection: MockElectionInstance
const nonOwner = accounts[1]
- const balanceRequirements = { group: new BigNumber(1000), validator: new BigNumber(100) }
- const deregistrationLockups = {
- group: new BigNumber(100 * DAY),
- validator: new BigNumber(60 * DAY),
+ const validatorLockedGoldRequirements = {
+ value: new BigNumber(1000),
+ duration: new BigNumber(60 * DAY),
+ }
+ const groupLockedGoldRequirements = {
+ value: new BigNumber(1000),
+ duration: new BigNumber(100 * DAY),
}
const validatorScoreParameters = {
exponent: new BigNumber(5),
adjustmentSpeed: toFixed(0.25),
}
const validatorEpochPayment = new BigNumber(10000000000000)
- const membershipHistoryLength = new BigNumber(3)
+ const membershipHistoryLength = new BigNumber(5)
const maxGroupSize = new BigNumber(5)
// A random 64 byte hex string.
@@ -97,10 +110,10 @@ contract('Validators', (accounts: string[]) => {
await registry.setAddressFor(CeloContractName.Election, mockElection.address)
await validators.initialize(
registry.address,
- balanceRequirements.group,
- balanceRequirements.validator,
- deregistrationLockups.group,
- deregistrationLockups.validator,
+ groupLockedGoldRequirements.value,
+ groupLockedGoldRequirements.duration,
+ validatorLockedGoldRequirements.value,
+ validatorLockedGoldRequirements.duration,
validatorScoreParameters.exponent,
validatorScoreParameters.adjustmentSpeed,
validatorEpochPayment,
@@ -110,7 +123,7 @@ contract('Validators', (accounts: string[]) => {
})
const registerValidator = async (validator: string) => {
- await mockLockedGold.setAccountTotalLockedGold(validator, balanceRequirements.validator)
+ await mockLockedGold.setAccountTotalLockedGold(validator, validatorLockedGoldRequirements.value)
await validators.registerValidator(
name,
url,
@@ -121,7 +134,7 @@ contract('Validators', (accounts: string[]) => {
}
const registerValidatorGroup = async (group: string) => {
- await mockLockedGold.setAccountTotalLockedGold(group, balanceRequirements.group)
+ await mockLockedGold.setAccountTotalLockedGold(group, groupLockedGoldRequirements.value)
await validators.registerValidatorGroup(name, url, commission, { from: group })
}
@@ -144,16 +157,16 @@ contract('Validators', (accounts: string[]) => {
assert.equal(owner, accounts[0])
})
- it('should have set the balance requirements', async () => {
- const [group, validator] = await validators.getBalanceRequirements()
- assertEqualBN(group, balanceRequirements.group)
- assertEqualBN(validator, balanceRequirements.validator)
+ it('should have set the group locked gold requirements', async () => {
+ const [value, duration] = await validators.getGroupLockedGoldRequirements()
+ assertEqualBN(value, groupLockedGoldRequirements.value)
+ assertEqualBN(duration, groupLockedGoldRequirements.duration)
})
- it('should have set the deregistration lockups', async () => {
- const [group, validator] = await validators.getDeregistrationLockups()
- assertEqualBN(group, deregistrationLockups.group)
- assertEqualBN(validator, deregistrationLockups.validator)
+ it('should have set the validator locked gold requirements', async () => {
+ const [value, duration] = await validators.getValidatorLockedGoldRequirements()
+ assertEqualBN(value, validatorLockedGoldRequirements.value)
+ assertEqualBN(duration, validatorLockedGoldRequirements.duration)
})
it('should have set the validator score parameters', async () => {
@@ -181,10 +194,10 @@ contract('Validators', (accounts: string[]) => {
await assertRevert(
validators.initialize(
registry.address,
- balanceRequirements.group,
- balanceRequirements.validator,
- deregistrationLockups.group,
- deregistrationLockups.validator,
+ groupLockedGoldRequirements.value,
+ groupLockedGoldRequirements.duration,
+ validatorLockedGoldRequirements.value,
+ validatorLockedGoldRequirements.duration,
validatorScoreParameters.exponent,
validatorScoreParameters.adjustmentSpeed,
validatorEpochPayment,
@@ -285,37 +298,37 @@ contract('Validators', (accounts: string[]) => {
})
})
- describe('#setBalanceRequirements()', () => {
+ describe('#setGroupLockedGoldRequirements()', () => {
describe('when the requirements are different', () => {
const newRequirements = {
- group: balanceRequirements.group.plus(1),
- validator: balanceRequirements.validator.plus(1),
+ value: groupLockedGoldRequirements.value.plus(1),
+ duration: groupLockedGoldRequirements.duration.plus(1),
}
describe('when called by the owner', () => {
let resp: any
beforeEach(async () => {
- resp = await validators.setBalanceRequirements(
- newRequirements.group,
- newRequirements.validator
+ resp = await validators.setGroupLockedGoldRequirements(
+ newRequirements.value,
+ newRequirements.duration
)
})
- it('should set the group and validator requirements', async () => {
- const [group, validator] = await validators.getBalanceRequirements()
- assertEqualBN(group, newRequirements.group)
- assertEqualBN(validator, newRequirements.validator)
+ it('should have set the group locked gold requirements', async () => {
+ const [value, duration] = await validators.getGroupLockedGoldRequirements()
+ assertEqualBN(value, newRequirements.value)
+ assertEqualBN(duration, newRequirements.duration)
})
- it('should emit the BalanceRequirementsSet event', async () => {
+ it('should emit the GroupLockedGoldRequirementsSet event', async () => {
assert.equal(resp.logs.length, 1)
const log = resp.logs[0]
assertContainSubset(log, {
- event: 'BalanceRequirementsSet',
+ event: 'GroupLockedGoldRequirementsSet',
args: {
- group: new BigNumber(newRequirements.group),
- validator: new BigNumber(newRequirements.validator),
+ value: newRequirements.value,
+ duration: newRequirements.duration,
},
})
})
@@ -323,9 +336,11 @@ contract('Validators', (accounts: string[]) => {
describe('when called by a non-owner', () => {
it('should revert', async () => {
await assertRevert(
- validators.setBalanceRequirements(newRequirements.group, newRequirements.validator, {
- from: nonOwner,
- })
+ validators.setGroupLockedGoldRequirements(
+ newRequirements.value,
+ newRequirements.duration,
+ { from: nonOwner }
+ )
)
})
})
@@ -334,9 +349,9 @@ contract('Validators', (accounts: string[]) => {
describe('when the requirements are the same', () => {
it('should revert', async () => {
await assertRevert(
- validators.setBalanceRequirements(
- balanceRequirements.group,
- balanceRequirements.validator
+ validators.setGroupLockedGoldRequirements(
+ groupLockedGoldRequirements.value,
+ groupLockedGoldRequirements.duration
)
)
})
@@ -344,34 +359,37 @@ contract('Validators', (accounts: string[]) => {
})
})
- describe('#setDeregistrationLockups()', () => {
- describe('when the lockups are different', () => {
- const newLockups = {
- group: deregistrationLockups.group.plus(1),
- validator: deregistrationLockups.validator.plus(1),
+ describe('#setValidatorLockedGoldRequirements()', () => {
+ describe('when the requirements are different', () => {
+ const newRequirements = {
+ value: validatorLockedGoldRequirements.value.plus(1),
+ duration: validatorLockedGoldRequirements.duration.plus(1),
}
describe('when called by the owner', () => {
let resp: any
beforeEach(async () => {
- resp = await validators.setDeregistrationLockups(newLockups.group, newLockups.validator)
+ resp = await validators.setValidatorLockedGoldRequirements(
+ newRequirements.value,
+ newRequirements.duration
+ )
})
- it('should set the group and validator lockups', async () => {
- const [group, validator] = await validators.getDeregistrationLockups()
- assertEqualBN(group, newLockups.group)
- assertEqualBN(validator, newLockups.validator)
+ it('should have set the validator locked gold requirements', async () => {
+ const [value, duration] = await validators.getValidatorLockedGoldRequirements()
+ assertEqualBN(value, newRequirements.value)
+ assertEqualBN(duration, newRequirements.duration)
})
- it('should emit the DeregistrationLockupsSet event', async () => {
+ it('should emit the ValidatorLockedGoldRequirementsSet event', async () => {
assert.equal(resp.logs.length, 1)
const log = resp.logs[0]
assertContainSubset(log, {
- event: 'DeregistrationLockupsSet',
+ event: 'ValidatorLockedGoldRequirementsSet',
args: {
- group: new BigNumber(newLockups.group),
- validator: new BigNumber(newLockups.validator),
+ value: newRequirements.value,
+ duration: newRequirements.duration,
},
})
})
@@ -379,20 +397,22 @@ contract('Validators', (accounts: string[]) => {
describe('when called by a non-owner', () => {
it('should revert', async () => {
await assertRevert(
- validators.setDeregistrationLockups(newLockups.group, newLockups.validator, {
- from: nonOwner,
- })
+ validators.setValidatorLockedGoldRequirements(
+ newRequirements.value,
+ newRequirements.duration,
+ { from: nonOwner }
+ )
)
})
})
})
- describe('when the lockups are the same', () => {
+ describe('when the requirements are the same', () => {
it('should revert', async () => {
await assertRevert(
- validators.setDeregistrationLockups(
- deregistrationLockups.group,
- deregistrationLockups.validator
+ validators.setValidatorLockedGoldRequirements(
+ validatorLockedGoldRequirements.value,
+ validatorLockedGoldRequirements.duration
)
)
})
@@ -509,7 +529,10 @@ contract('Validators', (accounts: string[]) => {
let resp: any
describe('when the account is not a registered validator', () => {
beforeEach(async () => {
- await mockLockedGold.setAccountTotalLockedGold(validator, balanceRequirements.validator)
+ await mockLockedGold.setAccountTotalLockedGold(
+ validator,
+ validatorLockedGoldRequirements.value
+ )
resp = await validators.registerValidator(
name,
url,
@@ -533,9 +556,9 @@ contract('Validators', (accounts: string[]) => {
assert.equal(parsedValidator.publicKeysData, publicKeysData)
})
- it('should set account balance requirements', async () => {
- const requirement = await validators.getAccountBalanceRequirement(validator)
- assertEqualBN(requirement, balanceRequirements.validator)
+ it('should set account locked gold requirements', async () => {
+ const requirement = await validators.getAccountLockedGoldRequirement(validator)
+ assertEqualBN(requirement, validatorLockedGoldRequirements.value)
})
it('should emit the ValidatorRegistered event', async () => {
@@ -553,22 +576,9 @@ contract('Validators', (accounts: string[]) => {
})
})
- describe('when the account is already a registered validator', () => {
- beforeEach(async () => {
- await mockLockedGold.setAccountTotalLockedGold(validator, balanceRequirements.validator)
- await validators.registerValidator(
- name,
- url,
- // @ts-ignore bytes type
- publicKeysData
- )
- assert.deepEqual(await validators.getRegisteredValidators(), [validator])
- })
- })
-
- describe('when the account is already a registered validator', () => {
+ describe('when the account is already a registered validator group', () => {
beforeEach(async () => {
- await mockLockedGold.setAccountTotalLockedGold(validator, balanceRequirements.group)
+ await mockLockedGold.setAccountTotalLockedGold(validator, groupLockedGoldRequirements.value)
await validators.registerValidatorGroup(name, url, commission)
})
@@ -584,11 +594,11 @@ contract('Validators', (accounts: string[]) => {
})
})
- describe('when the account does not meet the balance requirements', () => {
+ describe('when the account does not meet the locked gold requirements', () => {
beforeEach(async () => {
await mockLockedGold.setAccountTotalLockedGold(
validator,
- balanceRequirements.validator.minus(1)
+ validatorLockedGoldRequirements.value.minus(1)
)
})
@@ -612,92 +622,96 @@ contract('Validators', (accounts: string[]) => {
describe('when the account is a registered validator', () => {
beforeEach(async () => {
await registerValidator(validator)
- resp = await validators.deregisterValidator(index)
- })
-
- it('should mark the account as not a validator', async () => {
- assert.isFalse(await validators.isValidator(validator))
})
- it('should remove the account from the list of validators', async () => {
- assert.deepEqual(await validators.getRegisteredValidators(), [])
- })
-
- it('should preserve account balance requirements', async () => {
- const requirement = await validators.getAccountBalanceRequirement(validator)
- assertEqualBN(requirement, balanceRequirements.validator)
- })
-
- it('should set the validator deregistration timestamp', async () => {
- const latestTimestamp = (await web3.eth.getBlock('latest')).timestamp
- const [groupTimestamp, validatorTimestamp] = await validators.getDeregistrationTimestamps(
- validator
- )
- assertEqualBN(groupTimestamp, 0)
- assertEqualBN(validatorTimestamp, latestTimestamp)
- })
-
- it('should emit the ValidatorDeregistered event', async () => {
- assert.equal(resp.logs.length, 1)
- const log = resp.logs[0]
- assertContainSubset(log, {
- event: 'ValidatorDeregistered',
- args: {
- validator,
- },
+ describe('when the validator has never been a member of a validator group', () => {
+ beforeEach(async () => {
+ resp = await validators.deregisterValidator(index)
})
- })
- })
- describe('when the validator is affiliated with a validator group', () => {
- const group = accounts[1]
- beforeEach(async () => {
- await registerValidator(validator)
- await registerValidatorGroup(group)
- await validators.affiliate(group)
- })
-
- it('should emit the ValidatorDeafilliated event', async () => {
- const resp = await validators.deregisterValidator(index)
- assert.equal(resp.logs.length, 2)
- const log = resp.logs[0]
- assertContainSubset(log, {
- event: 'ValidatorDeaffiliated',
- args: {
- validator,
- group,
- },
+ it('should mark the account as not a validator', async () => {
+ assert.isFalse(await validators.isValidator(validator))
})
- })
- describe('when the validator is a member of that group', () => {
- beforeEach(async () => {
- await validators.addFirstMember(validator, NULL_ADDRESS, NULL_ADDRESS, { from: group })
+ it('should remove the account from the list of validators', async () => {
+ assert.deepEqual(await validators.getRegisteredValidators(), [])
})
- it('should remove the validator from the group membership list', async () => {
- await validators.deregisterValidator(index)
- const parsedGroup = parseValidatorGroupParams(await validators.getValidatorGroup(group))
- assert.deepEqual(parsedGroup.members, [])
+ it('should reset account balance requirements', async () => {
+ const requirement = await validators.getAccountLockedGoldRequirement(validator)
+ assertEqualBN(requirement, 0)
})
- it('should emit the ValidatorGroupMemberRemoved event', async () => {
- const resp = await validators.deregisterValidator(index)
- assert.equal(resp.logs.length, 3)
+ it('should emit the ValidatorDeregistered event', async () => {
+ assert.equal(resp.logs.length, 1)
const log = resp.logs[0]
assertContainSubset(log, {
- event: 'ValidatorGroupMemberRemoved',
+ event: 'ValidatorDeregistered',
args: {
validator,
- group,
},
})
})
+ })
+
+ describe('when the validator has been a member of a validator group', () => {
+ const group = accounts[1]
+ beforeEach(async () => {
+ await registerValidatorGroup(group)
+ await validators.affiliate(group)
+ await validators.addFirstMember(validator, NULL_ADDRESS, NULL_ADDRESS, { from: group })
+ })
+
+ describe('when the validator is no longer a member of a validator group', () => {
+ beforeEach(async () => {
+ await validators.removeMember(validator, { from: group })
+ })
+
+ describe('when it has been more than `validatorLockedGoldRequirements.duration` since the validator was removed from the group', () => {
+ beforeEach(async () => {
+ await timeTravel(validatorLockedGoldRequirements.duration.plus(1).toNumber(), web3)
+ resp = await validators.deregisterValidator(index)
+ })
+
+ it('should mark the account as not a validator', async () => {
+ assert.isFalse(await validators.isValidator(validator))
+ })
+
+ it('should remove the account from the list of validators', async () => {
+ assert.deepEqual(await validators.getRegisteredValidators(), [])
+ })
+
+ it('should reset account balance requirements', async () => {
+ const requirement = await validators.getAccountLockedGoldRequirement(validator)
+ assertEqualBN(requirement, 0)
+ })
+
+ it('should emit the ValidatorDeregistered event', async () => {
+ assert.equal(resp.logs.length, 1)
+ const log = resp.logs[0]
+ assertContainSubset(log, {
+ event: 'ValidatorDeregistered',
+ args: {
+ validator,
+ },
+ })
+ })
+ })
+
+ describe('when it has been `validatorLockedGoldRequirements.duration` since the validator was removed from the group', () => {
+ beforeEach(async () => {
+ await timeTravel(validatorLockedGoldRequirements.duration.toNumber(), web3)
+ })
+
+ it('should revert', async () => {
+ await assertRevert(validators.deregisterValidator(index))
+ })
+ })
+ })
- describe('when the validator is the only member of that group', () => {
- it('should should mark the group as ineligible for election', async () => {
- await validators.deregisterValidator(index)
- assert.isTrue(await mockElection.isIneligible(group))
+ describe('when the validator is still a member of a validator group', () => {
+ it('should revert', async () => {
+ await assertRevert(validators.deregisterValidator(index))
})
})
})
@@ -715,108 +729,174 @@ contract('Validators', (accounts: string[]) => {
describe('#affiliate', () => {
const validator = accounts[0]
const group = accounts[1]
- beforeEach(async () => {
- await registerValidator(validator)
- await registerValidatorGroup(group)
- })
-
- it('should set the affiliate', async () => {
- await validators.affiliate(group)
- const parsedValidator = parseValidatorParams(await validators.getValidator(validator))
- assert.equal(parsedValidator.affiliation, group)
- })
-
- it('should emit the ValidatorAffiliated event', async () => {
- const resp = await validators.affiliate(group)
- assert.equal(resp.logs.length, 1)
- const log = resp.logs[0]
- assertContainSubset(log, {
- event: 'ValidatorAffiliated',
- args: {
- validator,
- group,
- },
- })
- })
-
- describe('when the validator is already affiliated with a validator group', () => {
- const otherGroup = accounts[2]
+ let resp: any
+ describe('when the account has a registered validator', () => {
beforeEach(async () => {
- await validators.affiliate(group)
- await registerValidatorGroup(otherGroup)
+ await registerValidator(validator)
})
+ describe('when affiliating with a registered validator group', () => {
+ beforeEach(async () => {
+ await registerValidatorGroup(group)
+ })
- it('should set the affiliate', async () => {
- await validators.affiliate(otherGroup)
- const parsedValidator = parseValidatorParams(await validators.getValidator(validator))
- assert.equal(parsedValidator.affiliation, otherGroup)
- })
+ describe('when the validator meets the locked gold requirements', () => {
+ describe('when the group meets the locked gold requirements', () => {
+ beforeEach(async () => {
+ resp = await validators.affiliate(group)
+ })
+
+ it('should set the affiliate', async () => {
+ await validators.affiliate(group)
+ const parsedValidator = parseValidatorParams(await validators.getValidator(validator))
+ assert.equal(parsedValidator.affiliation, group)
+ })
+
+ it('should emit the ValidatorAffiliated event', async () => {
+ assert.equal(resp.logs.length, 1)
+ const log = resp.logs[0]
+ assertContainSubset(log, {
+ event: 'ValidatorAffiliated',
+ args: {
+ validator,
+ group,
+ },
+ })
+ })
- it('should emit the ValidatorDeafilliated event', async () => {
- const resp = await validators.affiliate(otherGroup)
- assert.equal(resp.logs.length, 2)
- const log = resp.logs[0]
- assertContainSubset(log, {
- event: 'ValidatorDeaffiliated',
- args: {
- validator,
- group,
- },
- })
- })
+ describe('when the validator is already affiliated with a validator group', () => {
+ const otherGroup = accounts[2]
+ beforeEach(async () => {
+ await registerValidatorGroup(otherGroup)
+ })
- it('should emit the ValidatorAffiliated event', async () => {
- const resp = await validators.affiliate(otherGroup)
- assert.equal(resp.logs.length, 2)
- const log = resp.logs[1]
- assertContainSubset(log, {
- event: 'ValidatorAffiliated',
- args: {
- validator,
- group: otherGroup,
- },
- })
- })
+ describe('when the validator is not a member of that validator group', () => {
+ beforeEach(async () => {
+ resp = await validators.affiliate(otherGroup)
+ })
+
+ it('should set the affiliate', async () => {
+ const parsedValidator = parseValidatorParams(
+ await validators.getValidator(validator)
+ )
+ assert.equal(parsedValidator.affiliation, otherGroup)
+ })
+
+ it('should emit the ValidatorDeafilliated event', async () => {
+ assert.equal(resp.logs.length, 2)
+ const log = resp.logs[0]
+ assertContainSubset(log, {
+ event: 'ValidatorDeaffiliated',
+ args: {
+ validator,
+ group,
+ },
+ })
+ })
+
+ it('should emit the ValidatorAffiliated event', async () => {
+ assert.equal(resp.logs.length, 2)
+ const log = resp.logs[1]
+ assertContainSubset(log, {
+ event: 'ValidatorAffiliated',
+ args: {
+ validator,
+ group: otherGroup,
+ },
+ })
+ })
+ })
- describe('when the validator is a member of that group', () => {
- beforeEach(async () => {
- await validators.addFirstMember(validator, NULL_ADDRESS, NULL_ADDRESS, { from: group })
- })
+ describe('when the validator is a member of that group', () => {
+ beforeEach(async () => {
+ await validators.addFirstMember(validator, NULL_ADDRESS, NULL_ADDRESS, {
+ from: group,
+ })
+ resp = await validators.affiliate(otherGroup)
+ })
+
+ it('should remove the validator from the group membership list', async () => {
+ const parsedGroup = parseValidatorGroupParams(
+ await validators.getValidatorGroup(group)
+ )
+ assert.deepEqual(parsedGroup.members, [])
+ })
+
+ it("should update the validator's membership history", async () => {
+ const membershipHistory = parseMembershipHistory(
+ await validators.getMembershipHistory(validator)
+ )
+ const latestBlock = await web3.eth.getBlock('latest')
+ const expectedEpoch = new BigNumber(Math.floor(latestBlock.number / EPOCH))
+ assert.equal(membershipHistory.epochs.length, 1)
+ assertEqualBN(membershipHistory.epochs[0], expectedEpoch)
+ assert.equal(membershipHistory.groups.length, 1)
+ assertSameAddress(membershipHistory.groups[0], NULL_ADDRESS)
+ assert.equal(
+ membershipHistory.lastRemovedFromGroupTimestamp,
+ latestBlock.timestamp
+ )
+ })
+
+ it('should emit the ValidatorGroupMemberRemoved event', async () => {
+ assert.equal(resp.logs.length, 3)
+ const log = resp.logs[0]
+ assertContainSubset(log, {
+ event: 'ValidatorGroupMemberRemoved',
+ args: {
+ validator,
+ group,
+ },
+ })
+ })
+
+ describe('when the validator is the only member of that group', () => {
+ it('should should mark the group as ineligible for election', async () => {
+ assert.isTrue(await mockElection.isIneligible(group))
+ })
+ })
+ })
+ })
+ })
- it('should remove the validator from the group membership list', async () => {
- await validators.affiliate(otherGroup)
- const parsedGroup = parseValidatorGroupParams(await validators.getValidatorGroup(group))
- assert.deepEqual(parsedGroup.members, [])
+ describe('when the group does not meet the locked gold requirements', () => {
+ beforeEach(async () => {
+ await mockLockedGold.setAccountTotalLockedGold(
+ group,
+ groupLockedGoldRequirements.value.minus(1)
+ )
+ })
+
+ it('should revert', async () => {
+ await assertRevert(validators.affiliate(group))
+ })
+ })
})
- it('should emit the ValidatorGroupMemberRemoved event', async () => {
- const resp = await validators.affiliate(otherGroup)
- assert.equal(resp.logs.length, 3)
- const log = resp.logs[0]
- assertContainSubset(log, {
- event: 'ValidatorGroupMemberRemoved',
- args: {
+ describe('when the validator does not meet the locked gold requirements', () => {
+ beforeEach(async () => {
+ await mockLockedGold.setAccountTotalLockedGold(
validator,
- group,
- },
+ validatorLockedGoldRequirements.value.minus(1)
+ )
})
- })
- describe('when the validator is the only member of that group', () => {
- it('should should mark the group as ineligible for election', async () => {
- await validators.affiliate(otherGroup)
- assert.isTrue(await mockElection.isIneligible(group))
+ it('should revert', async () => {
+ await assertRevert(validators.affiliate(group))
})
})
})
- })
- it('should revert when the account is not a registered validator', async () => {
- await assertRevert(validators.affiliate(group, { from: accounts[2] }))
+ describe('when affiliating with a non-registered validator group', () => {
+ it('should revert', async () => {
+ await assertRevert(validators.affiliate(group))
+ })
+ })
})
- it('should revert when the group is not a registered validator group', async () => {
- await assertRevert(validators.affiliate(accounts[2]))
+ describe('when the account does not have a registered validator', () => {
+ it('should revert', async () => {
+ await assertRevert(validators.affiliate(group))
+ })
})
})
@@ -861,14 +941,16 @@ contract('Validators', (accounts: string[]) => {
it("should update the member's membership history", async () => {
await validators.deaffiliate()
- const membershipHistory = await validators.getMembershipHistory(validator)
- const expectedEpoch = new BigNumber(
- Math.floor((await web3.eth.getBlock('latest')).number / EPOCH)
+ const membershipHistory = parseMembershipHistory(
+ await validators.getMembershipHistory(validator)
)
- assert.equal(membershipHistory[0].length, 1)
- assertEqualBN(membershipHistory[0][0], expectedEpoch)
- assert.equal(membershipHistory[1].length, 1)
- assertSameAddress(membershipHistory[1][0], NULL_ADDRESS)
+ const latestBlock = await web3.eth.getBlock('latest')
+ const expectedEpoch = new BigNumber(Math.floor(latestBlock.number / EPOCH))
+ assert.equal(membershipHistory.epochs.length, 1)
+ assertEqualBN(membershipHistory.epochs[0], expectedEpoch)
+ assert.equal(membershipHistory.groups.length, 1)
+ assertSameAddress(membershipHistory.groups[0], NULL_ADDRESS)
+ assert.equal(membershipHistory.lastRemovedFromGroupTimestamp, latestBlock.timestamp)
})
it('should emit the ValidatorGroupMemberRemoved event', async () => {
@@ -906,40 +988,54 @@ contract('Validators', (accounts: string[]) => {
const group = accounts[0]
let resp: any
describe('when the account is not a registered validator group', () => {
- beforeEach(async () => {
- await mockLockedGold.setAccountTotalLockedGold(group, balanceRequirements.group)
- resp = await validators.registerValidatorGroup(name, url, commission)
- })
+ describe('when the account meets the locked gold requirements', () => {
+ beforeEach(async () => {
+ await mockLockedGold.setAccountTotalLockedGold(group, groupLockedGoldRequirements.value)
+ resp = await validators.registerValidatorGroup(name, url, commission)
+ })
- it('should mark the account as a validator group', async () => {
- assert.isTrue(await validators.isValidatorGroup(group))
- })
+ it('should mark the account as a validator group', async () => {
+ assert.isTrue(await validators.isValidatorGroup(group))
+ })
- it('should add the account to the list of validator groups', async () => {
- assert.deepEqual(await validators.getRegisteredValidatorGroups(), [group])
- })
+ it('should add the account to the list of validator groups', async () => {
+ assert.deepEqual(await validators.getRegisteredValidatorGroups(), [group])
+ })
- it('should set the validator group name and url', async () => {
- const parsedGroup = parseValidatorGroupParams(await validators.getValidatorGroup(group))
- assert.equal(parsedGroup.name, name)
- assert.equal(parsedGroup.url, url)
- })
+ it('should set the validator group name and url', async () => {
+ const parsedGroup = parseValidatorGroupParams(await validators.getValidatorGroup(group))
+ assert.equal(parsedGroup.name, name)
+ assert.equal(parsedGroup.url, url)
+ })
- it('should set account balance requirements', async () => {
- const requirement = await validators.getAccountBalanceRequirement(group)
- assertEqualBN(requirement, balanceRequirements.group)
- })
+ it('should set account locked gold requirements', async () => {
+ const requirement = await validators.getAccountLockedGoldRequirement(group)
+ assertEqualBN(requirement, groupLockedGoldRequirements.value)
+ })
- it('should emit the ValidatorGroupRegistered event', async () => {
- assert.equal(resp.logs.length, 1)
- const log = resp.logs[0]
- assertContainSubset(log, {
- event: 'ValidatorGroupRegistered',
- args: {
+ it('should emit the ValidatorGroupRegistered event', async () => {
+ assert.equal(resp.logs.length, 1)
+ const log = resp.logs[0]
+ assertContainSubset(log, {
+ event: 'ValidatorGroupRegistered',
+ args: {
+ group,
+ name,
+ url,
+ },
+ })
+ })
+ })
+ describe('when the account does not meet the locked gold requirements', () => {
+ beforeEach(async () => {
+ await mockLockedGold.setAccountTotalLockedGold(
group,
- name,
- url,
- },
+ groupLockedGoldRequirements.value.minus(1)
+ )
+ })
+
+ it('should revert', async () => {
+ await assertRevert(validators.registerValidatorGroup(name, url, commission))
})
})
})
@@ -949,25 +1045,14 @@ contract('Validators', (accounts: string[]) => {
await registerValidator(group)
})
- it('should revert', async () => {
- await assertRevert(validators.registerValidatorGroup(name, url, balanceRequirements.group))
- })
- })
-
- describe('when the account is already a registered validator group', () => {
- beforeEach(async () => {
- await mockLockedGold.setAccountTotalLockedGold(group, balanceRequirements.group)
- await validators.registerValidatorGroup(name, url, commission)
- })
-
it('should revert', async () => {
await assertRevert(validators.registerValidatorGroup(name, url, commission))
})
})
- describe('when the account does not meet the balance requirements', () => {
+ describe('when the account is already a registered validator group', () => {
beforeEach(async () => {
- await mockLockedGold.setAccountTotalLockedGold(group, balanceRequirements.group.minus(1))
+ await registerValidatorGroup(group)
})
it('should revert', async () => {
@@ -980,61 +1065,108 @@ contract('Validators', (accounts: string[]) => {
const index = 0
const group = accounts[0]
let resp: any
- beforeEach(async () => {
- await registerValidatorGroup(group)
- resp = await validators.deregisterValidatorGroup(index)
- })
-
- it('should mark the account as not a validator group', async () => {
- assert.isFalse(await validators.isValidatorGroup(group))
- })
+ describe('when the account has a registered validator group', () => {
+ beforeEach(async () => {
+ await registerValidatorGroup(group)
+ })
+ describe('when the group has never had any members', () => {
+ beforeEach(async () => {
+ resp = await validators.deregisterValidatorGroup(index)
+ })
- it('should remove the account from the list of validator groups', async () => {
- assert.deepEqual(await validators.getRegisteredValidatorGroups(), [])
- })
+ it('should mark the account as not a validator group', async () => {
+ assert.isFalse(await validators.isValidatorGroup(group))
+ })
- it('should preserve account balance requirements', async () => {
- const requirement = await validators.getAccountBalanceRequirement(group)
- assertEqualBN(requirement, balanceRequirements.group)
- })
+ it('should remove the account from the list of validator groups', async () => {
+ assert.deepEqual(await validators.getRegisteredValidatorGroups(), [])
+ })
- it('should set the group deregistration timestamp', async () => {
- const latestTimestamp = (await web3.eth.getBlock('latest')).timestamp
- const [groupTimestamp, validatorTimestamp] = await validators.getDeregistrationTimestamps(
- group
- )
- assertEqualBN(groupTimestamp, latestTimestamp)
- assertEqualBN(validatorTimestamp, 0)
- })
+ it('should reset account balance requirements', async () => {
+ const requirement = await validators.getAccountLockedGoldRequirement(group)
+ assertEqualBN(requirement, 0)
+ })
- it('should emit the ValidatorGroupDeregistered event', async () => {
- assert.equal(resp.logs.length, 1)
- const log = resp.logs[0]
- assertContainSubset(log, {
- event: 'ValidatorGroupDeregistered',
- args: {
- group,
- },
+ it('should emit the ValidatorGroupDeregistered event', async () => {
+ assert.equal(resp.logs.length, 1)
+ const log = resp.logs[0]
+ assertContainSubset(log, {
+ event: 'ValidatorGroupDeregistered',
+ args: {
+ group,
+ },
+ })
+ })
})
- })
- it('should revert when the account is not a registered validator group', async () => {
- await assertRevert(validators.deregisterValidatorGroup(index, { from: accounts[2] }))
- })
+ describe('when the group has had members', () => {
+ const validator = accounts[1]
+ beforeEach(async () => {
+ await registerValidator(validator)
+ await validators.affiliate(group, { from: validator })
+ await validators.addFirstMember(validator, NULL_ADDRESS, NULL_ADDRESS)
+ })
- it('should revert when the wrong index is provided', async () => {
- await assertRevert(validators.deregisterValidatorGroup(index + 1))
- })
+ describe('when the group no longer has members', () => {
+ beforeEach(async () => {
+ await validators.removeMember(validator)
+ })
- describe('when the validator group is not empty', () => {
- const validator = accounts[1]
- beforeEach(async () => {
- await registerValidatorGroup(group)
- await registerValidator(validator)
- await validators.affiliate(group, { from: validator })
- await validators.addFirstMember(validator, NULL_ADDRESS, NULL_ADDRESS)
+ describe('when it has been more than `groupLockedGoldRequirements.duration` since the validator was removed from the group', () => {
+ beforeEach(async () => {
+ await timeTravel(groupLockedGoldRequirements.duration.plus(1).toNumber(), web3)
+ resp = await validators.deregisterValidatorGroup(index)
+ })
+
+ it('should mark the account as not a validator group', async () => {
+ assert.isFalse(await validators.isValidatorGroup(group))
+ })
+
+ it('should remove the account from the list of validator groups', async () => {
+ assert.deepEqual(await validators.getRegisteredValidatorGroups(), [])
+ })
+
+ it('should reset account balance requirements', async () => {
+ const requirement = await validators.getAccountLockedGoldRequirement(group)
+ assertEqualBN(requirement, 0)
+ })
+
+ it('should emit the ValidatorGroupDeregistered event', async () => {
+ assert.equal(resp.logs.length, 1)
+ const log = resp.logs[0]
+ assertContainSubset(log, {
+ event: 'ValidatorGroupDeregistered',
+ args: {
+ group,
+ },
+ })
+ })
+ })
+
+ describe('when it has been `groupLockedGoldRequirements.duration` since the validator was removed from the group', () => {
+ beforeEach(async () => {
+ await timeTravel(groupLockedGoldRequirements.duration.toNumber(), web3)
+ })
+
+ it('should revert', async () => {
+ await assertRevert(validators.deregisterValidatorGroup(index))
+ })
+ })
+ })
+
+ describe('when the group still has members', () => {
+ it('should revert', async () => {
+ await assertRevert(validators.deregisterValidatorGroup(index))
+ })
+ })
})
+ it('should revert when the wrong index is provided', async () => {
+ await assertRevert(validators.deregisterValidatorGroup(index + 1))
+ })
+ })
+
+ describe('when the account does not have a registered validator group', () => {
it('should revert', async () => {
await assertRevert(validators.deregisterValidatorGroup(index))
})
@@ -1045,63 +1177,150 @@ contract('Validators', (accounts: string[]) => {
const group = accounts[0]
const validator = accounts[1]
let resp: any
- beforeEach(async () => {
- await registerValidator(validator)
- await registerValidatorGroup(group)
- await validators.affiliate(group, { from: validator })
- resp = await validators.addFirstMember(validator, NULL_ADDRESS, NULL_ADDRESS)
- })
+ describe('when account has a registered validator group', () => {
+ beforeEach(async () => {
+ await registerValidatorGroup(group)
+ })
+ describe('when adding a validator affiliated with the group', () => {
+ beforeEach(async () => {
+ await registerValidator(validator)
+ await validators.affiliate(group, { from: validator })
+ })
- it('should add the member to the list of members', async () => {
- const parsedGroup = parseValidatorGroupParams(await validators.getValidatorGroup(group))
- assert.deepEqual(parsedGroup.members, [validator])
- })
+ describe('when the group meets the locked gold requirements', () => {
+ describe('when the validator meets the locked gold requirements', () => {
+ beforeEach(async () => {
+ resp = await validators.addFirstMember(validator, NULL_ADDRESS, NULL_ADDRESS)
+ })
- it("should update the member's membership history", async () => {
- const membershipHistory = await validators.getMembershipHistory(validator)
- const expectedEpoch = new BigNumber(
- Math.floor((await web3.eth.getBlock('latest')).number / EPOCH)
- )
- assert.equal(membershipHistory[0].length, 1)
- assertEqualBN(membershipHistory[0][0], expectedEpoch)
- assert.equal(membershipHistory[1].length, 1)
- assertSameAddress(membershipHistory[1][0], group)
- })
+ it('should add the member to the list of members', async () => {
+ const parsedGroup = parseValidatorGroupParams(
+ await validators.getValidatorGroup(group)
+ )
+ assert.deepEqual(parsedGroup.members, [validator])
+ })
- it('should emit the ValidatorGroupMemberAdded event', async () => {
- assert.equal(resp.logs.length, 1)
- const log = resp.logs[0]
- assertContainSubset(log, {
- event: 'ValidatorGroupMemberAdded',
- args: {
- group,
- validator,
- },
- })
- })
+ it("should update the groups's size history", async () => {
+ const parsedGroup = parseValidatorGroupParams(
+ await validators.getValidatorGroup(group)
+ )
+ assert.equal(parsedGroup.sizeHistory.length, 1)
+ assertEqualBN(
+ parsedGroup.sizeHistory[0],
+ (await web3.eth.getBlock('latest')).timestamp
+ )
+ })
- it('should revert when the account is not a registered validator group', async () => {
- await assertRevert(
- validators.addFirstMember(validator, NULL_ADDRESS, NULL_ADDRESS, { from: accounts[2] })
- )
- })
+ it("should update the member's membership history", async () => {
+ const membershipHistory = await validators.getMembershipHistory(validator)
+ const expectedEpoch = new BigNumber(
+ Math.floor((await web3.eth.getBlock('latest')).number / EPOCH)
+ )
+ assert.equal(membershipHistory[0].length, 1)
+ assertEqualBN(membershipHistory[0][0], expectedEpoch)
+ assert.equal(membershipHistory[1].length, 1)
+ assertSameAddress(membershipHistory[1][0], group)
+ })
+
+ it('should mark the group as eligible', async () => {
+ assert.isTrue(await mockElection.isEligible(group))
+ })
+
+ it('should emit the ValidatorGroupMemberAdded event', async () => {
+ assert.equal(resp.logs.length, 1)
+ const log = resp.logs[0]
+ assertContainSubset(log, {
+ event: 'ValidatorGroupMemberAdded',
+ args: {
+ group,
+ validator,
+ },
+ })
+ })
- it('should revert when the member is not a registered validator', async () => {
- await assertRevert(validators.addFirstMember(accounts[2], NULL_ADDRESS, NULL_ADDRESS))
- })
+ describe('when the group has no room to add another member', () => {
+ beforeEach(async () => {
+ await validators.setMaxGroupSize(1)
+ await registerValidator(accounts[2])
+ await validators.affiliate(group, { from: accounts[2] })
+ })
- it('should revert when trying to add too many members to group', async () => {
- await validators.setMaxGroupSize(1)
- await registerValidator(accounts[2])
- await validators.affiliate(group, { from: accounts[2] })
- await assertRevert(validators.addMember(accounts[2]))
- })
+ it('should revert', async () => {
+ await assertRevert(validators.addMember(accounts[2]))
+ })
+ })
+
+ describe('when adding many validators affiliated with the group', () => {
+ it("should update the groups's size history and balance requirements", async () => {
+ const expectedSizeHistory = parseValidatorGroupParams(
+ await validators.getValidatorGroup(group)
+ ).sizeHistory
+ assert.equal(expectedSizeHistory.length, 1)
+ for (let i = 2; i < maxGroupSize.toNumber() + 1; i++) {
+ const numMembers = i
+ const validator = accounts[i]
+ await registerValidator(validator)
+ await validators.affiliate(group, { from: validator })
+ await mockLockedGold.setAccountTotalLockedGold(
+ group,
+ groupLockedGoldRequirements.value.times(numMembers)
+ )
+ await validators.addMember(validator)
+ expectedSizeHistory.push((await web3.eth.getBlock('latest')).timestamp)
+ const parsedGroup = parseValidatorGroupParams(
+ await validators.getValidatorGroup(group)
+ )
+ assert.deepEqual(
+ parsedGroup.sizeHistory.map((x) => x.toString()),
+ expectedSizeHistory.map((x) => x.toString())
+ )
+ const requirement = await validators.getAccountLockedGoldRequirement(group)
+ assertEqualBN(requirement, groupLockedGoldRequirements.value.times(numMembers))
+ }
+ })
+ })
+ })
- describe('when the validator has not affiliated themselves with the group', () => {
- beforeEach(async () => {
- await validators.deaffiliate({ from: validator })
+ describe('when the validator does not meet the locked gold requirements', () => {
+ beforeEach(async () => {
+ await mockLockedGold.setAccountTotalLockedGold(
+ validator,
+ validatorLockedGoldRequirements.value.minus(1)
+ )
+ })
+
+ it('should revert', async () => {
+ await assertRevert(validators.addFirstMember(validator, NULL_ADDRESS, NULL_ADDRESS))
+ })
+ })
+ })
+
+ describe('when the group does not meet the locked gold requirements', () => {
+ beforeEach(async () => {
+ await mockLockedGold.setAccountTotalLockedGold(
+ group,
+ groupLockedGoldRequirements.value.minus(1)
+ )
+ })
+
+ it('should revert', async () => {
+ await assertRevert(validators.addFirstMember(validator, NULL_ADDRESS, NULL_ADDRESS))
+ })
+ })
})
+ describe('when adding a validator not affiliated with the group', () => {
+ beforeEach(async () => {
+ await registerValidator(validator)
+ })
+
+ it('should revert', async () => {
+ await assertRevert(validators.addFirstMember(validator, NULL_ADDRESS, NULL_ADDRESS))
+ })
+ })
+ })
+
+ describe('when the account does not have a registered validator group', () => {
it('should revert', async () => {
await assertRevert(validators.addFirstMember(validator, NULL_ADDRESS, NULL_ADDRESS))
})
@@ -1129,23 +1348,32 @@ contract('Validators', (accounts: string[]) => {
it("should update the member's membership history", async () => {
await validators.removeMember(validator)
- const membershipHistory = await validators.getMembershipHistory(validator)
- const expectedEpoch = new BigNumber(
- Math.floor((await web3.eth.getBlock('latest')).number / EPOCH)
+ const membershipHistory = parseMembershipHistory(
+ await validators.getMembershipHistory(validator)
)
+ const latestBlock = await web3.eth.getBlock('latest')
+ const expectedEpoch = new BigNumber(Math.floor(latestBlock.number / EPOCH))
// Depending on test timing, we may or may not span an epoch boundary between registration
// and removal.
- const numEntries = membershipHistory[0].length
+ const numEntries = membershipHistory.epochs.length
assert.isTrue(numEntries == 1 || numEntries == 2)
- assert.equal(membershipHistory[1].length, numEntries)
+ assert.equal(membershipHistory.groups.length, numEntries)
if (numEntries == 1) {
- assertEqualBN(membershipHistory[0][0], expectedEpoch)
- assertSameAddress(membershipHistory[1][0], NULL_ADDRESS)
+ assertEqualBN(membershipHistory.epochs[0], expectedEpoch)
+ assertSameAddress(membershipHistory.groups[0], NULL_ADDRESS)
} else {
- assertEqualBN(membershipHistory[0][1], expectedEpoch)
- assertSameAddress(membershipHistory[1][1], NULL_ADDRESS)
+ assertEqualBN(membershipHistory.epochs[1], expectedEpoch)
+ assertSameAddress(membershipHistory.groups[1], NULL_ADDRESS)
}
+ assert.equal(membershipHistory.lastRemovedFromGroupTimestamp, latestBlock.timestamp)
+ })
+
+ it("should update the group's size history", async () => {
+ await validators.removeMember(validator)
+ const parsedGroup = parseValidatorGroupParams(await validators.getValidatorGroup(group))
+ assert.equal(parsedGroup.sizeHistory.length, 2)
+ assertEqualBN(parsedGroup.sizeHistory[1], (await web3.eth.getBlock('latest')).timestamp)
})
it('should emit the ValidatorGroupMemberRemoved event', async () => {
@@ -1301,22 +1529,25 @@ contract('Validators', (accounts: string[]) => {
await validators.addFirstMember(validator, NULL_ADDRESS, NULL_ADDRESS, {
from: groups[i],
})
+
await mineBlocks(EPOCH, web3)
- const membershipHistory = await validators.getMembershipHistory(validator)
+ const membershipHistory = parseMembershipHistory(
+ await validators.getMembershipHistory(validator)
+ )
const expectedMembershipHistoryLength = Math.min(
i + 1,
membershipHistoryLength.toNumber()
)
- assert.equal(membershipHistory[0].length, expectedMembershipHistoryLength)
- assert.equal(membershipHistory[1].length, expectedMembershipHistoryLength)
+ assert.equal(membershipHistory.epochs.length, expectedMembershipHistoryLength)
+ assert.equal(membershipHistory.groups.length, expectedMembershipHistoryLength)
for (let j = 0; j < expectedMembershipHistoryLength; j++) {
assert.include(
- membershipHistory[0].map((x) => x.toNumber()),
+ membershipHistory.epochs.map((x) => x.toNumber()),
currentEpoch.minus(j).toNumber()
)
assert.include(
- membershipHistory[1].map((x) => x.toLowerCase()),
+ membershipHistory.groups.map((x) => x.toLowerCase()),
groups[i - j].toLowerCase()
)
}
@@ -1339,7 +1570,9 @@ contract('Validators', (accounts: string[]) => {
it('should always return the correct membership for the last epoch', async () => {
for (let i = 0; i < membershipHistoryLength.plus(1).toNumber(); i++) {
const blockNumber = (await web3.eth.getBlock('latest')).number
- const blocksUntilNextEpoch = blockNumber % EPOCH
+ const currentEpoch = Math.floor(blockNumber / EPOCH)
+ const nextEpochBlockNumber = EPOCH * (currentEpoch + 1)
+ const blocksUntilNextEpoch = nextEpochBlockNumber - blockNumber
await mineBlocks(blocksUntilNextEpoch, web3)
await validators.affiliate(groups[i])
@@ -1363,7 +1596,73 @@ contract('Validators', (accounts: string[]) => {
})
})
- describe.only('#distributeEpochPayment', () => {
+ describe('#getAccountLockedGoldRequirement', () => {
+ describe('when a validator group has added members', () => {
+ const group = accounts[0]
+ const numMembers = 5
+ let actualRequirements: BigNumber[]
+ beforeEach(async () => {
+ actualRequirements = []
+ await registerValidatorGroup(group)
+ for (let i = 1; i < numMembers + 1; i++) {
+ const validator = accounts[i]
+ await registerValidator(validator)
+ await validators.affiliate(group, { from: validator })
+ await mockLockedGold.setAccountTotalLockedGold(
+ group,
+ groupLockedGoldRequirements.value.times(i)
+ )
+ if (i == 1) {
+ await validators.addFirstMember(validator, NULL_ADDRESS, NULL_ADDRESS)
+ } else {
+ await validators.addMember(validator)
+ }
+ actualRequirements.push(await validators.getAccountLockedGoldRequirement(group))
+ }
+ })
+
+ it('should increase the requirement with each added member', async () => {
+ for (let i = 0; i < numMembers; i++) {
+ assertEqualBN(actualRequirements[i], groupLockedGoldRequirements.value.times(i + 1))
+ }
+ })
+
+ describe('when a validator group is removing members', () => {
+ let removalTimestamps: number[]
+ beforeEach(async () => {
+ removalTimestamps = []
+ for (let i = 1; i < numMembers + 1; i++) {
+ const validator = accounts[i]
+ await validators.removeMember(validator)
+ removalTimestamps.push((await web3.eth.getBlock('latest')).timestamp)
+ // Space things out.
+ await timeTravel(47, web3)
+ }
+ })
+
+ it('should decrease the requirement `duration`+1 seconds after removal', async () => {
+ for (let i = 0; i < numMembers; i++) {
+ assertEqualBN(
+ await validators.getAccountLockedGoldRequirement(group),
+ groupLockedGoldRequirements.value.times(numMembers - i)
+ )
+ const removalTimestamp = removalTimestamps[i]
+ const requirementExpiry = groupLockedGoldRequirements.duration.plus(removalTimestamp)
+ const currentTimestamp = (await web3.eth.getBlock('latest')).timestamp
+ await timeTravel(
+ requirementExpiry
+ .minus(currentTimestamp)
+ .plus(1)
+ .toNumber(),
+ web3
+ )
+ }
+ })
+ })
+ })
+ })
+
+ describe('#distributeEpochPayment', () => {
const validator = accounts[0]
const group = accounts[1]
let mockStableToken: MockStableTokenInstance
@@ -1405,7 +1704,7 @@ contract('Validators', (accounts: string[]) => {
beforeEach(async () => {
await mockLockedGold.setAccountTotalLockedGold(
validator,
- balanceRequirements.validator.minus(1)
+ validatorLockedGoldRequirements.value.minus(1)
)
await validators.distributeEpochPayment(validator)
})
@@ -1419,9 +1718,12 @@ contract('Validators', (accounts: string[]) => {
})
})
- describe('when the validator does not meet the balance requirements', () => {
+ describe('when the group does not meet the balance requirements', () => {
beforeEach(async () => {
- await mockLockedGold.setAccountTotalLockedGold(group, balanceRequirements.group.minus(1))
+ await mockLockedGold.setAccountTotalLockedGold(
+ group,
+ groupLockedGoldRequirements.value.minus(1)
+ )
await validators.distributeEpochPayment(validator)
})
From b8e17977aadd3fb89d56aa137a614293fecbfcec Mon Sep 17 00:00:00 2001
From: Asa Oines
Date: Tue, 15 Oct 2019 17:30:43 -0700
Subject: [PATCH 064/149] Address comments
---
packages/cli/src/commands/lockedgold/lock.ts | 2 +-
packages/contractkit/src/wrappers/Election.ts | 4 ++--
packages/contractkit/src/wrappers/LockedGold.ts | 8 ++++----
packages/contractkit/src/wrappers/Validators.ts | 2 +-
packages/protocol/contracts/governance/Election.sol | 1 -
packages/protocol/contracts/governance/LockedGold.sol | 5 ++++-
6 files changed, 12 insertions(+), 10 deletions(-)
diff --git a/packages/cli/src/commands/lockedgold/lock.ts b/packages/cli/src/commands/lockedgold/lock.ts
index 4961d382cbd..d9f59f2bcaf 100644
--- a/packages/cli/src/commands/lockedgold/lock.ts
+++ b/packages/cli/src/commands/lockedgold/lock.ts
@@ -31,7 +31,7 @@ export default class Lock extends BaseCommand {
const value = new BigNumber(res.flags.value)
if (!value.gt(new BigNumber(0))) {
- failWith(`Provided value must be greater than zero => [${value}]`)
+ failWith(`Provided value must be greater than zero => [${value.toString()}]`)
}
const tx = lockedGold.lock()
diff --git a/packages/contractkit/src/wrappers/Election.ts b/packages/contractkit/src/wrappers/Election.ts
index 4da80f58f8b..8d2285fba35 100644
--- a/packages/contractkit/src/wrappers/Election.ts
+++ b/packages/contractkit/src/wrappers/Election.ts
@@ -156,7 +156,7 @@ export class ElectionWrapper extends BaseWrapper {
*/
async markGroupEligible(validatorGroup: Address): Promise> {
if (this.kit.defaultAccount == null) {
- throw new Error(`missing from at new ValdidatorUtils()`)
+ throw new Error(`missing kit.defaultAccount`)
}
const value = toBigNumber(
@@ -174,7 +174,7 @@ export class ElectionWrapper extends BaseWrapper {
*/
async vote(validatorGroup: Address, value: BigNumber): Promise> {
if (this.kit.defaultAccount == null) {
- throw new Error(`missing from at new ValdidatorUtils()`)
+ throw new Error(`missing kit.defaultAccount`)
}
const { lesser, greater } = await this.findLesserAndGreaterAfterVote(validatorGroup, value)
diff --git a/packages/contractkit/src/wrappers/LockedGold.ts b/packages/contractkit/src/wrappers/LockedGold.ts
index 979289a51dc..e61d8b8a869 100644
--- a/packages/contractkit/src/wrappers/LockedGold.ts
+++ b/packages/contractkit/src/wrappers/LockedGold.ts
@@ -29,8 +29,8 @@ interface AccountSummary {
nonvoting: BigNumber
}
authorizations: {
- voter: string
- validator: string
+ voter: null | string
+ validator: null | string
}
pendingWithdrawals: PendingWithdrawal[]
}
@@ -140,8 +140,8 @@ export class LockedGoldWrapper extends BaseWrapper {
nonvoting,
},
authorizations: {
- voter: eqAddress(voter, account) ? 'None' : voter,
- validator: eqAddress(validator, account) ? 'None' : validator,
+ voter: eqAddress(voter, account) ? null : voter,
+ validator: eqAddress(validator, account) ? null : validator,
},
pendingWithdrawals,
}
diff --git a/packages/contractkit/src/wrappers/Validators.ts b/packages/contractkit/src/wrappers/Validators.ts
index 4ecc8e9ceb2..94809d64a47 100644
--- a/packages/contractkit/src/wrappers/Validators.ts
+++ b/packages/contractkit/src/wrappers/Validators.ts
@@ -58,7 +58,7 @@ export class ValidatorsWrapper extends BaseWrapper {
commission: BigNumber
): Promise> {
if (this.kit.defaultAccount == null) {
- throw new Error(`missing from at new ValdidatorUtils()`)
+ throw new Error(`missing kit.defaultAccount`)
}
return wrapSend(
this.kit,
diff --git a/packages/protocol/contracts/governance/Election.sol b/packages/protocol/contracts/governance/Election.sol
index 3f1c7919503..1aa970b68ed 100644
--- a/packages/protocol/contracts/governance/Election.sol
+++ b/packages/protocol/contracts/governance/Election.sol
@@ -657,7 +657,6 @@ contract Election is IElection, Ownable, ReentrancyGuard, Initializable, UsingRe
require(index < list.length && list[index] == element);
uint256 lastIndex = list.length.sub(1);
list[index] = list[lastIndex];
- delete list[lastIndex];
list.length = lastIndex;
}
diff --git a/packages/protocol/contracts/governance/LockedGold.sol b/packages/protocol/contracts/governance/LockedGold.sol
index 1fbc1665b7b..23cf607f67b 100644
--- a/packages/protocol/contracts/governance/LockedGold.sol
+++ b/packages/protocol/contracts/governance/LockedGold.sol
@@ -207,7 +207,10 @@ contract LockedGold is ILockedGold, ReentrancyGuard, Initializable, UsingRegistr
require(isAccount(msg.sender));
Account storage account = accounts[msg.sender];
uint256 balanceRequirement = getValidators().getAccountBalanceRequirement(msg.sender);
- require(balanceRequirement <= getAccountTotalLockedGold(msg.sender).sub(value));
+ require(
+ balanceRequirement == 0 ||
+ balanceRequirement <= getAccountTotalLockedGold(msg.sender).sub(value)
+ );
_decrementNonvotingAccountBalance(msg.sender, value);
uint256 available = now.add(unlockingPeriod);
account.balances.pendingWithdrawals.push(PendingWithdrawal(value, available));
From 4a4d6e226ed88cac53993b532ae9aab3bfc28275 Mon Sep 17 00:00:00 2001
From: Asa Oines
Date: Wed, 16 Oct 2019 09:25:29 -0700
Subject: [PATCH 065/149] Address comments
---
packages/contractkit/src/wrappers/Election.ts | 6 ++--
.../contractkit/src/wrappers/LockedGold.ts | 12 ++++++--
.../src/wrappers/Validators.test.ts | 23 ++++++---------
.../contractkit/src/wrappers/Validators.ts | 28 ++-----------------
.../contracts/governance/Election.sol | 12 +++++---
5 files changed, 32 insertions(+), 49 deletions(-)
diff --git a/packages/contractkit/src/wrappers/Election.ts b/packages/contractkit/src/wrappers/Election.ts
index 8d2285fba35..9cd270aeb5f 100644
--- a/packages/contractkit/src/wrappers/Election.ts
+++ b/packages/contractkit/src/wrappers/Election.ts
@@ -12,7 +12,7 @@ import {
toBigNumber,
toNumber,
tupleParser,
- wrapSend,
+ toTransactionObject,
} from './BaseWrapper'
export interface Validator {
@@ -164,7 +164,7 @@ export class ElectionWrapper extends BaseWrapper {
)
const { lesser, greater } = await this.findLesserAndGreaterAfterVote(validatorGroup, value)
- return wrapSend(this.kit, this.contract.methods.markGroupEligible(lesser, greater))
+ return toTransactionObject(this.kit, this.contract.methods.markGroupEligible(lesser, greater))
}
/**
@@ -179,7 +179,7 @@ export class ElectionWrapper extends BaseWrapper {
const { lesser, greater } = await this.findLesserAndGreaterAfterVote(validatorGroup, value)
- return wrapSend(
+ return toTransactionObject(
this.kit,
this.contract.methods.vote(validatorGroup, value.toString(), lesser, greater)
)
diff --git a/packages/contractkit/src/wrappers/LockedGold.ts b/packages/contractkit/src/wrappers/LockedGold.ts
index b9a83ddf639..4ceccbece66 100644
--- a/packages/contractkit/src/wrappers/LockedGold.ts
+++ b/packages/contractkit/src/wrappers/LockedGold.ts
@@ -118,7 +118,12 @@ export class LockedGoldWrapper extends BaseWrapper {
getValidatorFromAccount: (account: string) => Promise = proxyCall(
this.contract.methods.getValidatorFromAccount
)
-
+ /**
+ * Check if an account already exists.
+ * @param account The address of the account
+ * @return Returns `true` if account exists. Returns `false` otherwise.
+ */
+ isAccount: (account: string) => Promise = proxyCall(this.contract.methods.isAccount)
/**
* Returns current configuration parameters.
*/
@@ -156,7 +161,10 @@ export class LockedGoldWrapper extends BaseWrapper {
async authorizeVoter(account: Address, voter: Address): Promise> {
const sig = await this.getParsedSignatureOfAddress(account, voter)
// TODO(asa): Pass default tx "from" argument.
- return wrapSend(this.kit, this.contract.methods.authorizeVoter(voter, sig.v, sig.r, sig.s))
+ return toTransactionObject(
+ this.kit,
+ this.contract.methods.authorizeVoter(voter, sig.v, sig.r, sig.s)
+ )
}
/**
diff --git a/packages/contractkit/src/wrappers/Validators.test.ts b/packages/contractkit/src/wrappers/Validators.test.ts
index 3d5c831e6e3..782b7b509db 100644
--- a/packages/contractkit/src/wrappers/Validators.test.ts
+++ b/packages/contractkit/src/wrappers/Validators.test.ts
@@ -1,3 +1,4 @@
+import BigNumber from 'bignumber.js'
import Web3 from 'web3'
import { newKitFromWeb3 } from '../kit'
import { testWithGanache } from '../test-utils/ganache-test'
@@ -9,8 +10,7 @@ TEST NOTES:
- In migrations: The only account that has cUSD is accounts[0]
*/
-const minLockedGoldValue = Web3.utils.toWei('100', 'ether') // 1 gold
-const minLockedGoldNoticePeriod = 120 * 24 * 60 * 60 // 120 days
+const minLockedGoldValue = Web3.utils.toWei('100', 'ether') // 100 gold
// A random 64 byte hex string.
const publicKey =
@@ -29,15 +29,10 @@ testWithGanache('Validators Wrapper', (web3) => {
let lockedGold: LockedGoldWrapper
const registerAccountWithCommitment = async (account: string) => {
- // console.log('isAccount', )
- // console.log('isDelegate', await lockedGold.isDelegate(account))
-
if (!(await lockedGold.isAccount(account))) {
await lockedGold.createAccount().sendAndWaitForReceipt({ from: account })
}
- await lockedGold
- .newCommitment(minLockedGoldNoticePeriod)
- .sendAndWaitForReceipt({ from: account, value: minLockedGoldValue })
+ await lockedGold.lock().sendAndWaitForReceipt({ from: account, value: minLockedGoldValue })
}
beforeAll(async () => {
@@ -48,9 +43,11 @@ testWithGanache('Validators Wrapper', (web3) => {
const setupGroup = async (groupAccount: string) => {
await registerAccountWithCommitment(groupAccount)
- await validators
- .registerValidatorGroup('thegroup', 'The Group', 'thegroup.com', [minLockedGoldNoticePeriod])
- .sendAndWaitForReceipt({ from: groupAccount })
+ await (await validators.registerValidatorGroup(
+ 'The Group',
+ 'thegroup.com',
+ new BigNumber(0.1)
+ )).sendAndWaitForReceipt({ from: groupAccount })
}
const setupValidator = async (validatorAccount: string) => {
@@ -58,12 +55,10 @@ testWithGanache('Validators Wrapper', (web3) => {
// set account1 as the validator
await validators
.registerValidator(
- 'goodoldvalidator',
'Good old validator',
'goodold.com',
// @ts-ignore
- publicKeysData,
- [minLockedGoldNoticePeriod]
+ publicKeysData
)
.sendAndWaitForReceipt({ from: validatorAccount })
}
diff --git a/packages/contractkit/src/wrappers/Validators.ts b/packages/contractkit/src/wrappers/Validators.ts
index cffd70e0af9..e17ada27c14 100644
--- a/packages/contractkit/src/wrappers/Validators.ts
+++ b/packages/contractkit/src/wrappers/Validators.ts
@@ -1,6 +1,6 @@
import { fromFixed, toFixed } from '@celo/utils/lib/fixidity'
import BigNumber from 'bignumber.js'
-import { Address } from '../base'
+import { Address, NULL_ADDRESS } from '../base'
import { Validators } from '../generated/types/Validators'
import {
BaseWrapper,
@@ -8,7 +8,6 @@ import {
proxyCall,
proxySend,
toBigNumber,
- toNumber,
toTransactionObject,
} from './BaseWrapper'
@@ -59,7 +58,7 @@ export class ValidatorsWrapper extends BaseWrapper {
if (this.kit.defaultAccount == null) {
throw new Error(`missing kit.defaultAccount`)
}
- return wrapSend(
+ return toTransactionObject(
this.kit,
this.contract.methods.registerValidatorGroup(name, url, toFixed(commission).toFixed())
)
@@ -127,20 +126,6 @@ export class ValidatorsWrapper extends BaseWrapper {
}
}
- /**
- * Returns whether a particular account is voting for a validator group.
- * @param account The account.
- * @return Whether a particular account is voting for a validator group.
- */
- isVoting = proxyCall(this.contract.methods.isVoting)
-
- /**
- * Returns whether a particular account is a registered validator or validator group.
- * @param account The account.
- * @return Whether a particular account is a registered validator or validator group.
- */
- isValidating = proxyCall(this.contract.methods.isValidating)
-
/**
* Returns whether a particular account has a registered validator.
* @param account The account.
@@ -155,15 +140,6 @@ export class ValidatorsWrapper extends BaseWrapper {
*/
isValidatorGroup = proxyCall(this.contract.methods.isValidatorGroup)
- /**
- * Returns whether an account meets the requirements to register a validator or group.
- * @param account The account.
- * @param noticePeriods An array of notice periods of the Locked Gold commitments
- * that cumulatively meet the requirements for validator registration.
- * @return Whether an account meets the requirements to register a validator or group.
- */
- meetsRegistrationRequirements = proxyCall(this.contract.methods.meetsRegistrationRequirements)
-
addMember = proxySend(this.kit, this.contract.methods.addMember)
removeMember = proxySend(this.kit, this.contract.methods.removeMember)
diff --git a/packages/protocol/contracts/governance/Election.sol b/packages/protocol/contracts/governance/Election.sol
index 1aa970b68ed..807d1a74f69 100644
--- a/packages/protocol/contracts/governance/Election.sol
+++ b/packages/protocol/contracts/governance/Election.sol
@@ -629,12 +629,16 @@ contract Election is IElection, Ownable, ReentrancyGuard, Initializable, UsingRe
* @param group The address of the validator group.
* @param value The number of active votes being added.
* @return The delta in active vote denominator for `group`.
+ * @dev Preserves unitsDelta / totalUnits = value / total
*/
function getActiveVotesUnitsDelta(address group, uint256 value) private view returns (uint256) {
- // Preserve unitsDelta * total = value * totalUnits
- return value.mul(votes.active.forGroup[group].totalUnits.add(1)).div(
- votes.active.forGroup[group].total.add(1)
- );
+ if (votes.active.forGroup[group].total == 0) {
+ return value;
+ } else {
+ return value.mul(votes.active.forGroup[group].totalUnits).div(
+ votes.active.forGroup[group].total
+ );
+ }
}
/**
From f3515ac1867e378ced0f151b86ef4a6bf4e08850 Mon Sep 17 00:00:00 2001
From: Asa Oines
Date: Wed, 16 Oct 2019 10:14:21 -0700
Subject: [PATCH 066/149] Fix tests, linting
---
packages/contractkit/src/wrappers/Election.ts | 2 +-
packages/contractkit/src/wrappers/LockedGold.ts | 2 +-
packages/contractkit/src/wrappers/Validators.test.ts | 8 ++++----
packages/contractkit/src/wrappers/Validators.ts | 3 ---
4 files changed, 6 insertions(+), 9 deletions(-)
diff --git a/packages/contractkit/src/wrappers/Election.ts b/packages/contractkit/src/wrappers/Election.ts
index 9cd270aeb5f..fbf35b6c87d 100644
--- a/packages/contractkit/src/wrappers/Election.ts
+++ b/packages/contractkit/src/wrappers/Election.ts
@@ -11,8 +11,8 @@ import {
proxySend,
toBigNumber,
toNumber,
- tupleParser,
toTransactionObject,
+ tupleParser,
} from './BaseWrapper'
export interface Validator {
diff --git a/packages/contractkit/src/wrappers/LockedGold.ts b/packages/contractkit/src/wrappers/LockedGold.ts
index 4ceccbece66..bc3833f090c 100644
--- a/packages/contractkit/src/wrappers/LockedGold.ts
+++ b/packages/contractkit/src/wrappers/LockedGold.ts
@@ -12,8 +12,8 @@ import {
proxyCall,
proxySend,
toBigNumber,
- tupleParser,
toTransactionObject,
+ tupleParser,
} from '../wrappers/BaseWrapper'
export interface VotingDetails {
diff --git a/packages/contractkit/src/wrappers/Validators.test.ts b/packages/contractkit/src/wrappers/Validators.test.ts
index 782b7b509db..1d54ed80bf8 100644
--- a/packages/contractkit/src/wrappers/Validators.test.ts
+++ b/packages/contractkit/src/wrappers/Validators.test.ts
@@ -10,7 +10,7 @@ TEST NOTES:
- In migrations: The only account that has cUSD is accounts[0]
*/
-const minLockedGoldValue = Web3.utils.toWei('100', 'ether') // 100 gold
+const minLockedGoldValue = Web3.utils.toWei('10', 'ether') // 10 gold
// A random 64 byte hex string.
const publicKey =
@@ -28,7 +28,7 @@ testWithGanache('Validators Wrapper', (web3) => {
let validators: ValidatorsWrapper
let lockedGold: LockedGoldWrapper
- const registerAccountWithCommitment = async (account: string) => {
+ const registerAccountWithLockedGold = async (account: string) => {
if (!(await lockedGold.isAccount(account))) {
await lockedGold.createAccount().sendAndWaitForReceipt({ from: account })
}
@@ -42,7 +42,7 @@ testWithGanache('Validators Wrapper', (web3) => {
})
const setupGroup = async (groupAccount: string) => {
- await registerAccountWithCommitment(groupAccount)
+ await registerAccountWithLockedGold(groupAccount)
await (await validators.registerValidatorGroup(
'The Group',
'thegroup.com',
@@ -51,7 +51,7 @@ testWithGanache('Validators Wrapper', (web3) => {
}
const setupValidator = async (validatorAccount: string) => {
- await registerAccountWithCommitment(validatorAccount)
+ await registerAccountWithLockedGold(validatorAccount)
// set account1 as the validator
await validators
.registerValidator(
diff --git a/packages/contractkit/src/wrappers/Validators.ts b/packages/contractkit/src/wrappers/Validators.ts
index e17ada27c14..754bedcadc2 100644
--- a/packages/contractkit/src/wrappers/Validators.ts
+++ b/packages/contractkit/src/wrappers/Validators.ts
@@ -55,9 +55,6 @@ export class ValidatorsWrapper extends BaseWrapper {
url: string,
commission: BigNumber
): Promise> {
- if (this.kit.defaultAccount == null) {
- throw new Error(`missing kit.defaultAccount`)
- }
return toTransactionObject(
this.kit,
this.contract.methods.registerValidatorGroup(name, url, toFixed(commission).toFixed())
From 9aa6ec300ac717bcb5ec079bcdda2548a4032b29 Mon Sep 17 00:00:00 2001
From: Asa Oines
Date: Wed, 16 Oct 2019 11:01:30 -0700
Subject: [PATCH 067/149] Remove bondeddeposits test
---
.../test/governance/bondeddeposits.ts | 1088 -----------------
.../protocol/test/governance/lockedgold.ts | 24 +-
2 files changed, 7 insertions(+), 1105 deletions(-)
delete mode 100644 packages/protocol/test/governance/bondeddeposits.ts
diff --git a/packages/protocol/test/governance/bondeddeposits.ts b/packages/protocol/test/governance/bondeddeposits.ts
deleted file mode 100644
index f2fc5d2bb20..00000000000
--- a/packages/protocol/test/governance/bondeddeposits.ts
+++ /dev/null
@@ -1,1088 +0,0 @@
-import { CeloContractName } from '@celo/protocol/lib/registry-utils'
-import { getParsedSignatureOfAddress } from '@celo/protocol/lib/signing-utils'
-import {
- assertEqualBN,
- assertLogMatches,
- assertRevert,
- NULL_ADDRESS,
- timeTravel,
-} from '@celo/protocol/lib/test-utils'
-import BigNumber from 'bignumber.js'
-import {
- LockedGoldContract,
- LockedGoldInstance,
- MockGoldTokenContract,
- MockGoldTokenInstance,
- MockGovernanceContract,
- MockGovernanceInstance,
- MockValidatorsContract,
- MockValidatorsInstance,
- RegistryContract,
- RegistryInstance,
-} from 'types'
-
-const LockedGold: LockedGoldContract = artifacts.require('LockedGold')
-const Registry: RegistryContract = artifacts.require('Registry')
-const MockGoldToken: MockGoldTokenContract = artifacts.require('MockGoldToken')
-const MockGovernance: MockGovernanceContract = artifacts.require('MockGovernance')
-const MockValidators: MockValidatorsContract = artifacts.require('MockValidators')
-
-// @ts-ignore
-// TODO(mcortesi): Use BN
-LockedGold.numberFormat = 'BigNumber'
-
-const HOUR = 60 * 60
-const DAY = 24 * HOUR
-const YEAR = 365 * DAY
-
-// TODO(asa): Test reward redemption
-contract('LockedGold', (accounts: string[]) => {
- let account = accounts[0]
- const nonOwner = accounts[1]
- const maxNoticePeriod = 2 * YEAR
- let mockGoldToken: MockGoldTokenInstance
- let mockGovernance: MockGovernanceInstance
- let mockValidators: MockValidatorsInstance
- let lockedGold: LockedGoldInstance
- let registry: RegistryInstance
-
- enum roles {
- validating,
- voting,
- rewards,
- }
- const forEachRole = (tests: (arg0: roles) => void) =>
- Object.keys(roles)
- .slice(3)
- .map((role) => describe(`when dealing with ${role} role`, () => tests(roles[role])))
-
- beforeEach(async () => {
- lockedGold = await LockedGold.new()
- mockGoldToken = await MockGoldToken.new()
- mockGovernance = await MockGovernance.new()
- mockValidators = await MockValidators.new()
- registry = await Registry.new()
- await registry.setAddressFor(CeloContractName.GoldToken, mockGoldToken.address)
- await registry.setAddressFor(CeloContractName.Governance, mockGovernance.address)
- await registry.setAddressFor(CeloContractName.Validators, mockValidators.address)
- await lockedGold.initialize(registry.address, maxNoticePeriod)
- await lockedGold.createAccount()
- })
-
- describe('#isAccount()', () => {
- it('created account should exist', async () => {
- const b = await lockedGold.isAccount(account)
- assert.equal(b, true)
- })
- it('account that was not created should not exist', async () => {
- const b = await lockedGold.isAccount(accounts[2])
- assert.equal(b, false)
- })
- })
-
- describe('#isDelegate()', () => {
- const delegate = accounts[1]
-
- beforeEach(async () => {
- const sig = await getParsedSignatureOfAddress(web3, account, delegate)
- await lockedGold.delegateRole(roles.voting, delegate, sig.v, sig.r, sig.s)
- })
-
- it('should return true for delegate', async () => {
- assert.equal(await lockedGold.isDelegate(delegate), true)
- })
- it('should return false for account', async () => {
- assert.equal(await lockedGold.isDelegate(account), false)
- })
- it('should return false for others', async () => {
- assert.equal(await lockedGold.isDelegate(accounts[4]), false)
- })
- })
-
- describe('#initialize()', () => {
- it('should set the owner', async () => {
- const owner: string = await lockedGold.owner()
- assert.equal(owner, account)
- })
-
- it('should set the maxNoticePeriod', async () => {
- const actual = await lockedGold.maxNoticePeriod()
- assert.equal(actual.toNumber(), maxNoticePeriod)
- })
-
- it('should set the registry address', async () => {
- const registryAddress: string = await lockedGold.registry()
- assert.equal(registryAddress, registry.address)
- })
-
- it('should revert if already initialized', async () => {
- await assertRevert(lockedGold.initialize(registry.address, maxNoticePeriod))
- })
- })
-
- describe('#setRegistry()', () => {
- const anAddress: string = accounts[2]
-
- it('should set the registry when called by the owner', async () => {
- await lockedGold.setRegistry(anAddress)
- assert.equal(await lockedGold.registry(), anAddress)
- })
-
- it('should revert when not called by the owner', async () => {
- await assertRevert(lockedGold.setRegistry(anAddress, { from: nonOwner }))
- })
- })
-
- describe('#setMaxNoticePeriod()', () => {
- it('should set maxNoticePeriod when called by the owner', async () => {
- await lockedGold.setMaxNoticePeriod(1)
- assert.equal((await lockedGold.maxNoticePeriod()).toNumber(), 1)
- })
-
- it('should emit a MaxNoticePeriodSet event', async () => {
- const resp = await lockedGold.setMaxNoticePeriod(1)
- assert.equal(resp.logs.length, 1)
- const log = resp.logs[0]
- assertLogMatches(log, 'MaxNoticePeriodSet', {
- maxNoticePeriod: new BigNumber(1),
- })
- })
-
- it('should revert when not called by the owner', async () => {
- await assertRevert(lockedGold.setMaxNoticePeriod(1, { from: nonOwner }))
- })
- })
-
- describe('#delegateRole()', () => {
- const delegate = accounts[1]
- let sig
-
- beforeEach(async () => {
- sig = await getParsedSignatureOfAddress(web3, account, delegate)
- })
-
- forEachRole((role) => {
- it('should set the role delegate', async () => {
- await lockedGold.delegateRole(role, delegate, sig.v, sig.r, sig.s)
- assert.equal(await lockedGold.delegations(delegate), account)
- assert.equal(await lockedGold.isDelegate(delegate), true)
- assert.equal(await lockedGold.getDelegateFromAccountAndRole(account, role), delegate)
- assert.equal(await lockedGold.getAccountFromDelegateAndRole(delegate, role), account)
- })
-
- it('should emit a RoleDelegated event', async () => {
- const resp = await lockedGold.delegateRole(role, delegate, sig.v, sig.r, sig.s)
- assert.equal(resp.logs.length, 1)
- const log = resp.logs[0]
- assertLogMatches(log, 'RoleDelegated', {
- role,
- account,
- delegate,
- })
- })
-
- it('should revert if the delegate is an account', async () => {
- await lockedGold.createAccount({ from: delegate })
- await assertRevert(lockedGold.delegateRole(role, delegate, sig.v, sig.r, sig.s))
- })
-
- it('should revert if the address is already being delegated to', async () => {
- const otherAccount = accounts[2]
- const otherSig = await getParsedSignatureOfAddress(web3, otherAccount, delegate)
- await lockedGold.createAccount({ from: otherAccount })
- await lockedGold.delegateRole(role, delegate, otherSig.v, otherSig.r, otherSig.s, {
- from: otherAccount,
- })
- await assertRevert(lockedGold.delegateRole(role, delegate, sig.v, sig.r, sig.s))
- })
-
- it('should revert if the signature is incorrect', async () => {
- const nonDelegate = accounts[3]
- const incorrectSig = await getParsedSignatureOfAddress(web3, account, nonDelegate)
- await assertRevert(
- lockedGold.delegateRole(role, delegate, incorrectSig.v, incorrectSig.r, incorrectSig.s)
- )
- })
-
- describe('when a previous delegation has been made', async () => {
- const newDelegate = accounts[2]
- let newSig
- beforeEach(async () => {
- await lockedGold.delegateRole(role, delegate, sig.v, sig.r, sig.s)
- newSig = await getParsedSignatureOfAddress(web3, account, newDelegate)
- })
-
- it('should set the new delegate', async () => {
- await lockedGold.delegateRole(role, newDelegate, newSig.v, newSig.r, newSig.s)
- assert.equal(await lockedGold.delegations(newDelegate), account)
- assert.equal(await lockedGold.getDelegateFromAccountAndRole(account, role), newDelegate)
- assert.equal(await lockedGold.getAccountFromDelegateAndRole(newDelegate, role), account)
- })
-
- it('should reset the previous delegate', async () => {
- await lockedGold.delegateRole(role, newDelegate, newSig.v, newSig.r, newSig.s)
- assert.equal(await lockedGold.delegations(delegate), NULL_ADDRESS)
- })
- })
- })
- })
-
- describe('#freezeVoting()', () => {
- it('should set the account voting to frozen', async () => {
- await lockedGold.freezeVoting()
- assert.isTrue(await lockedGold.isVotingFrozen(account))
- })
-
- it('should emit a VotingFrozen event', async () => {
- const resp = await lockedGold.freezeVoting()
- assert.equal(resp.logs.length, 1)
- const log = resp.logs[0]
- assertLogMatches(log, 'VotingFrozen', {
- account,
- })
- })
-
- it('should revert if the account voting is already frozen', async () => {
- await lockedGold.freezeVoting()
- await assertRevert(lockedGold.freezeVoting())
- })
- })
-
- describe('#unfreezeVoting()', () => {
- beforeEach(async () => {
- await lockedGold.freezeVoting()
- })
-
- it('should set the account voting to unfrozen', async () => {
- await lockedGold.unfreezeVoting()
- assert.isFalse(await lockedGold.isVotingFrozen(account))
- })
-
- it('should emit a VotingUnfrozen event', async () => {
- const resp = await lockedGold.unfreezeVoting()
- assert.equal(resp.logs.length, 1)
- const log = resp.logs[0]
- assertLogMatches(log, 'VotingUnfrozen', {
- account,
- })
- })
-
- it('should revert if the account voting is already unfrozen', async () => {
- await lockedGold.unfreezeVoting()
- await assertRevert(lockedGold.unfreezeVoting())
- })
- })
-
- describe('#newCommitment()', () => {
- const noticePeriod = 1 * DAY + 1 * HOUR
- const value = 1000
- const expectedWeight = 1033
-
- it('should add a Locked Gold commitment', async () => {
- // @ts-ignore: TODO(mcortesi) fix typings for TransactionDetails
- await lockedGold.newCommitment(noticePeriod, { value })
- const noticePeriods = await lockedGold.getNoticePeriods(account)
- assert.equal(noticePeriods.length, 1)
- assert.equal(noticePeriods[0].toNumber(), noticePeriod)
- const [lockedValue, index] = await lockedGold.getLockedCommitment(account, noticePeriod)
- assert.equal(lockedValue.toNumber(), value)
- assert.equal(index.toNumber(), 0)
- })
-
- it('should update the account weight', async () => {
- // @ts-ignore: TODO(mcortesi) fix typings for TransactionDetails
- await lockedGold.newCommitment(noticePeriod, { value })
- const weight = await lockedGold.getAccountWeight(account)
- assert.equal(weight.toNumber(), expectedWeight)
- })
-
- it('should update the total weight', async () => {
- // @ts-ignore: TODO(mcortesi) fix typings for TransactionDetails
- await lockedGold.newCommitment(noticePeriod, { value })
- const totalWeight = await lockedGold.totalWeight()
- assert.equal(totalWeight.toNumber(), expectedWeight)
- })
-
- it('should emit a NewCommitment event', async () => {
- // @ts-ignore: TODO(mcortesi) fix typings for TransactionDetails
- const resp = await lockedGold.newCommitment(noticePeriod, { value })
- assert.equal(resp.logs.length, 1)
- const log = resp.logs[0]
- assertLogMatches(log, 'NewCommitment', {
- account,
- value: new BigNumber(value),
- noticePeriod: new BigNumber(noticePeriod),
- })
- })
-
- it('should revert when the specified notice period is too large', async () => {
- // @ts-ignore: TODO(mcortesi) fix typings for TransactionDetails
- await assertRevert(lockedGold.newCommitment(maxNoticePeriod + 1, { value }))
- })
-
- it('should revert when the specified value is 0', async () => {
- await assertRevert(lockedGold.newCommitment(noticePeriod))
- })
-
- it('should revert when the account does not exist', async () => {
- await assertRevert(lockedGold.newCommitment(noticePeriod, { value, from: accounts[1] }))
- })
-
- it('should revert if the caller is voting', async () => {
- await mockGovernance.setVoting(account)
- // @ts-ignore: TODO(mcortesi) fix typings for TransactionDetails
- await assertRevert(lockedGold.newCommitment(noticePeriod, { value }))
- })
- })
-
- describe('#notifyCommitment()', () => {
- const noticePeriod = 60 * 60 * 24 // 1 day
- const value = 1000
- beforeEach(async () => {
- // @ts-ignore: TODO(mcortesi) fix typings for TransactionDetails
- await lockedGold.newCommitment(noticePeriod, { value })
- })
-
- it('should add a notified deposit', async () => {
- await lockedGold.notifyCommitment(value, noticePeriod)
- const availabilityTime = new BigNumber(noticePeriod).plus(
- (await web3.eth.getBlock('latest')).timestamp
- )
- const availabilityTimes = await lockedGold.getAvailabilityTimes(account)
- assert.equal(availabilityTimes.length, 1)
- assert.equal(availabilityTimes[0].toNumber(), availabilityTime.toNumber())
-
- const [notifiedValue, index] = await lockedGold.getNotifiedCommitment(
- account,
- availabilityTime
- )
- assert.equal(notifiedValue.toNumber(), value)
- assert.equal(index.toNumber(), 0)
- })
-
- it('should remove the Locked Gold commitment', async () => {
- await lockedGold.notifyCommitment(value, noticePeriod)
- const noticePeriods = await lockedGold.getNoticePeriods(account)
- assert.equal(noticePeriods.length, 0)
- const [lockedValue, index] = await lockedGold.getLockedCommitment(account, noticePeriod)
- assert.equal(lockedValue.toNumber(), 0)
- assert.equal(index.toNumber(), 0)
- })
-
- it('should update the account weight', async () => {
- await lockedGold.notifyCommitment(value, noticePeriod)
- const weight = await lockedGold.getAccountWeight(account)
- assert.equal(weight.toNumber(), value)
- })
-
- it('should update the total weight', async () => {
- await lockedGold.notifyCommitment(value, noticePeriod)
- const totalWeight = await lockedGold.totalWeight()
- assert.equal(totalWeight.toNumber(), value)
- })
-
- it('should emit a CommitmentNotified event', async () => {
- const resp = await lockedGold.notifyCommitment(value, noticePeriod)
- assert.equal(resp.logs.length, 1)
- const log = resp.logs[0]
- assertLogMatches(log, 'CommitmentNotified', {
- account,
- value: new BigNumber(value),
- noticePeriod: new BigNumber(noticePeriod),
- availabilityTime: new BigNumber(noticePeriod).plus(
- (await web3.eth.getBlock('latest')).timestamp
- ),
- })
- })
-
- it('should revert when the value of the Locked Gold commitment is 0', async () => {
- await assertRevert(lockedGold.notifyCommitment(1, noticePeriod + 1))
- })
-
- it('should revert when value is greater than the value of the Locked Gold commitment', async () => {
- await assertRevert(lockedGold.notifyCommitment(value + 1, noticePeriod))
- })
-
- it('should revert when the value is 0', async () => {
- await assertRevert(lockedGold.notifyCommitment(0, noticePeriod))
- })
-
- it('should revert if the account is validating', async () => {
- await mockValidators.setValidating(account)
- await assertRevert(lockedGold.notifyCommitment(value, noticePeriod))
- })
-
- it('should revert if the caller is voting', async () => {
- await mockGovernance.setVoting(account)
- await assertRevert(lockedGold.notifyCommitment(value, noticePeriod))
- })
- })
-
- describe('#extendCommitment()', () => {
- const value = 1000
- const expectedWeight = 1033
- let availabilityTime: BigNumber
-
- beforeEach(async () => {
- // Set an initial notice period of just over one day, so that when we rebond, we're
- // guaranteed that the new notice period is at least one day.
- const noticePeriod = 1 * DAY + 1 * HOUR
- // @ts-ignore: TODO(mcortesi) fix typings for TransactionDetails
- await lockedGold.newCommitment(noticePeriod, { value })
- await lockedGold.notifyCommitment(value, noticePeriod)
- availabilityTime = new BigNumber(noticePeriod).plus(
- (await web3.eth.getBlock('latest')).timestamp
- )
- })
-
- it('should add a Locked Gold commitment', async () => {
- await lockedGold.extendCommitment(value, availabilityTime)
- const noticePeriods = await lockedGold.getNoticePeriods(account)
- assert.equal(noticePeriods.length, 1)
- const noticePeriod = availabilityTime
- .minus((await web3.eth.getBlock('latest')).timestamp)
- .toNumber()
- assert.equal(noticePeriods[0].toNumber(), noticePeriod)
- const [lockedValue, index] = await lockedGold.getLockedCommitment(account, noticePeriod)
- assert.equal(lockedValue.toNumber(), value)
- assert.equal(index.toNumber(), 0)
- })
-
- it('should remove a notified deposit', async () => {
- await lockedGold.extendCommitment(value, availabilityTime)
- const availabilityTimes = await lockedGold.getAvailabilityTimes(account)
- assert.equal(availabilityTimes.length, 0)
- const [notifiedValue, index] = await lockedGold.getNotifiedCommitment(
- account,
- availabilityTime
- )
- assert.equal(notifiedValue.toNumber(), 0)
- assert.equal(index.toNumber(), 0)
- })
-
- it('should update the account weight', async () => {
- await lockedGold.extendCommitment(value, availabilityTime)
- const weight = await lockedGold.getAccountWeight(account)
- assert.equal(weight.toNumber(), expectedWeight)
- })
-
- it('should update the total weight', async () => {
- await lockedGold.extendCommitment(value, availabilityTime)
- const totalWeight = await lockedGold.totalWeight()
- assert.equal(totalWeight.toNumber(), expectedWeight)
- })
-
- it('should emit a CommitmentExtended event', async () => {
- const resp = await lockedGold.extendCommitment(value, availabilityTime)
- const noticePeriod = availabilityTime.minus((await web3.eth.getBlock('latest')).timestamp)
- assert.equal(resp.logs.length, 1)
- const log = resp.logs[0]
- assertLogMatches(log, 'CommitmentExtended', {
- account,
- value: new BigNumber(value),
- noticePeriod,
- availabilityTime,
- })
- })
-
- it('should revert when the notified deposit is withdrawable', async () => {
- await timeTravel(
- availabilityTime
- .minus((await web3.eth.getBlock('latest')).timestamp)
- .plus(1)
- .toNumber(),
- web3
- )
- await assertRevert(lockedGold.extendCommitment(value, availabilityTime))
- })
-
- it('should revert when the value of the notified deposit is 0', async () => {
- await assertRevert(lockedGold.extendCommitment(value, availabilityTime.plus(1)))
- })
-
- it('should revert when the value is 0', async () => {
- await assertRevert(lockedGold.extendCommitment(0, availabilityTime))
- })
-
- it('should revert if the caller is voting', async () => {
- await mockGovernance.setVoting(account)
- await assertRevert(lockedGold.extendCommitment(value, availabilityTime))
- })
- })
-
- describe('#withdrawCommitment()', () => {
- const noticePeriod = 1 * DAY
- const value = 1000
- let availabilityTime: BigNumber
-
- beforeEach(async () => {
- // @ts-ignore: TODO(mcortesi) fix typings for TransactionDetails
- await lockedGold.newCommitment(noticePeriod, { value })
- await lockedGold.notifyCommitment(value, noticePeriod)
- availabilityTime = new BigNumber(noticePeriod).plus(
- (await web3.eth.getBlock('latest')).timestamp
- )
- })
-
- it('should remove the notified deposit', async () => {
- await timeTravel(noticePeriod, web3)
- await lockedGold.withdrawCommitment(availabilityTime)
-
- const availabilityTimes = await lockedGold.getAvailabilityTimes(account)
- assert.equal(availabilityTimes.length, 0)
- })
-
- it('should update the account weight', async () => {
- await timeTravel(noticePeriod, web3)
- await lockedGold.withdrawCommitment(availabilityTime)
-
- const weight = await lockedGold.getAccountWeight(account)
- assert.equal(weight.toNumber(), 0)
- })
-
- it('should update the total weight', async () => {
- await timeTravel(noticePeriod, web3)
- await lockedGold.withdrawCommitment(availabilityTime)
-
- const totalWeight = await lockedGold.totalWeight()
- assert.equal(totalWeight.toNumber(), 0)
- })
-
- it('should emit a Withdrawal event', async () => {
- await timeTravel(noticePeriod, web3)
- const resp = await lockedGold.withdrawCommitment(availabilityTime)
- assert.equal(resp.logs.length, 1)
- const log = resp.logs[0]
- assertLogMatches(log, 'Withdrawal', {
- account,
- value: new BigNumber(value),
- })
- })
-
- it('should revert if the account is validating', async () => {
- await mockValidators.setValidating(account)
- await assertRevert(lockedGold.withdrawCommitment(availabilityTime))
- })
-
- it('should revert when the notice period has not passed', async () => {
- await assertRevert(lockedGold.withdrawCommitment(availabilityTime))
- })
-
- it('should revert when the value of the notified deposit is 0', async () => {
- await timeTravel(noticePeriod, web3)
- await assertRevert(lockedGold.withdrawCommitment(availabilityTime.plus(1)))
- })
-
- it('should revert if the caller is voting', async () => {
- await timeTravel(noticePeriod, web3)
- await mockGovernance.setVoting(account)
- await assertRevert(lockedGold.withdrawCommitment(availabilityTime))
- })
- })
-
- describe('#increaseNoticePeriod()', () => {
- const noticePeriod = 1 * DAY
- const value = 1000
- const increase = noticePeriod
- const expectedWeight = 1047
-
- beforeEach(async () => {
- // @ts-ignore: TODO(mcortesi) fix typings for TransactionDetails
- await lockedGold.newCommitment(noticePeriod, { value })
- })
-
- it('should update the Locked Gold commitment', async () => {
- await lockedGold.increaseNoticePeriod(value, noticePeriod, increase)
- const noticePeriods = await lockedGold.getNoticePeriods(account)
- assert.equal(noticePeriods.length, 1)
- assert.equal(noticePeriods[0].toNumber(), noticePeriod + increase)
- const [lockedValue, index] = await lockedGold.getLockedCommitment(
- account,
- noticePeriod + increase
- )
- assert.equal(lockedValue.toNumber(), value)
- assert.equal(index.toNumber(), 0)
- })
-
- it('should update the account weight', async () => {
- await lockedGold.increaseNoticePeriod(value, noticePeriod, increase)
- const weight = await lockedGold.getAccountWeight(account)
- assert.equal(weight.toNumber(), expectedWeight)
- })
-
- it('should update the total weight', async () => {
- await lockedGold.increaseNoticePeriod(value, noticePeriod, increase)
- const totalWeight = await lockedGold.totalWeight()
- assert.equal(totalWeight.toNumber(), expectedWeight)
- })
-
- it('should emit a NoticePeriodIncreased event', async () => {
- const resp = await lockedGold.increaseNoticePeriod(value, noticePeriod, increase)
- assert.equal(resp.logs.length, 1)
- const log = resp.logs[0]
- assertLogMatches(log, 'NoticePeriodIncreased', {
- account,
- value: new BigNumber(value),
- noticePeriod: new BigNumber(noticePeriod),
- increase: new BigNumber(increase),
- })
- })
-
- it('should revert if the value is 0', async () => {
- await assertRevert(lockedGold.increaseNoticePeriod(0, noticePeriod, increase))
- })
-
- it('should revert if the increase is 0', async () => {
- await assertRevert(lockedGold.increaseNoticePeriod(value, noticePeriod, 0))
- })
-
- it('should revert if the Locked Gold commitment is smaller than the value', async () => {
- await assertRevert(lockedGold.increaseNoticePeriod(value, noticePeriod + 1, increase))
- })
-
- it('should revert if the caller is voting', async () => {
- await mockGovernance.setVoting(account)
- await assertRevert(lockedGold.increaseNoticePeriod(value, noticePeriod, increase))
- })
- })
-
- describe('#getAccountFromDelegateAndRole()', () => {
- forEachRole((role) => {
- describe('when the account is not delegating', () => {
- it('should return the account when passed the account', async () => {
- assert.equal(await lockedGold.getAccountFromDelegateAndRole(account, role), account)
- })
-
- it('should revert when passed a delegate that is not the role delegate', async () => {
- const delegate = accounts[2]
- const diffRole = (role + 1) % 3
- const sig = await getParsedSignatureOfAddress(web3, account, delegate)
- await lockedGold.delegateRole(role, delegate, sig.v, sig.r, sig.s)
- await assertRevert(lockedGold.getAccountFromDelegateAndRole(delegate, diffRole))
- })
- })
-
- describe('when the account is delegating', () => {
- const delegate = accounts[1]
-
- beforeEach(async () => {
- const sig = await getParsedSignatureOfAddress(web3, account, delegate)
- await lockedGold.delegateRole(role, delegate, sig.v, sig.r, sig.s)
- })
-
- it('should return the account when passed the delegate', async () => {
- assert.equal(await lockedGold.getAccountFromDelegateAndRole(delegate, role), account)
- })
-
- it('should return the account when passed the account', async () => {
- assert.equal(await lockedGold.getAccountFromDelegateAndRole(account, role), account)
- })
-
- it('should revert when passed a delegate that is not the role delegate', async () => {
- const delegate = accounts[2]
- const diffRole = (role + 1) % 3
- const sig = await getParsedSignatureOfAddress(web3, account, delegate)
- await lockedGold.delegateRole(role, delegate, sig.v, sig.r, sig.s)
- await assertRevert(lockedGold.getAccountFromDelegateAndRole(delegate, diffRole))
- })
- })
- })
- })
-
- describe('#getDelegateFromAccountAndRole()', () => {
- forEachRole((role) => {
- describe('when the account is not delegating', () => {
- it('should return the account when passed the account', async () => {
- assert.equal(await lockedGold.getDelegateFromAccountAndRole(account, role), account)
- })
- })
-
- describe('when the account is delegating', () => {
- const delegate = accounts[1]
-
- beforeEach(async () => {
- const sig = await getParsedSignatureOfAddress(web3, account, delegate)
- await lockedGold.delegateRole(role, delegate, sig.v, sig.r, sig.s)
- })
-
- it('should return the account when passed undelegated role', async () => {
- const role2 = (role + 1) % 3
- assert.equal(await lockedGold.getDelegateFromAccountAndRole(account, role2), account)
- })
-
- it('should return the delegate when passed the delegated role', async () => {
- assert.equal(await lockedGold.getDelegateFromAccountAndRole(account, role), delegate)
- })
- })
- })
- })
-
- describe('#isVoting()', () => {
- describe('when the account is not delegating', () => {
- it('should return false if the account is not voting in governance or validator elections', async () => {
- assert.isFalse(await lockedGold.isVoting(account))
- })
-
- it('should return true if the account is voting in governance', async () => {
- await mockGovernance.setVoting(account)
- assert.isTrue(await lockedGold.isVoting(account))
- })
-
- it('should return true if the account is voting in validator elections', async () => {
- await mockValidators.setVoting(account)
- assert.isTrue(await lockedGold.isVoting(account))
- })
-
- it('should return true if the account is voting in governance and validator elections', async () => {
- await mockGovernance.setVoting(account)
- await mockValidators.setVoting(account)
- assert.isTrue(await lockedGold.isVoting(account))
- })
- })
-
- describe('when the account is delegating', () => {
- const delegate = accounts[1]
-
- beforeEach(async () => {
- const sig = await getParsedSignatureOfAddress(web3, account, delegate)
- await lockedGold.delegateRole(roles.voting, delegate, sig.v, sig.r, sig.s)
- })
-
- it('should return false if the delegate is not voting in governance or validator elections', async () => {
- assert.isFalse(await lockedGold.isVoting(account))
- })
-
- it('should return true if the delegate is voting in governance', async () => {
- await mockGovernance.setVoting(delegate)
- assert.isTrue(await lockedGold.isVoting(account))
- })
-
- it('should return true if the delegate is voting in validator elections', async () => {
- await mockValidators.setVoting(delegate)
- assert.isTrue(await lockedGold.isVoting(account))
- })
-
- it('should return true if the delegate is voting in governance and validator elections', async () => {
- await mockGovernance.setVoting(delegate)
- await mockValidators.setVoting(delegate)
- assert.isTrue(await lockedGold.isVoting(account))
- })
- })
- })
-
- describe('#getCommitmentWeight()', () => {
- const value = new BigNumber(521000)
- const oneDay = new BigNumber(DAY)
- it('should return the commitment value when notice period is zero', async () => {
- const noticePeriod = new BigNumber(0)
- assertEqualBN(await lockedGold.getCommitmentWeight(value, noticePeriod), value)
- })
-
- it('should return the commitment value when notice period is less than one day', async () => {
- const noticePeriod = oneDay.minus(1)
- assertEqualBN(await lockedGold.getCommitmentWeight(value, noticePeriod), value)
- })
-
- it('should return the commitment value times 1.0333 when notice period is one day', async () => {
- const noticePeriod = oneDay
- assertEqualBN(
- await lockedGold.getCommitmentWeight(value, noticePeriod),
- value.times(1.0333).integerValue(BigNumber.ROUND_DOWN)
- )
- })
-
- it('should return the commitment value times 1.047 when notice period is two days', async () => {
- const noticePeriod = oneDay.times(2)
- assertEqualBN(
- await lockedGold.getCommitmentWeight(value, noticePeriod),
- value.times(1.047).integerValue(BigNumber.ROUND_DOWN)
- )
- })
-
- it('should return the commitment value times 1.1823 when notice period is 30 days', async () => {
- const noticePeriod = oneDay.times(30)
- assertEqualBN(
- await lockedGold.getCommitmentWeight(value, noticePeriod),
- value.times(1.1823).integerValue(BigNumber.ROUND_DOWN)
- )
- })
-
- it('should return the commitment value times 2.103 when notice period is 3 years', async () => {
- const noticePeriod = oneDay.times(365).times(3)
- assertEqualBN(
- await lockedGold.getCommitmentWeight(value, noticePeriod),
- value.times(2.103).integerValue(BigNumber.ROUND_DOWN)
- )
- })
- })
-
- describe('when there are multiple commitments, notifies, rebondings, notice period increases, and withdrawals', () => {
- beforeEach(async () => {
- for (const accountToCreate of accounts) {
- // Account for `account` has already been created.
- if (accountToCreate !== account) {
- await lockedGold.createAccount({ from: accountToCreate })
- }
- }
- })
-
- enum ActionType {
- Deposit = 'Deposit',
- Notify = 'Notify',
- Increase = 'Increase',
- Rebond = 'Rebond',
- Withdraw = 'Withdraw',
- }
-
- const initializeState = (numAccounts: number) => {
- const locked: Map> = new Map()
- const notified: Map> = new Map()
- const noticePeriods: Map> = new Map()
- const availabilityTimes: Map> = new Map()
- const selectedAccounts = accounts.slice(0, numAccounts)
- for (const acc of selectedAccounts) {
- // Map keys, set elements appear not to be able to be BigNumbers, so we use strings instead.
- locked.set(acc, new Map())
- notified.set(acc, new Map())
- noticePeriods.set(acc, new Set([]))
- availabilityTimes.set(acc, new Set([]))
- }
-
- return { locked, notified, noticePeriods, availabilityTimes, selectedAccounts }
- }
-
- const rndElement = (elems: A[]) => {
- return elems[
- Math.floor(
- BigNumber.random()
- .times(elems.length)
- .toNumber()
- )
- ]
- }
- const rndSetElement = (s: Set) => rndElement(Array.from(s))
-
- const getOrElse = (map: Map, key: B, defaultValue: A) =>
- map.has(key) ? map.get(key) : defaultValue
-
- const executeActionsAndAssertState = async (numActions: number, numAccounts: number) => {
- const {
- selectedAccounts,
- locked,
- notified,
- noticePeriods,
- availabilityTimes,
- } = initializeState(numAccounts)
-
- for (let i = 0; i < numActions; i++) {
- const blockTime = 5
- await timeTravel(blockTime, web3)
- account = rndElement(selectedAccounts)
-
- const accountLockedGold = locked.get(account)
- const accountNotifiedCommitments = notified.get(account)
- const accountNoticePeriods = noticePeriods.get(account)
- const accountAvailabilityTimes = availabilityTimes.get(account)
-
- const getWithdrawableAvailabilityTimes = async (): Promise> => {
- const nextTimestamp = new BigNumber((await web3.eth.getBlock('latest')).timestamp)
- const items: string[] = Array.from(accountAvailabilityTimes)
- return new Set(items.filter((x: string) => nextTimestamp.gt(x)))
- }
-
- const getRebondableAvailabilityTimes = async (): Promise> => {
- const nextTimestamp = new BigNumber((await web3.eth.getBlock('latest')).timestamp).plus(
- blockTime
- )
- const items: string[] = Array.from(accountAvailabilityTimes)
- // Subtract one to cover edge case where block time is 6 seconds.
- return new Set(items.filter((x: string) => nextTimestamp.plus(1).lt(x)))
- }
-
- // Select random action type.
- const actionTypeOptions = [ActionType.Deposit]
- if (accountNoticePeriods.size > 0) {
- actionTypeOptions.push(ActionType.Notify)
- actionTypeOptions.push(ActionType.Increase)
- }
- const rebondableAvailabilityTimes = await getRebondableAvailabilityTimes()
- if (rebondableAvailabilityTimes.size > 0) {
- // Push twice to increase likelihood
- actionTypeOptions.push(ActionType.Rebond)
- actionTypeOptions.push(ActionType.Rebond)
- }
- const withdrawableAvailabilityTimes = await getWithdrawableAvailabilityTimes()
- if (withdrawableAvailabilityTimes.size > 0) {
- // Push twice to increase likelihood
- actionTypeOptions.push(ActionType.Withdraw)
- actionTypeOptions.push(ActionType.Withdraw)
- }
- const actionType = rndElement(actionTypeOptions)
-
- const getLockedCommitmentValue = (noticePeriod: string) =>
- getOrElse(accountLockedGold, noticePeriod, new BigNumber(0))
- const getNotifiedCommitmentValue = (availabilityTime: string) =>
- getOrElse(accountNotifiedCommitments, availabilityTime, new BigNumber(0))
-
- const randomSometimesMaximumValue = (maximum: BigNumber) => {
- assert.isFalse(maximum.eq(0))
- const random = BigNumber.random().toNumber()
- if (random < 0.5) {
- return maximum
- } else {
- return BigNumber.max(
- BigNumber.random()
- .times(maximum)
- .integerValue(),
- 1
- )
- }
- }
-
- // Perform random action and update test implementation state.
- if (actionType === ActionType.Deposit) {
- const value = new BigNumber(web3.utils.randomHex(2)).toNumber()
- // Notice period of at most 10 blocks.
- const noticePeriod = BigNumber.random()
- .times(10)
- .times(blockTime)
- .integerValue()
- .valueOf()
- await lockedGold.newCommitment(noticePeriod, { value, from: account })
- accountNoticePeriods.add(noticePeriod)
- accountLockedGold.set(noticePeriod, getLockedCommitmentValue(noticePeriod).plus(value))
- } else if (actionType === ActionType.Notify || actionType === ActionType.Increase) {
- const noticePeriod = rndSetElement(accountNoticePeriods)
- const lockedDepositValue = getLockedCommitmentValue(noticePeriod)
- const value = randomSometimesMaximumValue(lockedDepositValue)
-
- if (value.eq(lockedDepositValue)) {
- accountLockedGold.delete(noticePeriod)
- accountNoticePeriods.delete(noticePeriod)
- } else {
- accountLockedGold.set(noticePeriod, lockedDepositValue.minus(value))
- }
-
- if (actionType === ActionType.Notify) {
- await lockedGold.notifyCommitment(value, noticePeriod, { from: account })
- const availabilityTime = new BigNumber(noticePeriod)
- .plus((await web3.eth.getBlock('latest')).timestamp)
- .valueOf()
- accountAvailabilityTimes.add(availabilityTime)
- accountNotifiedCommitments.set(
- availabilityTime,
- getNotifiedCommitmentValue(availabilityTime).plus(value)
- )
- } else {
- // Notice period increase of at most 10 blocks.
- const increase = BigNumber.random()
- .times(10)
- .times(blockTime)
- .integerValue()
- .plus(1)
- await lockedGold.increaseNoticePeriod(value, noticePeriod, increase, {
- from: account,
- })
- const increasedNoticePeriod = increase.plus(noticePeriod).valueOf()
- accountNoticePeriods.add(increasedNoticePeriod)
- accountLockedGold.set(
- increasedNoticePeriod,
- getLockedCommitmentValue(increasedNoticePeriod).plus(value)
- )
- }
- } else if (actionType === ActionType.Rebond) {
- const availabilityTime = rndSetElement(rebondableAvailabilityTimes)
- const notifiedDepositValue = getNotifiedCommitmentValue(availabilityTime)
- const value = randomSometimesMaximumValue(notifiedDepositValue)
- await lockedGold.extendCommitment(value, availabilityTime, { from: account })
-
- if (value.eq(notifiedDepositValue)) {
- accountNotifiedCommitments.delete(availabilityTime)
- accountAvailabilityTimes.delete(availabilityTime)
- } else {
- accountNotifiedCommitments.set(availabilityTime, notifiedDepositValue.minus(value))
- }
- const noticePeriod = new BigNumber(availabilityTime)
- .minus((await web3.eth.getBlock('latest')).timestamp)
- .valueOf()
- accountLockedGold.set(noticePeriod, getLockedCommitmentValue(noticePeriod).plus(value))
- accountNoticePeriods.add(noticePeriod)
- } else if (actionType === ActionType.Withdraw) {
- const availabilityTime = rndSetElement(withdrawableAvailabilityTimes)
- await lockedGold.withdrawCommitment(availabilityTime, { from: account })
- accountAvailabilityTimes.delete(availabilityTime)
- accountNotifiedCommitments.delete(availabilityTime)
- } else {
- assert.isTrue(false)
- }
-
- // Sanity check our test implementation.
- selectedAccounts.forEach((acc) => {
- if (locked.get(acc).size > 0) {
- assert.hasAllKeys(
- noticePeriods.get(acc),
- Array.from(locked.get(acc).keys()),
- `notice periods don\'t match for account: ${acc}`
- )
- }
- if (notified.get(acc).size > 0) {
- assert.hasAllKeys(
- availabilityTimes.get(acc),
- Array.from(notified.get(acc).keys()),
- `availability times don\'t match for account: ${acc}`
- )
- }
- })
-
- // Test the contract state matches our test implementation.
- let expectedTotalWeight = new BigNumber(0)
- for (const acc of selectedAccounts) {
- let expectedAccountWeight = new BigNumber(0)
- const actualNoticePeriods = await lockedGold.getNoticePeriods(acc)
-
- assert.lengthOf(actualNoticePeriods, noticePeriods.get(acc).size)
- for (let k = 0; k < actualNoticePeriods.length; k++) {
- const noticePeriod = actualNoticePeriods[k]
- assert.isTrue(noticePeriods.get(acc).has(noticePeriod.valueOf()))
- const [actualValue, actualIndex] = await lockedGold.getLockedCommitment(
- acc,
- noticePeriod
- )
- assertEqualBN(actualIndex, k)
- const expectedValue = locked.get(acc).get(noticePeriod.valueOf())
- assertEqualBN(actualValue, expectedValue)
- assertEqualBN(actualNoticePeriods[actualIndex.toNumber()], noticePeriod)
- expectedAccountWeight = expectedAccountWeight.plus(
- await lockedGold.getCommitmentWeight(expectedValue, noticePeriod)
- )
- }
-
- const actualAvailabilityTimes = await lockedGold.getAvailabilityTimes(acc)
-
- assert.equal(actualAvailabilityTimes.length, availabilityTimes.get(acc).size)
- for (let k = 0; k < actualAvailabilityTimes.length; k++) {
- const availabilityTime = actualAvailabilityTimes[k]
- assert.isTrue(availabilityTimes.get(acc).has(availabilityTime.valueOf()))
- const [actualValue, actualIndex] = await lockedGold.getNotifiedCommitment(
- acc,
- availabilityTime
- )
- assertEqualBN(actualIndex, k)
- const expectedValue = notified.get(acc).get(availabilityTime.valueOf())
- assertEqualBN(actualValue, expectedValue)
- assertEqualBN(actualAvailabilityTimes[actualIndex.toNumber()], availabilityTime)
- expectedAccountWeight = expectedAccountWeight.plus(expectedValue)
- }
- assertEqualBN(await lockedGold.getAccountWeight(acc), expectedAccountWeight)
- expectedTotalWeight = expectedTotalWeight.plus(expectedAccountWeight)
- }
- }
- }
-
- it.skip('should match a simple typescript implementation', async () => {
- const numActions = 100
- const numAccounts = 2
- await executeActionsAndAssertState(numActions, numAccounts)
- })
- })
-})
diff --git a/packages/protocol/test/governance/lockedgold.ts b/packages/protocol/test/governance/lockedgold.ts
index 31ec2fbd9ab..c16d476da5c 100644
--- a/packages/protocol/test/governance/lockedgold.ts
+++ b/packages/protocol/test/governance/lockedgold.ts
@@ -1,4 +1,5 @@
import { CeloContractName } from '@celo/protocol/lib/registry-utils'
+import { getParsedSignatureOfAddress } from '@celo/protocol/lib/signing-utils'
import {
assertEqualBN,
assertLogMatches,
@@ -48,17 +49,6 @@ contract('LockedGold', (accounts: string[]) => {
return s.charAt(0).toUpperCase() + s.slice(1)
}
- const getParsedSignatureOfAddress = async (address: string, signer: string) => {
- // @ts-ignore
- const hash = web3.utils.soliditySha3({ type: 'address', value: address })
- const signature = (await web3.eth.sign(hash, signer)).slice(2)
- return {
- r: `0x${signature.slice(0, 64)}`,
- s: `0x${signature.slice(64, 128)}`,
- v: web3.utils.hexToNumber(signature.slice(128, 130)) + 27,
- }
- }
-
beforeEach(async () => {
const mockGoldToken: MockGoldTokenInstance = await MockGoldToken.new()
lockedGold = await LockedGold.new()
@@ -157,7 +147,7 @@ contract('LockedGold', (accounts: string[]) => {
let sig
beforeEach(async () => {
- sig = await getParsedSignatureOfAddress(account, authorized)
+ sig = await getParsedSignatureOfAddress(web3, account, authorized)
})
it(`should set the authorized ${key}`, async () => {
@@ -183,7 +173,7 @@ contract('LockedGold', (accounts: string[]) => {
it(`should revert if the ${key} is already authorized`, async () => {
const otherAccount = accounts[2]
- const otherSig = await getParsedSignatureOfAddress(otherAccount, authorized)
+ const otherSig = await getParsedSignatureOfAddress(web3, otherAccount, authorized)
await lockedGold.createAccount({ from: otherAccount })
await authorizationTest.fn(authorized, otherSig.v, otherSig.r, otherSig.s, {
from: otherAccount,
@@ -193,7 +183,7 @@ contract('LockedGold', (accounts: string[]) => {
it('should revert if the signature is incorrect', async () => {
const nonVoter = accounts[3]
- const incorrectSig = await getParsedSignatureOfAddress(account, nonVoter)
+ const incorrectSig = await getParsedSignatureOfAddress(web3, account, nonVoter)
await assertRevert(
authorizationTest.fn(authorized, incorrectSig.v, incorrectSig.r, incorrectSig.s)
)
@@ -204,7 +194,7 @@ contract('LockedGold', (accounts: string[]) => {
let newSig
beforeEach(async () => {
await authorizationTest.fn(authorized, sig.v, sig.r, sig.s)
- newSig = await getParsedSignatureOfAddress(account, newAuthorized)
+ newSig = await getParsedSignatureOfAddress(web3, account, newAuthorized)
await authorizationTest.fn(newAuthorized, newSig.v, newSig.r, newSig.s)
})
@@ -234,7 +224,7 @@ contract('LockedGold', (accounts: string[]) => {
describe(`when the account has authorized a ${key}`, () => {
const authorized = accounts[1]
beforeEach(async () => {
- const sig = await getParsedSignatureOfAddress(account, authorized)
+ const sig = await getParsedSignatureOfAddress(web3, account, authorized)
await authorizationTest.fn(authorized, sig.v, sig.r, sig.s)
})
@@ -263,7 +253,7 @@ contract('LockedGold', (accounts: string[]) => {
const authorized = accounts[1]
beforeEach(async () => {
- const sig = await getParsedSignatureOfAddress(account, authorized)
+ const sig = await getParsedSignatureOfAddress(web3, account, authorized)
await authorizationTest.fn(authorized, sig.v, sig.r, sig.s)
})
From 89364ab61f243c1ee4d5909193f81c1c37fae2a3 Mon Sep 17 00:00:00 2001
From: Asa Oines
Date: Wed, 16 Oct 2019 12:50:27 -0700
Subject: [PATCH 068/149] Fix test
---
packages/protocol/test/governance/governance.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/packages/protocol/test/governance/governance.ts b/packages/protocol/test/governance/governance.ts
index 757ebc5c10b..d4e11d534f7 100644
--- a/packages/protocol/test/governance/governance.ts
+++ b/packages/protocol/test/governance/governance.ts
@@ -1682,7 +1682,7 @@ contract('Governance', (accounts: string[]) => {
await timeTravel(dequeueFrequency, web3)
await governance.approve(proposalId, index)
await timeTravel(approvalStageDuration, web3)
- await mockLockedGold.setWeight(account, weight)
+ await mockLockedGold.setAccountTotalLockedGold(account, weight)
await governance.vote(proposalId, index, value)
await timeTravel(referendumStageDuration, web3)
})
From 931ceba37134fe756fe20ef79b4fe0dd8ac6c999 Mon Sep 17 00:00:00 2001
From: Asa Oines
Date: Thu, 17 Oct 2019 17:42:18 -0700
Subject: [PATCH 069/149] Address comments; remove url and authorizedBy.active,
allow for variable membershiphistorylength
---
.../src/cmds/deploy/initial/contracts.ts | 7 +-
packages/cli/src/commands/validator/list.ts | 1 -
.../cli/src/commands/validator/register.ts | 5 +-
.../cli/src/commands/validatorgroup/list.ts | 1 -
.../src/commands/validatorgroup/register.ts | 4 +-
packages/contractkit/src/wrappers/Election.ts | 6 +
.../contractkit/src/wrappers/Validators.ts | 15 +-
.../contracts/governance/Election.sol | 3 +
.../contracts/governance/LockedGold.sol | 54 +++---
.../contracts/governance/Validators.sol | 114 ++++++-----
packages/protocol/lib/test-utils.ts | 5 +
.../protocol/test/governance/validators.ts | 183 +++++++++++++-----
12 files changed, 253 insertions(+), 145 deletions(-)
diff --git a/packages/celotool/src/cmds/deploy/initial/contracts.ts b/packages/celotool/src/cmds/deploy/initial/contracts.ts
index 161ae6604d1..e104a972fce 100644
--- a/packages/celotool/src/cmds/deploy/initial/contracts.ts
+++ b/packages/celotool/src/cmds/deploy/initial/contracts.ts
@@ -113,7 +113,12 @@ export const handler = async (argv: InitialArgv) => {
validatorKeys,
},
stableToken: {
- initialAccounts: getAddressesFor(AccountType.FAUCET, mnemonic, 2),
+ initialBalances: {
+ addresses: getAddressesFor(AccountType.FAUCET, mnemonic, 2),
+ values: getAddressesFor(AccountType.FAUCET, mnemonic, 2).map(
+ () => '60000000000000000000000'
+ ), // 60k Celo Dollars
+ },
},
})
diff --git a/packages/cli/src/commands/validator/list.ts b/packages/cli/src/commands/validator/list.ts
index 2e4efa37e11..af742764809 100644
--- a/packages/cli/src/commands/validator/list.ts
+++ b/packages/cli/src/commands/validator/list.ts
@@ -21,7 +21,6 @@ export default class ValidatorList extends BaseCommand {
cli.table(validatorList, {
address: {},
name: {},
- url: {},
publicKey: {},
affiliation: {},
})
diff --git a/packages/cli/src/commands/validator/register.ts b/packages/cli/src/commands/validator/register.ts
index ea44245a896..7c826f8fc5c 100644
--- a/packages/cli/src/commands/validator/register.ts
+++ b/packages/cli/src/commands/validator/register.ts
@@ -11,12 +11,11 @@ export default class ValidatorRegister extends BaseCommand {
...BaseCommand.flags,
from: Flags.address({ required: true, description: 'Address for the Validator' }),
name: flags.string({ required: true }),
- url: flags.string({ required: true }),
publicKey: Flags.publicKey({ required: true }),
}
static examples = [
- 'register --from 0x47e172F6CfB6c7D01C1574fa3E2Be7CC73269D95 --name myName --url "http://validator.com" --publicKey 0xc52f3fab06e22a54915a8765c4f6826090cfac5e40282b43844bf1c0df83aaa632e55b67869758f2291d1aabe0ebecc7cbf4236aaa45e3e0cfbf997eda082ae19d3e1d8f49f6b0d8e9a03d80ca07b1d24cf1cc0557bdcc04f5e17a46e35d02d0d411d956dbd5d2d2464eebd7b74ae30005d223780d785d2abc5644fac7ac29fb0e302bdc80c81a5d45018b68b1045068a4b3a4861c93037685fd0d252d7405011220a66a6257562d0c26dabf64485a1d96bad27bb1c0fd6080a75b0ec9f75b50298a2a8e04b02b2688c8104fca61fb00',
+ 'register --from 0x47e172F6CfB6c7D01C1574fa3E2Be7CC73269D95 --name myName --publicKey 0xc52f3fab06e22a54915a8765c4f6826090cfac5e40282b43844bf1c0df83aaa632e55b67869758f2291d1aabe0ebecc7cbf4236aaa45e3e0cfbf997eda082ae19d3e1d8f49f6b0d8e9a03d80ca07b1d24cf1cc0557bdcc04f5e17a46e35d02d0d411d956dbd5d2d2464eebd7b74ae30005d223780d785d2abc5644fac7ac29fb0e302bdc80c81a5d45018b68b1045068a4b3a4861c93037685fd0d252d7405011220a66a6257562d0c26dabf64485a1d96bad27bb1c0fd6080a75b0ec9f75b50298a2a8e04b02b2688c8104fca61fb00',
]
async run() {
const res = this.parse(ValidatorRegister)
@@ -25,7 +24,7 @@ export default class ValidatorRegister extends BaseCommand {
const attestations = await this.kit.contracts.getAttestations()
await displaySendTx(
'registerValidator',
- validators.registerValidator(res.flags.name, res.flags.url, res.flags.publicKey as any)
+ validators.registerValidator(res.flags.name, res.flags.publicKey as any)
)
// register encryption key on attestations contract
diff --git a/packages/cli/src/commands/validatorgroup/list.ts b/packages/cli/src/commands/validatorgroup/list.ts
index 7743ab63f9a..096520b8ff9 100644
--- a/packages/cli/src/commands/validatorgroup/list.ts
+++ b/packages/cli/src/commands/validatorgroup/list.ts
@@ -21,7 +21,6 @@ export default class ValidatorGroupList extends BaseCommand {
cli.table(vgroups, {
address: {},
name: {},
- url: {},
commission: { get: (r) => r.commission.toFixed() },
members: { get: (r) => r.members.length },
})
diff --git a/packages/cli/src/commands/validatorgroup/register.ts b/packages/cli/src/commands/validatorgroup/register.ts
index 365bc9cf7f6..090e0d8ba85 100644
--- a/packages/cli/src/commands/validatorgroup/register.ts
+++ b/packages/cli/src/commands/validatorgroup/register.ts
@@ -11,12 +11,11 @@ export default class ValidatorGroupRegister extends BaseCommand {
...BaseCommand.flags,
from: Flags.address({ required: true, description: 'Address for the Validator Group' }),
name: flags.string({ required: true }),
- url: flags.string({ required: true }),
commission: flags.string({ required: true }),
}
static examples = [
- 'register --from 0x47e172F6CfB6c7D01C1574fa3E2Be7CC73269D95 --name myName --url "http://vgroup.com" --commission 0.1',
+ 'register --from 0x47e172F6CfB6c7D01C1574fa3E2Be7CC73269D95 --name myName --commission 0.1',
]
async run() {
@@ -26,7 +25,6 @@ export default class ValidatorGroupRegister extends BaseCommand {
const validators = await this.kit.contracts.getValidators()
const tx = await validators.registerValidatorGroup(
res.flags.name,
- res.flags.url,
new BigNumber(res.flags.commission)
)
await displaySendTx('registerValidatorGroup', tx)
diff --git a/packages/contractkit/src/wrappers/Election.ts b/packages/contractkit/src/wrappers/Election.ts
index 30676f15c9a..c18385fcbfb 100644
--- a/packages/contractkit/src/wrappers/Election.ts
+++ b/packages/contractkit/src/wrappers/Election.ts
@@ -80,6 +80,12 @@ export class ElectionWrapper extends BaseWrapper {
toNumber
)
+ /**
+ * 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`.
+ */
getTotalVotesForGroup = proxyCall(
this.contract.methods.getTotalVotesForGroup,
undefined,
diff --git a/packages/contractkit/src/wrappers/Validators.ts b/packages/contractkit/src/wrappers/Validators.ts
index 5d0a506cb3d..8fefd681ac1 100644
--- a/packages/contractkit/src/wrappers/Validators.ts
+++ b/packages/contractkit/src/wrappers/Validators.ts
@@ -14,7 +14,6 @@ import {
export interface Validator {
address: Address
name: string
- url: string
publicKey: string
affiliation: string | null
}
@@ -22,7 +21,6 @@ export interface Validator {
export interface ValidatorGroup {
address: Address
name: string
- url: string
members: Address[]
commission: BigNumber
}
@@ -53,12 +51,11 @@ export class ValidatorsWrapper extends BaseWrapper {
registerValidator = proxySend(this.kit, this.contract.methods.registerValidator)
async registerValidatorGroup(
name: string,
- url: string,
commission: BigNumber
): Promise> {
return toTransactionObject(
this.kit,
- this.contract.methods.registerValidatorGroup(name, url, toFixed(commission).toFixed())
+ this.contract.methods.registerValidatorGroup(name, toFixed(commission).toFixed())
)
}
async addMember(member: string): Promise> {
@@ -135,9 +132,8 @@ export class ValidatorsWrapper extends BaseWrapper {
return {
address,
name: res[0],
- url: res[1],
- publicKey: res[2] as any,
- affiliation: res[3],
+ publicKey: res[1] as any,
+ affiliation: res[2],
}
}
@@ -195,9 +191,8 @@ export class ValidatorsWrapper extends BaseWrapper {
return {
address,
name: res[0],
- url: res[1],
- members: res[2],
- commission: fromFixed(new BigNumber(res[3])),
+ members: res[1],
+ commission: fromFixed(new BigNumber(res[2])),
}
}
}
diff --git a/packages/protocol/contracts/governance/Election.sol b/packages/protocol/contracts/governance/Election.sol
index b675f1e991e..e810dd07329 100644
--- a/packages/protocol/contracts/governance/Election.sol
+++ b/packages/protocol/contracts/governance/Election.sol
@@ -484,6 +484,8 @@ contract Election is
view
returns (uint256)
{
+ // The group must meet the balance requirements in order for their voters to receive epoch
+ // rewards.
bool meetsBalanceRequirements = (
getLockedGold().getAccountTotalLockedGold(group) >=
getValidators().getAccountBalanceRequirement(group)
@@ -502,6 +504,7 @@ contract Election is
* @param value The amount of rewards to distribute to voters for the group.
* @param lesser The group receiving fewer votes than `group` after the rewards are added.
* @param greater The group receiving more votes than `group` after the rewards are added.
+ * @dev Can only be called directly by the protocol.
*/
function distributeEpochRewards(
address group,
diff --git a/packages/protocol/contracts/governance/LockedGold.sol b/packages/protocol/contracts/governance/LockedGold.sol
index 2bb13b2691c..ff713e45841 100644
--- a/packages/protocol/contracts/governance/LockedGold.sol
+++ b/packages/protocol/contracts/governance/LockedGold.sol
@@ -14,11 +14,6 @@ contract LockedGold is ILockedGold, ReentrancyGuard, Initializable, UsingRegistr
using SafeMath for uint256;
- struct AuthorizedBy {
- address account;
- bool active;
- }
-
struct Authorizations {
// The address that is authorized to vote on behalf of the account.
// The account can vote as well, whether or not an authorized voter has been specified.
@@ -59,7 +54,7 @@ contract LockedGold is ILockedGold, ReentrancyGuard, Initializable, UsingRegistr
mapping(address => Account) private accounts;
// Maps voting and validating keys to the account that provided the authorization.
// Authorized addresses may not be reused.
- mapping(address => AuthorizedBy) private authorizedBy;
+ mapping(address => address) private authorizedBy;
uint256 public totalNonvoting;
uint256 public unlockingPeriod;
@@ -105,7 +100,7 @@ contract LockedGold is ILockedGold, ReentrancyGuard, Initializable, UsingRegistr
nonReentrant
{
Account storage account = accounts[msg.sender];
- authorize(voter, account.authorizations.voting, v, r, s);
+ authorize(voter, v, r, s);
account.authorizations.voting = voter;
emit VoterAuthorized(msg.sender, voter);
}
@@ -128,7 +123,7 @@ contract LockedGold is ILockedGold, ReentrancyGuard, Initializable, UsingRegistr
nonReentrant
{
Account storage account = accounts[msg.sender];
- authorize(validator, account.authorizations.validating, v, r, s);
+ authorize(validator, v, r, s);
account.authorizations.validating = validator;
emit ValidatorAuthorized(msg.sender, validator);
}
@@ -262,9 +257,10 @@ contract LockedGold is ILockedGold, ReentrancyGuard, Initializable, UsingRegistr
* @return The associated account.
*/
function getAccountFromActiveVoter(address accountOrVoter) external view returns (address) {
- AuthorizedBy memory ab = authorizedBy[accountOrVoter];
- if (ab.active && ab.account != address(0)) {
- return ab.account;
+ address account = authorizedBy[accountOrVoter];
+ if (account != address(0)) {
+ require(accounts[account].authorizations.voting == accountOrVoter);
+ return account;
} else {
require(isAccount(accountOrVoter));
return accountOrVoter;
@@ -314,9 +310,10 @@ contract LockedGold is ILockedGold, ReentrancyGuard, Initializable, UsingRegistr
* @return The associated account.
*/
function getAccountFromActiveValidator(address accountOrValidator) public view returns (address) {
- AuthorizedBy memory ab = authorizedBy[accountOrValidator];
- if (ab.active && ab.account != address(0)) {
- return ab.account;
+ address account = authorizedBy[accountOrValidator];
+ if (account != address(0)) {
+ require(accounts[account].authorizations.validating == accountOrValidator);
+ return account;
} else {
require(isAccount(accountOrValidator));
return accountOrValidator;
@@ -330,9 +327,9 @@ contract LockedGold is ILockedGold, ReentrancyGuard, Initializable, UsingRegistr
* @return The associated account.
*/
function getAccountFromVoter(address accountOrVoter) public view returns (address) {
- AuthorizedBy memory ab = authorizedBy[accountOrVoter];
- if (ab.account != address(0)) {
- return ab.account;
+ address account = authorizedBy[accountOrVoter];
+ if (account != address(0)) {
+ return account;
} else {
require(isAccount(accountOrVoter));
return accountOrVoter;
@@ -346,9 +343,9 @@ contract LockedGold is ILockedGold, ReentrancyGuard, Initializable, UsingRegistr
* @return The associated account.
*/
function getAccountFromValidator(address accountOrValidator) public view returns (address) {
- AuthorizedBy memory ab = authorizedBy[accountOrValidator];
- if (ab.account != address(0)) {
- return ab.account;
+ address account = authorizedBy[accountOrValidator];
+ if (account != address(0)) {
+ return account;
} else {
require(isAccount(accountOrValidator));
return accountOrValidator;
@@ -405,8 +402,7 @@ contract LockedGold is ILockedGold, ReentrancyGuard, Initializable, UsingRegistr
/**
* @notice Authorizes voting or validating power of `msg.sender`'s account to another address.
- * @param current The address to authorize.
- * @param previous The previous authorized address.
+ * @param authorized The address to authorize.
* @param v The recovery id of the incoming ECDSA signature.
* @param r Output value r of the ECDSA signature.
* @param s Output value s of the ECDSA signature.
@@ -414,21 +410,19 @@ contract LockedGold is ILockedGold, ReentrancyGuard, Initializable, UsingRegistr
* @dev v, r, s constitute `current`'s signature on `msg.sender`.
*/
function authorize(
- address current,
- address previous,
+ address authorized,
uint8 v,
bytes32 r,
bytes32 s
)
private
{
- require(isAccount(msg.sender) && isNotAccount(current) && isNotAuthorized(current));
+ require(isAccount(msg.sender) && isNotAccount(authorized) && isNotAuthorized(authorized));
address signer = Signatures.getSignerOfAddress(msg.sender, v, r, s);
- require(signer == current);
+ require(signer == authorized);
- authorizedBy[previous].active = false;
- authorizedBy[current] = AuthorizedBy(msg.sender, true);
+ authorizedBy[authorized] = msg.sender;
}
/**
@@ -455,7 +449,7 @@ contract LockedGold is ILockedGold, ReentrancyGuard, Initializable, UsingRegistr
* @return Returns `true` if authorized. Returns `false` otherwise.
*/
function isAuthorized(address account) external view returns (bool) {
- return (authorizedBy[account].account != address(0));
+ return (authorizedBy[account] != address(0));
}
/**
@@ -464,7 +458,7 @@ contract LockedGold is ILockedGold, ReentrancyGuard, Initializable, UsingRegistr
* @return Returns `false` if authorized. Returns `true` otherwise.
*/
function isNotAuthorized(address account) internal view returns (bool) {
- return (authorizedBy[account].account == address(0));
+ return (authorizedBy[account] == address(0));
}
/**
diff --git a/packages/protocol/contracts/governance/Validators.sol b/packages/protocol/contracts/governance/Validators.sol
index 72aa8264be6..8c59a61e3e6 100644
--- a/packages/protocol/contracts/governance/Validators.sol
+++ b/packages/protocol/contracts/governance/Validators.sol
@@ -60,7 +60,6 @@ contract Validators is
struct ValidatorGroup {
string name;
- string url;
LinkedList.List members;
// TODO(asa): Add a function that allows groups to update their commission.
FixidityLib.Fraction commission;
@@ -72,15 +71,17 @@ contract Validators is
address group;
}
- // A circular buffer storing the membership history of a validator.
+ // Stores the membership history of a validator.
struct MembershipHistory {
- uint256 head;
- MembershipHistoryEntry[] entries;
+ // The key to the most recent entry in the entries mapping.
+ uint256 tail;
+ // The number of entries in this validators membership history.
+ uint256 numEntries;
+ mapping(uint256 => MembershipHistoryEntry) entries;
}
struct Validator {
string name;
- string url;
bytes publicKeysData;
address affiliation;
FixidityLib.Fraction score;
@@ -105,7 +106,6 @@ contract Validators is
uint256 public membershipHistoryLength;
uint256 public maxGroupSize;
- event Debug(uint256 value);
event MaxGroupSizeSet(
uint256 size
);
@@ -124,6 +124,8 @@ contract Validators is
uint256 validator
);
+ event MembershipHistoryLengthSet(uint256 length);
+
event DeregistrationLockupsSet(
uint256 group,
uint256 validator
@@ -132,7 +134,6 @@ contract Validators is
event ValidatorRegistered(
address indexed validator,
string name,
- string url,
bytes publicKeysData
);
@@ -153,7 +154,7 @@ contract Validators is
event ValidatorGroupRegistered(
address indexed group,
string name,
- string url
+ uint256 commission
);
event ValidatorGroupDeregistered(
@@ -209,15 +210,14 @@ contract Validators is
external
initializer
{
- require(validatorScoreAdjustmentSpeed <= FixidityLib.fixed1().unwrap());
_transferOwnership(msg.sender);
setRegistry(registryAddress);
setValidatorEpochPayment(_validatorEpochPayment);
- setValidatorScoreParameters(validatorScoreExponent, validatorScoreAdjustmentSpeed)
+ setValidatorScoreParameters(validatorScoreExponent, validatorScoreAdjustmentSpeed);
setBalanceRequirements(groupRequirement, validatorRequirement);
setDeregistrationLockups(groupLockup, validatorLockup);
setMaxGroupSize(_maxGroupSize);
- membershipHistoryLength = _membershipHistoryLength;
+ setMembershipHistoryLength(_membershipHistoryLength);
}
/**
@@ -232,6 +232,18 @@ contract Validators is
return true;
}
+ /**
+ * @notice Updates the number of validator group membership entries to store.
+ * @param length The number of validator group membership entries to store.
+ * @return True upon success.
+ */
+ function setMembershipHistoryLength(uint256 length) public onlyOwner returns (bool) {
+ require(0 < length && length != membershipHistoryLength);
+ membershipHistoryLength = length;
+ emit MembershipHistoryLengthSet(length);
+ return true;
+ }
+
/**
* @notice Sets the per-epoch payment in Celo Dollars for validators, less group commission.
* @param value The value in Celo Dollars.
@@ -322,7 +334,6 @@ contract Validators is
/**
* @notice Registers a validator unaffiliated with any validator group.
* @param name A name for the validator.
- * @param url A URL for the validator.
* @param publicKeysData Comprised of three tightly-packed elements:
* - publicKey - The public key that the validator is using for consensus, should match
* msg.sender. 64 bytes.
@@ -335,7 +346,6 @@ contract Validators is
*/
function registerValidator(
string calldata name,
- string calldata url,
bytes calldata publicKeysData
)
external
@@ -344,7 +354,6 @@ contract Validators is
{
require(
bytes(name).length > 0 &&
- bytes(url).length > 0 &&
// secp256k1 public key + BLS public key + BLS proof of possession
publicKeysData.length == (64 + 48 + 96)
);
@@ -356,11 +365,10 @@ contract Validators is
require(meetsValidatorBalanceRequirements(account));
validators[account].name = name;
- validators[account].url = url;
validators[account].publicKeysData = publicKeysData;
_validators.push(account);
updateMembershipHistory(account, address(0));
- emit ValidatorRegistered(account, name, url, publicKeysData);
+ emit ValidatorRegistered(account, name, publicKeysData);
return true;
}
@@ -413,12 +421,13 @@ contract Validators is
view
returns (uint256[] memory, address[] memory)
{
- MembershipHistoryEntry[] memory entries = validators[account].membershipHistory.entries;
- uint256[] memory epochs = new uint256[](entries.length);
- address[] memory membershipGroups = new address[](entries.length);
- for (uint256 i = 0; i < entries.length; i = i.add(1)) {
- epochs[i] = entries[i].epochNumber;
- membershipGroups[i] = entries[i].group;
+ MembershipHistory storage history = validators[account].membershipHistory;
+ uint256[] memory epochs = new uint256[](history.numEntries);
+ address[] memory membershipGroups = new address[](history.numEntries);
+ for (uint256 i = 0; i < history.numEntries; i = i.add(1)) {
+ uint256 index = history.tail.add(i);
+ epochs[i] = history.entries[index].epochNumber;
+ membershipGroups[i] = history.entries[index].group;
}
return (epochs, membershipGroups);
}
@@ -437,6 +446,7 @@ contract Validators is
* @notice Updates a validator's score based on its uptime for the epoch.
* @param validator The address of the validator.
* @param uptime The Fixidity representation of the validator's uptime, between 0 and 1.
+ * @dev new_score = uptime ** exponent * adjustmentSpeed + old_score * (1 - adjustmentSpeed)
* @return True upon success.
*/
function _updateValidatorScore(address validator, uint256 uptime) internal {
@@ -562,7 +572,6 @@ contract Validators is
/**
* @notice Registers a validator group with no member validators.
* @param name A name for the validator group.
- * @param url A URL for the validator group.
* @param commission Fixidity representation of the commission this group receives on epoch
* payments made to its members.
* @return True upon success.
@@ -571,7 +580,6 @@ contract Validators is
*/
function registerValidatorGroup(
string calldata name,
- string calldata url,
uint256 commission
)
external
@@ -579,7 +587,6 @@ contract Validators is
returns (bool)
{
require(bytes(name).length > 0);
- require(bytes(url).length > 0);
require(commission <= FixidityLib.fixed1().unwrap(), "Commission can't be greater than 100%");
address account = getLockedGold().getAccountFromActiveValidator(msg.sender);
require(!isValidator(account) && !isValidatorGroup(account));
@@ -587,10 +594,9 @@ contract Validators is
ValidatorGroup storage group = groups[account];
group.name = name;
- group.url = url;
group.commission = FixidityLib.wrap(commission);
_groups.push(account);
- emit ValidatorGroupRegistered(account, name, url);
+ emit ValidatorGroupRegistered(account, name, commission);
return true;
}
@@ -761,7 +767,6 @@ contract Validators is
view
returns (
string memory name,
- string memory url,
bytes memory publicKeysData,
address affiliation,
uint256 score
@@ -771,7 +776,6 @@ contract Validators is
Validator storage validator = validators[account];
return (
validator.name,
- validator.url,
validator.publicKeysData,
validator.affiliation,
validator.score.unwrap()
@@ -788,11 +792,11 @@ contract Validators is
)
external
view
- returns (string memory, string memory, address[] memory, uint256)
+ returns (string memory, address[] memory, uint256)
{
require(isValidatorGroup(account));
ValidatorGroup storage group = groups[account];
- return (group.name, group.url, group.members.getKeys(), group.commission.unwrap());
+ return (group.name, group.members.getKeys(), group.commission.unwrap());
}
/**
@@ -941,6 +945,15 @@ contract Validators is
}
/**
+ * Tail: 0
+ * numEntries: 0
+ * index: 0
+ *
+ * Tail: 0
+ * numEntries: 1
+ * index: 1
+ *
+ *
* @notice Updates the group membership history of a particular account.
* @param account The account whose group membership has changed.
* @param group The group that the account is now a member of.
@@ -951,20 +964,31 @@ contract Validators is
function updateMembershipHistory(address account, address group) private returns (bool) {
MembershipHistory storage history = validators[account].membershipHistory;
uint256 epochNumber = getEpochNumber();
- if (history.entries.length > 0 && epochNumber == history.entries[history.head].epochNumber) {
+ uint256 head = history.numEntries == 0 ? 0 : history.tail.add(history.numEntries.sub(1));
+
+ if (history.entries[head].epochNumber == epochNumber) {
// There have been no elections since the validator last changed membership, overwrite the
// previous entry.
- history.entries[history.head] = MembershipHistoryEntry(epochNumber, group);
+ history.entries[head] = MembershipHistoryEntry(epochNumber, group);
+ return true;
+ }
+
+ // There have been elections since the validator last changed membership, create a new entry.
+ uint256 index = history.numEntries == 0 ? 0 : head.add(1);
+ history.entries[index] = MembershipHistoryEntry(epochNumber, group);
+ if (history.numEntries < membershipHistoryLength) {
+ // Not enough entries, don't remove any.
+ history.numEntries = history.numEntries.add(1);
+ } else if (history.numEntries == membershipHistoryLength) {
+ // Exactly enough entries, delete the oldest one to account for the one we added.
+ delete history.entries[history.tail];
+ history.tail = history.tail.add(1);
} else {
- if (history.entries.length > 0) {
- // MembershipHistoryEntries are a circular buffer.
- history.head = history.head.add(1) % membershipHistoryLength;
- }
- if (history.head >= history.entries.length) {
- history.entries.push(MembershipHistoryEntry(epochNumber, group));
- } else {
- history.entries[history.head] = MembershipHistoryEntry(epochNumber, group);
- }
+ // Too many entries, delete the oldest two to account for the one we added.
+ delete history.entries[history.tail];
+ delete history.entries[history.tail.add(1)];
+ history.numEntries = history.numEntries.sub(1);
+ history.tail = history.tail.add(2);
}
}
@@ -976,14 +1000,12 @@ contract Validators is
function getMembershipInLastEpoch(address account) public view returns (address) {
uint256 epochNumber = getEpochNumber();
MembershipHistory storage history = validators[account].membershipHistory;
- uint256 head = history.head;
+ uint256 head = history.numEntries == 0 ? 0 : history.tail.add(history.numEntries.sub(1));
// If the most recent entry in the membership history is for the current epoch number, we need
// to look at the previous entry.
if (history.entries[head].epochNumber == epochNumber) {
- if (head > 0) {
+ if (head > history.tail) {
head = head.sub(1);
- } else if (history.entries.length > 1) {
- head = history.entries.length.sub(1);
}
}
return history.entries[head].group;
diff --git a/packages/protocol/lib/test-utils.ts b/packages/protocol/lib/test-utils.ts
index 982ea4a2a22..2e9ff68d5d5 100644
--- a/packages/protocol/lib/test-utils.ts
+++ b/packages/protocol/lib/test-utils.ts
@@ -239,6 +239,11 @@ export function assertEqualBN(
)
}
+export function assertEqualBNArray(value: number[] | BN[] | BigNumber[], expected: number[] | BN[] | BigNumber[], msg?: string) {
+ assert.equal(value.length, expected.length, msg)
+ value.forEach((x, i) => assertEqualBN(x, expected[i]))
+}
+
export function assertGteBN(
value: number | BN | BigNumber,
expected: number | BN | BigNumber,
diff --git a/packages/protocol/test/governance/validators.ts b/packages/protocol/test/governance/validators.ts
index d034dc03dd5..d587f0fe60e 100644
--- a/packages/protocol/test/governance/validators.ts
+++ b/packages/protocol/test/governance/validators.ts
@@ -2,6 +2,7 @@ import { CeloContractName } from '@celo/protocol/lib/registry-utils'
import {
assertContainSubset,
assertEqualBN,
+ assertEqualBNArray,
assertRevert,
assertSameAddress,
NULL_ADDRESS,
@@ -35,19 +36,17 @@ Validators.numberFormat = 'BigNumber'
const parseValidatorParams = (validatorParams: any) => {
return {
name: validatorParams[0],
- url: validatorParams[1],
- publicKeysData: validatorParams[2],
- affiliation: validatorParams[3],
- score: validatorParams[4],
+ publicKeysData: validatorParams[1],
+ affiliation: validatorParams[2],
+ score: validatorParams[3],
}
}
const parseValidatorGroupParams = (groupParams: any) => {
return {
name: groupParams[0],
- url: groupParams[1],
- members: groupParams[2],
- commission: groupParams[3],
+ members: groupParams[1],
+ commission: groupParams[2],
}
}
@@ -86,7 +85,6 @@ contract('Validators', (accounts: string[]) => {
'9d3e1d8f49f6b0d8e9a03d80ca07b1d24cf1cc0557bdcc04f5e17a46e35d02d0d411d956dbd5d2d2464eebd7b74ae30005d223780d785d2abc5644fac7ac29fb0e302bdc80c81a5d45018b68b1045068a4b3a4861c93037685fd0d252d740501'
const publicKeysData = '0x' + publicKey + blsPublicKey + blsPoP
const name = 'test-name'
- const url = 'test-url'
const commission = toFixed(1 / 100)
beforeEach(async () => {
validators = await Validators.new()
@@ -113,7 +111,6 @@ contract('Validators', (accounts: string[]) => {
await mockLockedGold.setAccountTotalLockedGold(validator, balanceRequirements.validator)
await validators.registerValidator(
name,
- url,
// @ts-ignore bytes type
publicKeysData,
{ from: validator }
@@ -122,7 +119,7 @@ contract('Validators', (accounts: string[]) => {
const registerValidatorGroup = async (group: string) => {
await mockLockedGold.setAccountTotalLockedGold(group, balanceRequirements.group)
- await validators.registerValidatorGroup(name, url, commission, { from: group })
+ await validators.registerValidatorGroup(name, commission, { from: group })
}
const registerValidatorGroupWithMembers = async (group: string, members: string[]) => {
@@ -240,6 +237,51 @@ contract('Validators', (accounts: string[]) => {
})
})
+ describe('#setMembershipHistoryLength()', () => {
+ describe('when the length is different', () => {
+ const newLength = membershipHistoryLength.plus(1)
+
+ describe('when called by the owner', () => {
+ let resp: any
+
+ beforeEach(async () => {
+ resp = await validators.setMembershipHistoryLength(newLength)
+ })
+
+ it('should set the membership history length', async () => {
+ assertEqualBN(await validators.membershipHistoryLength(), newLength)
+ })
+
+ it('should emit the MembershipHistoryLengthSet event', async () => {
+ assert.equal(resp.logs.length, 1)
+ const log = resp.logs[0]
+ assertContainSubset(log, {
+ event: 'MembershipHistoryLengthSet',
+ args: {
+ length: new BigNumber(newLength),
+ },
+ })
+ })
+
+ describe('when called by a non-owner', () => {
+ it('should revert', async () => {
+ await assertRevert(
+ validators.setMembershipHistoryLength(newLength, {
+ from: nonOwner,
+ })
+ )
+ })
+ })
+ })
+
+ describe('when the length is the same', () => {
+ it('should revert', async () => {
+ await assertRevert(validators.setMembershipHistoryLength(membershipHistoryLength))
+ })
+ })
+ })
+ })
+
describe('#setMaxGroupSize()', () => {
describe('when the group size is different', () => {
const newSize = maxGroupSize.plus(1)
@@ -508,14 +550,16 @@ contract('Validators', (accounts: string[]) => {
const validator = accounts[0]
let resp: any
describe('when the account is not a registered validator', () => {
+ let validatorRegistrationEpochNumber: number
beforeEach(async () => {
await mockLockedGold.setAccountTotalLockedGold(validator, balanceRequirements.validator)
resp = await validators.registerValidator(
name,
- url,
// @ts-ignore bytes type
publicKeysData
)
+ const blockNumber = (await web3.eth.getBlock('latest')).number
+ validatorRegistrationEpochNumber = Math.floor(blockNumber / EPOCH)
})
it('should mark the account as a validator', async () => {
@@ -526,10 +570,9 @@ contract('Validators', (accounts: string[]) => {
assert.deepEqual(await validators.getRegisteredValidators(), [validator])
})
- it('should set the validator name, url, and public key', async () => {
+ it('should set the validator name and public key', async () => {
const parsedValidator = parseValidatorParams(await validators.getValidator(validator))
assert.equal(parsedValidator.name, name)
- assert.equal(parsedValidator.url, url)
assert.equal(parsedValidator.publicKeysData, publicKeysData)
})
@@ -538,6 +581,12 @@ contract('Validators', (accounts: string[]) => {
assertEqualBN(requirement, balanceRequirements.validator)
})
+ it('should set the validator membership history', async () => {
+ const membershipHistory = await validators.getMembershipHistory(validator)
+ assertEqualBNArray(membershipHistory[0], [validatorRegistrationEpochNumber])
+ assert.deepEqual(membershipHistory[1], [NULL_ADDRESS])
+ })
+
it('should emit the ValidatorRegistered event', async () => {
assert.equal(resp.logs.length, 1)
const log = resp.logs[0]
@@ -546,7 +595,6 @@ contract('Validators', (accounts: string[]) => {
args: {
validator,
name,
- url,
publicKeysData,
},
})
@@ -558,7 +606,6 @@ contract('Validators', (accounts: string[]) => {
await mockLockedGold.setAccountTotalLockedGold(validator, balanceRequirements.validator)
await validators.registerValidator(
name,
- url,
// @ts-ignore bytes type
publicKeysData
)
@@ -569,14 +616,13 @@ contract('Validators', (accounts: string[]) => {
describe('when the account is already a registered validator', () => {
beforeEach(async () => {
await mockLockedGold.setAccountTotalLockedGold(validator, balanceRequirements.group)
- await validators.registerValidatorGroup(name, url, commission)
+ await validators.registerValidatorGroup(name, commission)
})
it('should revert', async () => {
await assertRevert(
validators.registerValidator(
name,
- url,
// @ts-ignore bytes type
publicKeysData
)
@@ -596,7 +642,6 @@ contract('Validators', (accounts: string[]) => {
await assertRevert(
validators.registerValidator(
name,
- url,
// @ts-ignore bytes type
publicKeysData
)
@@ -908,7 +953,7 @@ contract('Validators', (accounts: string[]) => {
describe('when the account is not a registered validator group', () => {
beforeEach(async () => {
await mockLockedGold.setAccountTotalLockedGold(group, balanceRequirements.group)
- resp = await validators.registerValidatorGroup(name, url, commission)
+ resp = await validators.registerValidatorGroup(name, commission)
})
it('should mark the account as a validator group', async () => {
@@ -919,10 +964,10 @@ contract('Validators', (accounts: string[]) => {
assert.deepEqual(await validators.getRegisteredValidatorGroups(), [group])
})
- it('should set the validator group name and url', async () => {
+ it('should set the validator group name and commission', async () => {
const parsedGroup = parseValidatorGroupParams(await validators.getValidatorGroup(group))
assert.equal(parsedGroup.name, name)
- assert.equal(parsedGroup.url, url)
+ assertEqualBN(parsedGroup.commission, commission)
})
it('should set account balance requirements', async () => {
@@ -938,7 +983,6 @@ contract('Validators', (accounts: string[]) => {
args: {
group,
name,
- url,
},
})
})
@@ -950,18 +994,18 @@ contract('Validators', (accounts: string[]) => {
})
it('should revert', async () => {
- await assertRevert(validators.registerValidatorGroup(name, url, balanceRequirements.group))
+ await assertRevert(validators.registerValidatorGroup(name, commission))
})
})
describe('when the account is already a registered validator group', () => {
beforeEach(async () => {
await mockLockedGold.setAccountTotalLockedGold(group, balanceRequirements.group)
- await validators.registerValidatorGroup(name, url, commission)
+ await validators.registerValidatorGroup(name, commission)
})
it('should revert', async () => {
- await assertRevert(validators.registerValidatorGroup(name, url, commission))
+ await assertRevert(validators.registerValidatorGroup(name, commission))
})
})
@@ -971,7 +1015,7 @@ contract('Validators', (accounts: string[]) => {
})
it('should revert', async () => {
- await assertRevert(validators.registerValidatorGroup(name, url, commission))
+ await assertRevert(validators.registerValidatorGroup(name, commission))
})
})
})
@@ -1284,42 +1328,80 @@ contract('Validators', (accounts: string[]) => {
describe('#updateMembershipHistory', () => {
const validator = accounts[0]
const groups = accounts.slice(1)
+ let validatorRegistrationEpochNumber: number
beforeEach(async () => {
await registerValidator(validator)
+ const blockNumber = (await web3.eth.getBlock('latest')).number
+ validatorRegistrationEpochNumber = Math.floor(blockNumber / EPOCH)
for (const group of groups) {
await registerValidatorGroup(group)
}
})
+ describe('when changing groups in the same epoch', () => {
+ it('should overwrite the previous entry', async () => {
+ const numTests = 10
+ // We store an entry upon registering the validator.
+ const expectedMembershipHistoryGroups = [NULL_ADDRESS]
+ const expectedMembershipHistoryEpochs = [new BigNumber(validatorRegistrationEpochNumber)]
+ for (let i = 0; i < numTests; i++) {
+ const blockNumber = (await web3.eth.getBlock('latest')).number
+ const epochNumber = Math.floor(blockNumber / EPOCH)
+ const blocksUntilNextEpoch = (epochNumber + 1) * EPOCH - blockNumber
+ await mineBlocks(blocksUntilNextEpoch, web3)
+
+ let group = groups[0]
+ await validators.affiliate(group)
+ await validators.addFirstMember(validator, NULL_ADDRESS, NULL_ADDRESS, {
+ from: group,
+ })
+ let membershipHistory = await validators.getMembershipHistory(validator)
+ expectedMembershipHistoryGroups.push(group)
+ expectedMembershipHistoryEpochs.push(new BigNumber(epochNumber + 1))
+ if (expectedMembershipHistoryGroups.length > membershipHistoryLength.toNumber()) {
+ expectedMembershipHistoryGroups.shift()
+ expectedMembershipHistoryEpochs.shift()
+ }
+ assertEqualBNArray(membershipHistory[0], expectedMembershipHistoryEpochs)
+ assert.deepEqual(membershipHistory[1], expectedMembershipHistoryGroups)
+
+ group = groups[1]
+ await validators.affiliate(group)
+ await validators.addFirstMember(validator, NULL_ADDRESS, NULL_ADDRESS, {
+ from: group,
+ })
+ membershipHistory = await validators.getMembershipHistory(validator)
+ expectedMembershipHistoryGroups[expectedMembershipHistoryGroups.length - 1] = group
+ assertEqualBNArray(membershipHistory[0], expectedMembershipHistoryEpochs)
+ assert.deepEqual(membershipHistory[1], expectedMembershipHistoryGroups)
+ }
+ })
+ })
+
describe('when changing groups more times than membership history length', () => {
it('should always store the most recent memberships', async () => {
+ // We store an entry upon registering the validator.
+ const expectedMembershipHistoryGroups = [NULL_ADDRESS]
+ const expectedMembershipHistoryEpochs = [new BigNumber(validatorRegistrationEpochNumber)]
for (let i = 0; i < membershipHistoryLength.plus(1).toNumber(); i++) {
+ const blockNumber = (await web3.eth.getBlock('latest')).number
+ const epochNumber = Math.floor(blockNumber / EPOCH)
+ const blocksUntilNextEpoch = (epochNumber + 1) * EPOCH - blockNumber
+ await mineBlocks(blocksUntilNextEpoch, web3)
+
await validators.affiliate(groups[i])
- const currentEpoch = new BigNumber(
- Math.floor((await web3.eth.getBlock('latest')).number / EPOCH)
- )
await validators.addFirstMember(validator, NULL_ADDRESS, NULL_ADDRESS, {
from: groups[i],
})
- await mineBlocks(EPOCH, web3)
-
- const membershipHistory = await validators.getMembershipHistory(validator)
- const expectedMembershipHistoryLength = Math.min(
- i + 1,
- membershipHistoryLength.toNumber()
- )
- assert.equal(membershipHistory[0].length, expectedMembershipHistoryLength)
- assert.equal(membershipHistory[1].length, expectedMembershipHistoryLength)
- for (let j = 0; j < expectedMembershipHistoryLength; j++) {
- assert.include(
- membershipHistory[0].map((x) => x.toNumber()),
- currentEpoch.minus(j).toNumber()
- )
- assert.include(
- membershipHistory[1].map((x) => x.toLowerCase()),
- groups[i - j].toLowerCase()
- )
+ expectedMembershipHistoryGroups.push(groups[i])
+ expectedMembershipHistoryEpochs.push(new BigNumber(epochNumber + 1))
+ if (expectedMembershipHistoryGroups.length > membershipHistoryLength.toNumber()) {
+ expectedMembershipHistoryGroups.shift()
+ expectedMembershipHistoryEpochs.shift()
}
+ const membershipHistory = await validators.getMembershipHistory(validator)
+ assertEqualBNArray(membershipHistory[0], expectedMembershipHistoryEpochs)
+ assert.deepEqual(membershipHistory[1], expectedMembershipHistoryGroups)
}
})
})
@@ -1339,7 +1421,8 @@ contract('Validators', (accounts: string[]) => {
it('should always return the correct membership for the last epoch', async () => {
for (let i = 0; i < membershipHistoryLength.plus(1).toNumber(); i++) {
const blockNumber = (await web3.eth.getBlock('latest')).number
- const blocksUntilNextEpoch = blockNumber % EPOCH
+ const epochNumber = Math.floor(blockNumber / EPOCH)
+ const blocksUntilNextEpoch = (epochNumber + 1) * EPOCH - blockNumber
await mineBlocks(blocksUntilNextEpoch, web3)
await validators.affiliate(groups[i])
@@ -1363,7 +1446,7 @@ contract('Validators', (accounts: string[]) => {
})
})
- describe.only('#distributeEpochPayment', () => {
+ describe('#distributeEpochPayment', () => {
const validator = accounts[0]
const group = accounts[1]
let mockStableToken: MockStableTokenInstance
@@ -1419,7 +1502,7 @@ contract('Validators', (accounts: string[]) => {
})
})
- describe('when the validator does not meet the balance requirements', () => {
+ describe('when the group does not meet the balance requirements', () => {
beforeEach(async () => {
await mockLockedGold.setAccountTotalLockedGold(group, balanceRequirements.group.minus(1))
await validators.distributeEpochPayment(validator)
From 92e254e99dff660769d9ec50a3320af582407209 Mon Sep 17 00:00:00 2001
From: Asa Oines
Date: Thu, 17 Oct 2019 17:52:53 -0700
Subject: [PATCH 070/149] Fix build
---
packages/contractkit/src/wrappers/Validators.test.ts | 8 ++++----
packages/contractkit/src/wrappers/Validators.ts | 7 +++++--
2 files changed, 9 insertions(+), 6 deletions(-)
diff --git a/packages/contractkit/src/wrappers/Validators.test.ts b/packages/contractkit/src/wrappers/Validators.test.ts
index 1d54ed80bf8..25e91c4ce79 100644
--- a/packages/contractkit/src/wrappers/Validators.test.ts
+++ b/packages/contractkit/src/wrappers/Validators.test.ts
@@ -45,7 +45,6 @@ testWithGanache('Validators Wrapper', (web3) => {
await registerAccountWithLockedGold(groupAccount)
await (await validators.registerValidatorGroup(
'The Group',
- 'thegroup.com',
new BigNumber(0.1)
)).sendAndWaitForReceipt({ from: groupAccount })
}
@@ -56,7 +55,6 @@ testWithGanache('Validators Wrapper', (web3) => {
await validators
.registerValidator(
'Good old validator',
- 'goodold.com',
// @ts-ignore
publicKeysData
)
@@ -81,7 +79,9 @@ testWithGanache('Validators Wrapper', (web3) => {
await setupGroup(groupAccount)
await setupValidator(validatorAccount)
await validators.affiliate(groupAccount).sendAndWaitForReceipt({ from: validatorAccount })
- await validators.addMember(validatorAccount).sendAndWaitForReceipt({ from: groupAccount })
+ await (await validators.addMember(validatorAccount)).sendAndWaitForReceipt({
+ from: groupAccount,
+ })
const members = await validators.getValidatorGroup(groupAccount).then((group) => group.members)
expect(members).toContain(validatorAccount)
@@ -100,7 +100,7 @@ testWithGanache('Validators Wrapper', (web3) => {
for (const validator of [validator1, validator2]) {
await setupValidator(validator)
await validators.affiliate(groupAccount).sendAndWaitForReceipt({ from: validator })
- await validators.addMember(validator).sendAndWaitForReceipt({ from: groupAccount })
+ await (await validators.addMember(validator)).sendAndWaitForReceipt({ from: groupAccount })
}
const members = await validators
diff --git a/packages/contractkit/src/wrappers/Validators.ts b/packages/contractkit/src/wrappers/Validators.ts
index 8fefd681ac1..0ed9973112f 100644
--- a/packages/contractkit/src/wrappers/Validators.ts
+++ b/packages/contractkit/src/wrappers/Validators.ts
@@ -70,9 +70,12 @@ export class ValidatorsWrapper extends BaseWrapper {
const voteWeight = await election.getTotalVotesForGroup(group)
const { lesser, greater } = await election.findLesserAndGreaterAfterVote(group, voteWeight)
- return wrapSend(this.kit, this.contract.methods.addFirstMember(member, lesser, greater))
+ return toTransactionObject(
+ this.kit,
+ this.contract.methods.addFirstMember(member, lesser, greater)
+ )
} else {
- return wrapSend(this.kit, this.contract.methods.addMember(member))
+ return toTransactionObject(this.kit, this.contract.methods.addMember(member))
}
}
/**
From d9c2cc031a0246bada371d4c05e736c5cbfa84f0 Mon Sep 17 00:00:00 2001
From: Asa Oines
Date: Thu, 17 Oct 2019 18:11:59 -0700
Subject: [PATCH 071/149] Fix migrations, end-to-end tests
---
packages/celotool/src/e2e-tests/governance_tests.ts | 5 +++--
packages/protocol/migrations/17_elect_validators.ts | 7 +------
packages/protocol/migrationsConfig.js | 1 -
3 files changed, 4 insertions(+), 9 deletions(-)
diff --git a/packages/celotool/src/e2e-tests/governance_tests.ts b/packages/celotool/src/e2e-tests/governance_tests.ts
index da77abe07f8..39424a5a973 100644
--- a/packages/celotool/src/e2e-tests/governance_tests.ts
+++ b/packages/celotool/src/e2e-tests/governance_tests.ts
@@ -55,11 +55,11 @@ describe('governance tests', () => {
const groupInfo = await validators.methods
.getValidatorGroup(groupAddress)
.call({}, blockNumber)
- return groupInfo[2]
+ return groupInfo[1]
} else {
const [groupAddress] = await validators.methods.getRegisteredValidatorGroups().call()
const groupInfo = await validators.methods.getValidatorGroup(groupAddress).call()
- return groupInfo[2]
+ return groupInfo[1]
}
}
@@ -136,6 +136,7 @@ describe('governance tests', () => {
}
await initAndStartGeth(context.hooks.gethBinaryPath, groupInstance)
allValidators = await getValidatorGroupMembers()
+ console.log(allValidators)
assert.equal(allValidators.length, 5)
epoch = new BigNumber(await validators.methods.getEpochSize().call()).toNumber()
assert.equal(epoch, 10)
diff --git a/packages/protocol/migrations/17_elect_validators.ts b/packages/protocol/migrations/17_elect_validators.ts
index 61fa8fc3d02..f1a4a128812 100644
--- a/packages/protocol/migrations/17_elect_validators.ts
+++ b/packages/protocol/migrations/17_elect_validators.ts
@@ -64,7 +64,6 @@ async function registerValidatorGroup(
// @ts-ignore
const tx = validators.contract.methods.registerValidatorGroup(
`${config.validators.groupName} ${encodedKey}`,
- config.validators.groupUrl,
toFixed(config.validators.commission).toString()
)
@@ -100,11 +99,7 @@ async function registerValidator(
)
// @ts-ignore
- const registerTx = validators.contract.methods.registerValidator(
- address,
- config.validators.groupUrl,
- add0x(publicKeysData)
- )
+ const registerTx = validators.contract.methods.registerValidator(address, add0x(publicKeysData))
await sendTransactionWithPrivateKey(web3, registerTx, validatorPrivateKey, {
to: validators.address,
diff --git a/packages/protocol/migrationsConfig.js b/packages/protocol/migrationsConfig.js
index 0b77083e2f6..f5c8b7c70d2 100644
--- a/packages/protocol/migrationsConfig.js
+++ b/packages/protocol/migrationsConfig.js
@@ -88,7 +88,6 @@ const DefaultConfig = {
validatorKeys: [],
// We register a single validator group during the migration.
groupName: 'C-Labs',
- groupUrl: 'celo.org',
commission: 0.1,
},
blockchainParameters: {
From 0b894ac7bebb2b27f2dd79a7b83ac0dfe64d4105 Mon Sep 17 00:00:00 2001
From: Asa Oines
Date: Thu, 17 Oct 2019 18:46:04 -0700
Subject: [PATCH 072/149] Fix tests
---
.../src/e2e-tests/governance_tests.ts | 22 ++++++++++++-------
1 file changed, 14 insertions(+), 8 deletions(-)
diff --git a/packages/celotool/src/e2e-tests/governance_tests.ts b/packages/celotool/src/e2e-tests/governance_tests.ts
index 39424a5a973..a49df9a9615 100644
--- a/packages/celotool/src/e2e-tests/governance_tests.ts
+++ b/packages/celotool/src/e2e-tests/governance_tests.ts
@@ -136,13 +136,12 @@ describe('governance tests', () => {
}
await initAndStartGeth(context.hooks.gethBinaryPath, groupInstance)
allValidators = await getValidatorGroupMembers()
- console.log(allValidators)
assert.equal(allValidators.length, 5)
epoch = new BigNumber(await validators.methods.getEpochSize().call()).toNumber()
assert.equal(epoch, 10)
- // Give the node time to sync.
- await sleep(15)
+ // Give the node time to sync, and time for an epoch transition so we can activate our vote.
+ await sleep(20)
await activate(allValidators[0])
const groupWeb3 = new Web3('ws://localhost:8567')
const groupKit = newKitFromWeb3(groupWeb3)
@@ -229,24 +228,28 @@ describe('governance tests', () => {
const assertScoreUnchanged = async (validator: string, blockNumber: number) => {
const score = new BigNumber(
- (await validators.methods.getValidator(validator).call({}, blockNumber))[4]
+ (await validators.methods.getValidator(validator).call({}, blockNumber))[3]
)
const previousScore = new BigNumber(
- (await validators.methods.getValidator(validator).call({}, blockNumber - 1))[4]
+ (await validators.methods.getValidator(validator).call({}, blockNumber - 1))[3]
)
+ assert.isNotNaN(score)
+ assert.isNotNaN(previousScore)
assert.equal(score.toFixed(), previousScore.toFixed())
}
const assertScoreChanged = async (validator: string, blockNumber: number) => {
const score = new BigNumber(
- (await validators.methods.getValidator(validator).call({}, blockNumber))[4]
+ (await validators.methods.getValidator(validator).call({}, blockNumber))[3]
)
const previousScore = new BigNumber(
- (await validators.methods.getValidator(validator).call({}, blockNumber - 1))[4]
+ (await validators.methods.getValidator(validator).call({}, blockNumber - 1))[3]
)
const expectedScore = adjustmentSpeed
.times(uptime)
.plus(new BigNumber(1).minus(adjustmentSpeed).times(fromFixed(previousScore)))
+ assert.isNotNaN(score)
+ assert.isNotNaN(previousScore)
assert.equal(score.toFixed(), toFixed(expectedScore).toFixed())
}
@@ -290,6 +293,8 @@ describe('governance tests', () => {
const previousBalance = new BigNumber(
await stableToken.methods.balanceOf(validator).call({}, blockNumber - 1)
)
+ assert.isNotNaN(currentBalance)
+ assert.isNotNaN(previousBalance)
assert.equal(expected.toFixed(), currentBalance.minus(previousBalance).toFixed())
}
@@ -299,8 +304,9 @@ describe('governance tests', () => {
const getExpectedTotalPayment = async (validator: string, blockNumber: number) => {
const score = new BigNumber(
- (await validators.methods.getValidator(validator).call({}, blockNumber))[4]
+ (await validators.methods.getValidator(validator).call({}, blockNumber))[3]
)
+ assert.isNotNaN(score)
return validatorEpochPayment.times(fromFixed(score))
}
From 44aa9f9ccadca3a5782367987768dfe2492ad421 Mon Sep 17 00:00:00 2001
From: Asa Oines
Date: Thu, 17 Oct 2019 19:51:30 -0700
Subject: [PATCH 073/149] Update CLI docs
---
packages/docs/command-line-interface/validator.md | 3 +--
packages/docs/command-line-interface/validatorgroup.md | 3 +--
2 files changed, 2 insertions(+), 4 deletions(-)
diff --git a/packages/docs/command-line-interface/validator.md b/packages/docs/command-line-interface/validator.md
index 4c72974b8fd..0d4d8cfd1f1 100644
--- a/packages/docs/command-line-interface/validator.md
+++ b/packages/docs/command-line-interface/validator.md
@@ -50,10 +50,9 @@ OPTIONS
--from=0xc1912fEE45d61C87Cc5EA59DaE31190FFFFf232d (required) Address for the Validator
--name=name (required)
--publicKey=0x (required) Public Key
- --url=url (required)
EXAMPLE
- register --from 0x47e172F6CfB6c7D01C1574fa3E2Be7CC73269D95 --name myName --url "http://validator.com" --publicKey
+ register --from 0x47e172F6CfB6c7D01C1574fa3E2Be7CC73269D95 --name myName --publicKey
0xc52f3fab06e22a54915a8765c4f6826090cfac5e40282b43844bf1c0df83aaa632e55b67869758f2291d1aabe0ebecc7cbf4236aaa45e3e0cfbf
997eda082ae19d3e1d8f49f6b0d8e9a03d80ca07b1d24cf1cc0557bdcc04f5e17a46e35d02d0d411d956dbd5d2d2464eebd7b74ae30005d223780d
785d2abc5644fac7ac29fb0e302bdc80c81a5d45018b68b1045068a4b3a4861c93037685fd0d252d7405011220a66a6257562d0c26dabf64485a1d
diff --git a/packages/docs/command-line-interface/validatorgroup.md b/packages/docs/command-line-interface/validatorgroup.md
index 291c735fc92..8883c2f772e 100644
--- a/packages/docs/command-line-interface/validatorgroup.md
+++ b/packages/docs/command-line-interface/validatorgroup.md
@@ -55,10 +55,9 @@ OPTIONS
--commission=commission (required)
--from=0xc1912fEE45d61C87Cc5EA59DaE31190FFFFf232d (required) Address for the Validator Group
--name=name (required)
- --url=url (required)
EXAMPLE
- register --from 0x47e172F6CfB6c7D01C1574fa3E2Be7CC73269D95 --name myName --url "http://vgroup.com" --commission 0.1
+ register --from 0x47e172F6CfB6c7D01C1574fa3E2Be7CC73269D95 --name myName --commission 0.1
```
_See code: [packages/cli/src/commands/validatorgroup/register.ts](https://github.com/celo-org/celo-monorepo/tree/master/packages/cli/src/commands/validatorgroup/register.ts)_
From 262c002aac1111d19e7879dc3121902578c293bc Mon Sep 17 00:00:00 2001
From: Asa Oines
Date: Fri, 18 Oct 2019 10:04:45 -0700
Subject: [PATCH 074/149] Fix end-to-end transfer tests
---
packages/celotool/src/e2e-tests/utils.ts | 18 +++++++++++--
.../contracts/stability/StableToken.sol | 26 ++++++++++++++-----
2 files changed, 35 insertions(+), 9 deletions(-)
diff --git a/packages/celotool/src/e2e-tests/utils.ts b/packages/celotool/src/e2e-tests/utils.ts
index aae32ca88ac..cbdd3af3809 100644
--- a/packages/celotool/src/e2e-tests/utils.ts
+++ b/packages/celotool/src/e2e-tests/utils.ts
@@ -318,7 +318,11 @@ export async function startGeth(gethBinaryPath: string, instance: GethInstanceCo
return instance
}
-export async function migrateContracts(validatorPrivateKeys: string[], to: number = 1000) {
+export async function migrateContracts(
+ validatorPrivateKeys: string[],
+ validators: string[],
+ to: number = 1000
+) {
const migrationOverrides = {
validators: {
validatorKeys: validatorPrivateKeys.map(ensure0x),
@@ -326,6 +330,12 @@ export async function migrateContracts(validatorPrivateKeys: string[], to: numbe
election: {
minElectableValidators: '1',
},
+ stableToken: {
+ initialBalances: {
+ addresses: validators.map(ensure0x),
+ values: validators.map(() => '10000000000000000000000'),
+ },
+ },
}
const args = [
'--cwd',
@@ -425,7 +435,11 @@ export function getContext(gethConfig: GethTestConfig) {
await initAndStartGeth(gethBinaryPath, instance)
}
if (gethConfig.migrate || gethConfig.migrateTo) {
- await migrateContracts(validatorPrivateKeys, gethConfig.migrateTo)
+ await migrateContracts(
+ validatorPrivateKeys,
+ validators.map((x) => x.address),
+ gethConfig.migrateTo
+ )
}
await killGeth()
await sleep(2)
diff --git a/packages/protocol/contracts/stability/StableToken.sol b/packages/protocol/contracts/stability/StableToken.sol
index 386a9ec978a..1146d534d5a 100644
--- a/packages/protocol/contracts/stability/StableToken.sol
+++ b/packages/protocol/contracts/stability/StableToken.sol
@@ -121,13 +121,6 @@ contract StableToken is IStableToken, IERC20Token, ICeloToken, Ownable,
initializer
{
require(inflationRate != 0, "Must provide a non-zero inflation rate.");
- require(initialBalanceAddresses.length == initialBalanceValues.length);
- for (uint256 i = 0; i < initialBalanceAddresses.length; i = i.add(1)) {
- totalSupply_ = totalSupply_.add(initialBalanceValues[i]);
- balances[initialBalanceAddresses[i]] = balances[initialBalanceAddresses[i]].add(
- initialBalanceValues[i]
- );
- }
_transferOwnership(msg.sender);
totalSupply_ = 0;
@@ -141,6 +134,10 @@ contract StableToken is IStableToken, IERC20Token, ICeloToken, Ownable,
// solhint-disable-next-line not-rely-on-time
inflationState.factorLastUpdated = now;
+ require(initialBalanceAddresses.length == initialBalanceValues.length);
+ for (uint256 i = 0; i < initialBalanceAddresses.length; i = i.add(1)) {
+ require(_mint(initialBalanceAddresses[i], initialBalanceValues[i]));
+ }
setRegistry(registryAddress);
}
@@ -248,7 +245,22 @@ contract StableToken is IStableToken, IERC20Token, ICeloToken, Ownable,
msg.sender == registry.getAddressFor(EXCHANGE_REGISTRY_ID) ||
msg.sender == registry.getAddressFor(VALIDATORS_REGISTRY_ID)
);
+ return _mint(to, value);
+ }
+ /**
+ * @notice Mints new StableToken and gives it to 'to'.
+ * @param to The account for which to mint tokens.
+ * @param value The amount of StableToken to mint.
+ */
+ function _mint(
+ address to,
+ uint256 value
+ )
+ private
+ updateInflationFactor
+ returns (bool)
+ {
uint256 units = _valueToUnits(inflationState.factor, value);
totalSupply_ = totalSupply_.add(units);
balances[to] = balances[to].add(units);
From 97372e7cbecff0722ce9d6b0daebd988f3a55b8a Mon Sep 17 00:00:00 2001
From: Asa Oines
Date: Fri, 18 Oct 2019 15:21:06 -0700
Subject: [PATCH 075/149] Fix contractkit tests
---
packages/cli/src/commands/validatorgroup/member.ts | 2 +-
packages/contractkit/package.json | 2 +-
packages/contractkit/src/wrappers/Validators.test.ts | 6 ++----
packages/contractkit/src/wrappers/Validators.ts | 12 +++++-------
packages/protocol/scripts/devchain.ts | 10 +++++++++-
5 files changed, 18 insertions(+), 14 deletions(-)
diff --git a/packages/cli/src/commands/validatorgroup/member.ts b/packages/cli/src/commands/validatorgroup/member.ts
index 000462e81b7..5470ece4b67 100644
--- a/packages/cli/src/commands/validatorgroup/member.ts
+++ b/packages/cli/src/commands/validatorgroup/member.ts
@@ -43,7 +43,7 @@ export default class ValidatorGroupMembers extends BaseCommand {
this.kit.defaultAccount = res.flags.from
const validators = await this.kit.contracts.getValidators()
if (res.flags.accept) {
- const tx = await validators.addMember((res.args as any).validatorAddress)
+ const tx = await validators.addMember(res.flags.from, (res.args as any).validatorAddress)
await displaySendTx('addMember', tx)
} else if (res.flags.remove) {
await displaySendTx(
diff --git a/packages/contractkit/package.json b/packages/contractkit/package.json
index d461819180e..723fbfaaa6a 100644
--- a/packages/contractkit/package.json
+++ b/packages/contractkit/package.json
@@ -40,7 +40,7 @@
"web3-eth-abi": "1.0.0-beta.37"
},
"devDependencies": {
- "@celo/ganache-cli": "git+https://github.com/celo-org/ganache-cli.git#98ad2ba",
+ "@celo/ganache-cli": "git+https://github.com/celo-org/ganache-cli.git#4cf9664",
"@celo/protocol": "1.0.0",
"@types/debug": "^4.1.5",
"@types/web3": "^1.0.18",
diff --git a/packages/contractkit/src/wrappers/Validators.test.ts b/packages/contractkit/src/wrappers/Validators.test.ts
index 25e91c4ce79..2f3844eb9e2 100644
--- a/packages/contractkit/src/wrappers/Validators.test.ts
+++ b/packages/contractkit/src/wrappers/Validators.test.ts
@@ -79,9 +79,7 @@ testWithGanache('Validators Wrapper', (web3) => {
await setupGroup(groupAccount)
await setupValidator(validatorAccount)
await validators.affiliate(groupAccount).sendAndWaitForReceipt({ from: validatorAccount })
- await (await validators.addMember(validatorAccount)).sendAndWaitForReceipt({
- from: groupAccount,
- })
+ await (await validators.addMember(groupAccount, validatorAccount)).sendAndWaitForReceipt()
const members = await validators.getValidatorGroup(groupAccount).then((group) => group.members)
expect(members).toContain(validatorAccount)
@@ -100,7 +98,7 @@ testWithGanache('Validators Wrapper', (web3) => {
for (const validator of [validator1, validator2]) {
await setupValidator(validator)
await validators.affiliate(groupAccount).sendAndWaitForReceipt({ from: validator })
- await (await validators.addMember(validator)).sendAndWaitForReceipt({ from: groupAccount })
+ await (await validators.addMember(groupAccount, validator)).sendAndWaitForReceipt()
}
const members = await validators
diff --git a/packages/contractkit/src/wrappers/Validators.ts b/packages/contractkit/src/wrappers/Validators.ts
index 0ed9973112f..24aca960c7d 100644
--- a/packages/contractkit/src/wrappers/Validators.ts
+++ b/packages/contractkit/src/wrappers/Validators.ts
@@ -16,6 +16,7 @@ export interface Validator {
name: string
publicKey: string
affiliation: string | null
+ score: BigNumber
}
export interface ValidatorGroup {
@@ -44,6 +45,7 @@ export interface ValidatorsConfig {
/**
* Contract for voting for validators and managing validator groups.
*/
+// TODO(asa): Support authorized validators
export class ValidatorsWrapper extends BaseWrapper {
affiliate = proxySend(this.kit, this.contract.methods.affiliate)
deaffiliate = proxySend(this.kit, this.contract.methods.deaffiliate)
@@ -58,12 +60,7 @@ export class ValidatorsWrapper extends BaseWrapper {
this.contract.methods.registerValidatorGroup(name, toFixed(commission).toFixed())
)
}
- async addMember(member: string): Promise> {
- if (this.kit.defaultAccount == null) {
- throw new Error(`missing from at new ValdidatorUtils()`)
- }
- // TODO(asa): Support authorized validators
- const group = this.kit.defaultAccount
+ async addMember(group: string, member: string): Promise> {
const numMembers = await this.getGroupNumMembers(group)
if (numMembers.isZero()) {
const election = await this.kit.contracts.getElection()
@@ -75,7 +72,7 @@ export class ValidatorsWrapper extends BaseWrapper {
this.contract.methods.addFirstMember(member, lesser, greater)
)
} else {
- return toTransactionObject(this.kit, this.contract.methods.addMember(member))
+ return toTransactionObject(this.kit, this.contract.methods.addMember(member), { from: group })
}
}
/**
@@ -137,6 +134,7 @@ export class ValidatorsWrapper extends BaseWrapper {
name: res[0],
publicKey: res[1] as any,
affiliation: res[2],
+ score: fromFixed(new BigNumber(res[3])),
}
}
diff --git a/packages/protocol/scripts/devchain.ts b/packages/protocol/scripts/devchain.ts
index 309dd4ad6ab..543c8f45578 100644
--- a/packages/protocol/scripts/devchain.ts
+++ b/packages/protocol/scripts/devchain.ts
@@ -138,7 +138,15 @@ function createDirIfMissing(dir: string) {
}
function runMigrations(opts: { upto?: number } = {}) {
- const cmdArgs = ['truffle', 'migrate']
+ const migrationOverrides = {
+ stableToken: {
+ initialBalances: {
+ addresses: ['0x5409ed021d9299bf6814279a6a1411a7e866a631'],
+ values: ['10000000000000000000000'],
+ },
+ },
+ }
+ const cmdArgs = ['truffle', 'migrate', '--migration_override', JSON.stringify(migrationOverrides)]
if (opts.upto) {
cmdArgs.push('--to')
From ef71139d6b78a28442e35f5ffe1d678b16f34ca8 Mon Sep 17 00:00:00 2001
From: Asa Oines
Date: Tue, 22 Oct 2019 13:46:59 -0700
Subject: [PATCH 076/149] Fix contractkit tests
---
packages/cli/src/commands/validatorgroup/member.ts | 1 +
packages/contractkit/src/wrappers/Validators.ts | 3 ++-
2 files changed, 3 insertions(+), 1 deletion(-)
diff --git a/packages/cli/src/commands/validatorgroup/member.ts b/packages/cli/src/commands/validatorgroup/member.ts
index 5470ece4b67..f06691f47bf 100644
--- a/packages/cli/src/commands/validatorgroup/member.ts
+++ b/packages/cli/src/commands/validatorgroup/member.ts
@@ -10,6 +10,7 @@ export default class ValidatorGroupMembers extends BaseCommand {
static flags = {
...BaseCommand.flags,
from: Flags.address({ required: true, description: "ValidatorGroup's address" }),
+ group: Flags.address({ required: false, description: "ValidatorGroup's address" }),
accept: flags.boolean({
exclusive: ['remove', 'reorder'],
description: 'Accept a validator whose affiliation is already set to the group',
diff --git a/packages/contractkit/src/wrappers/Validators.ts b/packages/contractkit/src/wrappers/Validators.ts
index 24aca960c7d..74b9ffcccff 100644
--- a/packages/contractkit/src/wrappers/Validators.ts
+++ b/packages/contractkit/src/wrappers/Validators.ts
@@ -69,7 +69,8 @@ export class ValidatorsWrapper extends BaseWrapper {
return toTransactionObject(
this.kit,
- this.contract.methods.addFirstMember(member, lesser, greater)
+ this.contract.methods.addFirstMember(member, lesser, greater),
+ { from: group }
)
} else {
return toTransactionObject(this.kit, this.contract.methods.addMember(member), { from: group })
From b8eafd7a92c6b500c08547141f0997b288852419 Mon Sep 17 00:00:00 2001
From: Asa Oines
Date: Tue, 22 Oct 2019 14:25:39 -0700
Subject: [PATCH 077/149] Fix
---
packages/cli/src/commands/validatorgroup/member.ts | 1 -
1 file changed, 1 deletion(-)
diff --git a/packages/cli/src/commands/validatorgroup/member.ts b/packages/cli/src/commands/validatorgroup/member.ts
index f06691f47bf..5470ece4b67 100644
--- a/packages/cli/src/commands/validatorgroup/member.ts
+++ b/packages/cli/src/commands/validatorgroup/member.ts
@@ -10,7 +10,6 @@ export default class ValidatorGroupMembers extends BaseCommand {
static flags = {
...BaseCommand.flags,
from: Flags.address({ required: true, description: "ValidatorGroup's address" }),
- group: Flags.address({ required: false, description: "ValidatorGroup's address" }),
accept: flags.boolean({
exclusive: ['remove', 'reorder'],
description: 'Accept a validator whose affiliation is already set to the group',
From 3847bef5cea7d831bf6593764ed48b3964c4ec36 Mon Sep 17 00:00:00 2001
From: Asa Oines
Date: Tue, 22 Oct 2019 14:27:43 -0700
Subject: [PATCH 078/149] Cleanup
---
.../contracts/common/UsingPrecompiles.sol | 1 +
.../contracts/governance/Election.sol | 2 +
.../contracts/governance/Validators.sol | 234 +++++++++---------
packages/protocol/test/governance/election.ts | 24 +-
.../protocol/test/governance/validators.ts | 4 +-
5 files changed, 135 insertions(+), 130 deletions(-)
diff --git a/packages/protocol/contracts/common/UsingPrecompiles.sol b/packages/protocol/contracts/common/UsingPrecompiles.sol
index e5a5a1657cd..7fd4e8e8cc8 100644
--- a/packages/protocol/contracts/common/UsingPrecompiles.sol
+++ b/packages/protocol/contracts/common/UsingPrecompiles.sol
@@ -1,5 +1,6 @@
pragma solidity ^0.5.3;
+// TODO(asa): Limit assembly usage by using X.staticcall instead.
contract UsingPrecompiles {
address constant PROOF_OF_POSSESSION = address(0xff - 4);
diff --git a/packages/protocol/contracts/governance/Election.sol b/packages/protocol/contracts/governance/Election.sol
index 63ceed9fad0..69bc33d1fc1 100644
--- a/packages/protocol/contracts/governance/Election.sol
+++ b/packages/protocol/contracts/governance/Election.sol
@@ -484,6 +484,8 @@ contract Election is
view
returns (uint256)
{
+ // The group must meet the balance requirements in order for their voters to receive epoch
+ // rewards.
if (getValidators().meetsAccountLockedGoldRequirements(group) && votes.active.total > 0) {
return totalEpochRewards.mul(votes.active.forGroup[group].total).div(votes.active.total);
} else {
diff --git a/packages/protocol/contracts/governance/Validators.sol b/packages/protocol/contracts/governance/Validators.sol
index ff8d90b29c6..a8277dfe04d 100644
--- a/packages/protocol/contracts/governance/Validators.sol
+++ b/packages/protocol/contracts/governance/Validators.sol
@@ -313,6 +313,118 @@ contract Validators is
return true;
}
+ /**
+ * @notice Returns the parameters that goven how a validator's score is calculated.
+ * @return The parameters that goven how a validator's score is calculated.
+ */
+ function getValidatorScoreParameters() external view returns (uint256, uint256) {
+ return (validatorScoreParameters.exponent, validatorScoreParameters.adjustmentSpeed.unwrap());
+ }
+
+ /**
+ * @notice Returns the group membership history of a validator.
+ * @param account The validator whose membership history to return.
+ * @return The group membership history of a validator.
+ */
+ function getMembershipHistory(
+ address account
+ )
+ external
+ view
+ returns (uint256[] memory, address[] memory, uint256)
+ {
+ MembershipHistory storage history = validators[account].membershipHistory;
+ uint256[] memory epochs = new uint256[](history.numEntries);
+ address[] memory membershipGroups = new address[](history.numEntries);
+ for (uint256 i = 0; i < history.numEntries; i = i.add(1)) {
+ uint256 index = history.tail.add(i);
+ epochs[i] = history.entries[index].epochNumber;
+ membershipGroups[i] = history.entries[index].group;
+ }
+ return (epochs, membershipGroups, history.lastRemovedFromGroupTimestamp);
+ }
+
+ /**
+ * @notice Updates a validator's score based on its uptime for the epoch.
+ * @param validator The address of the validator.
+ * @param uptime The Fixidity representation of the validator's uptime, between 0 and 1.
+ * @return True upon success.
+ */
+ function updateValidatorScore(address validator, uint256 uptime) external onlyVm() {
+ _updateValidatorScore(validator, uptime);
+ }
+
+ /**
+ * @notice Updates a validator's score based on its uptime for the epoch.
+ * @param validator The address of the validator.
+ * @param uptime The Fixidity representation of the validator's uptime, between 0 and 1.
+ * @dev new_score = uptime ** exponent * adjustmentSpeed + old_score * (1 - adjustmentSpeed)
+ * @return True upon success.
+ */
+ function _updateValidatorScore(address validator, uint256 uptime) internal {
+ address account = getLockedGold().getAccountFromValidator(validator);
+ require(isValidator(account));
+ require(uptime <= FixidityLib.fixed1().unwrap());
+
+ uint256 numerator;
+ uint256 denominator;
+ (numerator, denominator) = fractionMulExp(
+ FixidityLib.fixed1().unwrap(),
+ FixidityLib.fixed1().unwrap(),
+ uptime,
+ FixidityLib.fixed1().unwrap(),
+ validatorScoreParameters.exponent,
+ 18
+ );
+
+ FixidityLib.Fraction memory epochScore = FixidityLib.wrap(numerator).divide(
+ FixidityLib.wrap(denominator)
+ );
+ FixidityLib.Fraction memory newComponent = validatorScoreParameters.adjustmentSpeed.multiply(
+ epochScore
+ );
+
+ FixidityLib.Fraction memory currentComponent = FixidityLib.fixed1().subtract(
+ validatorScoreParameters.adjustmentSpeed
+ );
+ currentComponent = currentComponent.multiply(validators[account].score);
+ validators[account].score = FixidityLib.wrap(
+ Math.min(
+ epochScore.unwrap(),
+ newComponent.add(currentComponent).unwrap()
+ )
+ );
+ }
+
+ /**
+ * @notice Distributes epoch payments to `validator` and its group.
+ */
+ function distributeEpochPayment(address validator) external onlyVm() {
+ _distributeEpochPayment(validator);
+ }
+
+ /**
+ * @notice Distributes epoch payments to `validator` and its group.
+ */
+ function _distributeEpochPayment(address validator) internal {
+ address account = getLockedGold().getAccountFromValidator(validator);
+ require(isValidator(account));
+ // The group that should be paid is the group that the validator was a member of at the
+ // time it was elected.
+ address group = getMembershipInLastEpoch(account);
+ // Both the validator and the group must maintain the minimum locked gold balance in order to
+ // receive epoch payments.
+ if (meetsAccountLockedGoldRequirements(account) && meetsAccountLockedGoldRequirements(group)) {
+ FixidityLib.Fraction memory totalPayment = FixidityLib.newFixed(
+ validatorEpochPayment
+ ).multiply(validators[account].score);
+ uint256 groupPayment = totalPayment.multiply(groups[group].commission).fromFixed();
+ uint256 validatorPayment = totalPayment.fromFixed().sub(groupPayment);
+ getStableToken().mint(group, groupPayment);
+ getStableToken().mint(account, validatorPayment);
+ }
+ }
+
/**
* @notice De-registers a validator.
* @param index The index of this validator in the list of all registered validators.
@@ -327,12 +439,12 @@ contract Validators is
// `validatorLockedGoldRequirements.duration` seconds.
Validator storage validator = validators[account];
if (validator.affiliation != address(0)) {
- require(!groups[validator.affiliation].members.contains(account), 'two');
+ require(!groups[validator.affiliation].members.contains(account));
}
uint256 requirementEndTime = validator.membershipHistory.lastRemovedFromGroupTimestamp.add(
validatorLockedGoldRequirements.duration
);
- require(requirementEndTime < now, 'one');
+ require(requirementEndTime < now);
// Remove the validator.
deleteElement(registeredValidators, account, index);
@@ -350,7 +462,8 @@ contract Validators is
function affiliate(address group) external nonReentrant returns (bool) {
address account = getLockedGold().getAccountFromActiveValidator(msg.sender);
require(isValidator(account) && isValidatorGroup(group));
- require(meetsAccountLockedGoldRequirements(account) && meetsAccountLockedGoldRequirements(group), "three");
+ require(meetsAccountLockedGoldRequirements(account));
+ require(meetsAccountLockedGoldRequirements(group));
Validator storage validator = validators[account];
if (validator.affiliation != address(0)) {
_deaffiliate(validator, account);
@@ -485,7 +598,8 @@ contract Validators is
require(_group.members.numElements < maxGroupSize, "group would exceed maximum size");
require(validators[validator].affiliation == group && !_group.members.contains(validator));
uint256 numMembers = _group.members.numElements.add(1);
- require(meetsAccountLockedGoldRequirements(group) && meetsAccountLockedGoldRequirements(validator));
+ require(meetsAccountLockedGoldRequirements(group));
+ require(meetsAccountLockedGoldRequirements(validator));
_group.members.push(validator);
if (numMembers == 1) {
getElection().markGroupEligible(group, lesser, greater);
@@ -536,95 +650,6 @@ contract Validators is
return true;
}
- /**
- * @notice Updates a validator's score based on its uptime for the epoch.
- * @param validator The address of the validator.
- * @param uptime The Fixidity representation of the validator's uptime, between 0 and 1.
- * @return True upon success.
- */
- function updateValidatorScore(address validator, uint256 uptime) external onlyVm() {
- _updateValidatorScore(validator, uptime);
- }
-
- /**
- * @notice Updates a validator's score based on its uptime for the epoch.
- * @param validator The address of the validator.
- * @param uptime The Fixidity representation of the validator's uptime, between 0 and 1.
- * @dev new_score = uptime ** exponent * adjustmentSpeed + old_score * (1 - adjustmentSpeed)
- * @return True upon success.
- */
- function _updateValidatorScore(address validator, uint256 uptime) internal {
- address account = getLockedGold().getAccountFromValidator(validator);
- require(isValidator(account), "isvalidator");
- require(uptime <= FixidityLib.fixed1().unwrap(), "uptime");
-
- uint256 numerator;
- uint256 denominator;
- (numerator, denominator) = fractionMulExp(
- FixidityLib.fixed1().unwrap(),
- FixidityLib.fixed1().unwrap(),
- uptime,
- FixidityLib.fixed1().unwrap(),
- validatorScoreParameters.exponent,
- 18
- );
-
- FixidityLib.Fraction memory epochScore = FixidityLib.wrap(numerator).divide(
- FixidityLib.wrap(denominator)
- );
- FixidityLib.Fraction memory newComponent = validatorScoreParameters.adjustmentSpeed.multiply(
- epochScore
- );
-
- FixidityLib.Fraction memory currentComponent = FixidityLib.fixed1().subtract(
- validatorScoreParameters.adjustmentSpeed
- );
- currentComponent = currentComponent.multiply(validators[account].score);
- validators[account].score = FixidityLib.wrap(
- Math.min(
- epochScore.unwrap(),
- newComponent.add(currentComponent).unwrap()
- )
- );
- }
-
- /**
- * @notice Distributes epoch payments to `validator` and its group.
- */
- function distributeEpochPayment(address validator) external onlyVm() {
- _distributeEpochPayment(validator);
- }
-
- /**
- * @notice Distributes epoch payments to `validator` and its group.
- */
- function _distributeEpochPayment(address validator) internal {
- address account = getLockedGold().getAccountFromValidator(validator);
- require(isValidator(account));
- // The group that should be paid is the group that the validator was a member of at the
- // time it was elected.
- address group = getMembershipInLastEpoch(account);
- // Both the validator and the group must maintain the minimum locked gold balance in order to
- // receive epoch payments.
- if (meetsAccountLockedGoldRequirements(account) && meetsAccountLockedGoldRequirements(group)) {
- FixidityLib.Fraction memory totalPayment = FixidityLib.newFixed(
- validatorEpochPayment
- ).multiply(validators[account].score);
- uint256 groupPayment = totalPayment.multiply(groups[group].commission).fromFixed();
- uint256 validatorPayment = totalPayment.fromFixed().sub(groupPayment);
- getStableToken().mint(group, groupPayment);
- getStableToken().mint(account, validatorPayment);
- }
- }
-
- /**
- * @notice Returns the parameters that goven how a validator's score is calculated.
- * @return The parameters that goven how a validator's score is calculated.
- */
- function getValidatorScoreParameters() external view returns (uint256, uint256) {
- return (validatorScoreParameters.exponent, validatorScoreParameters.adjustmentSpeed.unwrap());
- }
-
/**
* @notice Returns the Locked Gold requirements for validators.
* @return The Locked Gold requirements for validators.
@@ -649,29 +674,6 @@ contract Validators is
return maxGroupSize;
}
- /**
- * @notice Returns the group membership history of a validator.
- * @param account The validator whose membership history to return.
- * @return The group membership history of a validator.
- */
- function getMembershipHistory(
- address account
- )
- external
- view
- returns (uint256[] memory, address[] memory, uint256)
- {
- MembershipHistory storage history = validators[account].membershipHistory;
- uint256[] memory epochs = new uint256[](history.numEntries);
- address[] memory membershipGroups = new address[](history.numEntries);
- for (uint256 i = 0; i < history.numEntries; i = i.add(1)) {
- uint256 index = history.tail.add(i);
- epochs[i] = history.entries[index].epochNumber;
- membershipGroups[i] = history.entries[index].group;
- }
- return (epochs, membershipGroups, history.lastRemovedFromGroupTimestamp);
- }
-
/**
* @notice Returns the locked gold balance requirement for the supplied account.
* @param account The account that may have to meet locked gold balance requirements.
diff --git a/packages/protocol/test/governance/election.ts b/packages/protocol/test/governance/election.ts
index 4fc979117aa..815c1131dca 100644
--- a/packages/protocol/test/governance/election.ts
+++ b/packages/protocol/test/governance/election.ts
@@ -906,7 +906,7 @@ contract('Election', (accounts: string[]) => {
const voteValue1 = new BigNumber(2000000)
const voteValue2 = new BigNumber(1000000)
const totalRewardValue = new BigNumber(3000000)
- const balanceRequirement = new BigNumber(1000000)
+ const lockedGoldRequirement = new BigNumber(1000000)
beforeEach(async () => {
await registry.setAddressFor(CeloContractName.Validators, accounts[0])
await election.markGroupEligible(group1, NULL_ADDRESS, NULL_ADDRESS)
@@ -919,8 +919,8 @@ contract('Election', (accounts: string[]) => {
await mockLockedGold.incrementNonvotingAccountBalance(voter, voteValue1.plus(voteValue2))
await election.vote(group1, voteValue1, group2, NULL_ADDRESS)
await election.vote(group2, voteValue2, NULL_ADDRESS, group1)
- await mockValidators.setAccountBalanceRequirement(group1, balanceRequirement)
- await mockValidators.setAccountBalanceRequirement(group2, balanceRequirement)
+ await mockValidators.setAccountLockedGoldRequirement(group1, lockedGoldRequirement)
+ await mockValidators.setAccountLockedGoldRequirement(group2, lockedGoldRequirement)
})
describe('when one group has active votes', () => {
@@ -929,9 +929,9 @@ contract('Election', (accounts: string[]) => {
await election.activate(group1)
})
- describe('when the group meets the balance requirements ', () => {
+ describe('when the group meets the locked gold requirements ', () => {
beforeEach(async () => {
- await mockLockedGold.setAccountTotalLockedGold(group1, balanceRequirement)
+ await mockLockedGold.setAccountTotalLockedGold(group1, lockedGoldRequirement)
})
it('should return the total reward value', async () => {
@@ -942,9 +942,9 @@ contract('Election', (accounts: string[]) => {
})
})
- describe('when the group does not meet the balance requirements ', () => {
+ describe('when the group does not meet the locked gold requirements ', () => {
beforeEach(async () => {
- await mockLockedGold.setAccountTotalLockedGold(group1, balanceRequirement.minus(1))
+ await mockLockedGold.setAccountTotalLockedGold(group1, lockedGoldRequirement.minus(1))
})
it('should return zero', async () => {
@@ -954,7 +954,7 @@ contract('Election', (accounts: string[]) => {
})
describe('when two groups have active votes', () => {
- const balanceRequirement = new BigNumber(1000000)
+ const lockedGoldRequirement = new BigNumber(1000000)
const expectedGroup1EpochRewards = voteValue1
.div(voteValue1.plus(voteValue2))
.times(totalRewardValue)
@@ -965,9 +965,9 @@ contract('Election', (accounts: string[]) => {
await election.activate(group2)
})
- describe('when one group meets the balance requirements ', () => {
+ describe('when one group meets the locked gold requirements ', () => {
beforeEach(async () => {
- await mockLockedGold.setAccountTotalLockedGold(group1, balanceRequirement)
+ await mockLockedGold.setAccountTotalLockedGold(group1, lockedGoldRequirement)
})
it('should return the proportional reward value for that group', async () => {
@@ -984,9 +984,9 @@ contract('Election', (accounts: string[]) => {
})
describe('when the group does not have active votes', () => {
- describe('when the group meets the balance requirements ', () => {
+ describe('when the group meets the locked gold requirements ', () => {
beforeEach(async () => {
- await mockLockedGold.setAccountTotalLockedGold(group1, balanceRequirement)
+ await mockLockedGold.setAccountTotalLockedGold(group1, lockedGoldRequirement)
})
it('should return zero', async () => {
diff --git a/packages/protocol/test/governance/validators.ts b/packages/protocol/test/governance/validators.ts
index 824d209c35d..fc1c1e25dc3 100644
--- a/packages/protocol/test/governance/validators.ts
+++ b/packages/protocol/test/governance/validators.ts
@@ -1201,9 +1201,9 @@ contract('Validators', (accounts: string[]) => {
})
})
- describe('when it has been `groupLockedGoldRequirements.duration` since the validator was removed from the group', () => {
+ describe('when it has been less than `groupLockedGoldRequirements.duration` since the validator was removed from the group', () => {
beforeEach(async () => {
- await timeTravel(groupLockedGoldRequirements.duration.toNumber(), web3)
+ await timeTravel(groupLockedGoldRequirements.duration.toNumber().minus(1), web3)
})
it('should revert', async () => {
From ae1be930c4ab5ada3c7e62a989348891db9147e4 Mon Sep 17 00:00:00 2001
From: Asa Oines
Date: Tue, 22 Oct 2019 15:27:39 -0700
Subject: [PATCH 079/149] Fix circle config
---
.circleci/config.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.circleci/config.yml b/.circleci/config.yml
index 31bafc17054..181ec0326c0 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -596,7 +596,7 @@ jobs:
command: |
set -e
cd packages/celotool
- ./ci_test_sync_with_network.sh checkout asaj/pos-2
+ ./ci_test_attestations.sh checkout asaj/pos-2
web:
working_directory: ~/app
From 2b1da945e95e37901ca2927d613344dc1ff69749 Mon Sep 17 00:00:00 2001
From: Asa Oines
Date: Tue, 22 Oct 2019 16:23:04 -0700
Subject: [PATCH 080/149] Contract unit tests passing
---
.circleci/config.yml | 2 +-
.../contracts/common/UsingRegistry.sol | 5 ++
.../contracts/governance/Governance.sol | 16 ++++
.../contracts/governance/LockedGold.sol | 8 +-
.../contracts/governance/Validators.sol | 56 ++++++-------
.../governance/interfaces/IGovernance.sol | 52 +-----------
.../governance/test/MockGovernance.sol | 3 +-
.../governance/test/MockValidators.sol | 9 ++-
packages/protocol/test/governance/election.ts | 28 ++-----
.../protocol/test/governance/lockedgold.ts | 80 ++++++++++++-------
.../protocol/test/governance/validators.ts | 16 ++--
11 files changed, 133 insertions(+), 142 deletions(-)
diff --git a/.circleci/config.yml b/.circleci/config.yml
index 31bafc17054..181ec0326c0 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -596,7 +596,7 @@ jobs:
command: |
set -e
cd packages/celotool
- ./ci_test_sync_with_network.sh checkout asaj/pos-2
+ ./ci_test_attestations.sh checkout asaj/pos-2
web:
working_directory: ~/app
diff --git a/packages/protocol/contracts/common/UsingRegistry.sol b/packages/protocol/contracts/common/UsingRegistry.sol
index f4b7350b6a8..84cfa8fad2b 100644
--- a/packages/protocol/contracts/common/UsingRegistry.sol
+++ b/packages/protocol/contracts/common/UsingRegistry.sol
@@ -6,6 +6,7 @@ import "./interfaces/IERC20Token.sol";
import "./interfaces/IRegistry.sol";
import "../governance/interfaces/IElection.sol";
+import "../governance/interfaces/IGovernance.sol";
import "../governance/interfaces/ILockedGold.sol";
import "../governance/interfaces/IValidators.sol";
@@ -63,6 +64,10 @@ contract UsingRegistry is Ownable {
return IERC20Token(registry.getAddressForOrDie(GOLD_TOKEN_REGISTRY_ID));
}
+ function getGovernance() internal view returns (IGovernance) {
+ return IGovernance(registry.getAddressForOrDie(GOVERNANCE_REGISTRY_ID));
+ }
+
function getLockedGold() internal view returns (ILockedGold) {
return ILockedGold(registry.getAddressForOrDie(LOCKED_GOLD_REGISTRY_ID));
}
diff --git a/packages/protocol/contracts/governance/Governance.sol b/packages/protocol/contracts/governance/Governance.sol
index 35b2895e139..765be341e72 100644
--- a/packages/protocol/contracts/governance/Governance.sol
+++ b/packages/protocol/contracts/governance/Governance.sol
@@ -690,6 +690,22 @@ contract Governance is IGovernance, Ownable, Initializable, ReentrancyGuard, Usi
);
}
+ /**
+ * @notice Returns whether or not a particular account is voting on proposals.
+ * @param account The address of the account.
+ * @return Whether or not the account is voting on proposals.
+ */
+ function isVoting(address account) external view returns (bool) {
+ Voter storage voter = voters[account];
+ uint256 upvotedProposal = voter.upvote.proposalId;
+ bool isVotingQueue = upvotedProposal != 0 && isQueued(upvotedProposal);
+ Proposals.Proposal storage proposal = proposals[voter.mostRecentReferendumProposal];
+ bool isVotingReferendum = (
+ proposal.getDequeuedStage(stageDurations) == Proposals.Stage.Referendum
+ );
+ return isVotingQueue || isVotingReferendum;
+ }
+
/**
* @notice Returns the number of seconds proposals stay in the approval stage.
* @return The number of seconds proposals stay in the execution stage.
diff --git a/packages/protocol/contracts/governance/LockedGold.sol b/packages/protocol/contracts/governance/LockedGold.sol
index b223283cd0b..8810262a86c 100644
--- a/packages/protocol/contracts/governance/LockedGold.sol
+++ b/packages/protocol/contracts/governance/LockedGold.sol
@@ -207,8 +207,14 @@ contract LockedGold is ILockedGold, ReentrancyGuard, Initializable, UsingRegistr
function unlock(uint256 value) external nonReentrant {
require(isAccount(msg.sender));
Account storage account = accounts[msg.sender];
+ // Prevent unlocking gold when voting on governance proposals so that the gold cannot be
+ // used to vote more than once.
+ require(!getGovernance().isVoting(msg.sender));
uint256 balanceRequirement = getValidators().getAccountLockedGoldRequirement(msg.sender);
- require(balanceRequirement == 0 || balanceRequirement <= getAccountTotalLockedGold(msg.sender).sub(value));
+ require(
+ balanceRequirement == 0 ||
+ balanceRequirement <= getAccountTotalLockedGold(msg.sender).sub(value)
+ );
_decrementNonvotingAccountBalance(msg.sender, value);
uint256 available = now.add(unlockingPeriod);
account.balances.pendingWithdrawals.push(PendingWithdrawal(value, available));
diff --git a/packages/protocol/contracts/governance/Validators.sol b/packages/protocol/contracts/governance/Validators.sol
index a8277dfe04d..bd1070cf6ce 100644
--- a/packages/protocol/contracts/governance/Validators.sol
+++ b/packages/protocol/contracts/governance/Validators.sol
@@ -229,6 +229,14 @@ contract Validators is
return true;
}
+ /**
+ * @notice Returns the maximum number of members a group can add.
+ * @return The maximum number of members a group can add.
+ */
+ function getMaxGroupSize() external view returns (uint256) {
+ return maxGroupSize;
+ }
+
/**
* @notice Updates the Locked Gold requirements for Validator Groups.
* @param value The per-member amount of Locked Gold required.
@@ -650,30 +658,6 @@ contract Validators is
return true;
}
- /**
- * @notice Returns the Locked Gold requirements for validators.
- * @return The Locked Gold requirements for validators.
- */
- function getValidatorLockedGoldRequirements() external view returns (uint256, uint256) {
- return (validatorLockedGoldRequirements.value, validatorLockedGoldRequirements.duration);
- }
-
- /**
- * @notice Returns the Locked Gold requirements for validator groups.
- * @return The Locked Gold requirements for validator groups.
- */
- function getGroupLockedGoldRequirements() external view returns (uint256, uint256) {
- return (groupLockedGoldRequirements.value, groupLockedGoldRequirements.duration);
- }
-
- /**
- * @notice Returns the maximum number of members a group can add.
- * @return The maximum number of members a group can add.
- */
- function getMaxGroupSize() external view returns (uint256) {
- return maxGroupSize;
- }
-
/**
* @notice Returns the locked gold balance requirement for the supplied account.
* @param account The account that may have to meet locked gold balance requirements.
@@ -816,6 +800,22 @@ contract Validators is
return registeredValidators.length;
}
+ /**
+ * @notice Returns the Locked Gold requirements for validators.
+ * @return The Locked Gold requirements for validators.
+ */
+ function getValidatorLockedGoldRequirements() external view returns (uint256, uint256) {
+ return (validatorLockedGoldRequirements.value, validatorLockedGoldRequirements.duration);
+ }
+
+ /**
+ * @notice Returns the Locked Gold requirements for validator groups.
+ * @return The Locked Gold requirements for validator groups.
+ */
+ function getGroupLockedGoldRequirements() external view returns (uint256, uint256) {
+ return (groupLockedGoldRequirements.value, groupLockedGoldRequirements.duration);
+ }
+
/**
* @notice Returns the list of registered validator accounts.
* @return The list of registered validator accounts.
@@ -833,18 +833,18 @@ contract Validators is
}
/**
- * @notice Returns whether a particular account has a validator group.
+ * @notice Returns whether a particular account has a registered validator group.
* @param account The account.
- * @return Whether a particular address is a validator group.
+ * @return Whether a particular address is a registered validator group.
*/
function isValidatorGroup(address account) public view returns (bool) {
return bytes(groups[account].name).length > 0;
}
/**
- * @notice Returns whether a particular account has a validator.
+ * @notice Returns whether a particular account has a registered validator.
* @param account The account.
- * @return Whether a particular address is a validator.
+ * @return Whether a particular address is a registered validator.
*/
function isValidator(address account) public view returns (bool) {
return bytes(validators[account].name).length > 0;
diff --git a/packages/protocol/contracts/governance/interfaces/IGovernance.sol b/packages/protocol/contracts/governance/interfaces/IGovernance.sol
index 2db70134e22..06ca0ea6fd8 100644
--- a/packages/protocol/contracts/governance/interfaces/IGovernance.sol
+++ b/packages/protocol/contracts/governance/interfaces/IGovernance.sol
@@ -2,55 +2,5 @@ pragma solidity ^0.5.3;
interface IGovernance {
- function setApprover(address) external;
- function setConcurrentProposals(uint256) external;
- function setMinDeposit(uint256) external;
- function setQueueExpiry(uint256) external;
- function setDequeueFrequency(uint256) external;
- function setApprovalStageDuration(uint256) external;
- function setReferendumStageDuration(uint256) external;
- function setExecutionStageDuration(uint256) external;
- function setParticipationBaseline(uint256) external;
- function setParticipationFloor(uint256) external;
- function setBaselineUpdateFactor(uint256) external;
- function setBaselineQuorumFactor(uint256) external;
- function setConstitution(address, bytes4, uint256) external;
-
- function propose(
- uint256[] calldata,
- address[] calldata,
- bytes calldata,
- uint256[] calldata
- ) external payable returns (uint256);
-
- function upvote(uint256, uint256, uint256) external returns (bool);
- function revokeUpvote(uint256, uint256) external returns (bool);
- function approve(uint256, uint256) external returns (bool);
- function execute(uint256, uint256) external returns (bool);
- function withdraw() external returns (bool);
- function dequeueProposalsIfReady() external;
- function getParticipationParameters() external view returns (uint256, uint256, uint256, uint256);
- function getApprovalStageDuration() external view returns (uint256);
- function getReferendumStageDuration() external view returns (uint256);
- function getExecutionStageDuration() external view returns (uint256);
- function getConstitution(address, bytes4) external view returns (uint256);
- function proposalExists(uint256) external view returns (bool);
- function getProposal(uint256) external view returns (address, uint256, uint256, uint256);
-
- function getProposalTransaction(
- uint256,
- uint256
- ) external view returns (uint256, address, bytes memory);
-
- function isApproved(uint256) external view returns (bool);
- function getVoteTotals(uint256) external view returns (uint256, uint256, uint256);
- function getVoteRecord(address, uint256) external view returns (uint256, uint256);
- function getQueueLength() external view returns (uint256);
- function getUpvotes(uint256) external view returns (uint256);
- function getQueue() external view returns (uint256[] memory, uint256[] memory);
- function getDequeue() external view returns (uint256[] memory);
- function getUpvoteRecord(address) external view returns (uint256, uint256);
- function getMostRecentReferendumProposal(address) external view returns (uint256);
- function isQueued(uint256) external view returns (bool);
- function isProposalPassing(uint256) external view returns (bool);
+ function isVoting(address) external view returns (bool);
}
diff --git a/packages/protocol/contracts/governance/test/MockGovernance.sol b/packages/protocol/contracts/governance/test/MockGovernance.sol
index 0fd41e232b1..9432ae9d2d3 100644
--- a/packages/protocol/contracts/governance/test/MockGovernance.sol
+++ b/packages/protocol/contracts/governance/test/MockGovernance.sol
@@ -1,10 +1,11 @@
pragma solidity ^0.5.3;
+import "../interfaces/IGovernance.sol";
/**
* @title A mock Governance for testing.
*/
-contract MockGovernance {
+contract MockGovernance is IGovernance {
mapping(address => bool) public isVoting;
function setVoting(address voter) external {
diff --git a/packages/protocol/contracts/governance/test/MockValidators.sol b/packages/protocol/contracts/governance/test/MockValidators.sol
index 995e7728227..203466a6ff8 100644
--- a/packages/protocol/contracts/governance/test/MockValidators.sol
+++ b/packages/protocol/contracts/governance/test/MockValidators.sol
@@ -11,11 +11,16 @@ contract MockValidators is IValidators {
mapping(address => bool) private _isVoting;
mapping(address => uint256) private numGroupMembers;
mapping(address => uint256) private lockedGoldRequirements;
+ mapping(address => bool) private doesNotMeetAccountLockedGoldRequirements;
mapping(address => address[]) private members;
uint256 private numRegisteredValidators;
- function meetsAccountLockedGoldRequirements(address) external view returns (bool) {
- return true;
+ function setDoesNotMeetAccountLockedGoldRequirements(address account) external {
+ doesNotMeetAccountLockedGoldRequirements[account] = true;
+ }
+
+ function meetsAccountLockedGoldRequirements(address account) external view returns (bool) {
+ return !doesNotMeetAccountLockedGoldRequirements[account];
}
function isValidating(address account) external view returns (bool) {
diff --git a/packages/protocol/test/governance/election.ts b/packages/protocol/test/governance/election.ts
index 815c1131dca..94f82dfb826 100644
--- a/packages/protocol/test/governance/election.ts
+++ b/packages/protocol/test/governance/election.ts
@@ -906,7 +906,6 @@ contract('Election', (accounts: string[]) => {
const voteValue1 = new BigNumber(2000000)
const voteValue2 = new BigNumber(1000000)
const totalRewardValue = new BigNumber(3000000)
- const lockedGoldRequirement = new BigNumber(1000000)
beforeEach(async () => {
await registry.setAddressFor(CeloContractName.Validators, accounts[0])
await election.markGroupEligible(group1, NULL_ADDRESS, NULL_ADDRESS)
@@ -919,8 +918,6 @@ contract('Election', (accounts: string[]) => {
await mockLockedGold.incrementNonvotingAccountBalance(voter, voteValue1.plus(voteValue2))
await election.vote(group1, voteValue1, group2, NULL_ADDRESS)
await election.vote(group2, voteValue2, NULL_ADDRESS, group1)
- await mockValidators.setAccountLockedGoldRequirement(group1, lockedGoldRequirement)
- await mockValidators.setAccountLockedGoldRequirement(group2, lockedGoldRequirement)
})
describe('when one group has active votes', () => {
@@ -930,10 +927,6 @@ contract('Election', (accounts: string[]) => {
})
describe('when the group meets the locked gold requirements ', () => {
- beforeEach(async () => {
- await mockLockedGold.setAccountTotalLockedGold(group1, lockedGoldRequirement)
- })
-
it('should return the total reward value', async () => {
assertEqualBN(
await election.getGroupEpochRewards(group1, totalRewardValue),
@@ -944,7 +937,7 @@ contract('Election', (accounts: string[]) => {
describe('when the group does not meet the locked gold requirements ', () => {
beforeEach(async () => {
- await mockLockedGold.setAccountTotalLockedGold(group1, lockedGoldRequirement.minus(1))
+ await mockValidators.setDoesNotMeetAccountLockedGoldRequirements(group1)
})
it('should return zero', async () => {
@@ -954,7 +947,6 @@ contract('Election', (accounts: string[]) => {
})
describe('when two groups have active votes', () => {
- const lockedGoldRequirement = new BigNumber(1000000)
const expectedGroup1EpochRewards = voteValue1
.div(voteValue1.plus(voteValue2))
.times(totalRewardValue)
@@ -965,30 +957,26 @@ contract('Election', (accounts: string[]) => {
await election.activate(group2)
})
- describe('when one group meets the locked gold requirements ', () => {
+ describe('when one group does not meet the locked gold requirements ', () => {
beforeEach(async () => {
- await mockLockedGold.setAccountTotalLockedGold(group1, lockedGoldRequirement)
+ await mockValidators.setDoesNotMeetAccountLockedGoldRequirements(group2)
+ })
+
+ it('should return zero for that group', async () => {
+ assertEqualBN(await election.getGroupEpochRewards(group2, totalRewardValue), 0)
})
- it('should return the proportional reward value for that group', async () => {
+ it('should return the proportional reward value for the other group', async () => {
assertEqualBN(
await election.getGroupEpochRewards(group1, totalRewardValue),
expectedGroup1EpochRewards
)
})
-
- it('should return zero for the other group', async () => {
- assertEqualBN(await election.getGroupEpochRewards(group2, totalRewardValue), 0)
- })
})
})
describe('when the group does not have active votes', () => {
describe('when the group meets the locked gold requirements ', () => {
- beforeEach(async () => {
- await mockLockedGold.setAccountTotalLockedGold(group1, lockedGoldRequirement)
- })
-
it('should return zero', async () => {
assertEqualBN(await election.getGroupEpochRewards(group1, totalRewardValue), 0)
})
diff --git a/packages/protocol/test/governance/lockedgold.ts b/packages/protocol/test/governance/lockedgold.ts
index d98e692ff9a..88c6d8276b8 100644
--- a/packages/protocol/test/governance/lockedgold.ts
+++ b/packages/protocol/test/governance/lockedgold.ts
@@ -15,6 +15,8 @@ import {
MockElectionInstance,
MockGoldTokenContract,
MockGoldTokenInstance,
+ MockGovernanceContract,
+ MockGovernanceInstance,
MockValidatorsContract,
MockValidatorsInstance,
RegistryContract,
@@ -24,6 +26,7 @@ import {
const LockedGold: LockedGoldContract = artifacts.require('LockedGold')
const MockElection: MockElectionContract = artifacts.require('MockElection')
const MockGoldToken: MockGoldTokenContract = artifacts.require('MockGoldToken')
+const MockGovernance: MockGovernanceContract = artifacts.require('MockGovernance')
const MockValidators: MockValidatorsContract = artifacts.require('MockValidators')
const Registry: RegistryContract = artifacts.require('Registry')
@@ -41,6 +44,7 @@ contract('LockedGold', (accounts: string[]) => {
const unlockingPeriod = 3 * DAY
let lockedGold: LockedGoldInstance
let mockElection: MockElectionInstance
+ let mockGovernance: MockGovernanceInstance
let mockValidators: MockValidatorsInstance
let registry: RegistryInstance
@@ -53,9 +57,11 @@ contract('LockedGold', (accounts: string[]) => {
lockedGold = await LockedGold.new()
mockElection = await MockElection.new()
mockValidators = await MockValidators.new()
+ mockGovernance = await MockGovernance.new()
registry = await Registry.new()
- await registry.setAddressFor(CeloContractName.GoldToken, mockGoldToken.address)
await registry.setAddressFor(CeloContractName.Election, mockElection.address)
+ await registry.setAddressFor(CeloContractName.GoldToken, mockGoldToken.address)
+ await registry.setAddressFor(CeloContractName.Governance, mockGovernance.address)
await registry.setAddressFor(CeloContractName.Validators, mockValidators.address)
await lockedGold.initialize(registry.address, unlockingPeriod)
await lockedGold.createAccount()
@@ -326,43 +332,57 @@ contract('LockedGold', (accounts: string[]) => {
beforeEach(async () => {
// @ts-ignore: TODO(mcortesi) fix typings for TransactionDetails
await lockedGold.lock({ value })
- resp = await lockedGold.unlock(value)
- availabilityTime = new BigNumber(unlockingPeriod).plus(
- (await web3.eth.getBlock('latest')).timestamp
- )
})
+ describe('when the account is not voting in governance', () => {
+ beforeEach(async () => {
+ resp = await lockedGold.unlock(value)
+ availabilityTime = new BigNumber(unlockingPeriod).plus(
+ (await web3.eth.getBlock('latest')).timestamp
+ )
+ })
- it('should add a pending withdrawal', async () => {
- const [values, timestamps] = await lockedGold.getPendingWithdrawals(account)
- assert.equal(values.length, 1)
- assert.equal(timestamps.length, 1)
- assertEqualBN(values[0], value)
- assertEqualBN(timestamps[0], availabilityTime)
- })
+ it('should add a pending withdrawal', async () => {
+ const [values, timestamps] = await lockedGold.getPendingWithdrawals(account)
+ assert.equal(values.length, 1)
+ assert.equal(timestamps.length, 1)
+ assertEqualBN(values[0], value)
+ assertEqualBN(timestamps[0], availabilityTime)
+ })
- it("should decrease the account's nonvoting locked gold balance", async () => {
- assertEqualBN(await lockedGold.getAccountNonvotingLockedGold(account), 0)
- })
+ it("should decrease the account's nonvoting locked gold balance", async () => {
+ assertEqualBN(await lockedGold.getAccountNonvotingLockedGold(account), 0)
+ })
- it("should decrease the account's total locked gold balance", async () => {
- assertEqualBN(await lockedGold.getAccountTotalLockedGold(account), 0)
- })
+ it("should decrease the account's total locked gold balance", async () => {
+ assertEqualBN(await lockedGold.getAccountTotalLockedGold(account), 0)
+ })
- it('should decrease the nonvoting locked gold balance', async () => {
- assertEqualBN(await lockedGold.getNonvotingLockedGold(), 0)
- })
+ it('should decrease the nonvoting locked gold balance', async () => {
+ assertEqualBN(await lockedGold.getNonvotingLockedGold(), 0)
+ })
- it('should decrease the total locked gold balance', async () => {
- assertEqualBN(await lockedGold.getTotalLockedGold(), 0)
+ it('should decrease the total locked gold balance', async () => {
+ assertEqualBN(await lockedGold.getTotalLockedGold(), 0)
+ })
+
+ it('should emit a GoldUnlocked event', async () => {
+ assert.equal(resp.logs.length, 1)
+ const log = resp.logs[0]
+ assertLogMatches(log, 'GoldUnlocked', {
+ account,
+ value: new BigNumber(value),
+ available: availabilityTime,
+ })
+ })
})
- it('should emit a GoldUnlocked event', async () => {
- assert.equal(resp.logs.length, 1)
- const log = resp.logs[0]
- assertLogMatches(log, 'GoldUnlocked', {
- account,
- value: new BigNumber(value),
- available: availabilityTime,
+ describe('when the account is voting in governance', () => {
+ beforeEach(async () => {
+ await mockGovernance.setVoting(account)
+ })
+
+ it('should revert', async () => {
+ await assertRevert(lockedGold.unlock(value))
})
})
})
diff --git a/packages/protocol/test/governance/validators.ts b/packages/protocol/test/governance/validators.ts
index fc1c1e25dc3..6ea4cc15ef5 100644
--- a/packages/protocol/test/governance/validators.ts
+++ b/packages/protocol/test/governance/validators.ts
@@ -11,10 +11,10 @@ import {
} from '@celo/protocol/lib/test-utils'
import BigNumber from 'bignumber.js'
import {
- MockLockedGoldContract,
- MockLockedGoldInstance,
MockElectionContract,
MockElectionInstance,
+ MockLockedGoldContract,
+ MockLockedGoldInstance,
MockStableTokenContract,
MockStableTokenInstance,
RegistryContract,
@@ -25,8 +25,8 @@ import {
import { fromFixed, toFixed } from '@celo/utils/lib/fixidity'
const Validators: ValidatorsTestContract = artifacts.require('ValidatorsTest')
-const MockLockedGold: MockLockedGoldContract = artifacts.require('MockLockedGold')
const MockElection: MockElectionContract = artifacts.require('MockElection')
+const MockLockedGold: MockLockedGoldContract = artifacts.require('MockLockedGold')
const MockStableToken: MockStableTokenContract = artifacts.require('MockStableToken')
const Registry: RegistryContract = artifacts.require('Registry')
@@ -69,8 +69,8 @@ const EPOCH = 100
contract('Validators', (accounts: string[]) => {
let validators: ValidatorsTestInstance
let registry: RegistryInstance
- let mockLockedGold: MockLockedGoldInstance
let mockElection: MockElectionInstance
+ let mockLockedGold: MockLockedGoldInstance
const nonOwner = accounts[1]
const validatorLockedGoldRequirements = {
@@ -101,11 +101,11 @@ contract('Validators', (accounts: string[]) => {
const commission = toFixed(1 / 100)
beforeEach(async () => {
validators = await Validators.new()
- mockLockedGold = await MockLockedGold.new()
- mockElection = await MockElection.new()
registry = await Registry.new()
- await registry.setAddressFor(CeloContractName.LockedGold, mockLockedGold.address)
+ mockElection = await MockElection.new()
+ mockLockedGold = await MockLockedGold.new()
await registry.setAddressFor(CeloContractName.Election, mockElection.address)
+ await registry.setAddressFor(CeloContractName.LockedGold, mockLockedGold.address)
await validators.initialize(
registry.address,
groupLockedGoldRequirements.value,
@@ -1203,7 +1203,7 @@ contract('Validators', (accounts: string[]) => {
describe('when it has been less than `groupLockedGoldRequirements.duration` since the validator was removed from the group', () => {
beforeEach(async () => {
- await timeTravel(groupLockedGoldRequirements.duration.toNumber().minus(1), web3)
+ await timeTravel(groupLockedGoldRequirements.duration.minus(1).toNumber(), web3)
})
it('should revert', async () => {
From 19488c7ead2a7cff5d91e3f4b7375b0bdb0b666c Mon Sep 17 00:00:00 2001
From: Asa Oines
Date: Tue, 22 Oct 2019 16:32:29 -0700
Subject: [PATCH 081/149] Re-add isVoting tests
---
packages/protocol/test/governance/governance.ts | 2 --
1 file changed, 2 deletions(-)
diff --git a/packages/protocol/test/governance/governance.ts b/packages/protocol/test/governance/governance.ts
index d4e11d534f7..48d06c24d42 100644
--- a/packages/protocol/test/governance/governance.ts
+++ b/packages/protocol/test/governance/governance.ts
@@ -1872,7 +1872,6 @@ contract('Governance', (accounts: string[]) => {
})
})
- /*
describe('#isVoting()', () => {
describe('when the account has never acted on a proposal', () => {
it('should return false', async () => {
@@ -1955,7 +1954,6 @@ contract('Governance', (accounts: string[]) => {
})
})
})
- */
describe('#isProposalPassing()', () => {
const proposalId = 1
From e3e3809888df5b2e88643ae6a7c44d9268cfa090 Mon Sep 17 00:00:00 2001
From: Asa Oines
Date: Tue, 22 Oct 2019 16:34:58 -0700
Subject: [PATCH 082/149] Small cleanup
---
packages/protocol/contracts/governance/Validators.sol | 9 ---------
1 file changed, 9 deletions(-)
diff --git a/packages/protocol/contracts/governance/Validators.sol b/packages/protocol/contracts/governance/Validators.sol
index 8c59a61e3e6..d73593323f7 100644
--- a/packages/protocol/contracts/governance/Validators.sol
+++ b/packages/protocol/contracts/governance/Validators.sol
@@ -945,15 +945,6 @@ contract Validators is
}
/**
- * Tail: 0
- * numEntries: 0
- * index: 0
- *
- * Tail: 0
- * numEntries: 1
- * index: 1
- *
- *
* @notice Updates the group membership history of a particular account.
* @param account The account whose group membership has changed.
* @param group The group that the account is now a member of.
From e39ff0a9743f43d371968ec0f8759e2695de05ae Mon Sep 17 00:00:00 2001
From: Asa Oines
Date: Wed, 23 Oct 2019 09:44:08 -0700
Subject: [PATCH 083/149] Clean up election setters
---
.../contracts/governance/Election.sol | 44 ++++---------------
1 file changed, 8 insertions(+), 36 deletions(-)
diff --git a/packages/protocol/contracts/governance/Election.sol b/packages/protocol/contracts/governance/Election.sol
index 69bc33d1fc1..43a1361b564 100644
--- a/packages/protocol/contracts/governance/Election.sol
+++ b/packages/protocol/contracts/governance/Election.sol
@@ -151,9 +151,9 @@ contract Election is
{
_transferOwnership(msg.sender);
setRegistry(registryAddress);
- _setElectableValidators(minElectableValidators, maxElectableValidators);
- _setMaxNumGroupsVotedFor(_maxNumGroupsVotedFor);
- _setElectabilityThreshold(_electabilityThreshold);
+ setElectableValidators(minElectableValidators, maxElectableValidators);
+ setMaxNumGroupsVotedFor(_maxNumGroupsVotedFor);
+ setElectabilityThreshold(_electabilityThreshold);
}
/**
@@ -163,7 +163,11 @@ contract Election is
* @return True upon success.
*/
function setElectableValidators(uint256 min, uint256 max) external onlyOwner returns (bool) {
- return _setElectableValidators(min, max);
+ require(0 < min && min <= max);
+ require(min != electableValidators.min || max != electableValidators.max);
+ electableValidators = ElectableValidators(min, max);
+ emit ElectableValidatorsSet(min, max);
+ return true;
}
/**
@@ -174,20 +178,6 @@ contract Election is
return (electableValidators.min, electableValidators.max);
}
- /**
- * @notice Updates the minimum and maximum number of validators that can be elected.
- * @param min The minimum number of validators that can be elected.
- * @param max The maximum number of validators that can be elected.
- * @return True upon success.
- */
- function _setElectableValidators(uint256 min, uint256 max) private returns (bool) {
- require(0 < min && min <= max);
- require(min != electableValidators.min || max != electableValidators.max);
- electableValidators = ElectableValidators(min, max);
- emit ElectableValidatorsSet(min, max);
- return true;
- }
-
/**
* @notice Updates the maximum number of groups an account can be voting for at once.
* @param _maxNumGroupsVotedFor The maximum number of groups an account can vote for.
@@ -200,15 +190,6 @@ contract Election is
onlyOwner
returns (bool)
{
- return _setMaxNumGroupsVotedFor(_maxNumGroupsVotedFor);
- }
-
- /**
- * @notice Updates the maximum number of groups an account can be voting for at once.
- * @param _maxNumGroupsVotedFor The maximum number of groups an account can vote for.
- * @return True upon success.
- */
- function _setMaxNumGroupsVotedFor(uint256 _maxNumGroupsVotedFor) private returns (bool) {
require(_maxNumGroupsVotedFor != maxNumGroupsVotedFor);
maxNumGroupsVotedFor = _maxNumGroupsVotedFor;
emit MaxNumGroupsVotedForSet(_maxNumGroupsVotedFor);
@@ -221,15 +202,6 @@ contract Election is
* @return True upon success.
*/
function setElectabilityThreshold(uint256 threshold) public onlyOwner returns (bool) {
- return _setElectabilityThreshold(threshold);
- }
-
- /**
- * @notice Sets the electability threshold.
- * @param threshold Electability threshold as unwrapped Fraction.
- * @return True upon success.
- */
- function _setElectabilityThreshold(uint256 threshold) private returns (bool) {
electabilityThreshold = FixidityLib.wrap(threshold);
require(
electabilityThreshold.lt(FixidityLib.fixed1()),
From 5a43d3f46593dd27e644cc2089ca35dcd6af6a9e Mon Sep 17 00:00:00 2001
From: Asa Oines
Date: Wed, 23 Oct 2019 16:59:45 -0700
Subject: [PATCH 084/149] Add first pass at epoch rewards
---
.../contracts/common/UsingRegistry.sol | 5 +
.../contracts/governance/Election.sol | 12 +-
.../contracts/governance/EpochRewards.sol | 184 ++++++++++++++++++
.../contracts/governance/Validators.sol | 30 +--
.../governance/interfaces/IElection.sol | 1 +
.../governance/proxies/EpochRewardsProxy.sol | 8 +
.../governance/test/EpochRewardsTest.sol | 30 +++
.../governance/test/MockElection.sol | 4 +
.../governance/test/ValidatorsTest.sol | 4 +-
.../contracts/stability/SortedOracles.sol | 1 +
packages/protocol/lib/registry-utils.ts | 1 +
packages/protocol/migrations/11_validators.ts | 1 -
.../protocol/migrations/13_epoch_rewards.ts | 21 ++
.../migrations/{13_random.ts => 14_random.ts} | 0
...{14_attestations.ts => 15_attestations.ts} | 0
...kchainparams.ts => 16_blockchainparams.ts} | 0
.../migrations/{15_escrow.ts => 17_escrow.ts} | 0
.../{16_governance.ts => 18_governance.ts} | 0
...t_validators.ts => 19_elect_validators.ts} | 0
packages/protocol/migrationsConfig.js | 6 +-
.../protocol/test/governance/epochrewards.ts | 163 ++++++++++++++++
.../protocol/test/governance/validators.ts | 77 ++------
22 files changed, 463 insertions(+), 85 deletions(-)
create mode 100644 packages/protocol/contracts/governance/EpochRewards.sol
create mode 100644 packages/protocol/contracts/governance/proxies/EpochRewardsProxy.sol
create mode 100644 packages/protocol/contracts/governance/test/EpochRewardsTest.sol
create mode 100644 packages/protocol/migrations/13_epoch_rewards.ts
rename packages/protocol/migrations/{13_random.ts => 14_random.ts} (100%)
rename packages/protocol/migrations/{14_attestations.ts => 15_attestations.ts} (100%)
rename packages/protocol/migrations/{15_blockchainparams.ts => 16_blockchainparams.ts} (100%)
rename packages/protocol/migrations/{15_escrow.ts => 17_escrow.ts} (100%)
rename packages/protocol/migrations/{16_governance.ts => 18_governance.ts} (100%)
rename packages/protocol/migrations/{17_elect_validators.ts => 19_elect_validators.ts} (100%)
create mode 100644 packages/protocol/test/governance/epochrewards.ts
diff --git a/packages/protocol/contracts/common/UsingRegistry.sol b/packages/protocol/contracts/common/UsingRegistry.sol
index 84cfa8fad2b..45ede8446ba 100644
--- a/packages/protocol/contracts/common/UsingRegistry.sol
+++ b/packages/protocol/contracts/common/UsingRegistry.sol
@@ -12,6 +12,7 @@ import "../governance/interfaces/IValidators.sol";
import "../identity/interfaces/IRandom.sol";
+import "../stability/interfaces/ISortedOracles.sol";
import "../stability/interfaces/IStableToken.sol";
// Ideally, UsingRegistry should inherit from Initializable and implement initialize() which calls
@@ -76,6 +77,10 @@ contract UsingRegistry is Ownable {
return IRandom(registry.getAddressForOrDie(RANDOM_REGISTRY_ID));
}
+ function getSortedOracles() internal view returns (ISortedOracles) {
+ return ISortedOracles(registry.getAddressForOrDie(SORTED_ORACLES_REGISTRY_ID));
+ }
+
function getStableToken() internal view returns (IStableToken) {
return IStableToken(registry.getAddressForOrDie(STABLE_TOKEN_REGISTRY_ID));
}
diff --git a/packages/protocol/contracts/governance/Election.sol b/packages/protocol/contracts/governance/Election.sol
index 43a1361b564..b85a3b6ec84 100644
--- a/packages/protocol/contracts/governance/Election.sol
+++ b/packages/protocol/contracts/governance/Election.sol
@@ -162,7 +162,7 @@ contract Election is
* @param max The maximum number of validators that can be elected.
* @return True upon success.
*/
- function setElectableValidators(uint256 min, uint256 max) external onlyOwner returns (bool) {
+ function setElectableValidators(uint256 min, uint256 max) public onlyOwner returns (bool) {
require(0 < min && min <= max);
require(min != electableValidators.min || max != electableValidators.max);
electableValidators = ElectableValidators(min, max);
@@ -186,7 +186,7 @@ contract Election is
function setMaxNumGroupsVotedFor(
uint256 _maxNumGroupsVotedFor
)
- external
+ public
onlyOwner
returns (bool)
{
@@ -756,6 +756,14 @@ contract Election is
return votes.active.total.add(votes.pending.total);
}
+ /**
+ * @notice Returns the active votes received across all groups.
+ * @return The active votes received across all groups.
+ */
+ function getActiveVotes() public view returns (uint256) {
+ return votes.active.total;
+ }
+
/**
* @notice Returns the list of validator groups eligible to elect validators.
* @return The list of validator groups eligible to elect validators.
diff --git a/packages/protocol/contracts/governance/EpochRewards.sol b/packages/protocol/contracts/governance/EpochRewards.sol
new file mode 100644
index 00000000000..f9f1d68412e
--- /dev/null
+++ b/packages/protocol/contracts/governance/EpochRewards.sol
@@ -0,0 +1,184 @@
+pragma solidity ^0.5.3;
+
+import "openzeppelin-solidity/contracts/math/SafeMath.sol";
+import "openzeppelin-solidity/contracts/ownership/Ownable.sol";
+
+import "../common/FixidityLib.sol";
+import "../common/Initializable.sol";
+import "../common/UsingRegistry.sol";
+import "../common/UsingPrecompiles.sol";
+
+/**
+ * @title Contract for calculating epoch rewards.
+ */
+contract EpochRewards is Ownable, Initializable, UsingPrecompiles, UsingRegistry {
+
+ using FixidityLib for FixidityLib.Fraction;
+ using SafeMath for uint256;
+
+ uint256 constant GENESIS_GOLD_SUPPLY = 600000000;
+ uint256 constant GOLD_SUPPLY_CAP = 1000000000;
+ uint256 constant YEARS_LINEAR = 15;
+ uint256 constant SECONDS_LINEAR = YEARS_LINEAR * 365 * 1 days;
+ uint256 constant FIXIDITY_E = 2718281828459045235360287;
+ uint256 constant FIXIDITY_LN2 = 693147180559945309417232;
+ uint256 private startTime = 0;
+ FixidityLib.Fraction private targetVotingGoldFraction;
+ FixidityLib.Fraction private targetVotingYield;
+ FixidityLib.Fraction private maxTargetVotingYield;
+ FixidityLib.Fraction private targetVotingYieldAdjustmentFactor;
+ uint256 public maxValidatorEpochPayment;
+
+ event MaxValidatorEpochPaymentSet(uint256 payment);
+ event MaxTargetVotingYieldSet(uint256 yield);
+
+ /**
+ * @param _maxValidatorEpochPayment The duration the above gold remains locked after deregistration.
+ */
+ function initialize(
+ address registryAddress,
+ uint256 _maxValidatorEpochPayment,
+ uint256 _maxTargetVotingYield,
+ uint256 _targetVotingYield
+ )
+ external
+ initializer
+ {
+ _transferOwnership(msg.sender);
+ setRegistry(registryAddress);
+ setMaxTargetVotingYield(_maxTargetVotingYield);
+ setMaxValidatorEpochPayment(_maxValidatorEpochPayment);
+ targetVotingYield = FixidityLib.wrap(_targetVotingYield);
+ startTime = now;
+ }
+
+ function getMaxTargetVotingYield() external view returns (uint256) {
+ return maxTargetVotingYield.unwrap();
+ }
+
+ function getTargetVotingYield() external view returns (uint256) {
+ return targetVotingYield.unwrap();
+ }
+
+ /**
+ m* @notice Sets the max per-epoch payment in Celo Dollars for validators.
+ * @param value The value in Celo Dollars.
+ * @return True upon success.
+ */
+ function setMaxValidatorEpochPayment(uint256 value) public onlyOwner returns (bool) {
+ require(value != maxValidatorEpochPayment);
+ maxValidatorEpochPayment = value;
+ emit MaxValidatorEpochPaymentSet(value);
+ return true;
+ }
+
+ function setMaxTargetVotingYield(uint256 yield) public onlyOwner returns (bool) {
+ require(yield != maxTargetVotingYield.unwrap());
+ maxTargetVotingYield = FixidityLib.wrap(yield);
+ require(
+ maxTargetVotingYield.lt(FixidityLib.fixed1()),
+ "Max voting yield must be lower than 100%"
+ );
+ emit MaxTargetVotingYieldSet(yield);
+ return true;
+ }
+
+ function _getTargetGoldTotalSupply() internal view returns (uint256) {
+ uint256 timeSinceInitialization = now.sub(startTime);
+ uint256 targetGoldSupply = 0;
+ if (timeSinceInitialization < SECONDS_LINEAR) {
+ // Pay out half of all block rewards linearly.
+ uint256 linearRewards = GOLD_SUPPLY_CAP.sub(GENESIS_GOLD_SUPPLY).div(2);
+ uint256 targetRewards = linearRewards.mul(timeSinceInitialization).div(SECONDS_LINEAR);
+ return targetRewards.add(GENESIS_GOLD_SUPPLY);
+ } else {
+ /*
+ FixidityLib.Fraction memory exponentialDecaryHalfLife = FixidityLib.wrap(
+ FIXIDITY_LN2.div(YEARS_LINEAR)
+ );
+ uint256 exponentialSeconds = timeSinceInitialization.sub(SECONDS_LINEAR);
+ uint256 exponentialRewards = GOLD_SUPPLY_CAP.sub(GENESIS_GOLD_SUPPLY).div(2);
+ // 1000 - 200 * e ^ ((- 1 / 15) * (x - 15))
+ (uint256 numerator, uint256 denominator) = fractionMulExp(FixidityLib.fixed1(), FixidityLib.fixed1(), FIXIDITY_E, FixidityLib.fixed1(), ???, ???);
+ */
+ // TODO(asa): This isn't implemented.
+ require(false);
+ return 0;
+ }
+ }
+
+ // TODO(asa): Finish this.
+ function _getRewardsMultiplier(uint256 targetGoldSupplyIncrease) internal view returns (FixidityLib.Fraction memory) {
+ uint256 targetSupply = _getTargetGoldTotalSupply();
+ uint256 totalSupplyWithRewards = getGoldToken().totalSupply().add(targetGoldSupplyIncrease);
+ if (totalSupplyWithRewards > targetSupply) {
+ // uint256 delta = totalSupplyWithRewards.sub(targetSupply);
+ return FixidityLib.fixed1();
+
+ // FixidityLib.Fraction memory deviation = FixidityLib.newFixed(delta).
+ /*
+ B_actual_t = supply_cap - (totalSupplyWithRewards);
+ B_target_t = supply_cap - (targetSupply);
+ B_actual_t - Z_t = B_actual_t - targetRewards -
+ */
+ } else if (totalSupplyWithRewards < targetSupply) {
+ // uint256 delta = targetSupply.sub(totalSupplyWithRewards);
+ return FixidityLib.fixed1();
+ } else {
+ return FixidityLib.fixed1();
+ }
+ }
+
+ function _getTargetEpochRewards() internal view returns (uint256) {
+ return FixidityLib.newFixed(getElection().getActiveVotes()).multiply(targetVotingYield).fromFixed();
+ }
+
+ function _getTargetTotalEpochPaymentsInGold() internal view returns (uint256) {
+ address stableTokenAddress = registry.getAddressForOrDie(STABLE_TOKEN_REGISTRY_ID);
+ (uint256 numerator, uint256 denominator) = getSortedOracles().medianRate(stableTokenAddress);
+ uint256 targetEpochPayment = numberValidatorsInCurrentSet().mul(maxValidatorEpochPayment).mul(numerator).div(denominator);
+ return targetEpochPayment;
+ }
+
+ function _updateTargetVotingYield() internal {
+ IERC20Token goldToken = getGoldToken();
+ // TODO(asa): Ignore custodial accounts.
+ address reserveAddress = registry.getAddressForOrDie(RESERVE_REGISTRY_ID);
+ uint256 liquidGold = goldToken.totalSupply().sub(goldToken.balanceOf(reserveAddress));
+ // TODO(asa): Should this be active votes?
+ uint256 votingGold = getElection().getTotalVotes();
+ FixidityLib.Fraction memory votingGoldFraction = FixidityLib.newFixed(liquidGold).divide(FixidityLib.newFixed(votingGold));
+ if (votingGoldFraction.gt(targetVotingGoldFraction)) {
+ FixidityLib.Fraction memory votingGoldFractionDelta = votingGoldFraction.subtract(targetVotingGoldFraction);
+ FixidityLib.Fraction memory targetVotingYieldDelta = votingGoldFractionDelta.multiply(targetVotingYieldAdjustmentFactor);
+ if (targetVotingYieldDelta.gte(targetVotingYield)) {
+ targetVotingYield = FixidityLib.newFixed(0);
+ } else {
+ targetVotingYield = targetVotingYield.subtract(targetVotingYieldDelta);
+ }
+ } else {
+ FixidityLib.Fraction memory votingGoldFractionDelta = targetVotingGoldFraction.subtract(votingGoldFraction);
+ FixidityLib.Fraction memory targetVotingYieldDelta = votingGoldFractionDelta.multiply(targetVotingYieldAdjustmentFactor);
+ targetVotingYield = targetVotingYield.add(targetVotingYieldDelta);
+ if (targetVotingYield.gt(maxTargetVotingYield)) {
+ targetVotingYield = maxTargetVotingYield;
+ }
+ }
+ }
+
+ function updateTargetVotingYield() external {
+ require(msg.sender == address(0));
+ _updateTargetVotingYield();
+ }
+
+ function calculateTargetEpochPaymentAndRewards() external view returns (uint256, uint256) {
+ uint256 targetEpochRewards = _getTargetEpochRewards();
+ uint256 targetTotalEpochPaymentsInGold = _getTargetTotalEpochPaymentsInGold();
+ uint256 targetGoldSupplyIncrease = targetEpochRewards.add(targetTotalEpochPaymentsInGold);
+ FixidityLib.Fraction memory rewardsMultiplier = _getRewardsMultiplier(targetGoldSupplyIncrease);
+ return (
+ FixidityLib.newFixed(maxValidatorEpochPayment).multiply(rewardsMultiplier).fromFixed(),
+ FixidityLib.newFixed(targetEpochRewards).multiply(rewardsMultiplier).fromFixed()
+ );
+ }
+}
diff --git a/packages/protocol/contracts/governance/Validators.sol b/packages/protocol/contracts/governance/Validators.sol
index bd1070cf6ce..bb22f1e140e 100644
--- a/packages/protocol/contracts/governance/Validators.sol
+++ b/packages/protocol/contracts/governance/Validators.sol
@@ -102,7 +102,6 @@ contract Validators is
LockedGoldRequirements public validatorLockedGoldRequirements;
LockedGoldRequirements public groupLockedGoldRequirements;
ValidatorScoreParameters private validatorScoreParameters;
- uint256 public validatorEpochPayment;
uint256 public membershipHistoryLength;
uint256 public maxGroupSize;
@@ -136,7 +135,6 @@ contract Validators is
* @param validatorRequirementDuration The Locked Gold requirement duration for validators.
* @param validatorScoreExponent The exponent used in calculating validator scores.
* @param validatorScoreAdjustmentSpeed The speed at which validator scores are adjusted.
- * @param _validatorEpochPayment The duration the above gold remains locked after deregistration.
* @param _membershipHistoryLength The max number of entries for validator membership history.
* @param _maxGroupSize The maximum group size.
* @dev Should be called only once.
@@ -149,7 +147,6 @@ contract Validators is
uint256 validatorRequirementDuration,
uint256 validatorScoreExponent,
uint256 validatorScoreAdjustmentSpeed,
- uint256 _validatorEpochPayment,
uint256 _membershipHistoryLength,
uint256 _maxGroupSize
)
@@ -162,7 +159,6 @@ contract Validators is
setValidatorLockedGoldRequirements(validatorRequirementValue, validatorRequirementDuration);
setValidatorScoreParameters(validatorScoreExponent, validatorScoreAdjustmentSpeed);
setMaxGroupSize(_maxGroupSize);
- setValidatorEpochPayment(_validatorEpochPayment);
setMembershipHistoryLength(_membershipHistoryLength);
}
@@ -190,18 +186,6 @@ contract Validators is
return true;
}
- /**
- * @notice Sets the per-epoch payment in Celo Dollars for validators, less group commission.
- * @param value The value in Celo Dollars.
- * @return True upon success.
- */
- function setValidatorEpochPayment(uint256 value) public onlyOwner returns (bool) {
- require(value != validatorEpochPayment);
- validatorEpochPayment = value;
- emit ValidatorEpochPaymentSet(value);
- return true;
- }
-
/**
* @notice Updates the validator score parameters.
* @param exponent The exponent used in calculating the score.
@@ -407,14 +391,14 @@ contract Validators is
/**
* @notice Distributes epoch payments to `validator` and its group.
*/
- function distributeEpochPayment(address validator) external onlyVm() {
- _distributeEpochPayment(validator);
+ function distributeEpochPayment(address validator, uint256 maxPayment) external onlyVm() returns (uint256) {
+ return _distributeEpochPayment(validator, maxPayment);
}
/**
* @notice Distributes epoch payments to `validator` and its group.
*/
- function _distributeEpochPayment(address validator) internal {
+ function _distributeEpochPayment(address validator, uint256 maxPayment) internal returns (uint256) {
address account = getLockedGold().getAccountFromValidator(validator);
require(isValidator(account));
// The group that should be paid is the group that the validator was a member of at the
@@ -423,14 +407,16 @@ contract Validators is
// Both the validator and the group must maintain the minimum locked gold balance in order to
// receive epoch payments.
if (meetsAccountLockedGoldRequirements(account) && meetsAccountLockedGoldRequirements(group)) {
- FixidityLib.Fraction memory totalPayment = FixidityLib.newFixed(
- validatorEpochPayment
- ).multiply(validators[account].score);
+ FixidityLib.Fraction memory totalPayment = FixidityLib.newFixed(maxPayment).multiply(
+ validators[account].score
+ );
uint256 groupPayment = totalPayment.multiply(groups[group].commission).fromFixed();
uint256 validatorPayment = totalPayment.fromFixed().sub(groupPayment);
getStableToken().mint(group, groupPayment);
getStableToken().mint(account, validatorPayment);
+ return totalPayment.fromFixed();
}
+ return 0;
}
/**
diff --git a/packages/protocol/contracts/governance/interfaces/IElection.sol b/packages/protocol/contracts/governance/interfaces/IElection.sol
index caa2df9a882..ec4f76ee62a 100644
--- a/packages/protocol/contracts/governance/interfaces/IElection.sol
+++ b/packages/protocol/contracts/governance/interfaces/IElection.sol
@@ -3,6 +3,7 @@ pragma solidity ^0.5.3;
interface IElection {
function getTotalVotes() external view returns (uint256);
+ function getActiveVotes() external view returns (uint256);
function getTotalVotesByAccount(address) external view returns (uint256);
function markGroupIneligible(address) external;
function markGroupEligible(address,address,address) external;
diff --git a/packages/protocol/contracts/governance/proxies/EpochRewardsProxy.sol b/packages/protocol/contracts/governance/proxies/EpochRewardsProxy.sol
new file mode 100644
index 00000000000..205f71fdb56
--- /dev/null
+++ b/packages/protocol/contracts/governance/proxies/EpochRewardsProxy.sol
@@ -0,0 +1,8 @@
+pragma solidity ^0.5.3;
+
+import "../../common/Proxy.sol";
+
+
+/* solhint-disable no-empty-blocks */
+contract EpochRewardsProxy is Proxy {
+}
diff --git a/packages/protocol/contracts/governance/test/EpochRewardsTest.sol b/packages/protocol/contracts/governance/test/EpochRewardsTest.sol
new file mode 100644
index 00000000000..76a00de1c4a
--- /dev/null
+++ b/packages/protocol/contracts/governance/test/EpochRewardsTest.sol
@@ -0,0 +1,30 @@
+pragma solidity ^0.5.8;
+
+import "../EpochRewards.sol";
+import "../../common/FixidityLib.sol";
+
+/**
+ * @title A wrapper around EpochRewards that exposes internal functions for testing.
+ */
+contract EpochRewardsTest is EpochRewards {
+
+ function getTargetGoldTotalSupply() external view returns (uint256) {
+ return _getTargetGoldTotalSupply();
+ }
+
+ function getTargetTotalEpochPaymentsInGold() external view returns (uint256) {
+ return _getTargetTotalEpochPaymentsInGold();
+ }
+
+ function getTargetEpochRewards() external view returns (uint256) {
+ return _getTargetEpochRewards();
+ }
+
+ function getRewardsMultiplier(uint256 targetGoldTotalSupplyIncrease) external view returns (uint256) {
+ return _getRewardsMultiplier(targetGoldTotalSupplyIncrease).unwrap();
+ }
+
+ function updateTargetVotingYield() external {
+ _updateTargetVotingYield();
+ }
+}
diff --git a/packages/protocol/contracts/governance/test/MockElection.sol b/packages/protocol/contracts/governance/test/MockElection.sol
index 47f46d42ca3..091b6f753dd 100644
--- a/packages/protocol/contracts/governance/test/MockElection.sol
+++ b/packages/protocol/contracts/governance/test/MockElection.sol
@@ -23,6 +23,10 @@ contract MockElection is IElection {
return 0;
}
+ function getActiveVotes() external view returns (uint256) {
+ return 0;
+ }
+
function getTotalVotesByAccount(address) external view returns (uint256) {
return 0;
}
diff --git a/packages/protocol/contracts/governance/test/ValidatorsTest.sol b/packages/protocol/contracts/governance/test/ValidatorsTest.sol
index beefe62389e..dc017bd6092 100644
--- a/packages/protocol/contracts/governance/test/ValidatorsTest.sol
+++ b/packages/protocol/contracts/governance/test/ValidatorsTest.sol
@@ -12,7 +12,7 @@ contract ValidatorsTest is Validators {
return _updateValidatorScore(validator, uptime);
}
- function distributeEpochPayment(address validator) external {
- return _distributeEpochPayment(validator);
+ function distributeEpochPayment(address validator, uint256 maxPayment) external returns (uint256) {
+ return _distributeEpochPayment(validator, maxPayment);
}
}
diff --git a/packages/protocol/contracts/stability/SortedOracles.sol b/packages/protocol/contracts/stability/SortedOracles.sol
index adc6c729eee..9e569f2f422 100644
--- a/packages/protocol/contracts/stability/SortedOracles.sol
+++ b/packages/protocol/contracts/stability/SortedOracles.sol
@@ -138,6 +138,7 @@ contract SortedOracles is ISortedOracles, Ownable, Initializable {
* @notice Updates an oracle value and the median.
* @param token The address of the token for which the Celo Gold exchange rate is being reported.
* @param numerator The amount of tokens equal to `denominator` Celo Gold.
+ * @param denominator The amount of Celo Gold equal to `numerator` tokens.
* @param lesserKey The element which should be just left of the new oracle value.
* @param greaterKey The element which should be just right of the new oracle value.
* @dev Note that only one of `lesserKey` or `greaterKey` needs to be correct to reduce friction.
diff --git a/packages/protocol/lib/registry-utils.ts b/packages/protocol/lib/registry-utils.ts
index 82d3f155d0c..38e6e478235 100644
--- a/packages/protocol/lib/registry-utils.ts
+++ b/packages/protocol/lib/registry-utils.ts
@@ -2,6 +2,7 @@ export enum CeloContractName {
Attestations = 'Attestations',
BlockchainParameters = 'BlockchainParameters',
Election = 'Election',
+ EpochRewards = 'EpochRewards',
Escrow = 'Escrow',
Exchange = 'Exchange',
GasCurrencyWhitelist = 'GasCurrencyWhitelist',
diff --git a/packages/protocol/migrations/11_validators.ts b/packages/protocol/migrations/11_validators.ts
index 5f24860c230..d59b5d1fda7 100644
--- a/packages/protocol/migrations/11_validators.ts
+++ b/packages/protocol/migrations/11_validators.ts
@@ -13,7 +13,6 @@ const initializeArgs = async (): Promise => {
config.validators.deregistrationLockups.validator,
config.validators.validatorScoreParameters.exponent,
toFixed(config.validators.validatorScoreParameters.adjustmentSpeed).toFixed(),
- config.validators.validatorEpochPayment,
config.validators.membershipHistoryLength,
config.validators.maxGroupSize,
]
diff --git a/packages/protocol/migrations/13_epoch_rewards.ts b/packages/protocol/migrations/13_epoch_rewards.ts
new file mode 100644
index 00000000000..22448ef942a
--- /dev/null
+++ b/packages/protocol/migrations/13_epoch_rewards.ts
@@ -0,0 +1,21 @@
+import { CeloContractName } from '@celo/protocol/lib/registry-utils'
+import { deploymentForCoreContract } from '@celo/protocol/lib/web3-utils'
+import { config } from '@celo/protocol/migrationsConfig'
+import { toFixed } from '@celo/utils/lib/fixidity'
+import { EpochRewardsInstance } from 'types'
+
+const initializeArgs = async (): Promise => {
+ return [
+ config.registry.predeployedProxyAddress,
+ config.epochRewards.maxValidatorEpochPayment,
+ toFixed(config.epochRewards.maxTargetVotingYield).toFixed(),
+ toFixed(config.epochRewards.initialTargetVotingYield).toFixed(),
+ ]
+}
+
+module.exports = deploymentForCoreContract(
+ web3,
+ artifacts,
+ CeloContractName.EpochRewards,
+ initializeArgs
+)
diff --git a/packages/protocol/migrations/13_random.ts b/packages/protocol/migrations/14_random.ts
similarity index 100%
rename from packages/protocol/migrations/13_random.ts
rename to packages/protocol/migrations/14_random.ts
diff --git a/packages/protocol/migrations/14_attestations.ts b/packages/protocol/migrations/15_attestations.ts
similarity index 100%
rename from packages/protocol/migrations/14_attestations.ts
rename to packages/protocol/migrations/15_attestations.ts
diff --git a/packages/protocol/migrations/15_blockchainparams.ts b/packages/protocol/migrations/16_blockchainparams.ts
similarity index 100%
rename from packages/protocol/migrations/15_blockchainparams.ts
rename to packages/protocol/migrations/16_blockchainparams.ts
diff --git a/packages/protocol/migrations/15_escrow.ts b/packages/protocol/migrations/17_escrow.ts
similarity index 100%
rename from packages/protocol/migrations/15_escrow.ts
rename to packages/protocol/migrations/17_escrow.ts
diff --git a/packages/protocol/migrations/16_governance.ts b/packages/protocol/migrations/18_governance.ts
similarity index 100%
rename from packages/protocol/migrations/16_governance.ts
rename to packages/protocol/migrations/18_governance.ts
diff --git a/packages/protocol/migrations/17_elect_validators.ts b/packages/protocol/migrations/19_elect_validators.ts
similarity index 100%
rename from packages/protocol/migrations/17_elect_validators.ts
rename to packages/protocol/migrations/19_elect_validators.ts
diff --git a/packages/protocol/migrationsConfig.js b/packages/protocol/migrationsConfig.js
index 1a82380e693..ad3eafdc90f 100644
--- a/packages/protocol/migrationsConfig.js
+++ b/packages/protocol/migrationsConfig.js
@@ -23,6 +23,11 @@ const DefaultConfig = {
maxVotesPerAccount: 3,
electabilityThreshold: 1 / 100,
},
+ epochRewards: {
+ maxValidatorEpochPayment: '1000000000000000000',
+ maxTargetVotingYield: 2 / 10,
+ initialTargetVotingYield: 5 / 100,
+ },
exchange: {
spread: 5 / 1000,
reserveFraction: 1,
@@ -81,7 +86,6 @@ const DefaultConfig = {
exponent: 1,
adjustmentSpeed: 0.1,
},
- validatorEpochPayment: '1000000000000000000',
membershipHistoryLength: 60,
maxGroupSize: '70',
diff --git a/packages/protocol/test/governance/epochrewards.ts b/packages/protocol/test/governance/epochrewards.ts
new file mode 100644
index 00000000000..22f25c07bb7
--- /dev/null
+++ b/packages/protocol/test/governance/epochrewards.ts
@@ -0,0 +1,163 @@
+import { CeloContractName } from '@celo/protocol/lib/registry-utils'
+import { assertContainSubset, assertEqualBN, assertRevert } from '@celo/protocol/lib/test-utils'
+import BigNumber from 'bignumber.js'
+import {
+ MockElectionContract,
+ MockElectionInstance,
+ EpochRewardsTestContract,
+ EpochRewardsTestInstance,
+ RegistryContract,
+ RegistryInstance,
+} from 'types'
+import { toFixed } from '@celo/utils/lib/fixidity'
+
+const EpochRewards: EpochRewardsTestContract = artifacts.require('EpochRewardsTest')
+const MockElection: MockElectionContract = artifacts.require('MockElection')
+const Registry: RegistryContract = artifacts.require('Registry')
+
+// @ts-ignore
+// TODO(mcortesi): Use BN
+EpochRewards.numberFormat = 'BigNumber'
+
+contract('EpochRewards', (accounts: string[]) => {
+ let epochRewards: EpochRewardsTestInstance
+ let mockElection: MockElectionInstance
+ let registry: RegistryInstance
+ const nonOwner = accounts[1]
+
+ const maxValidatorEpochPayment = new BigNumber(10000000000000)
+ const maxTargetVotingYield = toFixed(new BigNumber(1 / 20))
+ const initialTargetVotingYield = toFixed(new BigNumber(5 / 100))
+ beforeEach(async () => {
+ epochRewards = await EpochRewards.new()
+ mockElection = await MockElection.new()
+ registry = await Registry.new()
+ await registry.setAddressFor(CeloContractName.Election, mockElection.address)
+ await epochRewards.initialize(
+ registry.address,
+ maxValidatorEpochPayment,
+ maxTargetVotingYield,
+ initialTargetVotingYield
+ )
+ })
+
+ describe('#initialize()', () => {
+ it('should have set the owner', async () => {
+ const owner: string = await epochRewards.owner()
+ assert.equal(owner, accounts[0])
+ })
+
+ it('should have set the max validator epoch payment', async () => {
+ assertEqualBN(await epochRewards.maxValidatorEpochPayment(), maxValidatorEpochPayment)
+ })
+
+ it('should have set the max target voting yield', async () => {
+ assertEqualBN(await epochRewards.getMaxTargetVotingYield(), maxTargetVotingYield)
+ })
+
+ it('should have set the target voting yield', async () => {
+ assertEqualBN(await epochRewards.getTargetVotingYield(), initialTargetVotingYield)
+ })
+
+ it('should not be callable again', async () => {
+ await assertRevert(
+ epochRewards.initialize(
+ registry.address,
+ maxValidatorEpochPayment,
+ maxTargetVotingYield,
+ initialTargetVotingYield
+ )
+ )
+ })
+ })
+
+ describe('#setMaxValidatorEpochPayment()', () => {
+ describe('when the payment is different', () => {
+ const newPayment = maxValidatorEpochPayment.plus(1)
+
+ describe('when called by the owner', () => {
+ let resp: any
+
+ beforeEach(async () => {
+ resp = await epochRewards.setMaxValidatorEpochPayment(newPayment)
+ })
+
+ it('should set the max validator epoch payment', async () => {
+ assertEqualBN(await epochRewards.maxValidatorEpochPayment(), newPayment)
+ })
+
+ it('should emit the MaxValidatorEpochPaymentSet event', async () => {
+ assert.equal(resp.logs.length, 1)
+ const log = resp.logs[0]
+ assertContainSubset(log, {
+ event: 'MaxValidatorEpochPaymentSet',
+ args: {
+ value: new BigNumber(newPayment),
+ },
+ })
+ })
+
+ describe('when called by a non-owner', () => {
+ it('should revert', async () => {
+ await assertRevert(
+ epochRewards.setMaxValidatorEpochPayment(newPayment, {
+ from: nonOwner,
+ })
+ )
+ })
+ })
+ })
+
+ describe('when the payment is the same', () => {
+ it('should revert', async () => {
+ await assertRevert(epochRewards.setMaxValidatorEpochPayment(maxValidatorEpochPayment))
+ })
+ })
+ })
+ })
+
+ describe('#setMaxTargetVotingYield()', () => {
+ describe('when the yield is different', () => {
+ const newYield = maxTargetVotingYield.plus(1)
+
+ describe('when called by the owner', () => {
+ let resp: any
+
+ beforeEach(async () => {
+ resp = await epochRewards.setMaxTargetVotingYield(newYield)
+ })
+
+ it('should set the max target voting yield', async () => {
+ assertEqualBN(await epochRewards.getMaxTargetVotingYield(), newYield)
+ })
+
+ it('should emit the MaxTargetVotingYieldSet event', async () => {
+ assert.equal(resp.logs.length, 1)
+ const log = resp.logs[0]
+ assertContainSubset(log, {
+ event: 'MaxTargetVotingYieldSet',
+ args: {
+ yield: new BigNumber(newYield),
+ },
+ })
+ })
+
+ describe('when called by a non-owner', () => {
+ it('should revert', async () => {
+ await assertRevert(
+ epochRewards.setMaxTargetVotingYield(newYield, {
+ from: nonOwner,
+ })
+ )
+ })
+ })
+ })
+
+ describe('when the yield is the same', () => {
+ it('should revert', async () => {
+ await assertRevert(epochRewards.setMaxTargetVotingYield(maxTargetVotingYield))
+ })
+ })
+ })
+ })
+})
diff --git a/packages/protocol/test/governance/validators.ts b/packages/protocol/test/governance/validators.ts
index 6ea4cc15ef5..7ec6899cbfc 100644
--- a/packages/protocol/test/governance/validators.ts
+++ b/packages/protocol/test/governance/validators.ts
@@ -65,7 +65,6 @@ const DAY = 24 * HOUR
// Hard coded in ganache.
const EPOCH = 100
-// TODO(asa): Test epoch payment distribution
contract('Validators', (accounts: string[]) => {
let validators: ValidatorsTestInstance
let registry: RegistryInstance
@@ -85,7 +84,6 @@ contract('Validators', (accounts: string[]) => {
exponent: new BigNumber(5),
adjustmentSpeed: toFixed(0.25),
}
- const validatorEpochPayment = new BigNumber(10000000000000)
const membershipHistoryLength = new BigNumber(5)
const maxGroupSize = new BigNumber(5)
@@ -114,7 +112,6 @@ contract('Validators', (accounts: string[]) => {
validatorLockedGoldRequirements.duration,
validatorScoreParameters.exponent,
validatorScoreParameters.adjustmentSpeed,
- validatorEpochPayment,
membershipHistoryLength,
maxGroupSize
)
@@ -172,11 +169,6 @@ contract('Validators', (accounts: string[]) => {
assertEqualBN(adjustmentSpeed, validatorScoreParameters.adjustmentSpeed)
})
- it('should have set the validator epoch payment', async () => {
- const actual = await validators.validatorEpochPayment()
- assertEqualBN(actual, validatorEpochPayment)
- })
-
it('should have set the membership history length', async () => {
const actual = await validators.membershipHistoryLength()
assertEqualBN(actual, membershipHistoryLength)
@@ -197,7 +189,6 @@ contract('Validators', (accounts: string[]) => {
validatorLockedGoldRequirements.duration,
validatorScoreParameters.exponent,
validatorScoreParameters.adjustmentSpeed,
- validatorEpochPayment,
membershipHistoryLength,
maxGroupSize
)
@@ -205,51 +196,6 @@ contract('Validators', (accounts: string[]) => {
})
})
- describe('#setValidatorEpochPayment()', () => {
- describe('when the payment is different', () => {
- const newPayment = validatorEpochPayment.plus(1)
-
- describe('when called by the owner', () => {
- let resp: any
-
- beforeEach(async () => {
- resp = await validators.setValidatorEpochPayment(newPayment)
- })
-
- it('should set the validator epoch payment', async () => {
- assertEqualBN(await validators.validatorEpochPayment(), newPayment)
- })
-
- it('should emit the ValidatorEpochPaymentSet event', async () => {
- assert.equal(resp.logs.length, 1)
- const log = resp.logs[0]
- assertContainSubset(log, {
- event: 'ValidatorEpochPaymentSet',
- args: {
- value: new BigNumber(newPayment),
- },
- })
- })
-
- describe('when called by a non-owner', () => {
- it('should revert', async () => {
- await assertRevert(
- validators.setValidatorEpochPayment(newPayment, {
- from: nonOwner,
- })
- )
- })
- })
- })
-
- describe('when the payment is the same', () => {
- it('should revert', async () => {
- await assertRevert(validators.setValidatorEpochPayment(validatorEpochPayment))
- })
- })
- })
- })
-
describe('#setMembershipHistoryLength()', () => {
describe('when the length is different', () => {
const newLength = membershipHistoryLength.plus(1)
@@ -1757,6 +1703,7 @@ contract('Validators', (accounts: string[]) => {
describe('#distributeEpochPayment', () => {
const validator = accounts[0]
const group = accounts[1]
+ const maxPayment = new BigNumber(20122394876)
let mockStableToken: MockStableTokenInstance
beforeEach(async () => {
await registerValidatorGroupWithMembers(group, [validator])
@@ -1765,6 +1712,7 @@ contract('Validators', (accounts: string[]) => {
})
describe('when the validator score is non-zero', () => {
+ let ret: BigNumber
const uptime = new BigNumber(0.99)
const adjustmentSpeed = fromFixed(validatorScoreParameters.adjustmentSpeed)
// @ts-ignore
@@ -1780,7 +1728,8 @@ contract('Validators', (accounts: string[]) => {
describe('when the validator and group meet the balance requirements', () => {
beforeEach(async () => {
- await validators.distributeEpochPayment(validator)
+ ret = await validators.distributeEpochPayment(validator, maxPayment).call()
+ await validators.distributeEpochPayment(validator, maxPayment)
})
it('should pay the validator', async () => {
@@ -1790,6 +1739,10 @@ contract('Validators', (accounts: string[]) => {
it('should pay the group', async () => {
assertEqualBN(await mockStableToken.balanceOf(group), expectedGroupPayment)
})
+
+ it('should return the expected total payment', async () => {
+ assertEqualBN(ret, expectedTotalPayment)
+ })
})
describe('when the validator does not meet the balance requirements', () => {
@@ -1798,7 +1751,8 @@ contract('Validators', (accounts: string[]) => {
validator,
validatorLockedGoldRequirements.value.minus(1)
)
- await validators.distributeEpochPayment(validator)
+ ret = await validators.distributeEpochPayment(validator, maxPayment).call()
+ await validators.distributeEpochPayment(validator, maxPayment)
})
it('should not pay the validator', async () => {
@@ -1808,6 +1762,10 @@ contract('Validators', (accounts: string[]) => {
it('should not pay the group', async () => {
assertEqualBN(await mockStableToken.balanceOf(group), 0)
})
+
+ it('should return zero', async () => {
+ assertEqualBN(ret, 0)
+ })
})
describe('when the group does not meet the balance requirements', () => {
@@ -1816,7 +1774,8 @@ contract('Validators', (accounts: string[]) => {
group,
groupLockedGoldRequirements.value.minus(1)
)
- await validators.distributeEpochPayment(validator)
+ ret = await validators.distributeEpochPayment(validator, maxPayment).call()
+ await validators.distributeEpochPayment(validator, maxPayment)
})
it('should not pay the validator', async () => {
@@ -1826,6 +1785,10 @@ contract('Validators', (accounts: string[]) => {
it('should not pay the group', async () => {
assertEqualBN(await mockStableToken.balanceOf(group), 0)
})
+
+ it('should return zero', async () => {
+ assertEqualBN(ret, 0)
+ })
})
})
})
From 44d6f51a2abe02bc21590ce65fddd3944cfe3821 Mon Sep 17 00:00:00 2001
From: Asa Oines
Date: Thu, 24 Oct 2019 11:07:25 -0700
Subject: [PATCH 085/149] Fix contractkit test
---
packages/contractkit/src/wrappers/SortedOracles.test.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/packages/contractkit/src/wrappers/SortedOracles.test.ts b/packages/contractkit/src/wrappers/SortedOracles.test.ts
index 349dde6d439..cc9cbb26e9f 100644
--- a/packages/contractkit/src/wrappers/SortedOracles.test.ts
+++ b/packages/contractkit/src/wrappers/SortedOracles.test.ts
@@ -13,7 +13,7 @@ testWithGanache('SortedOracles Wrapper', (web3) => {
// from the MNEMONIC. If the MNEMONIC has changed, these will need to be reset.
// To do that, look at the output of web3.eth.getAccounts(), and pick a few
// addresses from that set to be oracles
- const stableTokenOracles: Address[] = NetworkConfig.stableToken.priceOracleAccounts
+ const stableTokenOracles: Address[] = NetworkConfig.stableToken.oracles
const oracleAddress = stableTokenOracles[stableTokenOracles.length - 1]
const kit = newKitFromWeb3(web3)
From aaf02cd0597a0a006fd65a49f7bdd35f68b7908b Mon Sep 17 00:00:00 2001
From: Asa Oines
Date: Thu, 24 Oct 2019 11:46:11 -0700
Subject: [PATCH 086/149] Remove outdated comment
---
packages/protocol/contracts/governance/Election.sol | 1 -
1 file changed, 1 deletion(-)
diff --git a/packages/protocol/contracts/governance/Election.sol b/packages/protocol/contracts/governance/Election.sol
index 43a1361b564..0d0f681971d 100644
--- a/packages/protocol/contracts/governance/Election.sol
+++ b/packages/protocol/contracts/governance/Election.sol
@@ -88,7 +88,6 @@ contract Election is
uint256 public maxNumGroupsVotedFor;
// Groups must receive at least this fraction of the total votes in order to be considered in
// elections.
- // TODO(asa): Implement this constraint.
FixidityLib.Fraction public electabilityThreshold;
event ElectableValidatorsSet(
From ec88d47c72fd3334d9393b1e248001f7c7c98ca4 Mon Sep 17 00:00:00 2001
From: Asa Oines
Date: Thu, 24 Oct 2019 15:29:40 -0700
Subject: [PATCH 087/149] More work
---
.../contracts/governance/EpochRewards.sol | 141 +++++++++++-------
.../protocol/migrations/13_epoch_rewards.ts | 7 +-
packages/protocol/migrationsConfig.js | 11 +-
.../protocol/test/governance/epochrewards.ts | 140 +++++++++++++----
4 files changed, 219 insertions(+), 80 deletions(-)
diff --git a/packages/protocol/contracts/governance/EpochRewards.sol b/packages/protocol/contracts/governance/EpochRewards.sol
index f9f1d68412e..a7889d6b50d 100644
--- a/packages/protocol/contracts/governance/EpochRewards.sol
+++ b/packages/protocol/contracts/governance/EpochRewards.sol
@@ -22,42 +22,63 @@ contract EpochRewards is Ownable, Initializable, UsingPrecompiles, UsingRegistry
uint256 constant SECONDS_LINEAR = YEARS_LINEAR * 365 * 1 days;
uint256 constant FIXIDITY_E = 2718281828459045235360287;
uint256 constant FIXIDITY_LN2 = 693147180559945309417232;
+
+ struct RewardsMultiplierAdjustmentFactors {
+ FixidityLib.Fraction underspend;
+ FixidityLib.Fraction overspend;
+ }
+
+ struct TargetVotingYieldParameters {
+ FixidityLib.Fraction target;
+ FixidityLib.Fraction max;
+ FixidityLib.Fraction adjustmentFactor;
+ }
+
uint256 private startTime = 0;
+ RewardsMultiplierAdjustmentFactors private rewardsMultiplierAdjustmentFactors;
+ TargetVotingYieldParameters private targetVotingYieldParams;
FixidityLib.Fraction private targetVotingGoldFraction;
- FixidityLib.Fraction private targetVotingYield;
- FixidityLib.Fraction private maxTargetVotingYield;
- FixidityLib.Fraction private targetVotingYieldAdjustmentFactor;
uint256 public maxValidatorEpochPayment;
event MaxValidatorEpochPaymentSet(uint256 payment);
- event MaxTargetVotingYieldSet(uint256 yield);
+ event TargetVotingYieldParametersSet(uint256 max, uint256 adjustmentFactor);
+ event RewardsMultiplierAdjustmentFactorsSet(uint256 underspend, uint256 overspend);
/**
* @param _maxValidatorEpochPayment The duration the above gold remains locked after deregistration.
*/
function initialize(
address registryAddress,
- uint256 _maxValidatorEpochPayment,
- uint256 _maxTargetVotingYield,
- uint256 _targetVotingYield
+ uint256 targetVotingYieldInitial,
+ uint256 targetVotingYieldMax,
+ uint256 targetVotingYieldAdjustmentFactor,
+ uint256 rewardsMultiplierUnderspendAdjustmentFactor,
+ uint256 rewardsMultiplierOverspendAdjustmentFactor,
+ uint256 _maxValidatorEpochPayment
)
external
initializer
{
_transferOwnership(msg.sender);
setRegistry(registryAddress);
- setMaxTargetVotingYield(_maxTargetVotingYield);
+ setTargetVotingYieldParameters(targetVotingYieldMax, targetVotingYieldAdjustmentFactor);
+ setRewardsMultiplierAdjustmentFactors(
+ rewardsMultiplierUnderspendAdjustmentFactor,
+ rewardsMultiplierOverspendAdjustmentFactor
+ );
setMaxValidatorEpochPayment(_maxValidatorEpochPayment);
- targetVotingYield = FixidityLib.wrap(_targetVotingYield);
+ targetVotingYieldParams.target = FixidityLib.wrap(targetVotingYieldInitial);
startTime = now;
}
- function getMaxTargetVotingYield() external view returns (uint256) {
- return maxTargetVotingYield.unwrap();
+ function getTargetVotingYieldParameters() external view returns (uint256, uint256, uint256) {
+ TargetVotingYieldParameters storage params = targetVotingYieldParams;
+ return (params.target.unwrap(), params.max.unwrap(), params.adjustmentFactor.unwrap());
}
- function getTargetVotingYield() external view returns (uint256) {
- return targetVotingYield.unwrap();
+ function getRewardsMultiplierAdjustmentFactors() external view returns (uint256, uint256) {
+ RewardsMultiplierAdjustmentFactors storage factors = rewardsMultiplierAdjustmentFactors;
+ return (factors.underspend.unwrap(), factors.overspend.unwrap());
}
/**
@@ -72,20 +93,36 @@ contract EpochRewards is Ownable, Initializable, UsingPrecompiles, UsingRegistry
return true;
}
- function setMaxTargetVotingYield(uint256 yield) public onlyOwner returns (bool) {
- require(yield != maxTargetVotingYield.unwrap());
- maxTargetVotingYield = FixidityLib.wrap(yield);
+ function setRewardsMultiplierAdjustmentFactors(uint256 underspend, uint256 overspend) public onlyOwner returns (bool) {
require(
- maxTargetVotingYield.lt(FixidityLib.fixed1()),
- "Max voting yield must be lower than 100%"
+ underspend != rewardsMultiplierAdjustmentFactors.underspend.unwrap() ||
+ overspend != rewardsMultiplierAdjustmentFactors.overspend.unwrap()
);
- emit MaxTargetVotingYieldSet(yield);
+ rewardsMultiplierAdjustmentFactors = RewardsMultiplierAdjustmentFactors(
+ FixidityLib.wrap(underspend),
+ FixidityLib.wrap(overspend)
+ );
+ emit RewardsMultiplierAdjustmentFactorsSet(underspend, overspend);
+ return true;
+ }
+
+ function setTargetVotingYieldParameters(uint256 max, uint256 adjustmentFactor) public onlyOwner returns (bool) {
+ require(
+ max != targetVotingYieldParams.max.unwrap() ||
+ adjustmentFactor != targetVotingYieldParams.adjustmentFactor.unwrap()
+ );
+ targetVotingYieldParams.max = FixidityLib.wrap(max);
+ targetVotingYieldParams.adjustmentFactor = FixidityLib.wrap(adjustmentFactor);
+ require(
+ targetVotingYieldParams.max.lt(FixidityLib.fixed1()),
+ "Max target voting yield must be lower than 100%"
+ );
+ emit TargetVotingYieldParamsSet(max, adjustmentFactor);
return true;
}
function _getTargetGoldTotalSupply() internal view returns (uint256) {
uint256 timeSinceInitialization = now.sub(startTime);
- uint256 targetGoldSupply = 0;
if (timeSinceInitialization < SECONDS_LINEAR) {
// Pay out half of all block rewards linearly.
uint256 linearRewards = GOLD_SUPPLY_CAP.sub(GENESIS_GOLD_SUPPLY).div(2);
@@ -93,44 +130,46 @@ contract EpochRewards is Ownable, Initializable, UsingPrecompiles, UsingRegistry
return targetRewards.add(GENESIS_GOLD_SUPPLY);
} else {
/*
- FixidityLib.Fraction memory exponentialDecaryHalfLife = FixidityLib.wrap(
- FIXIDITY_LN2.div(YEARS_LINEAR)
- );
+ // Pay out the remaining half according to the following rule:
+ // REMAINING_REWARDS = EXPONENTIAL_REWARDS * e ^ (-1*(t - SECONDS_LINEAR) / SECONDS_LINEAR)
uint256 exponentialSeconds = timeSinceInitialization.sub(SECONDS_LINEAR);
uint256 exponentialRewards = GOLD_SUPPLY_CAP.sub(GENESIS_GOLD_SUPPLY).div(2);
- // 1000 - 200 * e ^ ((- 1 / 15) * (x - 15))
- (uint256 numerator, uint256 denominator) = fractionMulExp(FixidityLib.fixed1(), FixidityLib.fixed1(), FIXIDITY_E, FixidityLib.fixed1(), ???, ???);
+ // TODO(asa): FractionMulExp does not support fractional exponents.
+ (uint256 numerator, uint256 denominator) = fractionMulExp(
+ FixidityLib.fixed1(),
+ FixidityLib.fixed1(),
+ FIXIDITY_E,
+ FixidityLib.fixed1(),
+ exponentialSeconds,
+ SECONDS_LINEAR
+ );
+ // Flip numerator and denominator to account for negative exponent.
+ uint256 remainingRewards = exponentialRewards.mul(denominator).div(numerator);
+ return GOLD_SUPPLY_CAP.sub(remainingRewards);
*/
- // TODO(asa): This isn't implemented.
- require(false);
return 0;
}
}
- // TODO(asa): Finish this.
function _getRewardsMultiplier(uint256 targetGoldSupplyIncrease) internal view returns (FixidityLib.Fraction memory) {
uint256 targetSupply = _getTargetGoldTotalSupply();
- uint256 totalSupplyWithRewards = getGoldToken().totalSupply().add(targetGoldSupplyIncrease);
- if (totalSupplyWithRewards > targetSupply) {
- // uint256 delta = totalSupplyWithRewards.sub(targetSupply);
- return FixidityLib.fixed1();
-
- // FixidityLib.Fraction memory deviation = FixidityLib.newFixed(delta).
- /*
- B_actual_t = supply_cap - (totalSupplyWithRewards);
- B_target_t = supply_cap - (targetSupply);
- B_actual_t - Z_t = B_actual_t - targetRewards -
- */
- } else if (totalSupplyWithRewards < targetSupply) {
- // uint256 delta = targetSupply.sub(totalSupplyWithRewards);
- return FixidityLib.fixed1();
+ uint256 totalSupply = getGoldToken().totalSupply();
+ uint256 remainingSupply = GOLD_SUPPLY_CAP.sub(totalSupply.add(targetGoldSupplyIncrease));
+ uint256 targetRemainingSupply = GOLD_SUPPLY_CAP.sub(targetSupply);
+ FixidityLib.Fraction memory ratio = FixidityLib.newFixed(remainingSupply).divide(FixidityLib.newFixed(targetRemainingSupply));
+ if (ratio.gt(FixidityLib.fixed1())) {
+ FixidityLib.Fraction memory delta = ratio.subtract(FixidityLib.fixed1());
+ return delta.multiply(rewardsMultiplierAdjustmentFactors.underspend);
+ } else if (ratio.lt(FixidityLib.fixed1())) {
+ FixidityLib.Fraction memory delta = FixidityLib.fixed1().subtract(ratio);
+ return delta.multiply(rewardsMultiplierAdjustmentFactors.overspend);
} else {
return FixidityLib.fixed1();
}
}
function _getTargetEpochRewards() internal view returns (uint256) {
- return FixidityLib.newFixed(getElection().getActiveVotes()).multiply(targetVotingYield).fromFixed();
+ return FixidityLib.newFixed(getElection().getActiveVotes()).multiply(targetVotingYieldParams.target).fromFixed();
}
function _getTargetTotalEpochPaymentsInGold() internal view returns (uint256) {
@@ -150,18 +189,18 @@ contract EpochRewards is Ownable, Initializable, UsingPrecompiles, UsingRegistry
FixidityLib.Fraction memory votingGoldFraction = FixidityLib.newFixed(liquidGold).divide(FixidityLib.newFixed(votingGold));
if (votingGoldFraction.gt(targetVotingGoldFraction)) {
FixidityLib.Fraction memory votingGoldFractionDelta = votingGoldFraction.subtract(targetVotingGoldFraction);
- FixidityLib.Fraction memory targetVotingYieldDelta = votingGoldFractionDelta.multiply(targetVotingYieldAdjustmentFactor);
- if (targetVotingYieldDelta.gte(targetVotingYield)) {
- targetVotingYield = FixidityLib.newFixed(0);
+ FixidityLib.Fraction memory targetVotingYieldDelta = votingGoldFractionDelta.multiply(targetVotingYieldParams.adjustmentFactor);
+ if (targetVotingYieldDelta.gte(targetVotingYieldParams.target)) {
+ targetVotingYieldParams.target = FixidityLib.newFixed(0);
} else {
- targetVotingYield = targetVotingYield.subtract(targetVotingYieldDelta);
+ targetVotingYieldParams.target = targetVotingYieldParams.target.subtract(targetVotingYieldDelta);
}
} else {
FixidityLib.Fraction memory votingGoldFractionDelta = targetVotingGoldFraction.subtract(votingGoldFraction);
- FixidityLib.Fraction memory targetVotingYieldDelta = votingGoldFractionDelta.multiply(targetVotingYieldAdjustmentFactor);
- targetVotingYield = targetVotingYield.add(targetVotingYieldDelta);
- if (targetVotingYield.gt(maxTargetVotingYield)) {
- targetVotingYield = maxTargetVotingYield;
+ FixidityLib.Fraction memory targetVotingYieldDelta = votingGoldFractionDelta.multiply(targetVotingYieldParams.adjustmentFactor);
+ targetVotingYieldParams.target = targetVotingYieldParams.target.add(targetVotingYieldDelta);
+ if (targetVotingYieldParams.target.gt(targetVotingYieldParams.max)) {
+ targetVotingYieldParams.target = targetVotingYieldParams.max;
}
}
}
diff --git a/packages/protocol/migrations/13_epoch_rewards.ts b/packages/protocol/migrations/13_epoch_rewards.ts
index 22448ef942a..19355526cef 100644
--- a/packages/protocol/migrations/13_epoch_rewards.ts
+++ b/packages/protocol/migrations/13_epoch_rewards.ts
@@ -7,9 +7,12 @@ import { EpochRewardsInstance } from 'types'
const initializeArgs = async (): Promise => {
return [
config.registry.predeployedProxyAddress,
+ toFixed(config.epochRewards.targetVotingYieldParameters.initial).toFixed(),
+ toFixed(config.epochRewards.targetVotingYieldParameters.max).toFixed(),
+ toFixed(config.epochRewards.targetVotingYieldParameters.adjustmentFactor).toFixed(),
+ toFixed(config.epochRewards.rewardsMultiplierAdjustmentFactors.underspend).toFixed(),
+ toFixed(config.epochRewards.rewardsMultiplierAdjustmentFactors.overspend).toFixed(),
config.epochRewards.maxValidatorEpochPayment,
- toFixed(config.epochRewards.maxTargetVotingYield).toFixed(),
- toFixed(config.epochRewards.initialTargetVotingYield).toFixed(),
]
}
diff --git a/packages/protocol/migrationsConfig.js b/packages/protocol/migrationsConfig.js
index ad3eafdc90f..f27ebc87a88 100644
--- a/packages/protocol/migrationsConfig.js
+++ b/packages/protocol/migrationsConfig.js
@@ -24,9 +24,16 @@ const DefaultConfig = {
electabilityThreshold: 1 / 100,
},
epochRewards: {
+ targetVotingYieldParameters: {
+ initial: 5 / 100,
+ max: 2 / 10,
+ adjustmentFactor: 1,
+ },
+ rewardsMultiplierAdjustmentFactors: {
+ underspend: 1 / 2,
+ overspend: 5,
+ },
maxValidatorEpochPayment: '1000000000000000000',
- maxTargetVotingYield: 2 / 10,
- initialTargetVotingYield: 5 / 100,
},
exchange: {
spread: 5 / 1000,
diff --git a/packages/protocol/test/governance/epochrewards.ts b/packages/protocol/test/governance/epochrewards.ts
index 22f25c07bb7..7ce8348ff21 100644
--- a/packages/protocol/test/governance/epochrewards.ts
+++ b/packages/protocol/test/governance/epochrewards.ts
@@ -25,9 +25,16 @@ contract('EpochRewards', (accounts: string[]) => {
let registry: RegistryInstance
const nonOwner = accounts[1]
+ const targetVotingYieldParams = {
+ initial: toFixed(new BigNumber(1 / 20)),
+ max: toFixed(new BigNumber(1 / 5)),
+ adjustmentFactor: toFixed(new BigNumber(1)),
+ }
+ const rewardsMultiplierAdjustments = {
+ underspend: toFixed(new BigNumber(1 / 2)),
+ overspend: toFixed(new BigNumber(5)),
+ }
const maxValidatorEpochPayment = new BigNumber(10000000000000)
- const maxTargetVotingYield = toFixed(new BigNumber(1 / 20))
- const initialTargetVotingYield = toFixed(new BigNumber(5 / 100))
beforeEach(async () => {
epochRewards = await EpochRewards.new()
mockElection = await MockElection.new()
@@ -35,9 +42,12 @@ contract('EpochRewards', (accounts: string[]) => {
await registry.setAddressFor(CeloContractName.Election, mockElection.address)
await epochRewards.initialize(
registry.address,
- maxValidatorEpochPayment,
- maxTargetVotingYield,
- initialTargetVotingYield
+ targetVotingYieldParams.initial,
+ targetVotingYieldParams.max,
+ targetVotingYieldParams.adjustmentFactor,
+ rewardsMultiplierAdjustments.underspend,
+ rewardsMultiplierAdjustments.overspend,
+ maxValidatorEpochPayment
)
})
@@ -51,21 +61,29 @@ contract('EpochRewards', (accounts: string[]) => {
assertEqualBN(await epochRewards.maxValidatorEpochPayment(), maxValidatorEpochPayment)
})
- it('should have set the max target voting yield', async () => {
- assertEqualBN(await epochRewards.getMaxTargetVotingYield(), maxTargetVotingYield)
+ it('should have set the target voting yield parameters', async () => {
+ const [target, max, adjustmentFactor] = await epochRewards.getTargetVotingYieldParameters()
+ assertEqualBN(target, targetVotingYieldParams.initial)
+ assertEqualBN(max, targetVotingYieldParams.max)
+ assertEqualBN(adjustmentFactor, targetVotingYieldParams.adjustmentFactor)
})
- it('should have set the target voting yield', async () => {
- assertEqualBN(await epochRewards.getTargetVotingYield(), initialTargetVotingYield)
+ it('should have set the rewards multiplier adjustment factors', async () => {
+ const [underspend, overspend] = await epochRewards.getRewardsMultiplierAdjustmentFactors()
+ assertEqualBN(underspend, rewardsMultiplierAdjustments.underspend)
+ assertEqualBN(overspend, rewardsMultiplierAdjustments.overspend)
})
it('should not be callable again', async () => {
await assertRevert(
epochRewards.initialize(
registry.address,
- maxValidatorEpochPayment,
- maxTargetVotingYield,
- initialTargetVotingYield
+ targetVotingYieldParams.initial,
+ targetVotingYieldParams.max,
+ targetVotingYieldParams.adjustmentFactor,
+ rewardsMultiplierAdjustments.underspend,
+ rewardsMultiplierAdjustments.overspend,
+ maxValidatorEpochPayment
)
)
})
@@ -92,7 +110,7 @@ contract('EpochRewards', (accounts: string[]) => {
assertContainSubset(log, {
event: 'MaxValidatorEpochPaymentSet',
args: {
- value: new BigNumber(newPayment),
+ payment: newPayment,
},
})
})
@@ -116,28 +134,37 @@ contract('EpochRewards', (accounts: string[]) => {
})
})
- describe('#setMaxTargetVotingYield()', () => {
- describe('when the yield is different', () => {
- const newYield = maxTargetVotingYield.plus(1)
+ describe('#setRewardsMultiplierAdjustmentFactors()', () => {
+ describe('when the factors are different', () => {
+ const newFactors = {
+ underspend: rewardsMultiplierAdjustments.underspend.plus(1),
+ overspend: rewardsMultiplierAdjustments.overspend.plus(1),
+ }
describe('when called by the owner', () => {
let resp: any
beforeEach(async () => {
- resp = await epochRewards.setMaxTargetVotingYield(newYield)
+ resp = await epochRewards.setRewardsMultiplierAdjustmentFactors(
+ newFactors.underspend,
+ newFactors.overspend
+ )
})
- it('should set the max target voting yield', async () => {
- assertEqualBN(await epochRewards.getMaxTargetVotingYield(), newYield)
+ it('should set the new rewards multiplier adjustment factors', async () => {
+ const [underspend, overspend] = await epochRewards.getRewardsMultiplierAdjustmentFactors()
+ assertEqualBN(underspend, newFactors.underspend)
+ assertEqualBN(overspend, newFactors.overspend)
})
- it('should emit the MaxTargetVotingYieldSet event', async () => {
+ it('should emit the RewardsMultiplierAdjustmentFactorsSet event', async () => {
assert.equal(resp.logs.length, 1)
const log = resp.logs[0]
assertContainSubset(log, {
- event: 'MaxTargetVotingYieldSet',
+ event: 'RewardsMultiplierAdjustmentFactorsSet',
args: {
- yield: new BigNumber(newYield),
+ underspend: newFactors.underspend,
+ overspend: newFactors.overspend,
},
})
})
@@ -145,7 +172,65 @@ contract('EpochRewards', (accounts: string[]) => {
describe('when called by a non-owner', () => {
it('should revert', async () => {
await assertRevert(
- epochRewards.setMaxTargetVotingYield(newYield, {
+ epochRewards.setRewardsMultiplierAdjustmentFactors(
+ newFactors.underspend,
+ newFactors.overspend,
+ {
+ from: nonOwner,
+ }
+ )
+ )
+ })
+ })
+ })
+
+ describe('when the parameters are the same', () => {
+ it('should revert', async () => {
+ await assertRevert(
+ epochRewards.setRewardsMultiplierAdjustmentFactors(
+ rewardsMultiplierAdjustments.underspend,
+ rewardsMultiplierAdjustments.overspend
+ )
+ )
+ })
+ })
+ })
+ })
+
+ describe('#setTargetVotingYieldParameters()', () => {
+ describe('when the parameters are different', () => {
+ const newMax = targetVotingYieldParams.max.plus(1)
+ const newFactor = targetVotingYieldParams.adjustmentFactor.plus(1)
+
+ describe('when called by the owner', () => {
+ let resp: any
+
+ beforeEach(async () => {
+ resp = await epochRewards.setTargetVotingYieldParameters(newMax, newFactor)
+ })
+
+ it('should set the new target voting yield parameters', async () => {
+ const [, max, adjustmentFactor] = await epochRewards.getTargetVotingYieldParameters()
+ assertEqualBN(max, newMax)
+ assertEqualBN(adjustmentFactor, newFactor)
+ })
+
+ it('should emit the TargetVotingYieldParametersSet event', async () => {
+ assert.equal(resp.logs.length, 1)
+ const log = resp.logs[0]
+ assertContainSubset(log, {
+ event: 'TargetVotingYieldParametersSet',
+ args: {
+ max: newMax,
+ adjustmentFactor: newFactor,
+ },
+ })
+ })
+
+ describe('when called by a non-owner', () => {
+ it('should revert', async () => {
+ await assertRevert(
+ epochRewards.setTargetVotingYieldParameters(newMax, newFactor, {
from: nonOwner,
})
)
@@ -153,9 +238,14 @@ contract('EpochRewards', (accounts: string[]) => {
})
})
- describe('when the yield is the same', () => {
+ describe('when the parameters are the same', () => {
it('should revert', async () => {
- await assertRevert(epochRewards.setMaxTargetVotingYield(maxTargetVotingYield))
+ await assertRevert(
+ epochRewards.setTargetVotingYieldParameters(
+ targetVotingYieldParams.max,
+ targetVotingYieldParams.adjustmentFactor
+ )
+ )
})
})
})
From 59dedeab9833705e7428306a957629d09ef744e2 Mon Sep 17 00:00:00 2001
From: Asa Oines
Date: Fri, 25 Oct 2019 11:24:52 -0700
Subject: [PATCH 088/149] Fix Validators contract wrapper
---
.../contractkit/src/wrappers/Validators.ts | 47 +++++++++----------
1 file changed, 21 insertions(+), 26 deletions(-)
diff --git a/packages/contractkit/src/wrappers/Validators.ts b/packages/contractkit/src/wrappers/Validators.ts
index 74b9ffcccff..412668b6827 100644
--- a/packages/contractkit/src/wrappers/Validators.ts
+++ b/packages/contractkit/src/wrappers/Validators.ts
@@ -26,19 +26,14 @@ export interface ValidatorGroup {
commission: BigNumber
}
-export interface BalanceRequirements {
- group: BigNumber
- validator: BigNumber
-}
-
-export interface DeregistrationLockups {
- group: BigNumber
- validator: BigNumber
+export interface LockedGoldRequirements {
+ value: BigNumber
+ duration: BigNumber
}
export interface ValidatorsConfig {
- balanceRequirements: BalanceRequirements
- deregistrationLockups: DeregistrationLockups
+ groupLockedGoldRequirements: LockedGoldRequirements
+ validatorLockedGoldRequirements: LockedGoldRequirements
maxGroupSize: BigNumber
}
@@ -77,26 +72,26 @@ export class ValidatorsWrapper extends BaseWrapper {
}
}
/**
- * Returns the current registration requirements.
- * @returns Group and validator registration requirements.
+ * Returns the Locked Gold requirements for validators.
+ * @returns The Locked Gold requirements for validators.
*/
- async getBalanceRequirements(): Promise {
- const res = await this.contract.methods.getBalanceRequirements().call()
+ async getValidatorLockedGoldRequirements(): Promise {
+ const res = await this.contract.methods.getValidatorLockedGoldRequirements().call()
return {
- group: toBigNumber(res[0]),
- validator: toBigNumber(res[1]),
+ value: toBigNumber(res[0]),
+ duration: toBigNumber(res[1]),
}
}
/**
- * Returns the lockup periods after deregistering groups and validators.
- * @return The lockup periods after deregistering groups and validators.
+ * Returns the Locked Gold requirements for validator groups.
+ * @returns The Locked Gold requirements for validator groups.
*/
- async getDeregistrationLockups(): Promise {
- const res = await this.contract.methods.getDeregistrationLockups().call()
+ async getGroupLockedGoldRequirements(): Promise {
+ const res = await this.contract.methods.getGroupLockedGoldRequirements().call()
return {
- group: toBigNumber(res[0]),
- validator: toBigNumber(res[1]),
+ value: toBigNumber(res[0]),
+ duration: toBigNumber(res[1]),
}
}
@@ -105,13 +100,13 @@ export class ValidatorsWrapper extends BaseWrapper {
*/
async getConfig(): Promise {
const res = await Promise.all([
- this.getBalanceRequirements(),
- this.getDeregistrationLockups(),
+ this.getValidatorLockedGoldRequirements(),
+ this.getGroupLockedGoldRequirements(),
this.contract.methods.maxGroupSize().call(),
])
return {
- balanceRequirements: res[0],
- deregistrationLockups: res[1],
+ validatorLockedGoldRequirements: res[0],
+ groupLockedGoldRequirements: res[1],
maxGroupSize: toBigNumber(res[2]),
}
}
From 367429dcbd248232e50e19a7f9cb572d85611bc4 Mon Sep 17 00:00:00 2001
From: Asa Oines
Date: Wed, 30 Oct 2019 18:45:06 -0700
Subject: [PATCH 089/149] Unit tests for EpochRewards passing
---
.../test/MockGoldToken.sol | 5 +
.../contracts/governance/EpochRewards.sol | 61 ++--
.../governance/test/EpochRewardsTest.sol | 4 +
.../governance/test/MockElection.sol | 14 +-
.../contracts/stability/SortedOracles.sol | 2 +-
.../stability/test/MockSortedOracles.sol | 28 +-
.../protocol/migrations/13_epoch_rewards.ts | 1 +
packages/protocol/migrationsConfig.js | 1 +
.../protocol/test/governance/epochrewards.ts | 322 +++++++++++++++++-
9 files changed, 385 insertions(+), 53 deletions(-)
rename packages/protocol/contracts/{stability => common}/test/MockGoldToken.sol (76%)
diff --git a/packages/protocol/contracts/stability/test/MockGoldToken.sol b/packages/protocol/contracts/common/test/MockGoldToken.sol
similarity index 76%
rename from packages/protocol/contracts/stability/test/MockGoldToken.sol
rename to packages/protocol/contracts/common/test/MockGoldToken.sol
index e8146625f9c..371e3ebfb4d 100644
--- a/packages/protocol/contracts/stability/test/MockGoldToken.sol
+++ b/packages/protocol/contracts/common/test/MockGoldToken.sol
@@ -8,6 +8,11 @@ pragma solidity ^0.5.3;
contract MockGoldToken {
uint8 public constant decimals = 18;
+ uint256 public totalSupply;
+
+ function setTotalSupply(uint256 value) external {
+ totalSupply = value;
+ }
function transfer(address, uint256) external pure returns (bool) {
return true;
diff --git a/packages/protocol/contracts/governance/EpochRewards.sol b/packages/protocol/contracts/governance/EpochRewards.sol
index a7889d6b50d..16c822034d5 100644
--- a/packages/protocol/contracts/governance/EpochRewards.sol
+++ b/packages/protocol/contracts/governance/EpochRewards.sol
@@ -16,8 +16,8 @@ contract EpochRewards is Ownable, Initializable, UsingPrecompiles, UsingRegistry
using FixidityLib for FixidityLib.Fraction;
using SafeMath for uint256;
- uint256 constant GENESIS_GOLD_SUPPLY = 600000000;
- uint256 constant GOLD_SUPPLY_CAP = 1000000000;
+ uint256 constant GENESIS_GOLD_SUPPLY = 600000000000000000000000000;
+ uint256 constant GOLD_SUPPLY_CAP = 1000000000000000000000000000;
uint256 constant YEARS_LINEAR = 15;
uint256 constant SECONDS_LINEAR = YEARS_LINEAR * 365 * 1 days;
uint256 constant FIXIDITY_E = 2718281828459045235360287;
@@ -40,9 +40,11 @@ contract EpochRewards is Ownable, Initializable, UsingPrecompiles, UsingRegistry
FixidityLib.Fraction private targetVotingGoldFraction;
uint256 public maxValidatorEpochPayment;
+ event TargetVotingGoldFractionSet(uint256 fraction);
event MaxValidatorEpochPaymentSet(uint256 payment);
event TargetVotingYieldParametersSet(uint256 max, uint256 adjustmentFactor);
event RewardsMultiplierAdjustmentFactorsSet(uint256 underspend, uint256 overspend);
+ event Debug(uint256 value, string desc);
/**
* @param _maxValidatorEpochPayment The duration the above gold remains locked after deregistration.
@@ -54,6 +56,7 @@ contract EpochRewards is Ownable, Initializable, UsingPrecompiles, UsingRegistry
uint256 targetVotingYieldAdjustmentFactor,
uint256 rewardsMultiplierUnderspendAdjustmentFactor,
uint256 rewardsMultiplierOverspendAdjustmentFactor,
+ uint256 _targetVotingGoldFraction,
uint256 _maxValidatorEpochPayment
)
external
@@ -66,6 +69,7 @@ contract EpochRewards is Ownable, Initializable, UsingPrecompiles, UsingRegistry
rewardsMultiplierUnderspendAdjustmentFactor,
rewardsMultiplierOverspendAdjustmentFactor
);
+ setTargetVotingGoldFraction(_targetVotingGoldFraction);
setMaxValidatorEpochPayment(_maxValidatorEpochPayment);
targetVotingYieldParams.target = FixidityLib.wrap(targetVotingYieldInitial);
startTime = now;
@@ -81,8 +85,19 @@ contract EpochRewards is Ownable, Initializable, UsingPrecompiles, UsingRegistry
return (factors.underspend.unwrap(), factors.overspend.unwrap());
}
+ function setTargetVotingGoldFraction(uint256 value) public onlyOwner returns (bool) {
+ require(value != targetVotingGoldFraction.unwrap() && value < FixidityLib.fixed1().unwrap());
+ targetVotingGoldFraction = FixidityLib.wrap(value);
+ emit TargetVotingGoldFractionSet(value);
+ return true;
+ }
+
+ function getTargetVotingGoldFraction() external view returns (uint256) {
+ return targetVotingGoldFraction.unwrap();
+ }
+
/**
- m* @notice Sets the max per-epoch payment in Celo Dollars for validators.
+ * @notice Sets the max per-epoch payment in Celo Dollars for validators.
* @param value The value in Celo Dollars.
* @return True upon success.
*/
@@ -117,7 +132,7 @@ contract EpochRewards is Ownable, Initializable, UsingPrecompiles, UsingRegistry
targetVotingYieldParams.max.lt(FixidityLib.fixed1()),
"Max target voting yield must be lower than 100%"
);
- emit TargetVotingYieldParamsSet(max, adjustmentFactor);
+ emit TargetVotingYieldParametersSet(max, adjustmentFactor);
return true;
}
@@ -129,24 +144,7 @@ contract EpochRewards is Ownable, Initializable, UsingPrecompiles, UsingRegistry
uint256 targetRewards = linearRewards.mul(timeSinceInitialization).div(SECONDS_LINEAR);
return targetRewards.add(GENESIS_GOLD_SUPPLY);
} else {
- /*
- // Pay out the remaining half according to the following rule:
- // REMAINING_REWARDS = EXPONENTIAL_REWARDS * e ^ (-1*(t - SECONDS_LINEAR) / SECONDS_LINEAR)
- uint256 exponentialSeconds = timeSinceInitialization.sub(SECONDS_LINEAR);
- uint256 exponentialRewards = GOLD_SUPPLY_CAP.sub(GENESIS_GOLD_SUPPLY).div(2);
- // TODO(asa): FractionMulExp does not support fractional exponents.
- (uint256 numerator, uint256 denominator) = fractionMulExp(
- FixidityLib.fixed1(),
- FixidityLib.fixed1(),
- FIXIDITY_E,
- FixidityLib.fixed1(),
- exponentialSeconds,
- SECONDS_LINEAR
- );
- // Flip numerator and denominator to account for negative exponent.
- uint256 remainingRewards = exponentialRewards.mul(denominator).div(numerator);
- return GOLD_SUPPLY_CAP.sub(remainingRewards);
- */
+ // TODO(asa): Implement block reward calculation for years 15-30.
return 0;
}
}
@@ -159,10 +157,10 @@ contract EpochRewards is Ownable, Initializable, UsingPrecompiles, UsingRegistry
FixidityLib.Fraction memory ratio = FixidityLib.newFixed(remainingSupply).divide(FixidityLib.newFixed(targetRemainingSupply));
if (ratio.gt(FixidityLib.fixed1())) {
FixidityLib.Fraction memory delta = ratio.subtract(FixidityLib.fixed1());
- return delta.multiply(rewardsMultiplierAdjustmentFactors.underspend);
+ return delta.multiply(rewardsMultiplierAdjustmentFactors.underspend).add(FixidityLib.fixed1());
} else if (ratio.lt(FixidityLib.fixed1())) {
FixidityLib.Fraction memory delta = FixidityLib.fixed1().subtract(ratio);
- return delta.multiply(rewardsMultiplierAdjustmentFactors.overspend);
+ return FixidityLib.fixed1().subtract(delta.multiply(rewardsMultiplierAdjustmentFactors.overspend));
} else {
return FixidityLib.fixed1();
}
@@ -175,29 +173,34 @@ contract EpochRewards is Ownable, Initializable, UsingPrecompiles, UsingRegistry
function _getTargetTotalEpochPaymentsInGold() internal view returns (uint256) {
address stableTokenAddress = registry.getAddressForOrDie(STABLE_TOKEN_REGISTRY_ID);
(uint256 numerator, uint256 denominator) = getSortedOracles().medianRate(stableTokenAddress);
- uint256 targetEpochPayment = numberValidatorsInCurrentSet().mul(maxValidatorEpochPayment).mul(numerator).div(denominator);
+ uint256 targetEpochPayment = numberValidatorsInCurrentSet().mul(maxValidatorEpochPayment).mul(denominator).div(numerator);
return targetEpochPayment;
}
function _updateTargetVotingYield() internal {
- IERC20Token goldToken = getGoldToken();
// TODO(asa): Ignore custodial accounts.
address reserveAddress = registry.getAddressForOrDie(RESERVE_REGISTRY_ID);
- uint256 liquidGold = goldToken.totalSupply().sub(goldToken.balanceOf(reserveAddress));
+ uint256 liquidGold = getGoldToken().totalSupply().sub(reserveAddress.balance);
// TODO(asa): Should this be active votes?
uint256 votingGold = getElection().getTotalVotes();
- FixidityLib.Fraction memory votingGoldFraction = FixidityLib.newFixed(liquidGold).divide(FixidityLib.newFixed(votingGold));
+ FixidityLib.Fraction memory votingGoldFraction = FixidityLib.newFixed(votingGold).divide(FixidityLib.newFixed(liquidGold));
+ emit Debug(votingGoldFraction.unwrap(), "voting gold fraction");
+ emit Debug(targetVotingGoldFraction.unwrap(), "target voting gold fraction");
if (votingGoldFraction.gt(targetVotingGoldFraction)) {
FixidityLib.Fraction memory votingGoldFractionDelta = votingGoldFraction.subtract(targetVotingGoldFraction);
+ emit Debug(votingGoldFractionDelta.unwrap(), "voting gold fraction delta");
FixidityLib.Fraction memory targetVotingYieldDelta = votingGoldFractionDelta.multiply(targetVotingYieldParams.adjustmentFactor);
+ emit Debug(targetVotingYieldDelta.unwrap(), "target voting yield delta");
if (targetVotingYieldDelta.gte(targetVotingYieldParams.target)) {
targetVotingYieldParams.target = FixidityLib.newFixed(0);
} else {
targetVotingYieldParams.target = targetVotingYieldParams.target.subtract(targetVotingYieldDelta);
}
- } else {
+ } else if (votingGoldFraction.lt(targetVotingGoldFraction)) {
FixidityLib.Fraction memory votingGoldFractionDelta = targetVotingGoldFraction.subtract(votingGoldFraction);
+ emit Debug(votingGoldFractionDelta.unwrap(), "voting gold fraction delta");
FixidityLib.Fraction memory targetVotingYieldDelta = votingGoldFractionDelta.multiply(targetVotingYieldParams.adjustmentFactor);
+ emit Debug(targetVotingYieldDelta.unwrap(), "target voting yield delta");
targetVotingYieldParams.target = targetVotingYieldParams.target.add(targetVotingYieldDelta);
if (targetVotingYieldParams.target.gt(targetVotingYieldParams.max)) {
targetVotingYieldParams.target = targetVotingYieldParams.max;
diff --git a/packages/protocol/contracts/governance/test/EpochRewardsTest.sol b/packages/protocol/contracts/governance/test/EpochRewardsTest.sol
index 76a00de1c4a..d2b7f82a17f 100644
--- a/packages/protocol/contracts/governance/test/EpochRewardsTest.sol
+++ b/packages/protocol/contracts/governance/test/EpochRewardsTest.sol
@@ -27,4 +27,8 @@ contract EpochRewardsTest is EpochRewards {
function updateTargetVotingYield() external {
_updateTargetVotingYield();
}
+
+ function numberValidatorsInCurrentSet() public view returns (uint256) {
+ return 100;
+ }
}
diff --git a/packages/protocol/contracts/governance/test/MockElection.sol b/packages/protocol/contracts/governance/test/MockElection.sol
index 091b6f753dd..e4065c5c2ba 100644
--- a/packages/protocol/contracts/governance/test/MockElection.sol
+++ b/packages/protocol/contracts/governance/test/MockElection.sol
@@ -10,6 +10,8 @@ contract MockElection is IElection {
mapping(address => bool) public isIneligible;
mapping(address => bool) public isEligible;
address[] public electedValidators;
+ uint256 active;
+ uint256 total;
function markGroupIneligible(address account) external {
isIneligible[account] = true;
@@ -20,17 +22,25 @@ contract MockElection is IElection {
}
function getTotalVotes() external view returns (uint256) {
- return 0;
+ return total;
}
function getActiveVotes() external view returns (uint256) {
- return 0;
+ return active;
}
function getTotalVotesByAccount(address) external view returns (uint256) {
return 0;
}
+ function setActiveVotes(uint256 value) external {
+ active = value;
+ }
+
+ function setTotalVotes(uint256 value) external {
+ total = value;
+ }
+
function setElectedValidators(address[] calldata _electedValidators) external {
electedValidators = _electedValidators;
}
diff --git a/packages/protocol/contracts/stability/SortedOracles.sol b/packages/protocol/contracts/stability/SortedOracles.sol
index 9e569f2f422..f44e1abaca7 100644
--- a/packages/protocol/contracts/stability/SortedOracles.sol
+++ b/packages/protocol/contracts/stability/SortedOracles.sol
@@ -8,7 +8,7 @@ import "../common/linkedlists/AddressSortedLinkedListWithMedian.sol";
import "../common/linkedlists/SortedLinkedListWithMedian.sol";
-// TODO: don't treat timestamps as Fixidity values
+// TODO: Move SortedOracles to Fixidity
/**
* @title Maintains a sorted list of oracle exchange rates between Celo Gold and other currencies.
*/
diff --git a/packages/protocol/contracts/stability/test/MockSortedOracles.sol b/packages/protocol/contracts/stability/test/MockSortedOracles.sol
index e09ec291f2c..c519ebddeea 100644
--- a/packages/protocol/contracts/stability/test/MockSortedOracles.sol
+++ b/packages/protocol/contracts/stability/test/MockSortedOracles.sol
@@ -6,25 +6,17 @@ pragma solidity ^0.5.3;
*/
contract MockSortedOracles {
- mapping(address => uint128) public numerators;
- mapping(address => uint128) public denominators;
- mapping(address => uint128) public medianTimestamp;
- mapping(address => uint128) public numRates;
-
- function setMedianRate(
- address token,
- uint128 numerator,
- uint128 denominator
- )
- external
- returns (bool)
- {
+ uint256 public constant DENOMINATOR = 0x10000000000000000;
+ mapping(address => uint256) public numerators;
+ mapping(address => uint256) public medianTimestamp;
+ mapping(address => uint256) public numRates;
+
+ function setMedianRate(address token, uint256 numerator) external returns (bool) {
numerators[token] = numerator;
- denominators[token] = denominator;
return true;
}
- function setMedianTimestamp(address token, uint128 timestamp) external {
+ function setMedianTimestamp(address token, uint256 timestamp) external {
medianTimestamp[token] = timestamp;
}
@@ -33,11 +25,11 @@ contract MockSortedOracles {
medianTimestamp[token] = uint128(now);
}
- function setNumRates(address token, uint128 rate) external {
+ function setNumRates(address token, uint256 rate) external {
numRates[token] = rate;
}
- function medianRate(address token) external view returns (uint128, uint128) {
- return (numerators[token], denominators[token]);
+ function medianRate(address token) external view returns (uint256, uint256) {
+ return (numerators[token], DENOMINATOR);
}
}
diff --git a/packages/protocol/migrations/13_epoch_rewards.ts b/packages/protocol/migrations/13_epoch_rewards.ts
index 19355526cef..6562c70cd41 100644
--- a/packages/protocol/migrations/13_epoch_rewards.ts
+++ b/packages/protocol/migrations/13_epoch_rewards.ts
@@ -12,6 +12,7 @@ const initializeArgs = async (): Promise => {
toFixed(config.epochRewards.targetVotingYieldParameters.adjustmentFactor).toFixed(),
toFixed(config.epochRewards.rewardsMultiplierAdjustmentFactors.underspend).toFixed(),
toFixed(config.epochRewards.rewardsMultiplierAdjustmentFactors.overspend).toFixed(),
+ toFixed(config.epochRewards.targetVotingGoldFraction).toFixed(),
config.epochRewards.maxValidatorEpochPayment,
]
}
diff --git a/packages/protocol/migrationsConfig.js b/packages/protocol/migrationsConfig.js
index 424bf148d05..4a3b97d092b 100644
--- a/packages/protocol/migrationsConfig.js
+++ b/packages/protocol/migrationsConfig.js
@@ -34,6 +34,7 @@ const DefaultConfig = {
underspend: 1 / 2,
overspend: 5,
},
+ targetVotingGoldFraction: 2 / 3,
maxValidatorEpochPayment: '1000000000000000000',
},
exchange: {
diff --git a/packages/protocol/test/governance/epochrewards.ts b/packages/protocol/test/governance/epochrewards.ts
index 7ce8348ff21..3818d86d083 100644
--- a/packages/protocol/test/governance/epochrewards.ts
+++ b/packages/protocol/test/governance/epochrewards.ts
@@ -1,45 +1,74 @@
import { CeloContractName } from '@celo/protocol/lib/registry-utils'
-import { assertContainSubset, assertEqualBN, assertRevert } from '@celo/protocol/lib/test-utils'
+import {
+ assertContainSubset,
+ assertEqualBN,
+ assertRevert,
+ timeTravel,
+} from '@celo/protocol/lib/test-utils'
import BigNumber from 'bignumber.js'
import {
MockElectionContract,
MockElectionInstance,
+ MockGoldTokenContract,
+ MockGoldTokenInstance,
+ MockSortedOraclesContract,
+ MockSortedOraclesInstance,
EpochRewardsTestContract,
EpochRewardsTestInstance,
RegistryContract,
RegistryInstance,
} from 'types'
-import { toFixed } from '@celo/utils/lib/fixidity'
+import { fromFixed, toFixed } from '@celo/utils/lib/fixidity'
const EpochRewards: EpochRewardsTestContract = artifacts.require('EpochRewardsTest')
const MockElection: MockElectionContract = artifacts.require('MockElection')
+const MockGoldToken: MockGoldTokenContract = artifacts.require('MockGoldToken')
+const MockSortedOracles: MockSortedOraclesContract = artifacts.require('MockSortedOracles')
const Registry: RegistryContract = artifacts.require('Registry')
// @ts-ignore
// TODO(mcortesi): Use BN
EpochRewards.numberFormat = 'BigNumber'
+const YEAR = new BigNumber(365 * 24 * 60 * 60)
+const SUPPLY_CAP = new BigNumber(web3.utils.toWei('1000000000'))
+
+const getExpectedTargetTotalSupply = (timeDelta: BigNumber) => {
+ const genesisSupply = new BigNumber(web3.utils.toWei('600000000'))
+ const linearRewards = new BigNumber(web3.utils.toWei('200000000'))
+ return genesisSupply
+ .plus(timeDelta.times(linearRewards).div(YEAR.times(15)))
+ .integerValue(BigNumber.ROUND_FLOOR)
+}
+
contract('EpochRewards', (accounts: string[]) => {
let epochRewards: EpochRewardsTestInstance
let mockElection: MockElectionInstance
+ let mockGoldToken: MockGoldTokenInstance
+ let mockSortedOracles: MockSortedOraclesInstance
let registry: RegistryInstance
const nonOwner = accounts[1]
const targetVotingYieldParams = {
initial: toFixed(new BigNumber(1 / 20)),
max: toFixed(new BigNumber(1 / 5)),
- adjustmentFactor: toFixed(new BigNumber(1)),
+ adjustmentFactor: toFixed(new BigNumber(1 / 365)),
}
const rewardsMultiplierAdjustments = {
underspend: toFixed(new BigNumber(1 / 2)),
overspend: toFixed(new BigNumber(5)),
}
+ const targetVotingGoldFraction = toFixed(new BigNumber(2 / 3))
const maxValidatorEpochPayment = new BigNumber(10000000000000)
beforeEach(async () => {
epochRewards = await EpochRewards.new()
mockElection = await MockElection.new()
+ mockGoldToken = await MockGoldToken.new()
+ mockSortedOracles = await MockSortedOracles.new()
registry = await Registry.new()
await registry.setAddressFor(CeloContractName.Election, mockElection.address)
+ await registry.setAddressFor(CeloContractName.GoldToken, mockGoldToken.address)
+ await registry.setAddressFor(CeloContractName.SortedOracles, mockSortedOracles.address)
await epochRewards.initialize(
registry.address,
targetVotingYieldParams.initial,
@@ -47,6 +76,7 @@ contract('EpochRewards', (accounts: string[]) => {
targetVotingYieldParams.adjustmentFactor,
rewardsMultiplierAdjustments.underspend,
rewardsMultiplierAdjustments.overspend,
+ targetVotingGoldFraction,
maxValidatorEpochPayment
)
})
@@ -83,12 +113,58 @@ contract('EpochRewards', (accounts: string[]) => {
targetVotingYieldParams.adjustmentFactor,
rewardsMultiplierAdjustments.underspend,
rewardsMultiplierAdjustments.overspend,
+ targetVotingGoldFraction,
maxValidatorEpochPayment
)
)
})
})
+ describe('#setTargetVotingGoldFraction()', () => {
+ describe('when the fraction is different', () => {
+ const newFraction = targetVotingGoldFraction.plus(1)
+
+ describe('when called by the owner', () => {
+ let resp: any
+
+ beforeEach(async () => {
+ resp = await epochRewards.setTargetVotingGoldFraction(newFraction)
+ })
+
+ it('should set the target voting gold fraction', async () => {
+ assertEqualBN(await epochRewards.getTargetVotingGoldFraction(), newFraction)
+ })
+
+ it('should emit the TargetVotingGoldFractionSet event', async () => {
+ assert.equal(resp.logs.length, 1)
+ const log = resp.logs[0]
+ assertContainSubset(log, {
+ event: 'TargetVotingGoldFractionSet',
+ args: {
+ fraction: newFraction,
+ },
+ })
+ })
+
+ describe('when called by a non-owner', () => {
+ it('should revert', async () => {
+ await assertRevert(
+ epochRewards.setTargetVotingGoldFraction(newFraction, {
+ from: nonOwner,
+ })
+ )
+ })
+ })
+ })
+
+ describe('when the fraction is the same', () => {
+ it('should revert', async () => {
+ await assertRevert(epochRewards.setTargetVotingGoldFraction(targetVotingGoldFraction))
+ })
+ })
+ })
+ })
+
describe('#setMaxValidatorEpochPayment()', () => {
describe('when the payment is different', () => {
const newPayment = maxValidatorEpochPayment.plus(1)
@@ -250,4 +326,244 @@ contract('EpochRewards', (accounts: string[]) => {
})
})
})
+
+ describe('#getTargetGoldTotalSupply()', () => {
+ describe('when it has been fewer than 15 years since genesis', () => {
+ const timeDelta = YEAR.times(10)
+ beforeEach(async () => {
+ await timeTravel(timeDelta.toNumber(), web3)
+ })
+
+ it('should return 600MM + 200MM * t / 15', async () => {
+ assertEqualBN(
+ await epochRewards.getTargetGoldTotalSupply(),
+ getExpectedTargetTotalSupply(timeDelta)
+ )
+ })
+ })
+ })
+
+ describe('#getTargetEpochRewards()', () => {
+ describe('when there are active votes', () => {
+ const activeVotes = 1000000
+ beforeEach(async () => {
+ await mockElection.setActiveVotes(activeVotes)
+ })
+
+ it('should return a percentage of the active votes', async () => {
+ const expected = fromFixed(targetVotingYieldParams.initial).times(activeVotes)
+ assertEqualBN(await epochRewards.getTargetEpochRewards(), expected)
+ })
+ })
+ })
+
+ describe.only('#getTargetTotalEpochPaymentsInGold()', () => {
+ describe('when a StableToken exchange rate is set', () => {
+ // 7 StableToken to one Celo Gold
+ const exchangeRate = 7
+ const sortedOraclesDenominator = new BigNumber('0x10000000000000000')
+ const randomAddress = web3.utils.randomHex(20)
+ // Hard coded in EpochRewardsTest.sol
+ const numValidators = 100
+ beforeEach(async () => {
+ await registry.setAddressFor(CeloContractName.StableToken, randomAddress)
+ await mockSortedOracles.setMedianRate(
+ randomAddress,
+ sortedOraclesDenominator.times(exchangeRate)
+ )
+ })
+
+ it('should return the number of validators times the max payment divided by the exchange rate', async () => {
+ const expected = maxValidatorEpochPayment
+ .times(numValidators)
+ .div(exchangeRate)
+ .integerValue(BigNumber.ROUND_FLOOR)
+ assertEqualBN(await epochRewards.getTargetTotalEpochPaymentsInGold(), expected)
+ })
+ })
+ })
+
+ describe.only('#getRewardsMultiplier()', () => {
+ const timeDelta = YEAR.times(10)
+ const expectedTargetTotalSupply = getExpectedTargetTotalSupply(timeDelta)
+ const expectedTargetRemainingSupply = SUPPLY_CAP.minus(expectedTargetTotalSupply)
+ const targetEpochReward = new BigNumber(120397694238746)
+ beforeEach(async () => {
+ await timeTravel(timeDelta.toNumber(), web3)
+ })
+
+ describe('when the target supply is equal to the actual supply after rewards', () => {
+ beforeEach(async () => {
+ await mockGoldToken.setTotalSupply(expectedTargetTotalSupply.minus(targetEpochReward))
+ })
+
+ it('should return one', async () => {
+ assertEqualBN(await epochRewards.getRewardsMultiplier(targetEpochReward), toFixed(1))
+ })
+ })
+
+ describe('when the actual remaining supply is 10% more than the target remaining supply after rewards', () => {
+ beforeEach(async () => {
+ const actualRemainingSupply = expectedTargetRemainingSupply.times(1.1)
+ const totalSupply = SUPPLY_CAP.minus(actualRemainingSupply)
+ .minus(targetEpochReward)
+ .integerValue(BigNumber.ROUND_FLOOR)
+ await mockGoldToken.setTotalSupply(totalSupply)
+ })
+
+ it('should return one plus 10% times the underspend adjustment', async () => {
+ const actual = fromFixed(await epochRewards.getRewardsMultiplier(targetEpochReward))
+ const expected = new BigNumber(1).plus(
+ fromFixed(rewardsMultiplierAdjustments.underspend).times(0.1)
+ )
+ // Assert equal to 9 decimal places due to fixidity imprecision.
+ assert.equal(expected.dp(9).toFixed(), actual.dp(9).toFixed())
+ })
+ })
+
+ describe('when the actual remaining supply is 10% less than the target remaining supply after rewards', () => {
+ beforeEach(async () => {
+ const actualRemainingSupply = expectedTargetRemainingSupply.times(0.9)
+ const totalSupply = SUPPLY_CAP.minus(actualRemainingSupply)
+ .minus(targetEpochReward)
+ .integerValue(BigNumber.ROUND_FLOOR)
+ await mockGoldToken.setTotalSupply(totalSupply)
+ })
+
+ it('should return one minus 10% times the underspend adjustment', async () => {
+ const actual = fromFixed(await epochRewards.getRewardsMultiplier(targetEpochReward))
+ const expected = new BigNumber(1).minus(
+ fromFixed(rewardsMultiplierAdjustments.overspend).times(0.1)
+ )
+ // Assert equal to 9 decimal places due to fixidity imprecision.
+ assert.equal(expected.dp(9).toFixed(), actual.dp(9).toFixed())
+ })
+ })
+ })
+
+ describe.only('#updateTargetVotingYield()', () => {
+ const randomAddress = web3.utils.randomHex(20)
+ const totalSupply = new BigNumber(129762987346298761037469283746)
+ const reserveBalance = new BigNumber(2397846127684712867321)
+ const floatingSupply = totalSupply.minus(reserveBalance)
+ beforeEach(async () => {
+ await mockGoldToken.setTotalSupply(totalSupply)
+ await web3.eth.sendTransaction({
+ from: accounts[9],
+ to: randomAddress,
+ value: reserveBalance.toString(),
+ })
+ await registry.setAddressFor(CeloContractName.Reserve, randomAddress)
+ })
+
+ describe('when the percentage of voting gold is equal to the target', () => {
+ beforeEach(async () => {
+ const totalVotes = floatingSupply
+ .times(fromFixed(targetVotingGoldFraction))
+ .integerValue(BigNumber.ROUND_FLOOR)
+ await mockElection.setTotalVotes(totalVotes)
+ await epochRewards.updateTargetVotingYield()
+ })
+
+ it('should not change the target voting yield', async () => {
+ assertEqualBN(
+ (await epochRewards.getTargetVotingYieldParameters())[0],
+ targetVotingYieldParams.initial
+ )
+ })
+ })
+
+ describe('when the percentage of voting gold is 10% less than the target', () => {
+ beforeEach(async () => {
+ const totalVotes = floatingSupply
+ .times(fromFixed(targetVotingGoldFraction).minus(0.1))
+ .integerValue(BigNumber.ROUND_FLOOR)
+ await mockElection.setTotalVotes(totalVotes)
+ await epochRewards.updateTargetVotingYield()
+ })
+
+ it('should increase the target voting yield by 10% times the adjustment factor', async () => {
+ const expected = fromFixed(
+ targetVotingYieldParams.initial.plus(targetVotingYieldParams.adjustmentFactor.times(0.1))
+ )
+ const actual = fromFixed((await epochRewards.getTargetVotingYieldParameters())[0])
+ // Assert equal to 9 decimal places due to fixidity imprecision.
+ assert.equal(expected.dp(9).toFixed(), actual.dp(9).toFixed())
+ })
+ })
+
+ describe('when the percentage of voting gold is 10% more than the target', () => {
+ beforeEach(async () => {
+ const totalVotes = floatingSupply
+ .times(fromFixed(targetVotingGoldFraction).plus(0.1))
+ .integerValue(BigNumber.ROUND_FLOOR)
+ await mockElection.setTotalVotes(totalVotes)
+ await epochRewards.updateTargetVotingYield()
+ })
+
+ it('should decrease the target voting yield by 10% times the adjustment factor', async () => {
+ const expected = fromFixed(
+ targetVotingYieldParams.initial.minus(targetVotingYieldParams.adjustmentFactor.times(0.1))
+ )
+ const actual = fromFixed((await epochRewards.getTargetVotingYieldParameters())[0])
+ // Assert equal to 9 decimal places due to fixidity imprecision.
+ assert.equal(expected.dp(9).toFixed(), actual.dp(9).toFixed())
+ })
+ })
+ })
+
+ describe.only('#calculateTargetEpochPaymentAndRewards()', () => {
+ describe('when there are active votes, a stable token exchange rate is set and the actual remaining supply is 10% more than the target remaining supply after rewards', () => {
+ const activeVotes = 1000000
+ const sortedOraclesDenominator = new BigNumber('0x10000000000000000')
+ const randomAddress = web3.utils.randomHex(20)
+ const timeDelta = YEAR.times(10)
+ // 7 StableToken to one Celo Gold
+ const exchangeRate = 7
+ // Hard coded in EpochRewardsTest.sol
+ const numValidators = 100
+ let expectedMultiplier: BigNumber
+ beforeEach(async () => {
+ await mockElection.setActiveVotes(activeVotes)
+ await registry.setAddressFor(CeloContractName.StableToken, randomAddress)
+ await mockSortedOracles.setMedianRate(
+ randomAddress,
+ sortedOraclesDenominator.times(exchangeRate)
+ )
+ await timeTravel(timeDelta.toNumber(), web3)
+ const expectedTargetTotalEpochPaymentsInGold = maxValidatorEpochPayment
+ .times(numValidators)
+ .div(exchangeRate)
+ .integerValue(BigNumber.ROUND_FLOOR)
+ const expectedTargetEpochRewards = fromFixed(targetVotingYieldParams.initial).times(
+ activeVotes
+ )
+ const expectedTargetGoldSupplyIncrease = expectedTargetEpochRewards.plus(
+ expectedTargetTotalEpochPaymentsInGold
+ )
+ const expectedTargetTotalSupply = getExpectedTargetTotalSupply(timeDelta)
+ const expectedTargetRemainingSupply = SUPPLY_CAP.minus(expectedTargetTotalSupply)
+ const actualRemainingSupply = expectedTargetRemainingSupply.times(1.1)
+ const totalSupply = SUPPLY_CAP.minus(actualRemainingSupply)
+ .minus(expectedTargetGoldSupplyIncrease)
+ .integerValue(BigNumber.ROUND_FLOOR)
+ await mockGoldToken.setTotalSupply(totalSupply)
+ expectedMultiplier = new BigNumber(1).plus(
+ fromFixed(rewardsMultiplierAdjustments.underspend).times(0.1)
+ )
+ })
+
+ it('should return the max validator epoch payment times the rewards multiplier', async () => {
+ const expected = maxValidatorEpochPayment.times(expectedMultiplier)
+ assertEqualBN((await epochRewards.calculateTargetEpochPaymentAndRewards())[0], expected)
+ })
+
+ it('should return the target yield times the number of active votes times the rewards multiplier', async () => {
+ const expected = fromFixed(targetVotingYieldParams.initial)
+ .times(activeVotes)
+ .times(expectedMultiplier)
+ assertEqualBN((await epochRewards.calculateTargetEpochPaymentAndRewards())[1], expected)
+ })
+ })
+ })
})
From 9281b4c7429ef9d2d73615af4a2f5e7d77357aa4 Mon Sep 17 00:00:00 2001
From: Asa Oines
Date: Thu, 31 Oct 2019 14:11:05 -0700
Subject: [PATCH 090/149] Make precompile public
---
packages/protocol/contracts/common/UsingPrecompiles.sol | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/packages/protocol/contracts/common/UsingPrecompiles.sol b/packages/protocol/contracts/common/UsingPrecompiles.sol
index c2add1fc0d6..1bdb26152c8 100644
--- a/packages/protocol/contracts/common/UsingPrecompiles.sol
+++ b/packages/protocol/contracts/common/UsingPrecompiles.sol
@@ -134,7 +134,7 @@ contract UsingPrecompiles {
address sender,
bytes memory proofOfPossessionBytes
)
- private
+ public
returns (bool)
{
bool success;
From 95c77d975049f0d48d8a5aef9f156346179806e4 Mon Sep 17 00:00:00 2001
From: Asa Oines
Date: Thu, 31 Oct 2019 14:52:12 -0700
Subject: [PATCH 091/149] Fix migrations
---
packages/protocol/migrations/11_validators.ts | 8 +++----
.../migrations/17_elect_validators.ts | 21 ++++++++++++++-----
packages/protocol/migrationsConfig.js | 12 +++++------
3 files changed, 26 insertions(+), 15 deletions(-)
diff --git a/packages/protocol/migrations/11_validators.ts b/packages/protocol/migrations/11_validators.ts
index 5f24860c230..2d692d15040 100644
--- a/packages/protocol/migrations/11_validators.ts
+++ b/packages/protocol/migrations/11_validators.ts
@@ -7,10 +7,10 @@ import { ValidatorsInstance } from 'types'
const initializeArgs = async (): Promise => {
return [
config.registry.predeployedProxyAddress,
- config.validators.registrationRequirements.group,
- config.validators.registrationRequirements.validator,
- config.validators.deregistrationLockups.group,
- config.validators.deregistrationLockups.validator,
+ config.validators.groupLockedGoldRequirements.value,
+ config.validators.groupLockedGoldRequirements.duration,
+ config.validators.validatorLockedGoldRequirements.value,
+ config.validators.validatorLockedGoldRequirements.duration,
config.validators.validatorScoreParameters.exponent,
toFixed(config.validators.validatorScoreParameters.adjustmentSpeed).toFixed(),
config.validators.validatorEpochPayment,
diff --git a/packages/protocol/migrations/17_elect_validators.ts b/packages/protocol/migrations/17_elect_validators.ts
index 8314f288495..84dbc7019aa 100644
--- a/packages/protocol/migrations/17_elect_validators.ts
+++ b/packages/protocol/migrations/17_elect_validators.ts
@@ -39,7 +39,8 @@ async function lockGold(lockedGold: LockedGoldInstance, value: BigNumber, privat
async function registerValidatorGroup(
lockedGold: LockedGoldInstance,
validators: ValidatorsInstance,
- privateKey: string
+ privateKey: string,
+ numMembers: number
) {
// Validators can't also be validator groups, so we create a new account to register the
// validator group with, and set the group identifier to the private key of this account
@@ -53,13 +54,18 @@ async function registerValidatorGroup(
const encryptedPrivateKey = encryptionWeb3.eth.accounts.encrypt(account.privateKey, privateKey)
const encodedKey = serializeKeystore(encryptedPrivateKey)
+ // Value is per-validator.
+ const lockedGoldValue = new BigNumber(config.validators.groupLockedGoldRequirements.value).times(
+ numMembers
+ )
+
await web3.eth.sendTransaction({
from: generateAccountAddressFromPrivateKey(privateKey.slice(0)),
to: account.address,
- value: config.validators.registrationRequirements.group * 2, // Add a premium to cover tx fees
+ value: lockedGoldValue.times(1.01).toFixed(), // Add a premium to cover tx fees
})
- await lockGold(lockedGold, config.validators.registrationRequirements.group, account.privateKey)
+ await lockGold(lockedGold, lockedGoldValue, account.privateKey)
// @ts-ignore
const tx = validators.contract.methods.registerValidatorGroup(
@@ -97,7 +103,7 @@ async function registerValidator(
await lockGold(
lockedGold,
- config.validators.registrationRequirements.validator,
+ config.validators.validatorLockedGoldRequirements.value,
validatorPrivateKey
)
@@ -151,7 +157,12 @@ module.exports = async (_deployer: any) => {
console.info(' Registering ValidatorGroup ...')
const firstPrivateKey = valKeys[0]
- const account = await registerValidatorGroup(lockedGold, validators, firstPrivateKey)
+ const account = await registerValidatorGroup(
+ lockedGold,
+ validators,
+ firstPrivateKey,
+ valKeys.length
+ )
console.info(' Registering Validators ...')
await Promise.all(
diff --git a/packages/protocol/migrationsConfig.js b/packages/protocol/migrationsConfig.js
index c5747fa40b3..eaf26bfd86d 100644
--- a/packages/protocol/migrationsConfig.js
+++ b/packages/protocol/migrationsConfig.js
@@ -80,13 +80,13 @@ const DefaultConfig = {
oracles: [],
},
validators: {
- registrationRequirements: {
- group: '1000000000000000000', // 1 gold
- validator: '1000000000000000000', // 1 gold
+ groupLockedGoldRequirements: {
+ value: '10000000000000000000000', // 10000 gold
+ duration: 60 * 24 * 60 * 60, // 60 days
},
- deregistrationLockups: {
- group: 60 * 24 * 60 * 60, // 60 days
- validator: 60 * 24 * 60 * 60, // 60 days
+ validatorLockedGoldRequirements: {
+ value: '10000000000000000000000', // 10000 gold
+ duration: 60 * 24 * 60 * 60, // 60 days
},
validatorScoreParameters: {
exponent: 1,
From e2eec9b55d48a2ac9307e0e84ee2ced00867278a Mon Sep 17 00:00:00 2001
From: Asa Oines
Date: Fri, 1 Nov 2019 12:08:25 -0700
Subject: [PATCH 092/149] end-to-end tests passing, not accounting for voting
yield and multiplier
---
.../src/e2e-tests/governance_tests.ts | 101 +++++++++++++++---
packages/contractkit/src/base.ts | 1 +
packages/contractkit/src/contract-cache.ts | 6 ++
packages/contractkit/src/kit.ts | 3 +
.../contractkit/src/web3-contract-cache.ts | 5 +
.../contracts/governance/EpochRewards.sol | 7 ++
packages/protocol/lib/web3-utils.ts | 2 +-
.../migrations/19_elect_validators.ts | 5 +
packages/protocol/migrationsConfig.js | 2 +-
packages/protocol/scripts/build.ts | 5 +-
10 files changed, 117 insertions(+), 20 deletions(-)
diff --git a/packages/celotool/src/e2e-tests/governance_tests.ts b/packages/celotool/src/e2e-tests/governance_tests.ts
index b026ba5ff24..e301731ead1 100644
--- a/packages/celotool/src/e2e-tests/governance_tests.ts
+++ b/packages/celotool/src/e2e-tests/governance_tests.ts
@@ -20,9 +20,12 @@ describe('governance tests', () => {
const context: any = getContext(gethConfig)
let web3: any
let election: any
- let validators: any
+ let stableToken: any
+ let sortedOracles: any
+ let epochRewards: any
let goldToken: any
let registry: any
+ let validators: any
let kit: ContractKit
before(async function(this: any) {
@@ -37,9 +40,12 @@ describe('governance tests', () => {
web3 = new Web3('http://localhost:8545')
kit = newKitFromWeb3(web3)
goldToken = await kit._web3Contracts.getGoldToken()
+ stableToken = await kit._web3Contracts.getStableToken()
+ sortedOracles = await kit._web3Contracts.getSortedOracles()
validators = await kit._web3Contracts.getValidators()
registry = await kit._web3Contracts.getRegistry()
election = await kit._web3Contracts.getElection()
+ epochRewards = await kit._web3Contracts.getEpochRewards()
}
const unlockAccount = async (address: string, theWeb3: any) => {
@@ -116,7 +122,7 @@ describe('governance tests', () => {
return blockNumber % epochSize === 0
}
- describe('when the validator set is changing', () => {
+ describe.only('when the validator set is changing', () => {
let epoch: number
const blockNumbers: number[] = []
let allValidators: string[]
@@ -275,10 +281,9 @@ describe('governance tests', () => {
})
it('should distribute epoch payments at the end of each epoch', async () => {
- const stableToken = await kit._web3Contracts.getStableToken()
const commission = 0.1
- const validatorEpochPayment = new BigNumber(
- await validators.methods.validatorEpochPayment().call()
+ const maxValidatorEpochPayment = new BigNumber(
+ await epochRewards.methods.maxValidatorEpochPayment().call()
)
const [group] = await validators.methods.getRegisteredValidatorGroups().call()
@@ -295,7 +300,7 @@ describe('governance tests', () => {
)
assert.isNotNaN(currentBalance)
assert.isNotNaN(previousBalance)
- assert.equal(expected.toFixed(), currentBalance.minus(previousBalance).toFixed())
+ assert.equal(currentBalance.minus(previousBalance).toFixed(), expected.toFixed())
}
const assertBalanceUnchanged = async (validator: string, blockNumber: number) => {
@@ -307,7 +312,7 @@ describe('governance tests', () => {
(await validators.methods.getValidator(validator).call({}, blockNumber))[3]
)
assert.isNotNaN(score)
- return validatorEpochPayment.times(fromFixed(score))
+ return maxValidatorEpochPayment.times(fromFixed(score))
}
for (const blockNumber of blockNumbers) {
@@ -343,8 +348,6 @@ describe('governance tests', () => {
it('should distribute epoch rewards at the end of each epoch', async () => {
const lockedGold = await kit._web3Contracts.getLockedGold()
const governance = await kit._web3Contracts.getGovernance()
- const epochReward = new BigNumber(10).pow(18)
- const infraReward = new BigNumber(10).pow(18)
const [group] = await validators.methods.getRegisteredValidatorGroups().call()
const assertVotesChanged = async (blockNumber: number, expected: BigNumber) => {
@@ -354,7 +357,7 @@ describe('governance tests', () => {
const previousVotes = new BigNumber(
await election.methods.getTotalVotesForGroup(group).call({}, blockNumber - 1)
)
- assert.equal(expected.toFixed(), currentVotes.minus(previousVotes).toFixed())
+ assert.equal(currentVotes.minus(previousVotes).toFixed(), expected.toFixed())
}
const assertGoldTokenTotalSupplyChanged = async (
@@ -367,7 +370,7 @@ describe('governance tests', () => {
const previousSupply = new BigNumber(
await goldToken.methods.totalSupply().call({}, blockNumber - 1)
)
- assert.equal(expected.toFixed(), currentSupply.minus(previousSupply).toFixed())
+ assert.equal(currentSupply.minus(previousSupply).toFixed(), expected.toFixed())
}
const assertBalanceChanged = async (
@@ -381,7 +384,7 @@ describe('governance tests', () => {
const previousBalance = new BigNumber(
await goldToken.methods.balanceOf(address).call({}, blockNumber - 1)
)
- assert.equal(expected.toFixed(), currentBalance.minus(previousBalance).toFixed())
+ assert.equal(currentBalance.minus(previousBalance).toFixed(), expected.toFixed())
}
const assertLockedGoldBalanceChanged = async (blockNumber: number, expected: BigNumber) => {
@@ -408,12 +411,45 @@ describe('governance tests', () => {
await assertGovernanceBalanceChanged(blockNumber, new BigNumber(0))
}
+ const getStableTokenSupplyChange = async (blockNumber: number) => {
+ const currentSupply = new BigNumber(
+ await stableToken.methods.totalSupply().call({}, blockNumber)
+ )
+ const previousSupply = new BigNumber(
+ await stableToken.methods.totalSupply().call({}, blockNumber - 1)
+ )
+ return currentSupply.minus(previousSupply)
+ }
+
+ const getStableTokenExchangeRate = async (blockNumber: number) => {
+ const rate = await sortedOracles.methods
+ .medianRate(stableToken.options.address)
+ .call({}, blockNumber)
+ return new BigNumber(rate[0]).div(rate[1])
+ }
+
for (const blockNumber of blockNumbers) {
if (isLastBlockOfEpoch(blockNumber, epoch)) {
- await assertVotesChanged(blockNumber, epochReward)
- await assertGoldTokenTotalSupplyChanged(blockNumber, epochReward.plus(infraReward))
- await assertLockedGoldBalanceChanged(blockNumber, epochReward)
- await assertGovernanceBalanceChanged(blockNumber, infraReward)
+ // We use the number of active votes from the previous block to calculate the expected
+ // epoch reward as the number of active votes for the current block will include the
+ // epoch reward.
+ const activeVotes = new BigNumber(
+ await election.methods.getActiveVotes().call({}, blockNumber - 1)
+ )
+ const targetVotingYield = new BigNumber(
+ (await epochRewards.methods.getTargetVotingYieldParameters().call({}, blockNumber))[0]
+ )
+ const expectedEpochReward = activeVotes.times(fromFixed(targetVotingYield))
+ const expectedInfraReward = new BigNumber(10).pow(18)
+ const stableTokenSupplyChange = await getStableTokenSupplyChange(blockNumber)
+ const exchangeRate = await getStableTokenExchangeRate(blockNumber)
+ const expectedGoldTotalSupplyChange = expectedInfraReward
+ .plus(expectedEpochReward)
+ .plus(stableTokenSupplyChange.div(exchangeRate))
+ await assertVotesChanged(blockNumber, expectedEpochReward)
+ await assertLockedGoldBalanceChanged(blockNumber, expectedEpochReward)
+ await assertGovernanceBalanceChanged(blockNumber, expectedInfraReward)
+ await assertGoldTokenTotalSupplyChanged(blockNumber, expectedGoldTotalSupplyChange)
} else {
await assertVotesUnchanged(blockNumber)
await assertGoldTokenTotalSupplyUnchanged(blockNumber)
@@ -422,6 +458,39 @@ describe('governance tests', () => {
}
}
})
+
+ it('should update the target voting yield', async () => {
+ const assertTargetVotingYieldChanged = async (blockNumber: number, expected: BigNumber) => {
+ const currentTarget = new BigNumber(
+ (await epochRewards.methods.getTargetVotingYieldParameters().call({}, blockNumber))[0]
+ )
+ const previousTarget = new BigNumber(
+ (await epochRewards.methods.getTargetVotingYieldParameters().call({}, blockNumber - 1))[0]
+ )
+ assert.equal(currentTarget.minus(previousTarget).toFixed(), expected.toFixed())
+ }
+
+ const assertTargetVotingYieldUnchanged = async (blockNumber: number) => {
+ await assertTargetVotingYieldChanged(blockNumber, new BigNumber(0))
+ }
+
+ for (const blockNumber of blockNumbers) {
+ if (isLastBlockOfEpoch(blockNumber, epoch)) {
+ const actualVotingPercentage = toFixed(new BigNumber(1))
+ const targetVotingGoldPercentage = new BigNumber(
+ await epochRewards.methods.getTargetVotingGoldFraction().call({}, blockNumber)
+ )
+ const difference = actualVotingPercentage.minus(targetVotingGoldPercentage)
+ const adjustmentFactor = new BigNumber(
+ (await epochRewards.methods.getTargetVotingYieldParameters().call({}, blockNumber))[1]
+ )
+ const delta = difference.times(adjustmentFactor)
+ await assertTargetVotingYieldChanged(blockNumber, fromFixed(delta))
+ } else {
+ await assertTargetVotingYieldUnchanged(blockNumber)
+ }
+ }
+ })
})
describe('after the gold token smart contract is registered', () => {
diff --git a/packages/contractkit/src/base.ts b/packages/contractkit/src/base.ts
index a410cbb7fac..156e6342fa7 100644
--- a/packages/contractkit/src/base.ts
+++ b/packages/contractkit/src/base.ts
@@ -3,6 +3,7 @@ export type Address = string
export enum CeloContract {
Attestations = 'Attestations',
Election = 'Election',
+ EpochRewards = 'EpochRewards',
Escrow = 'Escrow',
Exchange = 'Exchange',
GasCurrencyWhitelist = 'GasCurrencyWhitelist',
diff --git a/packages/contractkit/src/contract-cache.ts b/packages/contractkit/src/contract-cache.ts
index c93a5cf7713..b975a430bca 100644
--- a/packages/contractkit/src/contract-cache.ts
+++ b/packages/contractkit/src/contract-cache.ts
@@ -2,6 +2,7 @@ import { CeloContract } from './base'
import { ContractKit } from './kit'
import { AttestationsWrapper } from './wrappers/Attestations'
import { ElectionWrapper } from './wrappers/Election'
+// import { EpochRewardsWrapper } from './wrappers/EpochRewards'
import { ExchangeWrapper } from './wrappers/Exchange'
import { GasPriceMinimumWrapper } from './wrappers/GasPriceMinimum'
import { GoldTokenWrapper } from './wrappers/GoldTokenWrapper'
@@ -15,6 +16,7 @@ import { ValidatorsWrapper } from './wrappers/Validators'
const WrapperFactories = {
[CeloContract.Attestations]: AttestationsWrapper,
[CeloContract.Election]: ElectionWrapper,
+ // [CeloContract.EpochRewards]?: EpochRewardsWrapper,
// [CeloContract.Escrow]: EscrowWrapper,
[CeloContract.Exchange]: ExchangeWrapper,
// [CeloContract.GasCurrencyWhitelist]: GasCurrencyWhitelistWrapper,
@@ -37,6 +39,7 @@ export type ValidWrappers = keyof CFType
interface WrapperCacheMap {
[CeloContract.Attestations]?: AttestationsWrapper
[CeloContract.Election]?: ElectionWrapper
+ // [CeloContract.EpochRewards]?: EpochRewardsWrapper
// [CeloContract.Escrow]?: EscrowWrapper,
[CeloContract.Exchange]?: ExchangeWrapper
// [CeloContract.GasCurrencyWhitelist]?: GasCurrencyWhitelistWrapper,
@@ -70,6 +73,9 @@ export class WrapperCache {
getElection() {
return this.getContract(CeloContract.Election)
}
+ // getEpochRewards() {
+ // return this.getContract(CeloContract.EpochRewards)
+ // }
// getEscrow() {
// return this.getWrapper(CeloContract.Escrow, newEscrow)
// }
diff --git a/packages/contractkit/src/kit.ts b/packages/contractkit/src/kit.ts
index a997ff88e06..9fb45094880 100644
--- a/packages/contractkit/src/kit.ts
+++ b/packages/contractkit/src/kit.ts
@@ -89,6 +89,7 @@ export class ContractKit {
this.contracts.getReserve(),
this.contracts.getStableToken(),
this.contracts.getValidators(),
+ // this.contracts.getEpochRewards(),
])
const res = await Promise.all([
contracts[0].getConfig(),
@@ -101,6 +102,7 @@ export class ContractKit {
contracts[7].getConfig(),
contracts[8].getConfig(),
contracts[9].getConfig(),
+ // contracts[10].getConfig(),
])
return {
exchange: res[0],
@@ -113,6 +115,7 @@ export class ContractKit {
reserve: res[7],
stableToken: res[8],
validators: res[9],
+ // epochRewards: res[10],
}
}
diff --git a/packages/contractkit/src/web3-contract-cache.ts b/packages/contractkit/src/web3-contract-cache.ts
index 35d705170eb..f4d65e3cec9 100644
--- a/packages/contractkit/src/web3-contract-cache.ts
+++ b/packages/contractkit/src/web3-contract-cache.ts
@@ -2,6 +2,7 @@ import debugFactory from 'debug'
import { CeloContract } from './base'
import { newAttestations } from './generated/Attestations'
import { newElection } from './generated/Election'
+import { newEpochRewards } from './generated/EpochRewards'
import { newEscrow } from './generated/Escrow'
import { newExchange } from './generated/Exchange'
import { newGasCurrencyWhitelist } from './generated/GasCurrencyWhitelist'
@@ -22,6 +23,7 @@ const debug = debugFactory('kit:web3-contract-cache')
const ContractFactories = {
[CeloContract.Attestations]: newAttestations,
[CeloContract.Election]: newElection,
+ [CeloContract.EpochRewards]: newEpochRewards,
[CeloContract.Escrow]: newEscrow,
[CeloContract.Exchange]: newExchange,
[CeloContract.GasCurrencyWhitelist]: newGasCurrencyWhitelist,
@@ -62,6 +64,9 @@ export class Web3ContractCache {
getElection() {
return this.getContract(CeloContract.Election)
}
+ getEpochRewards() {
+ return this.getContract(CeloContract.EpochRewards)
+ }
getEscrow() {
return this.getContract(CeloContract.Escrow)
}
diff --git a/packages/protocol/contracts/governance/EpochRewards.sol b/packages/protocol/contracts/governance/EpochRewards.sol
index 16c822034d5..860c5252ea6 100644
--- a/packages/protocol/contracts/governance/EpochRewards.sol
+++ b/packages/protocol/contracts/governance/EpochRewards.sol
@@ -155,6 +155,7 @@ contract EpochRewards is Ownable, Initializable, UsingPrecompiles, UsingRegistry
uint256 remainingSupply = GOLD_SUPPLY_CAP.sub(totalSupply.add(targetGoldSupplyIncrease));
uint256 targetRemainingSupply = GOLD_SUPPLY_CAP.sub(targetSupply);
FixidityLib.Fraction memory ratio = FixidityLib.newFixed(remainingSupply).divide(FixidityLib.newFixed(targetRemainingSupply));
+ /*
if (ratio.gt(FixidityLib.fixed1())) {
FixidityLib.Fraction memory delta = ratio.subtract(FixidityLib.fixed1());
return delta.multiply(rewardsMultiplierAdjustmentFactors.underspend).add(FixidityLib.fixed1());
@@ -164,6 +165,8 @@ contract EpochRewards is Ownable, Initializable, UsingPrecompiles, UsingRegistry
} else {
return FixidityLib.fixed1();
}
+ */
+ return FixidityLib.fixed1();
}
function _getTargetEpochRewards() internal view returns (uint256) {
@@ -219,8 +222,12 @@ contract EpochRewards is Ownable, Initializable, UsingPrecompiles, UsingRegistry
uint256 targetGoldSupplyIncrease = targetEpochRewards.add(targetTotalEpochPaymentsInGold);
FixidityLib.Fraction memory rewardsMultiplier = _getRewardsMultiplier(targetGoldSupplyIncrease);
return (
+ /*
FixidityLib.newFixed(maxValidatorEpochPayment).multiply(rewardsMultiplier).fromFixed(),
FixidityLib.newFixed(targetEpochRewards).multiply(rewardsMultiplier).fromFixed()
+ */
+ maxValidatorEpochPayment,
+ targetEpochRewards
);
}
}
diff --git a/packages/protocol/lib/web3-utils.ts b/packages/protocol/lib/web3-utils.ts
index d1d38057d4f..224d42b4d1f 100644
--- a/packages/protocol/lib/web3-utils.ts
+++ b/packages/protocol/lib/web3-utils.ts
@@ -58,7 +58,7 @@ export async function sendTransactionWithPrivateKey(
...txArgs,
data: encodedTxData,
from: address,
- gas: estimatedGas * 2,
+ gas: estimatedGas * 10,
},
privateKey
)
diff --git a/packages/protocol/migrations/19_elect_validators.ts b/packages/protocol/migrations/19_elect_validators.ts
index 84dbc7019aa..7c028eac28d 100644
--- a/packages/protocol/migrations/19_elect_validators.ts
+++ b/packages/protocol/migrations/19_elect_validators.ts
@@ -101,12 +101,14 @@ async function registerValidator(
).toString('hex')
const publicKeysData = publicKey + blsPublicKey + blsPoP
+ console.log('locking gold for validator registration')
await lockGold(
lockedGold,
config.validators.validatorLockedGoldRequirements.value,
validatorPrivateKey
)
+ console.log('registering validator')
// @ts-ignore
const registerTx = validators.contract.methods.registerValidator(address, add0x(publicKeysData))
@@ -114,6 +116,7 @@ async function registerValidator(
to: validators.address,
})
+ console.log('affiliating')
// @ts-ignore
const affiliateTx = validators.contract.methods.affiliate(groupAddress)
@@ -121,6 +124,8 @@ async function registerValidator(
to: validators.address,
})
+ console.log('done registering validator')
+
return
}
diff --git a/packages/protocol/migrationsConfig.js b/packages/protocol/migrationsConfig.js
index 95873e1cee3..de3ba5e4146 100644
--- a/packages/protocol/migrationsConfig.js
+++ b/packages/protocol/migrationsConfig.js
@@ -28,7 +28,7 @@ const DefaultConfig = {
targetVotingYieldParameters: {
initial: 5 / 100,
max: 2 / 10,
- adjustmentFactor: 1,
+ adjustmentFactor: 1 / 365,
},
rewardsMultiplierAdjustmentFactors: {
underspend: 1 / 2,
diff --git a/packages/protocol/scripts/build.ts b/packages/protocol/scripts/build.ts
index 6106cd275c4..16ab976217b 100644
--- a/packages/protocol/scripts/build.ts
+++ b/packages/protocol/scripts/build.ts
@@ -10,6 +10,7 @@ const CONTRACTKIT_GEN_DIR = path.normalize(path.join(ROOT_DIR, '../contractkit/s
export const ProxyContracts = [
'AttestationsProxy',
'ElectionProxy',
+ 'EpochRewardsProxy',
'EscrowProxy',
'ExchangeProxy',
'GasCurrencyWhitelistProxy',
@@ -25,15 +26,16 @@ export const ProxyContracts = [
]
export const CoreContracts = [
// common
-
'GasPriceMinimum',
'GasCurrencyWhitelist',
+ 'GoldToken',
'MultiSig',
'Registry',
'Validators',
// governance
'Election',
+ 'EpochRewards',
'Governance',
'LockedGold',
'Validators',
@@ -45,7 +47,6 @@ export const CoreContracts = [
// stability
'Exchange',
- 'GoldToken',
'Reserve',
'StableToken',
'SortedOracles',
From 2bc75cc35d6d913921a9fe758c19f64527e9953c Mon Sep 17 00:00:00 2001
From: Asa Oines
Date: Fri, 1 Nov 2019 13:14:37 -0700
Subject: [PATCH 093/149] Fix typo
---
packages/protocol/contracts/governance/Validators.sol | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/packages/protocol/contracts/governance/Validators.sol b/packages/protocol/contracts/governance/Validators.sol
index 8f4ec311a9d..d6eb07fb6ce 100644
--- a/packages/protocol/contracts/governance/Validators.sol
+++ b/packages/protocol/contracts/governance/Validators.sol
@@ -68,7 +68,7 @@ contract Validators is
address group;
}
- // Stores a the per-epoch membership history of a validator, used to determine which group
+ // Stores the per-epoch membership history of a validator, used to determine which group
// commission should be paid to at the end of an epoch.
// Stores a timestamp of the last time the validator was removed from a group, used to determine
// whether or not a group can de-register.
From 72ff0ba6fee0cbab2933f26cade7516dfade064b Mon Sep 17 00:00:00 2001
From: Asa Oines
Date: Fri, 1 Nov 2019 14:44:58 -0700
Subject: [PATCH 094/149] end-to-end tests adjusted for rewards multiplier
---
.../src/e2e-tests/governance_tests.ts | 58 ++++++++++--
.../contracts/governance/EpochRewards.sol | 88 ++++++++++++-------
.../governance/test/EpochRewardsTest.sol | 12 ---
.../protocol/migrations/13_epoch_rewards.ts | 5 +-
packages/protocol/migrationsConfig.js | 9 +-
5 files changed, 117 insertions(+), 55 deletions(-)
diff --git a/packages/celotool/src/e2e-tests/governance_tests.ts b/packages/celotool/src/e2e-tests/governance_tests.ts
index e301731ead1..cf5ce53a7cf 100644
--- a/packages/celotool/src/e2e-tests/governance_tests.ts
+++ b/packages/celotool/src/e2e-tests/governance_tests.ts
@@ -122,6 +122,20 @@ describe('governance tests', () => {
return blockNumber % epochSize === 0
}
+ const assertDifferenceCloseTo = (
+ current: BigNumber,
+ previous: BigNumber,
+ expected: BigNumber,
+ delta: BigNumber
+ ) => {
+ const difference = current.minus(previous)
+ if (expected.isZero()) {
+ assert.equal(difference.toFixed(), expected.toFixed())
+ } else {
+ assert.closeTo(difference.toNumber(), expected.toNumber(), delta.toNumber())
+ }
+ }
+
describe.only('when the validator set is changing', () => {
let epoch: number
const blockNumbers: number[] = []
@@ -300,7 +314,12 @@ describe('governance tests', () => {
)
assert.isNotNaN(currentBalance)
assert.isNotNaN(previousBalance)
- assert.equal(currentBalance.minus(previousBalance).toFixed(), expected.toFixed())
+ assertDifferenceCloseTo(
+ currentBalance,
+ previousBalance,
+ expected,
+ new BigNumber(10).pow(12).times(5)
+ )
}
const assertBalanceUnchanged = async (validator: string, blockNumber: number) => {
@@ -312,7 +331,12 @@ describe('governance tests', () => {
(await validators.methods.getValidator(validator).call({}, blockNumber))[3]
)
assert.isNotNaN(score)
- return maxValidatorEpochPayment.times(fromFixed(score))
+ // We need to calculate the rewards multiplier for the previous block, before
+ // the rewards actually are awarded.
+ const rewardsMultiplier = new BigNumber(
+ await epochRewards.methods.getRewardsMultiplier().call({}, blockNumber - 1)
+ )
+ return maxValidatorEpochPayment.times(fromFixed(score)).times(fromFixed(rewardsMultiplier))
}
for (const blockNumber of blockNumbers) {
@@ -357,7 +381,12 @@ describe('governance tests', () => {
const previousVotes = new BigNumber(
await election.methods.getTotalVotesForGroup(group).call({}, blockNumber - 1)
)
- assert.equal(currentVotes.minus(previousVotes).toFixed(), expected.toFixed())
+ assertDifferenceCloseTo(
+ currentVotes,
+ previousVotes,
+ expected,
+ new BigNumber(10).pow(12).times(5)
+ )
}
const assertGoldTokenTotalSupplyChanged = async (
@@ -370,7 +399,12 @@ describe('governance tests', () => {
const previousSupply = new BigNumber(
await goldToken.methods.totalSupply().call({}, blockNumber - 1)
)
- assert.equal(currentSupply.minus(previousSupply).toFixed(), expected.toFixed())
+ assertDifferenceCloseTo(
+ currentSupply,
+ previousSupply,
+ expected,
+ new BigNumber(10).pow(12).times(5)
+ )
}
const assertBalanceChanged = async (
@@ -384,7 +418,12 @@ describe('governance tests', () => {
const previousBalance = new BigNumber(
await goldToken.methods.balanceOf(address).call({}, blockNumber - 1)
)
- assert.equal(currentBalance.minus(previousBalance).toFixed(), expected.toFixed())
+ assertDifferenceCloseTo(
+ currentBalance,
+ previousBalance,
+ expected,
+ new BigNumber(10).pow(12).times(5)
+ )
}
const assertLockedGoldBalanceChanged = async (blockNumber: number, expected: BigNumber) => {
@@ -439,7 +478,14 @@ describe('governance tests', () => {
const targetVotingYield = new BigNumber(
(await epochRewards.methods.getTargetVotingYieldParameters().call({}, blockNumber))[0]
)
- const expectedEpochReward = activeVotes.times(fromFixed(targetVotingYield))
+ // We need to calculate the rewards multiplier for the previous block, before
+ // the rewards actually are awarded.
+ const rewardsMultiplier = new BigNumber(
+ await epochRewards.methods.getRewardsMultiplier().call({}, blockNumber - 1)
+ )
+ const expectedEpochReward = activeVotes
+ .times(fromFixed(targetVotingYield))
+ .times(fromFixed(rewardsMultiplier))
const expectedInfraReward = new BigNumber(10).pow(18)
const stableTokenSupplyChange = await getStableTokenSupplyChange(blockNumber)
const exchangeRate = await getStableTokenExchangeRate(blockNumber)
diff --git a/packages/protocol/contracts/governance/EpochRewards.sol b/packages/protocol/contracts/governance/EpochRewards.sol
index 860c5252ea6..dc58ee8f9d0 100644
--- a/packages/protocol/contracts/governance/EpochRewards.sol
+++ b/packages/protocol/contracts/governance/EpochRewards.sol
@@ -28,14 +28,19 @@ contract EpochRewards is Ownable, Initializable, UsingPrecompiles, UsingRegistry
FixidityLib.Fraction overspend;
}
+ struct RewardsMultiplierParameters {
+ RewardsMultiplierAdjustmentFactors adjustmentFactors;
+ FixidityLib.Fraction max;
+ }
+
struct TargetVotingYieldParameters {
FixidityLib.Fraction target;
- FixidityLib.Fraction max;
FixidityLib.Fraction adjustmentFactor;
+ FixidityLib.Fraction max;
}
uint256 private startTime = 0;
- RewardsMultiplierAdjustmentFactors private rewardsMultiplierAdjustmentFactors;
+ RewardsMultiplierParameters private rewardsMultiplierParams;
TargetVotingYieldParameters private targetVotingYieldParams;
FixidityLib.Fraction private targetVotingGoldFraction;
uint256 public maxValidatorEpochPayment;
@@ -43,7 +48,7 @@ contract EpochRewards is Ownable, Initializable, UsingPrecompiles, UsingRegistry
event TargetVotingGoldFractionSet(uint256 fraction);
event MaxValidatorEpochPaymentSet(uint256 payment);
event TargetVotingYieldParametersSet(uint256 max, uint256 adjustmentFactor);
- event RewardsMultiplierAdjustmentFactorsSet(uint256 underspend, uint256 overspend);
+ event RewardsMultiplierParametersSet(uint256 max, uint256 underspendAdjustmentFactor, uint256 overspendAdjustmentFactor);
event Debug(uint256 value, string desc);
/**
@@ -54,6 +59,7 @@ contract EpochRewards is Ownable, Initializable, UsingPrecompiles, UsingRegistry
uint256 targetVotingYieldInitial,
uint256 targetVotingYieldMax,
uint256 targetVotingYieldAdjustmentFactor,
+ uint256 rewardsMultiplierMax,
uint256 rewardsMultiplierUnderspendAdjustmentFactor,
uint256 rewardsMultiplierOverspendAdjustmentFactor,
uint256 _targetVotingGoldFraction,
@@ -65,7 +71,8 @@ contract EpochRewards is Ownable, Initializable, UsingPrecompiles, UsingRegistry
_transferOwnership(msg.sender);
setRegistry(registryAddress);
setTargetVotingYieldParameters(targetVotingYieldMax, targetVotingYieldAdjustmentFactor);
- setRewardsMultiplierAdjustmentFactors(
+ setRewardsMultiplierParameters(
+ rewardsMultiplierMax,
rewardsMultiplierUnderspendAdjustmentFactor,
rewardsMultiplierOverspendAdjustmentFactor
);
@@ -80,9 +87,9 @@ contract EpochRewards is Ownable, Initializable, UsingPrecompiles, UsingRegistry
return (params.target.unwrap(), params.max.unwrap(), params.adjustmentFactor.unwrap());
}
- function getRewardsMultiplierAdjustmentFactors() external view returns (uint256, uint256) {
- RewardsMultiplierAdjustmentFactors storage factors = rewardsMultiplierAdjustmentFactors;
- return (factors.underspend.unwrap(), factors.overspend.unwrap());
+ function getRewardsMultiplierParameters() external view returns (uint256, uint256, uint256) {
+ RewardsMultiplierParameters storage params = rewardsMultiplierParams;
+ return (params.max.unwrap(), params.adjustmentFactors.underspend.unwrap(), params.adjustmentFactors.overspend.unwrap());
}
function setTargetVotingGoldFraction(uint256 value) public onlyOwner returns (bool) {
@@ -108,16 +115,20 @@ contract EpochRewards is Ownable, Initializable, UsingPrecompiles, UsingRegistry
return true;
}
- function setRewardsMultiplierAdjustmentFactors(uint256 underspend, uint256 overspend) public onlyOwner returns (bool) {
+ function setRewardsMultiplierParameters(uint256 max, uint256 underspendAdjustmentFactor, uint256 overspendAdjustmentFactor) public onlyOwner returns (bool) {
require(
- underspend != rewardsMultiplierAdjustmentFactors.underspend.unwrap() ||
- overspend != rewardsMultiplierAdjustmentFactors.overspend.unwrap()
+ max != rewardsMultiplierParams.max.unwrap() ||
+ underspendAdjustmentFactor != rewardsMultiplierParams.adjustmentFactors.underspend.unwrap() ||
+ overspendAdjustmentFactor != rewardsMultiplierParams.adjustmentFactors.overspend.unwrap()
);
- rewardsMultiplierAdjustmentFactors = RewardsMultiplierAdjustmentFactors(
- FixidityLib.wrap(underspend),
- FixidityLib.wrap(overspend)
+ rewardsMultiplierParams = RewardsMultiplierParameters(
+ RewardsMultiplierAdjustmentFactors(
+ FixidityLib.wrap(underspendAdjustmentFactor),
+ FixidityLib.wrap(overspendAdjustmentFactor)
+ ),
+ FixidityLib.wrap(max)
);
- emit RewardsMultiplierAdjustmentFactorsSet(underspend, overspend);
+ emit RewardsMultiplierParametersSet(max, underspendAdjustmentFactor, overspendAdjustmentFactor);
return true;
}
@@ -136,7 +147,7 @@ contract EpochRewards is Ownable, Initializable, UsingPrecompiles, UsingRegistry
return true;
}
- function _getTargetGoldTotalSupply() internal view returns (uint256) {
+ function getTargetGoldTotalSupply() public view returns (uint256) {
uint256 timeSinceInitialization = now.sub(startTime);
if (timeSinceInitialization < SECONDS_LINEAR) {
// Pay out half of all block rewards linearly.
@@ -150,30 +161,47 @@ contract EpochRewards is Ownable, Initializable, UsingPrecompiles, UsingRegistry
}
function _getRewardsMultiplier(uint256 targetGoldSupplyIncrease) internal view returns (FixidityLib.Fraction memory) {
- uint256 targetSupply = _getTargetGoldTotalSupply();
+ uint256 targetSupply = getTargetGoldTotalSupply();
uint256 totalSupply = getGoldToken().totalSupply();
uint256 remainingSupply = GOLD_SUPPLY_CAP.sub(totalSupply.add(targetGoldSupplyIncrease));
uint256 targetRemainingSupply = GOLD_SUPPLY_CAP.sub(targetSupply);
FixidityLib.Fraction memory ratio = FixidityLib.newFixed(remainingSupply).divide(FixidityLib.newFixed(targetRemainingSupply));
- /*
if (ratio.gt(FixidityLib.fixed1())) {
- FixidityLib.Fraction memory delta = ratio.subtract(FixidityLib.fixed1());
- return delta.multiply(rewardsMultiplierAdjustmentFactors.underspend).add(FixidityLib.fixed1());
+ FixidityLib.Fraction memory delta = ratio.subtract(FixidityLib.fixed1()).multiply(
+ rewardsMultiplierParams.adjustmentFactors.underspend
+ );
+ FixidityLib.Fraction memory r = FixidityLib.fixed1().add(delta);
+ if (r.lt(rewardsMultiplierParams.max)) {
+ return r;
+ } else {
+ return rewardsMultiplierParams.max;
+ }
} else if (ratio.lt(FixidityLib.fixed1())) {
- FixidityLib.Fraction memory delta = FixidityLib.fixed1().subtract(ratio);
- return FixidityLib.fixed1().subtract(delta.multiply(rewardsMultiplierAdjustmentFactors.overspend));
+ FixidityLib.Fraction memory delta = FixidityLib.fixed1().subtract(ratio).multiply(
+ rewardsMultiplierParams.adjustmentFactors.overspend
+ );
+ if (delta.lt(FixidityLib.fixed1())) {
+ return FixidityLib.fixed1().subtract(delta);
+ } else {
+ return FixidityLib.wrap(0);
+ }
} else {
return FixidityLib.fixed1();
}
- */
- return FixidityLib.fixed1();
}
- function _getTargetEpochRewards() internal view returns (uint256) {
+ function getRewardsMultiplier() external view returns (uint256) {
+ uint256 targetEpochRewards = getTargetEpochRewards();
+ uint256 targetTotalEpochPaymentsInGold = getTargetTotalEpochPaymentsInGold();
+ uint256 targetGoldSupplyIncrease = targetEpochRewards.add(targetTotalEpochPaymentsInGold);
+ return _getRewardsMultiplier(targetGoldSupplyIncrease).unwrap();
+ }
+
+ function getTargetEpochRewards() public view returns (uint256) {
return FixidityLib.newFixed(getElection().getActiveVotes()).multiply(targetVotingYieldParams.target).fromFixed();
}
- function _getTargetTotalEpochPaymentsInGold() internal view returns (uint256) {
+ function getTargetTotalEpochPaymentsInGold() public view returns (uint256) {
address stableTokenAddress = registry.getAddressForOrDie(STABLE_TOKEN_REGISTRY_ID);
(uint256 numerator, uint256 denominator) = getSortedOracles().medianRate(stableTokenAddress);
uint256 targetEpochPayment = numberValidatorsInCurrentSet().mul(maxValidatorEpochPayment).mul(denominator).div(numerator);
@@ -213,21 +241,17 @@ contract EpochRewards is Ownable, Initializable, UsingPrecompiles, UsingRegistry
function updateTargetVotingYield() external {
require(msg.sender == address(0));
- _updateTargetVotingYield();
+ // _updateTargetVotingYield();
}
function calculateTargetEpochPaymentAndRewards() external view returns (uint256, uint256) {
- uint256 targetEpochRewards = _getTargetEpochRewards();
- uint256 targetTotalEpochPaymentsInGold = _getTargetTotalEpochPaymentsInGold();
+ uint256 targetEpochRewards = getTargetEpochRewards();
+ uint256 targetTotalEpochPaymentsInGold = getTargetTotalEpochPaymentsInGold();
uint256 targetGoldSupplyIncrease = targetEpochRewards.add(targetTotalEpochPaymentsInGold);
FixidityLib.Fraction memory rewardsMultiplier = _getRewardsMultiplier(targetGoldSupplyIncrease);
return (
- /*
FixidityLib.newFixed(maxValidatorEpochPayment).multiply(rewardsMultiplier).fromFixed(),
FixidityLib.newFixed(targetEpochRewards).multiply(rewardsMultiplier).fromFixed()
- */
- maxValidatorEpochPayment,
- targetEpochRewards
);
}
}
diff --git a/packages/protocol/contracts/governance/test/EpochRewardsTest.sol b/packages/protocol/contracts/governance/test/EpochRewardsTest.sol
index d2b7f82a17f..8dd822aab0a 100644
--- a/packages/protocol/contracts/governance/test/EpochRewardsTest.sol
+++ b/packages/protocol/contracts/governance/test/EpochRewardsTest.sol
@@ -8,18 +8,6 @@ import "../../common/FixidityLib.sol";
*/
contract EpochRewardsTest is EpochRewards {
- function getTargetGoldTotalSupply() external view returns (uint256) {
- return _getTargetGoldTotalSupply();
- }
-
- function getTargetTotalEpochPaymentsInGold() external view returns (uint256) {
- return _getTargetTotalEpochPaymentsInGold();
- }
-
- function getTargetEpochRewards() external view returns (uint256) {
- return _getTargetEpochRewards();
- }
-
function getRewardsMultiplier(uint256 targetGoldTotalSupplyIncrease) external view returns (uint256) {
return _getRewardsMultiplier(targetGoldTotalSupplyIncrease).unwrap();
}
diff --git a/packages/protocol/migrations/13_epoch_rewards.ts b/packages/protocol/migrations/13_epoch_rewards.ts
index 6562c70cd41..43dfa88fc49 100644
--- a/packages/protocol/migrations/13_epoch_rewards.ts
+++ b/packages/protocol/migrations/13_epoch_rewards.ts
@@ -10,8 +10,9 @@ const initializeArgs = async (): Promise