From 5c9a7b2637e4281f732283225a37187cbdc5dcad Mon Sep 17 00:00:00 2001 From: pahor167 <47992132+pahor167@users.noreply.github.com> Date: Thu, 4 Jan 2024 17:24:28 +0100 Subject: [PATCH 01/25] Election up till activate --- .../protocol/test-sol/voting/Election.t.sol | 790 ++++++++++++++++++ 1 file changed, 790 insertions(+) create mode 100644 packages/protocol/test-sol/voting/Election.t.sol diff --git a/packages/protocol/test-sol/voting/Election.t.sol b/packages/protocol/test-sol/voting/Election.t.sol new file mode 100644 index 00000000000..ef29b1eef46 --- /dev/null +++ b/packages/protocol/test-sol/voting/Election.t.sol @@ -0,0 +1,790 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.5.13; +pragma experimental ABIEncoderV2; + +import { Test } from "celo-foundry/Test.sol"; +import { TestSortedLinkedList } from "../../contracts/stability/TestSortedLinkedList.sol"; +import "../../contracts/common/FixidityLib.sol"; +import "../../contracts/governance/Election.sol"; +import "../../contracts/governance/test/MockLockedGold.sol"; +import "../../contracts/governance/test/MockValidators.sol"; +import "../../contracts/common/Accounts.sol"; +import "../../contracts/identity/test/MockRandom.sol"; +import "../../contracts/common/Freezer.sol"; +import { Constants } from "../constants.sol"; +import "forge-std/console.sol"; + +contract ElectionTest is Election(true) { + function distributeEpochRewards(address group, uint256 value, address lesser, address greater) + external + { + return _distributeEpochRewards(group, value, lesser, greater); + } +} + +contract ElectionTestFoundry is Test, Constants { + using FixidityLib for FixidityLib.Fraction; + + event ElectableValidatorsSet(uint256 min, uint256 max); + event MaxNumGroupsVotedForSet(uint256 maxNumGroupsVotedFor); + event ElectabilityThresholdSet(uint256 electabilityThreshold); + event AllowedToVoteOverMaxNumberOfGroups(address indexed account, bool flag); + event ValidatorGroupMarkedEligible(address indexed group); + event ValidatorGroupMarkedIneligible(address indexed group); + event ValidatorGroupVoteCast(address indexed account, address indexed group, uint256 value); + event ValidatorGroupVoteActivated( + address indexed account, + address indexed group, + uint256 value, + uint256 units + ); + event ValidatorGroupPendingVoteRevoked( + address indexed account, + address indexed group, + uint256 value + ); + event ValidatorGroupActiveVoteRevoked( + address indexed account, + address indexed group, + uint256 value, + uint256 units + ); + event EpochRewardsDistributedToVoters(address indexed group, uint256 value); + + Accounts accounts; + ElectionTest election; + Freezer freezer; + MockLockedGold lockedGold; + MockValidators validators; + MockRandom random; + IRegistry registry; + + address registryAddress = 0x000000000000000000000000000000000000ce10; + address nonOwner = actor("nonOwner"); + address owner = address(this); + uint256 electableValidatorsMin = 4; + uint256 electableValidatorsMax = 6; + uint256 maxNumGroupsVotedFor = 3; + uint256 electabilityThreshold = FixidityLib.newFixedFraction(1, 100).unwrap(); + + address account1 = actor("account1"); + address account2 = actor("account2"); + address account3 = actor("account3"); + address account4 = actor("account4"); + address account5 = actor("account5"); + address account6 = actor("account6"); + address account7 = actor("account7"); + address account8 = actor("account8"); + address account9 = actor("account9"); + address account10 = actor("account10"); + + address[] accountsArray; + + function createAccount(address account) public { + vm.prank(account); + accounts.createAccount(); + } + + function setupGroupAndVote( + address newGroup, + address oldGroup, + address[] memory members, + bool vote + ) public { + validators.setMembers(newGroup, members); + registry.setAddressFor("Validators", address(this)); + election.markGroupEligible(newGroup, oldGroup, address(0)); + registry.setAddressFor("Validators", address(validators)); + if (vote) { + election.vote(newGroup, 1, oldGroup, address(0)); + } + } + + function setUp() public { + deployCodeTo("Registry.sol", abi.encode(false), registryAddress); + + accounts = new Accounts(true); + + accountsArray.push(account1); + accountsArray.push(account2); + accountsArray.push(account3); + accountsArray.push(account4); + accountsArray.push(account5); + accountsArray.push(account6); + accountsArray.push(account7); + accountsArray.push(account8); + accountsArray.push(account9); + accountsArray.push(account10); + + for (uint256 i = 0; i < accountsArray.length; i++) { + createAccount(accountsArray[i]); + } + + createAccount(address(this)); + + election = new ElectionTest(); + freezer = new Freezer(true); + lockedGold = new MockLockedGold(); + validators = new MockValidators(); + registry = IRegistry(registryAddress); + + registry.setAddressFor("Accounts", address(accounts)); + registry.setAddressFor("Freezer", address(freezer)); + registry.setAddressFor("LockedGold", address(lockedGold)); + registry.setAddressFor("Validators", address(validators)); + + election.initialize( + registryAddress, + electableValidatorsMin, + electableValidatorsMax, + maxNumGroupsVotedFor, + electabilityThreshold + ); + } +} + +contract Election_Initialize is ElectionTestFoundry { + function test_shouldHaveSetOwner() public { + assertEq(election.owner(), owner); + } + + function test_ShouldHaveSetElectableValidators() public { + (uint256 min, uint256 max) = election.getElectableValidators(); + assertEq(min, electableValidatorsMin); + assertEq(max, electableValidatorsMax); + } + + function test_ShouldHaveSetMaxNumGroupsVotedFor() public { + assertEq(election.maxNumGroupsVotedFor(), maxNumGroupsVotedFor); + } + + function test_ShouldHaveSetElectabilityThreshold() public { + assertEq(election.electabilityThreshold(), electabilityThreshold); + } + + function test_shouldRevertWhenCalledAgain() public { + vm.expectRevert("contract already initialized"); + election.initialize( + registryAddress, + electableValidatorsMin, + electableValidatorsMax, + maxNumGroupsVotedFor, + electabilityThreshold + ); + } +} + +contract Election_SetElectabilityThreshold is ElectionTestFoundry { + function test_shouldSetElectabilityThreshold() public { + uint256 newElectabilityThreshold = FixidityLib.newFixedFraction(1, 200).unwrap(); + election.setElectabilityThreshold(newElectabilityThreshold); + assertEq(election.electabilityThreshold(), newElectabilityThreshold); + } + + function test_ShouldRevertWhenThresholdLargerThan100Percent() public { + vm.expectRevert("Electability threshold must be lower than 100%"); + election.setElectabilityThreshold(FixidityLib.fixed1().unwrap() + 1); + } +} + +contract Election_SetElectableValidators is ElectionTestFoundry { + function test_shouldSetElectableValidators() public { + uint256 newElectableValidatorsMin = 2; + uint256 newElectableValidatorsMax = 4; + election.setElectableValidators(newElectableValidatorsMin, newElectableValidatorsMax); + (uint256 min, uint256 max) = election.getElectableValidators(); + assertEq(min, newElectableValidatorsMin); + assertEq(max, newElectableValidatorsMax); + } + + function test_ShouldEmitTHeElectableValidatorsSetEvent() public { + uint256 newElectableValidatorsMin = 2; + uint256 newElectableValidatorsMax = 4; + vm.expectEmit(true, false, false, false); + emit ElectableValidatorsSet(newElectableValidatorsMin, newElectableValidatorsMax); + election.setElectableValidators(newElectableValidatorsMin, newElectableValidatorsMax); + } + + function test_ShouldRevertWhenMinElectableValidatorsIsZero() public { + vm.expectRevert("Minimum electable validators cannot be zero"); + election.setElectableValidators(0, electableValidatorsMax); + } + + function test_ShouldRevertWhenTHeminIsGreaterThanMax() public { + vm.expectRevert("Maximum electable validators cannot be smaller than minimum"); + election.setElectableValidators(electableValidatorsMax, electableValidatorsMin); + } + + function test_ShouldRevertWhenValuesAreUnchanged() public { + vm.expectRevert("Electable validators not changed"); + election.setElectableValidators(electableValidatorsMin, electableValidatorsMax); + } + + function test_ShouldRevertWhenCalledByNonOwner() public { + vm.expectRevert("Ownable: caller is not the owner"); + vm.prank(nonOwner); + election.setElectableValidators(1, 2); + } +} + +contract Election_SetMaxNumGroupsVotedFor is ElectionTestFoundry { + function test_shouldSetMaxNumGroupsVotedFor() public { + uint256 newMaxNumGroupsVotedFor = 4; + election.setMaxNumGroupsVotedFor(newMaxNumGroupsVotedFor); + assertEq(election.maxNumGroupsVotedFor(), newMaxNumGroupsVotedFor); + } + + function test_ShouldEmitMaxNumGroupsVotedForSetEvent() public { + uint256 newMaxNumGroupsVotedFor = 4; + vm.expectEmit(true, false, false, false); + emit MaxNumGroupsVotedForSet(newMaxNumGroupsVotedFor); + election.setMaxNumGroupsVotedFor(newMaxNumGroupsVotedFor); + } + + function test_ShouldRevertWhenCalledByNonOwner() public { + vm.expectRevert("Ownable: caller is not the owner"); + vm.prank(nonOwner); + election.setMaxNumGroupsVotedFor(1); + } +} + +contract Election_SetAllowedToVoteOverMaxNumberOfGroups is ElectionTestFoundry { + function test_shouldSetAllowedToVoteOverMaxNumberOfGroups() public { + election.setAllowedToVoteOverMaxNumberOfGroups(true); + assertEq(election.allowedToVoteOverMaxNumberOfGroups(address(this)), true); + } + + function test_ShouldRevertWhenCalledByValidator() public { + validators.setValidator(address(this)); + vm.expectRevert("Validators cannot vote for more than max number of groups"); + election.setAllowedToVoteOverMaxNumberOfGroups(true); + } + + function test_ShouldRevertWhenCalledByValidatorGroup() public { + validators.setValidatorGroup(address(this)); + vm.expectRevert("Validator groups cannot vote for more than max number of groups"); + election.setAllowedToVoteOverMaxNumberOfGroups(true); + } + + function test_ShouldEmitAllowedToVoteOverMaxNumberOfGroupsEvent() public { + vm.expectEmit(true, false, false, false); + emit AllowedToVoteOverMaxNumberOfGroups(address(this), true); + election.setAllowedToVoteOverMaxNumberOfGroups(true); + } + + function test_ShouldSwitchAllowedToVoteOVerMaxNumberOfGroupsOff_WhenTurnedOn() public { + election.setAllowedToVoteOverMaxNumberOfGroups(true); + assertEq(election.allowedToVoteOverMaxNumberOfGroups(address(this)), true); + election.setAllowedToVoteOverMaxNumberOfGroups(false); + assertEq(election.allowedToVoteOverMaxNumberOfGroups(address(this)), false); + } + + function test_ShouldEmitAllowedToVoteOverMaxNumberOfGroupsEvent_WhenTurnedOn() public { + election.setAllowedToVoteOverMaxNumberOfGroups(true); + vm.expectEmit(true, false, false, false); + emit AllowedToVoteOverMaxNumberOfGroups(address(this), false); + election.setAllowedToVoteOverMaxNumberOfGroups(false); + } + +} + +contract Election_MarkGroupEligible is ElectionTestFoundry { + function setUp() public { + super.setUp(); + + registry.setAddressFor("Validators", address(address(this))); + } + + function test_shouldMarkGroupEligible() public { + address group = address(this); + election.markGroupEligible(group, address(0), address(0)); + address[] memory eligibleGroups = election.getEligibleValidatorGroups(); + assertEq(eligibleGroups.length, 1); + assertEq(eligibleGroups[0], group); + } + + function test_ShouldEmitValidatorGroupMarkedEligibleEvent() public { + address group = address(this); + vm.expectEmit(true, false, false, false); + emit ValidatorGroupMarkedEligible(group); + election.markGroupEligible(group, address(0), address(0)); + } + + function test_ShouldRevertWhenAlreadyMarkedEligible() public { + address group = address(this); + election.markGroupEligible(group, address(0), address(0)); + vm.expectRevert("invalid key"); + election.markGroupEligible(group, address(0), address(0)); + } + + function test_ShouldRevertWhenCalledByNonValidator() public { + vm.expectRevert("only registered contract"); + vm.prank(nonOwner); + election.markGroupEligible(address(this), address(0), address(0)); + } +} + +contract Election_MarkGroupInEligible is ElectionTestFoundry { + function setUp() public { + super.setUp(); + + registry.setAddressFor("Validators", address(address(this))); + } + + function test_shouldMarkGroupIneligible() public { + address group = address(this); + election.markGroupEligible(group, address(0), address(0)); + election.markGroupIneligible(group); + address[] memory eligibleGroups = election.getEligibleValidatorGroups(); + assertEq(eligibleGroups.length, 0); + } + + function test_ShouldEmitValidatorGroupMarkedIneligibleEvent() public { + address group = address(this); + election.markGroupEligible(group, address(0), address(0)); + vm.expectEmit(true, false, false, false); + emit ValidatorGroupMarkedIneligible(group); + election.markGroupIneligible(group); + } + + function test_ShouldRevertWhenAlreadyMarkedIneligible() public { + address group = address(this); + vm.expectRevert("key not in list"); + election.markGroupIneligible(group); + } + + function test_ShouldRevertWhenCalledByNonValidator() public { + vm.expectRevert("only registered contract"); + vm.prank(nonOwner); + election.markGroupIneligible(address(this)); + } +} + +contract Election_Vote is ElectionTestFoundry { + address voter = address(this); + address group = account1; + uint256 value = 1000; + + uint256 originallyNotVotedWithAmount = 1; + uint256 voterFirstGroupVote = value - maxNumGroupsVotedFor - originallyNotVotedWithAmount; + uint256 rewardValue = 1000000; + + function setUp() public { + super.setUp(); + + address[] memory members = new address[](1); + members[0] = account9; + validators.setMembers(group, members); + } + + function WhenGroupEligible() public { + registry.setAddressFor("Validators", address(this)); + election.markGroupEligible(group, address(0), address(0)); + registry.setAddressFor("Validators", address(validators)); + } + + function WhenGroupCanReceiveVotes() public { + WhenGroupEligible(); + lockedGold.setTotalLockedGold(value); + validators.setNumRegisteredValidators(1); + } + + function WhenTheVoterCanVoteForAnAdditionalGroup() public { + lockedGold.incrementNonvotingAccountBalance(voter, value); + } + + function WhenTheVoterHasNotAlreadyVotedForThisGroup() public { + WhenGroupCanReceiveVotes(); + WhenTheVoterCanVoteForAnAdditionalGroup(); + election.vote(group, value, address(0), address(0)); + } + + function test_ShouldAddTheGroupToLIstOfGroupsTheAccountHasVotedFor_WhenTheVoterHasNotAlreadyVotedForThisGroup() + public + { + WhenTheVoterHasNotAlreadyVotedForThisGroup(); + address[] memory groupsVotedFor = election.getGroupsVotedForByAccount(voter); + assertEq(groupsVotedFor.length, 1); + assertEq(groupsVotedFor[0], group); + } + + function test_ShouldIncrementTheAccountsPendingVotesForTheGroup_WhenTheVoterHasNotAlreadyVotedForThisGroup() + public + { + WhenTheVoterHasNotAlreadyVotedForThisGroup(); + assertEq(election.getPendingVotesForGroupByAccount(group, voter), value); + } + + function test_ShouldIncrementTheAccountsTotalVotesForTheGroup_WhenTheVoterHasNotAlreadyVotedForThisGroup() + public + { + WhenTheVoterHasNotAlreadyVotedForThisGroup(); + assertEq(election.getTotalVotesForGroupByAccount(group, voter), value); + } + + function test_ShouldIncrementTheACcountsTotalVotes_WhenTheVoterHasNotAlreadyVotedForThisGroup() + public + { + WhenTheVoterHasNotAlreadyVotedForThisGroup(); + assertEq(election.getTotalVotesByAccount(voter), value); + } + + function test_ShouldIncrementTheTotalVotesForTheGroup_WhenTheVoterHasNotAlreadyVotedForThisGroup() + public + { + WhenTheVoterHasNotAlreadyVotedForThisGroup(); + assertEq(election.getTotalVotesForGroup(group), value); + } + + function test_ShouldIncrementTheTotalVotes_WhenTheVoterHasNotAlreadyVotedForThisGroup() public { + WhenTheVoterHasNotAlreadyVotedForThisGroup(); + assertEq(election.getTotalVotes(), value); + } + + function test_ShouldDecrementTheACcountsNonVotingLockedGoldBalance_WhenTheVoterHasNotAlreadyVotedForThisGroup() + public + { + WhenTheVoterHasNotAlreadyVotedForThisGroup(); + assertEq(lockedGold.nonvotingAccountBalance(voter), 0); + } + + function test_ShouldEmitTheValidatorGroupVoteCastEvent_WhenTheVoterHasNotAlreadyVotedForThisGroup() + public + { + WhenGroupCanReceiveVotes(); + WhenTheVoterCanVoteForAnAdditionalGroup(); + vm.expectEmit(true, false, false, false); + emit ValidatorGroupVoteCast(voter, group, value); + election.vote(group, value, address(0), address(0)); + } + + function test_ShouldRevert_WhenTheVOterDoesNotHaveSufficientNonVotingBalance() public { + WhenGroupEligible(); + lockedGold.incrementNonvotingAccountBalance(voter, value - 1); + vm.expectRevert("SafeMath: subtraction overflow"); + election.vote(group, value, address(0), address(0)); + } + + function WhenVotedForMaxNumberOfGroups() public returns (address newGroup) { + WhenGroupEligible(); + lockedGold.incrementNonvotingAccountBalance(voter, value); + + for (uint256 i = 0; i < maxNumGroupsVotedFor; i++) { + address[] memory members = new address[](1); + members[0] = accountsArray[9]; + newGroup = accountsArray[i + 2]; + setupGroupAndVote(newGroup, group, members, true); + } + } + + function test_ShouldRevert_WhenTheVoterCannotVoteForAnAdditionalGroup() public { + address newGroup = WhenVotedForMaxNumberOfGroups(); + + vm.expectRevert("Voted for too many groups"); + election.vote(group, value - maxNumGroupsVotedFor, newGroup, address(0)); + } + + function test_ShouldAllowToVoteForAnotherGroup_WhenTheVoterIsOVerMaxNumberGroupsVotedForButCanVoteForAdditionalGroup() + public + { + address newGroup = WhenVotedForMaxNumberOfGroups(); + election.setAllowedToVoteOverMaxNumberOfGroups(true); + + vm.expectEmit(true, true, true, true); + emit ValidatorGroupVoteCast(voter, group, value - maxNumGroupsVotedFor); + election.vote(group, value - maxNumGroupsVotedFor, newGroup, address(0)); + assertEq(election.getPendingVotesForGroupByAccount(group, voter), value - maxNumGroupsVotedFor); + } + + function test_ShouldSetTotalVotesByAccount_WhenMaxNumberOfGroupsWasNotReached() public { + WhenVotedForMaxNumberOfGroups(); + assertEq(election.getTotalVotesByAccount(voter), maxNumGroupsVotedFor); + } + + function WhenVotedForMoreThanMaxNumberOfGroups() public returns (address newGroup) { + newGroup = WhenVotedForMaxNumberOfGroups(); + election.setAllowedToVoteOverMaxNumberOfGroups(true); + election.vote(group, voterFirstGroupVote, newGroup, address(0)); + } + + function test_ShouldRevert_WhenTurningOffSetAllowedToVoteOverMaxNUmberOfGroups() public { + WhenVotedForMoreThanMaxNumberOfGroups(); + + vm.expectRevert("Too many groups voted for!"); + election.setAllowedToVoteOverMaxNumberOfGroups(false); + } + + function test_ShouldReturnOnlyLastVotedWithSinceVotesWereNotManuallyCounted() public { + WhenVotedForMoreThanMaxNumberOfGroups(); + assertEq(election.getTotalVotesByAccount(voter), voterFirstGroupVote); + } + + function manuallyUpdateTotalVotesForAllGroups(address _voter) public { + for (uint256 i = 0; i < maxNumGroupsVotedFor; i++) { + election.updateTotalVotesByAccountForGroup(_voter, accountsArray[i + 2]); + } + election.updateTotalVotesByAccountForGroup(_voter, group); + } + + function WhenTotalVotesWereManuallyCounted() public { + WhenVotedForMoreThanMaxNumberOfGroups(); + manuallyUpdateTotalVotesForAllGroups(voter); + } + + function test_ShouldReturnTotalVotesByAccount_WhenTotalVotesAreManuallyCounted() public { + WhenTotalVotesWereManuallyCounted(); + assertEq(election.getTotalVotesByAccount(voter), value - originallyNotVotedWithAmount); + } + + function test_ShouldReturnLoweredTotalNumberOfVotes_WhenVotesRevoked_WhenTotalVotesWereManuallyCounted() + public + { + uint256 revokeDiff = 100; + uint256 revokeValue = voterFirstGroupVote - revokeDiff; + + WhenTotalVotesWereManuallyCounted(); + election.revokePending(group, revokeValue, accountsArray[4], address(0), 3); + assertEq(election.getTotalVotesByAccount(voter), maxNumGroupsVotedFor + revokeDiff); + } + + function WhenVotesAreBeingActivated() public returns (address newGroup) { + newGroup = WhenVotedForMoreThanMaxNumberOfGroups(); + vm.roll(EPOCH_SIZE + 1); + election.activateForAccount(group, voter); + } + + function test_ShouldIncrementTheAccountsActiveVotesForGroup_WhenVotesAreBeingActivated() public { + WhenVotesAreBeingActivated(); + assertEq(election.getActiveVotesForGroupByAccount(group, voter), voterFirstGroupVote); + } + + function test_ShouldReturnCorrectValueWhenManuallyCounted_WhenVotesAreBeingActivated() public { + WhenVotesAreBeingActivated(); + manuallyUpdateTotalVotesForAllGroups(voter); + + assertEq(election.getTotalVotesByAccount(voter), value - originallyNotVotedWithAmount); + } + + function WhenAwardsAreDistributed() public returns (address newGroup) { + newGroup = WhenVotesAreBeingActivated(); + election.distributeEpochRewards(group, rewardValue, newGroup, address(0)); + } + + function test_ShouldRevokeActiveVotes_WhenAwardsAreDistributed() public { + // (more then original votes without rewards) + address newGroup = WhenAwardsAreDistributed(); + election.revokeActive(group, value, newGroup, address(0), 3); + assertEq( + election.getActiveVotesForGroupByAccount(group, voter), + rewardValue - maxNumGroupsVotedFor - originallyNotVotedWithAmount + ); + } + + function test_ShouldReturnCorrectValueWhenManuallyCounted_WhenMoreVotesThanActiveIsRevoked_WhenAwardsAreDistributed() + public + { + address newGroup = WhenAwardsAreDistributed(); + election.revokeActive(group, value, newGroup, address(0), 3); + manuallyUpdateTotalVotesForAllGroups(voter); + + assertEq(election.getTotalVotesByAccount(voter), rewardValue - originallyNotVotedWithAmount); + } + + function test_ShouldReturnTotalVotesByAccount_WhenTotalVotesAreManuallyCountedOnReward_WhenAwardsAreDistributed() + public + { + WhenAwardsAreDistributed(); + manuallyUpdateTotalVotesForAllGroups(voter); + + assertEq( + election.getTotalVotesByAccount(voter), + value + rewardValue - originallyNotVotedWithAmount + ); + } + + function test_ShouldIncreaseTotalVotesCountOnceVoted_WhenTotalVotesAreManuallyCountedOnReward_WhenAwardsAreDistributed() + public + { + address newGroup = WhenAwardsAreDistributed(); + manuallyUpdateTotalVotesForAllGroups(voter); + + election.vote(newGroup, originallyNotVotedWithAmount, account4, group); + + assertEq(election.getTotalVotes(), value + rewardValue); + } + + function test_ShouldRevert_WhenTheGroupCannotReceiveVotes() public { + WhenGroupEligible(); + lockedGold.setTotalLockedGold(value / 2 - 1); + address[] memory members = new address[](1); + members[0] = account9; + validators.setMembers(group, members); + validators.setNumRegisteredValidators(1); + assertEq(election.getNumVotesReceivable(group), value - 2); + + vm.expectRevert("Group cannot receive votes"); + election.vote(group, value, address(0), address(0)); + } + + function test_ShouldRevert_WhenTheGroupIsNotEligible() public { + vm.expectRevert("Group not eligible"); + election.vote(group, value, address(0), address(0)); + } + +} + +contract Election_Activate is ElectionTestFoundry { + address voter = address(this); + address group = account1; + uint256 value = 1000; + + function setUp() public { + super.setUp(); + + address[] memory members = new address[](1); + members[0] = account9; + validators.setMembers(group, members); + + registry.setAddressFor("Validators", address(this)); + election.markGroupEligible(group, address(0), address(0)); + registry.setAddressFor("Validators", address(validators)); + + lockedGold.setTotalLockedGold(value); + validators.setMembers(group, members); + validators.setNumRegisteredValidators(1); + lockedGold.incrementNonvotingAccountBalance(voter, value); + } + + function WhenVoterHasPendingVotes() public { + election.vote(group, value, address(0), address(0)); + } + + function WhenEpochBoundaryHasPassed() public { + WhenVoterHasPendingVotes(); + vm.roll(EPOCH_SIZE + 1); + election.activate(group); + } + + function test_ShouldDecrementTheAccountsPendingVotesForTheGroup_WhenEpochBoundaryHasPassed() + public + { + WhenEpochBoundaryHasPassed(); + assertEq(election.getPendingVotesForGroupByAccount(group, voter), 0); + } + + function test_ShouldIncrementTheAccountsActiveVotesForTheGroup_WhenEpochBoundaryHasPassed() + public + { + WhenEpochBoundaryHasPassed(); + assertEq(election.getActiveVotesForGroupByAccount(group, voter), value); + } + + function test_ShouldNotModifyTheAccountsTotalVotesForTheGroup_WhenEpochBoundaryHasPassed() + public + { + WhenEpochBoundaryHasPassed(); + assertEq(election.getTotalVotesForGroupByAccount(group, voter), value); + } + + function test_ShouldNotModifyTheAccountsTotalVotes_WhenEpochBoundaryHasPassed() public { + WhenEpochBoundaryHasPassed(); + assertEq(election.getTotalVotesByAccount(voter), value); + } + + function test_ShouldNotModifyTotalVotesForGroup_WhenEpochBoundaryHasPassed() public { + WhenEpochBoundaryHasPassed(); + assertEq(election.getTotalVotesForGroup(group), value); + } + + function test_ShouldNotModifyTotalVotes_WhenEpochBoundaryHasPassed() public { + WhenEpochBoundaryHasPassed(); + assertEq(election.getTotalVotes(), value); + } + + function test_ShouldEmitValidatorGroupVoteActivatedEvent_WhenEpochBoundaryHasPassed() public { + WhenVoterHasPendingVotes(); + vm.roll(EPOCH_SIZE + 1); + vm.expectEmit(true, true, true, false); + emit ValidatorGroupVoteActivated(voter, group, value, value * 100000000000000000000); + election.activate(group); + } + + address voter2 = account2; + uint256 value2 = 573; + + function WhenAnotherVoterActivatesVotes() public { + WhenEpochBoundaryHasPassed(); + lockedGold.incrementNonvotingAccountBalance(voter2, value2); + vm.prank(voter2); + election.vote(group, value2, address(0), address(0)); + vm.roll(2 * EPOCH_SIZE + 2); + vm.prank(voter2); + election.activate(group); + } + + function test_ShouldNotModifyTheFirstAccountActiveVotesForTheGroup_WhenAnotherVoterActivatesVotes() + public + { + WhenAnotherVoterActivatesVotes(); + assertEq(election.getActiveVotesForGroupByAccount(group, voter), value); + } + + function test_ShouldNotModifyTheFirstAccountTotalVotesForTheGroup_WhenAnotherVoterActivatesVotes() + public + { + WhenAnotherVoterActivatesVotes(); + assertEq(election.getTotalVotesForGroupByAccount(group, voter), value); + } + + function test_ShouldNotModifyTheFirstAccountTotalVotes_WhenAnotherVoterActivatesVotes() public { + WhenAnotherVoterActivatesVotes(); + assertEq(election.getTotalVotesByAccount(voter), value); + } + + function test_ShouldDecrementTheSecondAccountsPendingVotesFOrTheGroup_WhenAnotherVoterActivatesVotes() + public + { + WhenAnotherVoterActivatesVotes(); + assertEq(election.getPendingVotesForGroupByAccount(group, voter2), 0); + } + + function test_ShouldIncrementTheSecondAccountActiveVotesForTheGroup_WhenAnotherVoterActivatesVotes() + public + { + WhenAnotherVoterActivatesVotes(); + assertEq(election.getActiveVotesForGroupByAccount(group, voter2), value2); + } + + function test_ShouldNotModifyTheSecondsAccountTotalVotesForTheGroup_WhenAnotherVoterActivatesVotes() + public + { + WhenAnotherVoterActivatesVotes(); + assertEq(election.getTotalVotesForGroupByAccount(group, voter2), value2); + } + + function test_ShouldNotMOdifyTheSecondAccountTotalVotes_WhenAnotherVoterActivatesVotes() public { + WhenAnotherVoterActivatesVotes(); + assertEq(election.getTotalVotesByAccount(voter2), value2); + } + + function test_ShouldNotModifyTotalVotesForGroup_WhenAnotherVoterActivatesVotes() public { + WhenAnotherVoterActivatesVotes(); + assertEq(election.getTotalVotesForGroup(group), value + value2); + } + + function test_ShouldNotModifyTotalVotes_WhenAnotherVoterActivatesVotes() public { + WhenAnotherVoterActivatesVotes(); + assertEq(election.getTotalVotes(), value + value2); + } + + function test_ShouldRevert_WhenAnEpochBoundaryHadNotPassedSinceThePendingVotesWereMade() public { + WhenVoterHasPendingVotes(); + vm.expectRevert("Pending vote epoch not passed"); + election.activateForAccount(group, voter); + } + + function test_ShouldRevert_WhenTheVoterDoesNotHavePendingVotes() public { + vm.expectRevert("Vote value cannot be zero"); + election.activate(group); + } +} From a69931199cacab2120ac86f791cc629facc3f4bb Mon Sep 17 00:00:00 2001 From: pahor167 <47992132+pahor167@users.noreply.github.com> Date: Fri, 5 Jan 2024 18:08:14 +0100 Subject: [PATCH 02/25] ElectionValidatorSigners mostly done --- packages/protocol/test-sol/utils.sol | 25 + .../protocol/test-sol/voting/Election.t.sol | 723 +++++++++++++++++- 2 files changed, 747 insertions(+), 1 deletion(-) diff --git a/packages/protocol/test-sol/utils.sol b/packages/protocol/test-sol/utils.sol index d7885dd3346..0494431ced6 100644 --- a/packages/protocol/test-sol/utils.sol +++ b/packages/protocol/test-sol/utils.sol @@ -7,4 +7,29 @@ contract Utils is Test { vm.warp(block.timestamp + timeDelta); } + function assertAlmostEqual(uint256 actual, uint256 expected, uint256 margin) public { + uint256 diff = actual > expected ? actual - expected : expected - actual; + assertTrue(diff <= margin, string(abi.encodePacked("Difference is ", uintToStr(diff)))); + } + + function uintToStr(uint256 _i) internal pure returns (string memory _uintAsString) { + uint256 number = _i; + if (number == 0) { + return "0"; + } + uint256 j = number; + uint256 len; + while (j != 0) { + len++; + j /= 10; + } + bytes memory bstr = new bytes(len); + uint256 k = len - 1; + while (number != 0) { + bstr[k--] = bytes1(uint8(48 + (number % 10))); + number /= 10; + } + return string(bstr); + } + } diff --git a/packages/protocol/test-sol/voting/Election.t.sol b/packages/protocol/test-sol/voting/Election.t.sol index ef29b1eef46..69f5b7f3cd2 100644 --- a/packages/protocol/test-sol/voting/Election.t.sol +++ b/packages/protocol/test-sol/voting/Election.t.sol @@ -9,9 +9,11 @@ import "../../contracts/governance/Election.sol"; import "../../contracts/governance/test/MockLockedGold.sol"; import "../../contracts/governance/test/MockValidators.sol"; import "../../contracts/common/Accounts.sol"; +import "../../contracts/common/linkedlists/AddressSortedLinkedList.sol"; import "../../contracts/identity/test/MockRandom.sol"; import "../../contracts/common/Freezer.sol"; import { Constants } from "../constants.sol"; +import "../utils.sol"; import "forge-std/console.sol"; contract ElectionTest is Election(true) { @@ -22,7 +24,7 @@ contract ElectionTest is Election(true) { } } -contract ElectionTestFoundry is Test, Constants { +contract ElectionTestFoundry is Utils, Constants { using FixidityLib for FixidityLib.Fraction; event ElectableValidatorsSet(uint256 min, uint256 max); @@ -127,11 +129,13 @@ contract ElectionTestFoundry is Test, Constants { lockedGold = new MockLockedGold(); validators = new MockValidators(); registry = IRegistry(registryAddress); + random = new MockRandom(); registry.setAddressFor("Accounts", address(accounts)); registry.setAddressFor("Freezer", address(freezer)); registry.setAddressFor("LockedGold", address(lockedGold)); registry.setAddressFor("Validators", address(validators)); + registry.setAddressFor("Random", address(random)); election.initialize( registryAddress, @@ -788,3 +792,720 @@ contract Election_Activate is ElectionTestFoundry { election.activate(group); } } + +contract Election_ActivateForAccount is ElectionTestFoundry { + address voter = address(this); + address group = account1; + uint256 value = 1000; + + function setUp() public { + super.setUp(); + + address[] memory members = new address[](1); + members[0] = account9; + validators.setMembers(group, members); + + registry.setAddressFor("Validators", address(this)); + election.markGroupEligible(group, address(0), address(0)); + registry.setAddressFor("Validators", address(validators)); + + lockedGold.setTotalLockedGold(value); + validators.setMembers(group, members); + validators.setNumRegisteredValidators(1); + lockedGold.incrementNonvotingAccountBalance(voter, value); + } + + function WhenVoterHasPendingVotes() public { + election.vote(group, value, address(0), address(0)); + } + + function WhenEpochBoundaryHasPassed() public { + WhenVoterHasPendingVotes(); + vm.roll(EPOCH_SIZE + 1); + election.activateForAccount(group, voter); + } + + function test_ShouldDecrementTheAccountsPendingVotesForTheGroup_WhenEpochBoundaryHasPassed() + public + { + WhenEpochBoundaryHasPassed(); + assertEq(election.getPendingVotesForGroupByAccount(group, voter), 0); + } + + function test_ShouldIncrementTheAccountsActiveVotesForTheGroup_WhenEpochBoundaryHasPassed() + public + { + WhenEpochBoundaryHasPassed(); + assertEq(election.getActiveVotesForGroupByAccount(group, voter), value); + } + + function test_ShouldNotModifyTheAccountsTotalVotesForTheGroup_WhenEpochBoundaryHasPassed() + public + { + WhenEpochBoundaryHasPassed(); + assertEq(election.getTotalVotesForGroupByAccount(group, voter), value); + } + + function test_ShouldNotModifyTheAccountsTotalVotes_WhenEpochBoundaryHasPassed() public { + WhenEpochBoundaryHasPassed(); + assertEq(election.getTotalVotesByAccount(voter), value); + } + + function test_ShouldNotModifyTotalVotesForGroup_WhenEpochBoundaryHasPassed() public { + WhenEpochBoundaryHasPassed(); + assertEq(election.getTotalVotesForGroup(group), value); + } + + function test_ShouldNotModifyTotalVotes_WhenEpochBoundaryHasPassed() public { + WhenEpochBoundaryHasPassed(); + assertEq(election.getTotalVotes(), value); + } + + function test_ShouldEmitValidatorGroupVoteActivatedEvent_WhenEpochBoundaryHasPassed() public { + WhenVoterHasPendingVotes(); + vm.roll(EPOCH_SIZE + 1); + vm.expectEmit(true, true, true, false); + emit ValidatorGroupVoteActivated(voter, group, value, value * 100000000000000000000); + election.activate(group); + } + + address voter2 = account2; + uint256 value2 = 573; + + function WhenAnotherVoterActivatesVotes() public { + WhenEpochBoundaryHasPassed(); + lockedGold.incrementNonvotingAccountBalance(voter2, value2); + vm.prank(voter2); + election.vote(group, value2, address(0), address(0)); + vm.roll(2 * EPOCH_SIZE + 2); + election.activateForAccount(group, voter2); + } + + function test_ShouldNotModifyTheFirstAccountActiveVotesForTheGroup_WhenAnotherVoterActivatesVotes() + public + { + WhenAnotherVoterActivatesVotes(); + assertEq(election.getActiveVotesForGroupByAccount(group, voter), value); + } + + function test_ShouldNotModifyTheFirstAccountTotalVotesForTheGroup_WhenAnotherVoterActivatesVotes() + public + { + WhenAnotherVoterActivatesVotes(); + assertEq(election.getTotalVotesForGroupByAccount(group, voter), value); + } + + function test_ShouldNotModifyTheFirstAccountTotalVotes_WhenAnotherVoterActivatesVotes() public { + WhenAnotherVoterActivatesVotes(); + assertEq(election.getTotalVotesByAccount(voter), value); + } + + function test_ShouldDecrementTheSecondAccountsPendingVotesFOrTheGroup_WhenAnotherVoterActivatesVotes() + public + { + WhenAnotherVoterActivatesVotes(); + assertEq(election.getPendingVotesForGroupByAccount(group, voter2), 0); + } + + function test_ShouldIncrementTheSecondAccountActiveVotesForTheGroup_WhenAnotherVoterActivatesVotes() + public + { + WhenAnotherVoterActivatesVotes(); + assertEq(election.getActiveVotesForGroupByAccount(group, voter2), value2); + } + + function test_ShouldNotModifyTheSecondsAccountTotalVotesForTheGroup_WhenAnotherVoterActivatesVotes() + public + { + WhenAnotherVoterActivatesVotes(); + assertEq(election.getTotalVotesForGroupByAccount(group, voter2), value2); + } + + function test_ShouldNotMOdifyTheSecondAccountTotalVotes_WhenAnotherVoterActivatesVotes() public { + WhenAnotherVoterActivatesVotes(); + assertEq(election.getTotalVotesByAccount(voter2), value2); + } + + function test_ShouldNotModifyTotalVotesForGroup_WhenAnotherVoterActivatesVotes() public { + WhenAnotherVoterActivatesVotes(); + assertEq(election.getTotalVotesForGroup(group), value + value2); + } + + function test_ShouldNotModifyTotalVotes_WhenAnotherVoterActivatesVotes() public { + WhenAnotherVoterActivatesVotes(); + assertEq(election.getTotalVotes(), value + value2); + } + + function test_ShouldRevert_WhenEpochBoundaryHasNotPassedSinceThePendingVotesWereMade() public { + WhenVoterHasPendingVotes(); + vm.expectRevert("Pending vote epoch not passed"); + election.activateForAccount(group, voter); + } + + function test_ShouldRevert_WhenTheVoterDoesNotHavePendingVotes() public { + vm.expectRevert("Vote value cannot be zero"); + election.activateForAccount(group, voter); + } + +} + +contract Election_RevokePending is ElectionTestFoundry { + address voter = address(this); + address group = account1; + uint256 value = 1000; + + uint256 index = 0; + uint256 revokedValue = value - 1; + uint256 remaining = value - revokedValue; + + function setUp() public { + super.setUp(); + + address[] memory members = new address[](1); + members[0] = account9; + validators.setMembers(group, members); + + registry.setAddressFor("Validators", address(this)); + election.markGroupEligible(group, address(0), address(0)); + registry.setAddressFor("Validators", address(validators)); + + lockedGold.setTotalLockedGold(value); + validators.setMembers(group, members); + validators.setNumRegisteredValidators(1); + lockedGold.incrementNonvotingAccountBalance(voter, value); + election.vote(group, value, address(0), address(0)); + } + + function WhenValidatorGroupHasVotesButIsIneligible() public { + registry.setAddressFor("Validators", address(this)); + election.markGroupIneligible(group); + election.revokePending(group, revokedValue, address(0), address(0), index); + } + + function test_ShouldDecrementTheAccountsPendingVotesForTheGroup_WhenValidatorGroupHasVotesButIsIneligible() + public + { + WhenValidatorGroupHasVotesButIsIneligible(); + assertEq(election.getPendingVotesForGroupByAccount(group, voter), remaining); + } + + function test_ShouldDecrementAccountsTotalVotesForTheGroup_WhenValidatorGroupHasVotesButIsIneligible() + public + { + WhenValidatorGroupHasVotesButIsIneligible(); + assertEq(election.getTotalVotesForGroupByAccount(group, voter), remaining); + } + + function test_ShouldDecrementTheAccountsTotalVotes_WhenValidatorGroupHasVotesButIsIneligible() + public + { + WhenValidatorGroupHasVotesButIsIneligible(); + assertEq(election.getTotalVotesByAccount(voter), remaining); + } + + function test_ShouldDecrementTotalVotesForTheGroup_WhenValidatorGroupHasVotesButIsIneligible() + public + { + WhenValidatorGroupHasVotesButIsIneligible(); + assertEq(election.getTotalVotesForGroup(group), remaining); + } + + function test_ShouldDecrementTotalVotes_WhenValidatorGroupHasVotesButIsIneligible() public { + WhenValidatorGroupHasVotesButIsIneligible(); + assertEq(election.getTotalVotes(), remaining); + } + + function test_ShouldIncrementTheAccountsNonvotingLockedGoldBalance_WhenValidatorGroupHasVotesButIsIneligible() + public + { + WhenValidatorGroupHasVotesButIsIneligible(); + assertEq(lockedGold.nonvotingAccountBalance(voter), revokedValue); + } + + function test_ShouldEmitValidatorGroupPendingVoteRevokedEvent_WhenValidatorGroupHasVotesButIsIneligible() + public + { + registry.setAddressFor("Validators", address(this)); + election.markGroupIneligible(group); + vm.expectEmit(true, true, true, false); + emit ValidatorGroupPendingVoteRevoked(voter, group, revokedValue); + election.revokePending(group, revokedValue, address(0), address(0), index); + } + + function WhenRevokedValueIsLessThanPendingVotesButGroupIsEligible() public { + election.revokePending(group, revokedValue, address(0), address(0), index); + } + + function test_ShouldDecrementTheAccountsPendingVotesForTheGroup_WhenRevokedValueIsLessThanPendingVotesButGroupIsEligible() + public + { + WhenRevokedValueIsLessThanPendingVotesButGroupIsEligible(); + assertEq(election.getPendingVotesForGroupByAccount(group, voter), remaining); + } + + function test_ShouldDecrementAccountsTotalVotesForTheGroup_WhenRevokedValueIsLessThanPendingVotesButGroupIsEligible() + public + { + WhenRevokedValueIsLessThanPendingVotesButGroupIsEligible(); + assertEq(election.getTotalVotesForGroupByAccount(group, voter), remaining); + } + + function test_ShouldDecrementTheAccountsTotalVotes_WhenRevokedValueIsLessThanPendingVotesButGroupIsEligible() + public + { + WhenRevokedValueIsLessThanPendingVotesButGroupIsEligible(); + assertEq(election.getTotalVotesByAccount(voter), remaining); + } + + function test_ShouldDecrementTotalVotesForTheGroup_WhenRevokedValueIsLessThanPendingVotesButGroupIsEligible() + public + { + WhenRevokedValueIsLessThanPendingVotesButGroupIsEligible(); + assertEq(election.getTotalVotesForGroup(group), remaining); + } + + function test_ShouldDecrementTotalVotes_WhenRevokedValueIsLessThanPendingVotesButGroupIsEligible() + public + { + WhenRevokedValueIsLessThanPendingVotesButGroupIsEligible(); + assertEq(election.getTotalVotes(), remaining); + } + + function test_ShouldIncrementTheAccountsNonvotingLockedGoldBalance_WhenRevokedValueIsLessThanPendingVotesButGroupIsEligible() + public + { + WhenRevokedValueIsLessThanPendingVotesButGroupIsEligible(); + assertEq(lockedGold.nonvotingAccountBalance(voter), revokedValue); + } + + function test_ShouldEmitValidatorGroupPendingVoteRevokedEvent_WhenRevokedValueIsLessThanPendingVotesButGroupIsEligible() + public + { + registry.setAddressFor("Validators", address(this)); + election.markGroupIneligible(group); + vm.expectEmit(true, true, true, false); + emit ValidatorGroupPendingVoteRevoked(voter, group, revokedValue); + election.revokePending(group, revokedValue, address(0), address(0), index); + } + + function test_ShouldRemoveTheGroup_WhenCorrectIndexProvided_WhenRevokedValueIsEqualToPendingVotes() + public + { + election.revokePending(group, value, address(0), address(0), index); + assertEq(election.getGroupsVotedForByAccount(voter).length, 0); + } + + function test_ShouldRevert_WhenWrongIndexIsProvided() public { + vm.expectRevert("Bad index"); + election.revokePending(group, value, address(0), address(0), index + 1); + } + + function test_ShouldRevert_WhenRevokedValuesIsGreaterThanThePendingVotes() public { + vm.expectRevert("Vote value larger than pending votes"); + election.revokePending(group, value + 1, address(0), address(0), index); + } +} + +contract Election_RevokeActive is ElectionTestFoundry { + address voter0 = address(this); + address voter1 = account1; + address group = account2; + uint256 voteValue0 = 1000; + uint256 reward0 = 111; + uint256 voteValue1 = 1000; + + uint256 index = 0; + uint256 remaining = 1; + uint256 revokedValue = voteValue0 + reward0 - remaining; + + function assertConsistentSums() public { + uint256 activeTotal = election.getActiveVotesForGroupByAccount(group, voter0) + + election.getActiveVotesForGroupByAccount(group, voter1); + uint256 pendingTotal = election.getPendingVotesForGroupByAccount(group, voter0) + + election.getPendingVotesForGroupByAccount(group, voter1); + uint256 totalGroup = election.getTotalVotesForGroup(group); + assertAlmostEqual(election.getActiveVotesForGroup(group), activeTotal, 1); + assertAlmostEqual(totalGroup, activeTotal + pendingTotal, 1); + assertEq(election.getTotalVotes(), totalGroup); + } + + function setUp() public { + super.setUp(); + + address[] memory members = new address[](1); + members[0] = account9; + validators.setMembers(group, members); + + registry.setAddressFor("Validators", address(this)); + election.markGroupEligible(group, address(0), address(0)); + registry.setAddressFor("Validators", address(validators)); + + lockedGold.setTotalLockedGold(voteValue0 + voteValue1); + validators.setNumRegisteredValidators(1); + lockedGold.incrementNonvotingAccountBalance(voter0, voteValue0); + lockedGold.incrementNonvotingAccountBalance(voter1, voteValue1); + + // Gives 1000 units to voter 0 + election.vote(group, voteValue0, address(0), address(0)); + assertConsistentSums(); + vm.roll(EPOCH_SIZE + 1); + election.activate(group); + assertConsistentSums(); + + // Makes those 1000 units represent 1111 votes. + election.distributeEpochRewards(group, reward0, address(0), address(0)); + assertConsistentSums(); + + // Gives 900 units to voter 1. + vm.prank(voter1); + election.vote(group, voteValue1, address(0), address(0)); + assertConsistentSums(); + vm.roll(2 * EPOCH_SIZE + 2); + vm.prank(voter1); + election.activate(group); + assertConsistentSums(); + } + + function WhenTheValidatorGroupHasVotesButIsIneligible() public { + registry.setAddressFor("Validators", address(this)); + election.markGroupIneligible(group); + election.revokeActive(group, revokedValue, address(0), address(0), 0); + } + + function test_ShouldBeConsistent_WhenTheValidatorGroupHasVotesButIsIneligible() public { + WhenTheValidatorGroupHasVotesButIsIneligible(); + assertConsistentSums(); + } + + function test_ShouldDecrementTheAccountsActiveVotesForTheGroup_WhenTheValidatorGroupHasVotesButIsIneligible() + public + { + WhenTheValidatorGroupHasVotesButIsIneligible(); + assertEq(election.getActiveVotesForGroupByAccount(group, voter0), remaining); + } + + function test_ShouldDecrementTheAccountsTotalVotesForTheGroup_WhenTheValidatorGroupHasVotesButIsIneligible() + public + { + WhenTheValidatorGroupHasVotesButIsIneligible(); + assertEq(election.getTotalVotesForGroupByAccount(group, voter0), remaining); + } + + function test_ShouldDecrementTheAccountsTotalVotes_WhenTheValidatorGroupHasVotesButIsIneligible() + public + { + WhenTheValidatorGroupHasVotesButIsIneligible(); + assertEq(election.getTotalVotesByAccount(voter0), remaining); + } + + function test_ShouldDecrementTotalVotesForTheGroup_WhenTheValidatorGroupHasVotesButIsIneligible() + public + { + WhenTheValidatorGroupHasVotesButIsIneligible(); + assertEq( + election.getTotalVotesForGroup(group), + voteValue0 + reward0 + voteValue1 - revokedValue + ); + } + + function test_ShouldDecrementTotalVotes_WhenTheValidatorGroupHasVotesButIsIneligible() public { + WhenTheValidatorGroupHasVotesButIsIneligible(); + assertEq(election.getTotalVotes(), voteValue0 + reward0 + voteValue1 - revokedValue); + } + + function test_ShouldIncrementTheAccountsNonvotingLockedGoldBalance_WhenTheValidatorGroupHasVotesButIsIneligible() + public + { + WhenTheValidatorGroupHasVotesButIsIneligible(); + assertEq(lockedGold.nonvotingAccountBalance(voter0), revokedValue); + } + + function test_ShouldEmitValidatorGroupActiveVoteRevokedEvent_WhenTheValidatorGroupHasVotesButIsIneligible() + public + { + registry.setAddressFor("Validators", address(this)); + election.markGroupIneligible(group); + vm.expectEmit(true, true, true, false); + emit ValidatorGroupActiveVoteRevoked( + voter0, + group, + revokedValue, + revokedValue * 100000000000000000000 + ); + election.revokeActive(group, revokedValue, address(0), address(0), 0); + } + + function WhenRevokedValueIsLessThanTheActiveVotesButGroupIsEligible() public { + election.revokeActive(group, revokedValue, address(0), address(0), 0); + } + + function test_ShouldBeConsistent_WhenRevokedValueIsLessThanTheActiveVotesButGroupIsEligible() + public + { + WhenRevokedValueIsLessThanTheActiveVotesButGroupIsEligible(); + assertConsistentSums(); + } + + function test_ShouldDecrementTheAccountsActiveVotesForTheGroup_WhenRevokedValueIsLessThanTheActiveVotesButGroupIsEligible() + public + { + WhenRevokedValueIsLessThanTheActiveVotesButGroupIsEligible(); + assertEq(election.getActiveVotesForGroupByAccount(group, voter0), remaining); + } + + function test_ShouldDecrementTheAccountsTotalVotesForTheGroup_WhenRevokedValueIsLessThanTheActiveVotesButGroupIsEligible() + public + { + WhenRevokedValueIsLessThanTheActiveVotesButGroupIsEligible(); + assertEq(election.getTotalVotesForGroupByAccount(group, voter0), remaining); + } + + function test_ShouldDecrementTheAccountsTotalVotes_WhenRevokedValueIsLessThanTheActiveVotesButGroupIsEligible() + public + { + WhenRevokedValueIsLessThanTheActiveVotesButGroupIsEligible(); + assertEq(election.getTotalVotesByAccount(voter0), remaining); + } + + function test_ShouldDecrementTotalVotesForTheGroup_WhenRevokedValueIsLessThanTheActiveVotesButGroupIsEligible() + public + { + WhenRevokedValueIsLessThanTheActiveVotesButGroupIsEligible(); + assertEq( + election.getTotalVotesForGroup(group), + voteValue0 + reward0 + voteValue1 - revokedValue + ); + } + + function test_ShouldDecrementTotalVotes_WhenRevokedValueIsLessThanTheActiveVotesButGroupIsEligible() + public + { + WhenRevokedValueIsLessThanTheActiveVotesButGroupIsEligible(); + assertEq(election.getTotalVotes(), voteValue0 + reward0 + voteValue1 - revokedValue); + } + + function test_ShouldIncrementTheAccountsNonvotingLockedGoldBalance_WhenRevokedValueIsLessThanTheActiveVotesButGroupIsEligible() + public + { + WhenRevokedValueIsLessThanTheActiveVotesButGroupIsEligible(); + assertEq(lockedGold.nonvotingAccountBalance(voter0), revokedValue); + } + + function test_ShouldEmitValidatorGroupActiveVoteRevokedEvent_WhenRevokedValueIsLessThanTheActiveVotesButGroupIsEligible() + public + { + registry.setAddressFor("Validators", address(this)); + election.markGroupIneligible(group); + vm.expectEmit(true, true, true, false); + emit ValidatorGroupActiveVoteRevoked( + voter0, + group, + revokedValue, + revokedValue * 100000000000000000000 + ); + election.revokeActive(group, revokedValue, address(0), address(0), 0); + } + + function test_ShouldBeConsistent_WhenRevokeAllActive() public { + election.revokeAllActive(group, address(0), address(0), 0); + assertConsistentSums(); + } + + function test_ShouldDecrementAllOfTheAccountsActiveVotesForTheGroup_WhenRevokeAllActive() public { + election.revokeAllActive(group, address(0), address(0), 0); + assertEq(election.getActiveVotesForGroupByAccount(group, voter0), 0); + } + + function WhenCorrectIndexIsProvided() public { + election.revokeActive(group, voteValue0 + reward0, address(0), address(0), index); + } + + function test_ShouldBeConsistent_WhenCorrectIndexIsProvided() public { + WhenCorrectIndexIsProvided(); + assertConsistentSums(); + } + + function test_ShouldDecrementTheAccountsActiveVotesForTheGroup_WhenCorrectIndexIsProvided() + public + { + WhenCorrectIndexIsProvided(); + assertEq(election.getActiveVotesForGroupByAccount(group, voter0), 0); + } + + function test_ShouldDecrementTheAccountsTotalVotesForTheGroup_WhenCorrectIndexIsProvided() + public + { + WhenCorrectIndexIsProvided(); + assertEq(election.getTotalVotesForGroupByAccount(group, voter0), 0); + } + + function test_ShouldDecrementTheAccountsTotalVotes_WhenCorrectIndexIsProvided() public { + WhenCorrectIndexIsProvided(); + assertEq(election.getTotalVotesByAccount(voter0), 0); + } + + function test_ShouldDecrementTotalVotesForTheGroup_WhenCorrectIndexIsProvided() public { + WhenCorrectIndexIsProvided(); + assertEq(election.getTotalVotesForGroup(group), voteValue1); + } + + function test_ShouldDecrementTotalVotes_WhenCorrectIndexIsProvided() public { + WhenCorrectIndexIsProvided(); + assertEq(election.getTotalVotes(), voteValue1); + } + + function test_ShouldIncrementTheAccountsNonvotingLockedGoldBalance_WhenCorrectIndexIsProvided() + public + { + WhenCorrectIndexIsProvided(); + assertEq(lockedGold.nonvotingAccountBalance(voter0), voteValue0 + reward0); + } + + function test_ShouldRemoveTheGroupFromTheListOfGroupsTheAccountHasVotedFor_WhenCorrectIndexIsProvided() + public + { + WhenCorrectIndexIsProvided(); + assertEq(election.getGroupsVotedForByAccount(voter0).length, 0); + } + + function test_ShouldRevert_WhenWrongIndexIsProvided() public { + vm.expectRevert("Bad index"); + election.revokeActive(group, voteValue0 + reward0, address(0), address(0), index + 1); + } + + function test_ShouldRevert_WhenRevokedValueIsGreaterThanTheActiveVotes() public { + vm.expectRevert("Vote value larger than active votes"); + election.revokeActive(group, voteValue0 + reward0 + 1, address(0), address(0), index); + } + +} + +contract Election_ElectionValidatorSigners is ElectionTestFoundry { + address group1 = address(this); + address group2 = account1; + address group3 = account2; + + address validator1 = account3; + address validator2 = account4; + address validator3 = account5; + address validator4 = account6; + address validator5 = account7; + address validator6 = account8; + address validator7 = account9; + + bytes32 hash = 0xa5b9d60f32436310afebcfda832817a68921beb782fabf7915cc0460b443116a; + + // 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. + address voter1 = address(this); + address voter2 = account1; + address voter3 = account2; + + uint256 voter1Weight = 80; + uint256 voter2Weight = 50; + uint256 voter3Weight = 30; + + uint256 totalLockedGold = voter1Weight + voter2Weight + voter3Weight; + + struct MemberWithVotes { + address member; + uint256 votes; + } + + mapping(address => uint256) votesConsideredForElection; + + MemberWithVotes[] membersWithVotes; + + function setRandomness(uint256 randomness) public { + random.addTestRandomness(block.number + 1, hash); + } + + // Helper function to sort an array of uint256 + function sort(uint256[] memory data) internal pure returns (uint256[] memory) { + uint256 length = data.length; + for (uint256 i = 0; i < length; i++) { + for (uint256 j = i + 1; j < length; j++) { + if (data[i] > data[j]) { + uint256 temp = data[i]; + data[i] = data[j]; + data[j] = temp; + } + } + } + return data; + } + + function sortMembersWithVotesDesc(MemberWithVotes[] memory data) + internal + pure + returns (MemberWithVotes[] memory) + { + uint256 length = data.length; + for (uint256 i = 0; i < length; i++) { + for (uint256 j = i + 1; j < length; j++) { + if (data[i].votes < data[j].votes) { + MemberWithVotes memory temp = data[i]; + data[i] = data[j]; + data[j] = temp; + } + } + } + return data; + } + + function WhenThereIsALargeNumberOfGroups() public { + lockedGold.setTotalLockedGold(1e25); + validators.setNumRegisteredValidators(400); + lockedGold.incrementNonvotingAccountBalance(voter1, 1e25); + election.setElectabilityThreshold(0); + election.setElectableValidators(10, 100); + + election.setMaxNumGroupsVotedFor(200); + + address prev = address(0); + uint256[] memory randomVotes = new uint256[](100); + for (uint256 i = 0; i < 100; i++) { + randomVotes[i] = uint256(keccak256(abi.encodePacked(i))) % 1e14; + } + randomVotes = sort(randomVotes); + for (uint256 i = 0; i < 100; i++) { + address group = actor(string(abi.encodePacked("group", i))); + address[] memory members = new address[](4); + for (uint256 j = 0; j < 4; j++) { + members[j] = actor(string(abi.encodePacked("group", i, "member", j))); + // If there are already n elected members in a group, the votes for the next member + // are total votes of group divided by n+1 + votesConsideredForElection[members[j]] = randomVotes[i] / (j + 1); + membersWithVotes.push(MemberWithVotes(members[j], votesConsideredForElection[members[j]])); + } + validators.setMembers(group, members); + registry.setAddressFor("Validators", address(this)); + election.markGroupEligible(group, address(0), prev); + registry.setAddressFor("Validators", address(validators)); + vm.prank(voter1); + election.vote(group, randomVotes[i], prev, address(0)); + prev = group; + } + } + + function test_ShouldElectCorrectValidators_WhenThereIsALargeNumberOfGroups() public { + WhenThereIsALargeNumberOfGroups(); + address[] memory elected = election.electValidatorSigners(); + MemberWithVotes[] memory sortedMembersWithVotes = sortMembersWithVotesDesc(membersWithVotes); + MemberWithVotes[] memory electedUnsorted = new MemberWithVotes[](100); + + for (uint256 i = 0; i < 100; i++) { + electedUnsorted[i] = MemberWithVotes(elected[i], votesConsideredForElection[elected[i]]); + } + MemberWithVotes[] memory electedSorted = sortMembersWithVotesDesc(electedUnsorted); + + for (uint256 i = 0; i < 100; i++) { + assertEq(electedSorted[i].member, sortedMembersWithVotes[i].member); + assertEq(electedSorted[i].votes, sortedMembersWithVotes[i].votes); + } + } + +} From 2c610ddde5a029c96b6970e091919600529c484b Mon Sep 17 00:00:00 2001 From: pahor167 <47992132+pahor167@users.noreply.github.com> Date: Fri, 5 Jan 2024 18:47:42 +0100 Subject: [PATCH 03/25] ElectionValidatorSigners done --- packages/protocol/test-sol/utils.sol | 32 +++++ .../protocol/test-sol/voting/Election.t.sol | 130 +++++++++++++++++- 2 files changed, 161 insertions(+), 1 deletion(-) diff --git a/packages/protocol/test-sol/utils.sol b/packages/protocol/test-sol/utils.sol index 0494431ced6..8bd1a23b47f 100644 --- a/packages/protocol/test-sol/utils.sol +++ b/packages/protocol/test-sol/utils.sol @@ -1,8 +1,13 @@ pragma solidity ^0.5.13; import "celo-foundry/Test.sol"; +import "openzeppelin-solidity/contracts/utils/EnumerableSet.sol"; contract Utils is Test { + using EnumerableSet for EnumerableSet.AddressSet; + + EnumerableSet.AddressSet addressSet; + function timeTravel(uint256 timeDelta) public { vm.warp(block.timestamp + timeDelta); } @@ -32,4 +37,31 @@ contract Utils is Test { return string(bstr); } + function arraysEqual(address[] memory arr1, address[] memory arr2) public returns (bool) { + if (arr1.length != arr2.length) { + return false; // Arrays of different lengths cannot be equal + } + + // Add addresses from arr1 to the set + for (uint256 i = 0; i < arr1.length; i++) { + addressSet.add(arr1[i]); + } + + // Check if each address in arr2 is in the set + for (uint256 i = 0; i < arr2.length; i++) { + if (!addressSet.contains(arr2[i])) { + clearSet(arr1); + return false; + } + } + + clearSet(arr1); + return true; + } + + function clearSet(address[] memory arr1) private { + for (uint256 i = 0; i < arr1.length; i++) { + addressSet.remove(arr1[i]); + } + } } diff --git a/packages/protocol/test-sol/voting/Election.t.sol b/packages/protocol/test-sol/voting/Election.t.sol index 69f5b7f3cd2..bedb675ebcd 100644 --- a/packages/protocol/test-sol/voting/Election.t.sol +++ b/packages/protocol/test-sol/voting/Election.t.sol @@ -1393,6 +1393,10 @@ contract Election_ElectionValidatorSigners is ElectionTestFoundry { address validator6 = account8; address validator7 = account9; + address[] group1Members = new address[](4); + address[] group2Members = new address[](2); + address[] group3Members = new address[](1); + bytes32 hash = 0xa5b9d60f32436310afebcfda832817a68921beb782fabf7915cc0460b443116a; // If voterN votes for groupN: @@ -1419,7 +1423,21 @@ contract Election_ElectionValidatorSigners is ElectionTestFoundry { MemberWithVotes[] membersWithVotes; - function setRandomness(uint256 randomness) public { + function setUp() public { + super.setUp(); + + group1Members[0] = validator1; + group1Members[1] = validator2; + group1Members[2] = validator3; + group1Members[3] = validator4; + + group2Members[0] = validator5; + group2Members[1] = validator6; + + group3Members[0] = validator7; + } + + function setRandomness() public { random.addTestRandomness(block.number + 1, hash); } @@ -1508,4 +1526,114 @@ contract Election_ElectionValidatorSigners is ElectionTestFoundry { } } + function WhenThereAreSomeGroups() public { + validators.setMembers(group1, group1Members); + validators.setMembers(group2, group2Members); + validators.setMembers(group3, group3Members); + + registry.setAddressFor("Validators", address(this)); + election.markGroupEligible(group1, address(0), address(0)); + election.markGroupEligible(group2, address(0), group1); + election.markGroupEligible(group3, address(0), group2); + registry.setAddressFor("Validators", address(validators)); + + lockedGold.incrementNonvotingAccountBalance(address(voter1), voter1Weight); + lockedGold.incrementNonvotingAccountBalance(address(voter2), voter2Weight); + lockedGold.incrementNonvotingAccountBalance(address(voter3), voter3Weight); + + lockedGold.setTotalLockedGold(totalLockedGold); + validators.setNumRegisteredValidators(7); + } + + function test_ShouldReturnThatGroupsMemberLIst_WhenASingleGroupHasMoreOrEqualToMinElectableValidatorsAsMembersAndReceivedVotes() + public + { + WhenThereAreSomeGroups(); + vm.prank(voter1); + election.vote(group1, voter1Weight, group2, address(0)); + setRandomness(); + arraysEqual(election.electValidatorSigners(), group1Members); + } + + function test_ShouldReturnMaxElectableValidatorsElectedValidators_WhenGroupWithMoreThenMaxElectableValidatorsMembersReceivesVotes() + public + { + WhenThereAreSomeGroups(); + vm.prank(voter1); + election.vote(group1, voter1Weight, group2, address(0)); + vm.prank(voter2); + election.vote(group2, voter2Weight, address(0), group1); + vm.prank(voter3); + election.vote(group3, voter3Weight, address(0), group2); + + setRandomness(); + address[] memory expected = new address[](6); + expected[0] = validator1; + expected[1] = validator2; + expected[2] = validator3; + expected[3] = validator5; + expected[4] = validator6; + expected[5] = validator7; + arraysEqual(election.electValidatorSigners(), expected); + } + + function test_ShouldElectOnlyNMembersFromThatGroup_WhenAGroupReceivesEnoughVotesForMoreThanNSeatsButOnlyHasNMembers() + public + { + WhenThereAreSomeGroups(); + uint256 increment = 80; + uint256 votes = 80; + lockedGold.incrementNonvotingAccountBalance(address(voter3), increment); + lockedGold.setTotalLockedGold(totalLockedGold + increment); + vm.prank(voter3); + election.vote(group3, votes, group2, address(0)); + vm.prank(voter1); + election.vote(group1, voter1Weight, address(0), group3); + vm.prank(voter2); + election.vote(group2, voter2Weight, address(0), group1); + setRandomness(); + + address[] memory expected = new address[](6); + expected[0] = validator1; + expected[1] = validator2; + expected[2] = validator3; + expected[3] = validator5; + expected[4] = validator6; + expected[5] = validator7; + arraysEqual(election.electValidatorSigners(), expected); + } + + function test_ShouldNotElectAnyMembersFromThatGroup_WhenAGroupDoesNotReceiveElectabilityThresholdVotes() + public + { + WhenThereAreSomeGroups(); + uint256 thresholdExcludingGroup3 = (voter3Weight + 1) / totalLockedGold; + election.setElectabilityThreshold(thresholdExcludingGroup3); + vm.prank(voter1); + election.vote(group1, voter1Weight, group2, address(0)); + vm.prank(voter2); + election.vote(group2, voter2Weight, address(0), group1); + vm.prank(voter3); + election.vote(group3, voter3Weight, address(0), group2); + + address[] memory expected = new address[](6); + expected[0] = validator1; + expected[1] = validator2; + expected[2] = validator3; + expected[3] = validator4; + expected[4] = validator5; + expected[5] = validator6; + arraysEqual(election.electValidatorSigners(), expected); + } + + function test_ShouldRevert_WhenThereAnoNotEnoughElectableValidators() public { + WhenThereAreSomeGroups(); + vm.prank(voter2); + election.vote(group2, voter2Weight, group1, address(0)); + vm.prank(voter3); + election.vote(group3, voter3Weight, address(0), group2); + setRandomness(); + vm.expectRevert("Not enough elected validators"); + election.electValidatorSigners(); + } } From 0146d17b4dbba1b455880cd80865358a8ec5c9ca Mon Sep 17 00:00:00 2001 From: pahor167 <47992132+pahor167@users.noreply.github.com> Date: Mon, 8 Jan 2024 15:19:54 +0100 Subject: [PATCH 04/25] Last test batch missing --- .../protocol/test-sol/voting/Election.t.sol | 731 ++++++++++++++++++ 1 file changed, 731 insertions(+) diff --git a/packages/protocol/test-sol/voting/Election.t.sol b/packages/protocol/test-sol/voting/Election.t.sol index bedb675ebcd..2d61f304c18 100644 --- a/packages/protocol/test-sol/voting/Election.t.sol +++ b/packages/protocol/test-sol/voting/Election.t.sol @@ -1637,3 +1637,734 @@ contract Election_ElectionValidatorSigners is ElectionTestFoundry { election.electValidatorSigners(); } } + +contract Election_GetGroupEpochRewards is ElectionTestFoundry { + address voter = address(this); + address group1 = account2; + address group2 = account3; + uint256 voteValue1 = 2000000000; + uint256 voteValue2 = 1000000000; + uint256 totalRewardValue = 3000000000; + + function setUp() public { + super.setUp(); + + registry.setAddressFor("Validators", address(this)); + election.markGroupEligible(group1, address(0), address(0)); + election.markGroupEligible(group2, address(0), group1); + registry.setAddressFor("Validators", address(validators)); + lockedGold.setTotalLockedGold(voteValue1 + voteValue2); + + address[] memory membersGroup1 = new address[](1); + membersGroup1[0] = account8; + + validators.setMembers(group1, membersGroup1); + + address[] memory membersGroup2 = new address[](1); + membersGroup2[0] = account9; + validators.setMembers(group2, membersGroup2); + validators.setNumRegisteredValidators(2); + lockedGold.incrementNonvotingAccountBalance(voter, voteValue1 + voteValue2); + election.vote(group1, voteValue1, group2, address(0)); + election.vote(group2, voteValue2, address(0), group1); + } + + function WhenOneGroupHasActiveVotes() public { + vm.roll(EPOCH_SIZE + 1); + election.activate(group1); + } + + function test_ShouldReturnTheTotalRewardValue_WhenGroupUptimeIs100Percent_WhenOneGroupHasActiveVotes() + public + { + WhenOneGroupHasActiveVotes(); + + uint256[] memory uptimes = new uint256[](1); + uptimes[0] = FIXED1; + assertEq(election.getGroupEpochRewards(group1, totalRewardValue, uptimes), totalRewardValue); + } + + function test_ShouldReturnPartOfTheTotalRewardValue_WhenWhenGroupUptimeIsLessThan100Percent_WhenOneGroupHasActiveVotes() + public + { + WhenOneGroupHasActiveVotes(); + + uint256[] memory uptimes = new uint256[](1); + uptimes[0] = FIXED1 / 2; + assertEq( + election.getGroupEpochRewards(group1, totalRewardValue, uptimes), + totalRewardValue / 2 + ); + } + + function test_ShouldReturnZero_WhenTheGroupDoesNotMeetTheLockedGoldRequirements_WhenOneGroupHasActiveVotes() + public + { + WhenOneGroupHasActiveVotes(); + + validators.setDoesNotMeetAccountLockedGoldRequirements(group1); + uint256[] memory uptimes = new uint256[](1); + uptimes[0] = FIXED1; + assertEq(election.getGroupEpochRewards(group1, totalRewardValue, uptimes), 0); + } + + function WhenTwoGroupsHaveActiveVotes() public { + vm.roll(EPOCH_SIZE + 1); + election.activate(group1); + election.activate(group2); + } + + function test_ShouldReturn0_WhenOneGroupDoesNotMeetLockedGoldRequirements_WhenTwoGroupsHaveActiveVotes() + public + { + WhenTwoGroupsHaveActiveVotes(); + + validators.setDoesNotMeetAccountLockedGoldRequirements(group2); + uint256[] memory uptimes = new uint256[](1); + uptimes[0] = FIXED1; + assertEq(election.getGroupEpochRewards(group2, totalRewardValue, uptimes), 0); + } + + uint256 expectedGroup1EpochRewards = FixidityLib + .newFixedFraction(voteValue1, voteValue1 + voteValue2) + .multiply(FixidityLib.newFixed(totalRewardValue)) + .fromFixed(); + + function test_ShouldReturnProportionalRewardValueForOtherGroup_WhenOneGroupDoesNotMeetLockedGoldRequirements_WhenTwoGroupsHaveActiveVotes() + public + { + WhenTwoGroupsHaveActiveVotes(); + + validators.setDoesNotMeetAccountLockedGoldRequirements(group2); + uint256[] memory uptimes = new uint256[](1); + uptimes[0] = FIXED1; + + assertEq( + election.getGroupEpochRewards(group1, totalRewardValue, uptimes), + expectedGroup1EpochRewards + ); + } + + function test_ShouldReturn0_WhenTheGroupMeetsLockedGoldRequirements_WhenThenGroupDoesNotHaveActiveVotes() + public + { + uint256[] memory uptimes = new uint256[](1); + uptimes[0] = FIXED1; + assertEq(election.getGroupEpochRewards(group1, totalRewardValue, uptimes), 0); + } +} + +contract Election_DistributeEpochRewards is ElectionTestFoundry { + address voter = address(this); + address voter2 = account4; + address group = account2; + address group2 = account3; + uint256 voteValue = 1000000; + uint256 voteValue2 = 1000000; + uint256 rewardValue = 1000000; + uint256 rewardValue2 = 10000000; + + function setUp() public { + super.setUp(); + + registry.setAddressFor("Validators", address(this)); + election.markGroupEligible(group, address(0), address(0)); + registry.setAddressFor("Validators", address(validators)); + lockedGold.setTotalLockedGold(voteValue); + + address[] memory membersGroup = new address[](1); + membersGroup[0] = account8; + + validators.setMembers(group, membersGroup); + + validators.setNumRegisteredValidators(1); + lockedGold.incrementNonvotingAccountBalance(voter, voteValue); + election.vote(group, voteValue, address(0), address(0)); + + vm.roll(EPOCH_SIZE + 1); + election.activate(group); + } + + function test_ShouldIncrementTheAccountActiveVotesForGroup_WhenThereIsSingleGroupWithActiveVotes() + public + { + election.distributeEpochRewards(group, rewardValue, address(0), address(0)); + assertEq(election.getActiveVotesForGroupByAccount(group, voter), voteValue + rewardValue); + } + + function test_ShouldIncrementAccountTotalVotesForGroup_WhenThereIsSingleGroupWithActiveVotes() + public + { + election.distributeEpochRewards(group, rewardValue, address(0), address(0)); + assertEq(election.getTotalVotesForGroupByAccount(group, voter), voteValue + rewardValue); + } + + function test_ShouldIncrementAccountTotalVotes_WhenThereIsSingleGroupWithActiveVotes() public { + election.distributeEpochRewards(group, rewardValue, address(0), address(0)); + assertEq(election.getTotalVotesByAccount(voter), voteValue + rewardValue); + } + + function test_ShouldIncrementTotalVotesForGroup_WhenThereIsSingleGroupWithActiveVotes() public { + election.distributeEpochRewards(group, rewardValue, address(0), address(0)); + assertEq(election.getTotalVotesForGroup(group), voteValue + rewardValue); + } + + function test_ShouldIncrementTotalVotes_WhenThereIsSingleGroupWithActiveVotes() public { + election.distributeEpochRewards(group, rewardValue, address(0), address(0)); + assertEq(election.getTotalVotes(), voteValue + rewardValue); + } + + uint256 expectedGroupTotalActiveVotes = voteValue + voteValue2 / 2 + rewardValue; + uint256 expectedVoterActiveVotesForGroup = FixidityLib + .newFixedFraction(expectedGroupTotalActiveVotes * 2, 3) + .fromFixed(); + uint256 expectedVoter2ActiveVotesForGroup = FixidityLib + .newFixedFraction(expectedGroupTotalActiveVotes, 3) + .fromFixed(); + uint256 expectedVoter2ActiveVotesForGroup2 = voteValue / 2 + rewardValue2; + + function WhenThereAreTwoGroupsWithActiveVotes() public { + registry.setAddressFor("Validators", address(this)); + election.markGroupEligible(group2, address(0), group); + registry.setAddressFor("Validators", address(validators)); + lockedGold.setTotalLockedGold(voteValue + voteValue2); + + validators.setNumRegisteredValidators(2); + lockedGold.incrementNonvotingAccountBalance(voter2, voteValue2); + + vm.startPrank(voter2); + // Split voter2's vote between the two groups. + election.vote(group, voteValue2 / 2, group2, address(0)); + election.vote(group2, voteValue2 / 2, address(0), group); + vm.roll(2 * EPOCH_SIZE + 2); + election.activate(group); + election.activate(group2); + vm.stopPrank(); + + election.distributeEpochRewards(group, rewardValue, group2, address(0)); + election.distributeEpochRewards(group2, rewardValue2, group, address(0)); + } + + function test_ShouldIncrementTheAccountsActiveVotesForBothGroups_WhenThereAreTwoGroupsWithActiveVotes() + public + { + WhenThereAreTwoGroupsWithActiveVotes(); + assertEq( + election.getActiveVotesForGroupByAccount(group, voter), + expectedVoterActiveVotesForGroup + ); + assertEq( + election.getActiveVotesForGroupByAccount(group, voter2), + expectedVoter2ActiveVotesForGroup + ); + assertEq( + election.getActiveVotesForGroupByAccount(group2, voter2), + expectedVoter2ActiveVotesForGroup2 + ); + } + + function test_ShouldIncrementTheAccountsTotalVOtesForBothGroups_WhenThereAreTwoGroupsWithActiveVotes() + public + { + WhenThereAreTwoGroupsWithActiveVotes(); + assertEq( + election.getTotalVotesForGroupByAccount(group, voter), + expectedVoterActiveVotesForGroup + ); + assertEq( + election.getTotalVotesForGroupByAccount(group, voter2), + expectedVoter2ActiveVotesForGroup + ); + assertEq( + election.getTotalVotesForGroupByAccount(group2, voter2), + expectedVoter2ActiveVotesForGroup2 + ); + } + + function test_ShouldIncrementTheAccountsTotalVotes_WhenThereAreTwoGroupsWithActiveVotes() public { + WhenThereAreTwoGroupsWithActiveVotes(); + assertEq(election.getTotalVotesByAccount(voter), expectedVoterActiveVotesForGroup); + assertEq( + election.getTotalVotesByAccount(voter2), + expectedVoter2ActiveVotesForGroup + expectedVoter2ActiveVotesForGroup2 + ); + } + + function test_ShouldIncrementTotalVotesForBothGroups_WhenThereAreTwoGroupsWithActiveVotes() + public + { + WhenThereAreTwoGroupsWithActiveVotes(); + assertEq(election.getTotalVotesForGroup(group), expectedGroupTotalActiveVotes); + assertEq(election.getTotalVotesForGroup(group2), expectedVoter2ActiveVotesForGroup2); + } + + function test_ShouldIncrementTotalVotes_WhenThereAreTwoGroupsWithActiveVotes() public { + WhenThereAreTwoGroupsWithActiveVotes(); + assertEq( + election.getTotalVotes(), + expectedGroupTotalActiveVotes + expectedVoter2ActiveVotesForGroup2 + ); + } + + function test_ShouldUpdateTheORderingOFEligibleGroups_WhenThereAreTwoGroupsWithActiveVotes() + public + { + WhenThereAreTwoGroupsWithActiveVotes(); + assertEq(election.getEligibleValidatorGroups().length, 2); + assertEq(election.getEligibleValidatorGroups()[0], group2); + assertEq(election.getEligibleValidatorGroups()[1], group); + } +} + +contract Election_ForceDecrementVotes is ElectionTestFoundry { + address voter = address(this); + address group = account2; + address group2 = account7; + uint256 value = 1000; + uint256 value2 = 1500; + uint256 index = 0; + uint256 slashedValue = value; + uint256 remaining = value - slashedValue; + + function setUp() public { + super.setUp(); + + } + + function WhenAccountHasVotedForOneGroup() public { + address[] memory membersGroup = new address[](1); + membersGroup[0] = account8; + + validators.setMembers(group, membersGroup); + + registry.setAddressFor("Validators", address(this)); + election.markGroupEligible(group, address(0), address(0)); + registry.setAddressFor("Validators", address(validators)); + lockedGold.setTotalLockedGold(value); + validators.setNumRegisteredValidators(1); + lockedGold.incrementNonvotingAccountBalance(voter, value); + election.vote(group, value, address(0), address(0)); + + registry.setAddressFor("LockedGold", account2); + } + + function WhenAccountHasOnlyPendingVotes() public { + WhenAccountHasVotedForOneGroup(); + address[] memory lessers = new address[](1); + lessers[0] = address(0); + address[] memory greaters = new address[](1); + greaters[0] = address(0); + uint256[] memory indices = new uint256[](1); + indices[0] = index; + + vm.prank(account2); + election.forceDecrementVotes(voter, slashedValue, lessers, greaters, indices); + } + + function test_ShouldDecrementPendingVotesToZero_WhenAccountHasOnlyPendingVotes() public { + WhenAccountHasOnlyPendingVotes(); + assertEq(election.getPendingVotesForGroupByAccount(group, voter), remaining); + } + + function test_ShouldDecrementTotalVotesToZero_WhenAccountHasOnlyPendingVotes() public { + WhenAccountHasOnlyPendingVotes(); + assertEq(election.getTotalVotesForGroupByAccount(group, voter), remaining); + assertEq(election.getTotalVotesByAccount(voter), remaining); + assertEq(election.getTotalVotesForGroup(group), remaining); + assertEq(election.getTotalVotes(), remaining); + } + + function test_ShouldRemoveTheGroupFromTheVotersVotedSet_WhenAccountHasOnlyPendingVotes() public { + WhenAccountHasOnlyPendingVotes(); + assertEq(election.getGroupsVotedForByAccount(voter).length, 0); + } + + function WhenAccountHasOnlyActiveVotes() public { + WhenAccountHasVotedForOneGroup(); + vm.roll(EPOCH_SIZE + 1); + election.activate(group); + vm.prank(account2); + + address[] memory lessers = new address[](1); + lessers[0] = address(0); + address[] memory greaters = new address[](1); + greaters[0] = address(0); + uint256[] memory indices = new uint256[](1); + indices[0] = index; + + election.forceDecrementVotes(voter, slashedValue, lessers, greaters, indices); + } + + function test_ShouldDecrementActiveVotesToZero_WhenAccountHasOnlyActiveVotes() public { + WhenAccountHasOnlyActiveVotes(); + assertEq(election.getActiveVotesForGroupByAccount(group, voter), remaining); + } + + function test_ShouldDecrementTotalVotesToZero_WhenAccountHasOnlyActiveVotes() public { + WhenAccountHasOnlyActiveVotes(); + assertEq(election.getTotalVotesForGroupByAccount(group, voter), remaining); + assertEq(election.getTotalVotesByAccount(voter), remaining); + assertEq(election.getTotalVotesForGroup(group), remaining); + assertEq(election.getTotalVotes(), remaining); + } + + function test_ShouldRemoveTheGroupFromTheVotersVotedSet_WhenAccountHasOnlyActiveVotes() public { + WhenAccountHasOnlyActiveVotes(); + assertEq(election.getGroupsVotedForByAccount(voter).length, 0); + } + + function WhenAccountHasVotedForMoreThanOneGroupEqually() public { + address[] memory membersGroup = new address[](1); + membersGroup[0] = account8; + validators.setMembers(group, membersGroup); + + address[] memory membersGroup2 = new address[](1); + membersGroup2[0] = account9; + validators.setMembers(group2, membersGroup2); + + registry.setAddressFor("Validators", address(this)); + election.markGroupEligible(group, address(0), address(0)); + election.markGroupEligible(group2, group, address(0)); + registry.setAddressFor("Validators", address(validators)); + lockedGold.setTotalLockedGold(value); + validators.setNumRegisteredValidators(2); + lockedGold.incrementNonvotingAccountBalance(voter, value); + election.vote(group, value / 2, group2, address(0)); + election.vote(group2, value / 2, address(0), group); + registry.setAddressFor("LockedGold", account2); + } + + function WhenAccountsOnlyHavePendingVotes() public { + WhenAccountHasVotedForMoreThanOneGroupEqually(); + address[] memory lessers = new address[](2); + lessers[0] = group2; + lessers[1] = address(0); + address[] memory greaters = new address[](2); + greaters[0] = address(0); + greaters[1] = group; + uint256[] memory indices = new uint256[](2); + indices[0] = 0; + indices[1] = 1; + + vm.prank(account2); + election.forceDecrementVotes(voter, slashedValue, lessers, greaters, indices); + } + + function test_ShouldDecrementBothGroupPendingVotesToZero_WhenAccountsOnlyHavePendingVotes_WhenAccountHasVotedForMoreThanOneGroupEqually() + public + { + WhenAccountsOnlyHavePendingVotes(); + assertEq(election.getPendingVotesForGroupByAccount(group, voter), remaining); + assertEq(election.getPendingVotesForGroupByAccount(group2, voter), remaining); + } + + function test_ShouldDecrementBothGroupTotalVotesToZero_WhenAccountsOnlyHavePendingVotes_WhenAccountHasVotedForMoreThanOneGroupEqually() + public + { + WhenAccountsOnlyHavePendingVotes(); + assertEq(election.getTotalVotesForGroupByAccount(group, voter), remaining); + assertEq(election.getTotalVotesForGroupByAccount(group2, voter), remaining); + assertEq(election.getTotalVotesByAccount(voter), remaining); + assertEq(election.getTotalVotesForGroup(group), remaining); + assertEq(election.getTotalVotesForGroup(group2), remaining); + assertEq(election.getTotalVotes(), remaining); + } + + function test_ShouldRemoveBothGroupsFromTheVotersVotedSet_WhenAccountsOnlyHavePendingVotes_WhenAccountHasVotedForMoreThanOneGroupEqually() + public + { + WhenAccountsOnlyHavePendingVotes(); + assertEq(election.getGroupsVotedForByAccount(voter).length, 0); + } + + function WhenAccountHasVotedForMoreThanOneGroupInequally() public { + address[] memory membersGroup = new address[](1); + membersGroup[0] = account8; + validators.setMembers(group, membersGroup); + + address[] memory membersGroup2 = new address[](1); + membersGroup2[0] = account9; + validators.setMembers(group2, membersGroup2); + + registry.setAddressFor("Validators", address(this)); + election.markGroupEligible(group, address(0), address(0)); + election.markGroupEligible(group2, group, address(0)); + registry.setAddressFor("Validators", address(validators)); + lockedGold.setTotalLockedGold(value + value2); + validators.setNumRegisteredValidators(2); + lockedGold.incrementNonvotingAccountBalance(voter, value + value2); + election.vote(group2, value2 / 2, group, address(0)); + election.vote(group, value / 2, address(0), group2); + } + + function WhenBothGroupsHaveBothPendingAndActiveVotes_WhenAccountHasVotedForMoreThanOneGroupInequally() + public + { + WhenAccountHasVotedForMoreThanOneGroupInequally(); + vm.roll(EPOCH_SIZE + 1); + election.activate(group); + vm.roll(2 * EPOCH_SIZE + 2); + election.activate(group2); + + election.vote(group2, value2 / 2, group, address(0)); + election.vote(group, value / 2, address(0), group2); + + registry.setAddressFor("LockedGold", account2); + + slashedValue = value / 2 + 1; + remaining = value - slashedValue; + } + + function WhenBothGroupsHaveBothPendingAndActiveVotes_WhenAccountHasVotedForMoreThanOneGroupInequallyWithDecrement() + public + { + WhenBothGroupsHaveBothPendingAndActiveVotes_WhenAccountHasVotedForMoreThanOneGroupInequally(); + + address[] memory lessers = new address[](2); + lessers[0] = address(0); + lessers[1] = address(0); + address[] memory greaters = new address[](2); + greaters[0] = group; + greaters[1] = group2; + uint256[] memory indices = new uint256[](2); + indices[0] = 0; + indices[1] = 1; + + vm.prank(account2); + election.forceDecrementVotes(voter, slashedValue, lessers, greaters, indices); + } + + function test_ShouldNotAffectGroup2_WhenWeSlash1MoreVoteThanGroup1PendingVoteTotal_WhenAccountHasVotedForMoreThanOneGroupInequally() + public + { + WhenBothGroupsHaveBothPendingAndActiveVotes_WhenAccountHasVotedForMoreThanOneGroupInequallyWithDecrement(); + + assertEq(election.getTotalVotesForGroupByAccount(group2, voter), value2); + assertEq(election.getTotalVotesForGroup(group2), value2); + } + + function test_ShouldReduceGroup1Votes_WhenWeSlash1MoreVoteThanGroup1PendingVoteTotal_WhenAccountHasVotedForMoreThanOneGroupInequally() + public + { + WhenBothGroupsHaveBothPendingAndActiveVotes_WhenAccountHasVotedForMoreThanOneGroupInequallyWithDecrement(); + + assertEq(election.getTotalVotesForGroupByAccount(group, voter), remaining); + assertEq(election.getTotalVotesForGroup(group), remaining); + } + + function test_ShouldReduceVoterTotalVotes_WhenWeSlash1MoreVoteThanGroup1PendingVoteTotal_WhenAccountHasVotedForMoreThanOneGroupInequally() + public + { + WhenBothGroupsHaveBothPendingAndActiveVotes_WhenAccountHasVotedForMoreThanOneGroupInequallyWithDecrement(); + + assertEq(election.getTotalVotesByAccount(voter), remaining + value2); + } + + function test_ShouldReduceGroup1PendingVotesTo0_WhenWeSlash1MoreVoteThanGroup1PendingVoteTotal_WhenAccountHasVotedForMoreThanOneGroupInequally() + public + { + WhenBothGroupsHaveBothPendingAndActiveVotes_WhenAccountHasVotedForMoreThanOneGroupInequallyWithDecrement(); + assertEq(election.getPendingVotesForGroupByAccount(group, voter), 0); + } + + function test_ShouldReduceGroup1ActiveVotesBy1_WhenWeSlash1MoreVoteThanGroup1PendingVoteTotal_WhenAccountHasVotedForMoreThanOneGroupInequally() + public + { + WhenBothGroupsHaveBothPendingAndActiveVotes_WhenAccountHasVotedForMoreThanOneGroupInequallyWithDecrement(); + assertEq(election.getActiveVotesForGroupByAccount(group, voter), remaining); + } + + uint256 totalRemaining; + uint256 group1Remaining; + uint256 group2TotalRemaining; + uint256 group2PendingRemaining; + uint256 group2ActiveRemaining; + + function WhenWeSlashAllOfGroup1VotesAndSomeOfGroup2__WhenWeSlash1MoreVoteThanGroup1PendingVoteTotal_WhenAccountHasVotedForMoreThanOneGroupInequally() + public + { + WhenBothGroupsHaveBothPendingAndActiveVotes_WhenAccountHasVotedForMoreThanOneGroupInequally(); + + slashedValue = value + 1; + + totalRemaining = value + value2 - slashedValue; + group1Remaining = 0; + group2TotalRemaining = value2 - 1; + group2PendingRemaining = value2 / 2 - 1; + group2ActiveRemaining = value2 / 2; + console.log("slashedValue", slashedValue); + + address[] memory lessers = new address[](2); + lessers[0] = group; + lessers[1] = address(0); + address[] memory greaters = new address[](2); + greaters[0] = address(0); + greaters[1] = group2; + uint256[] memory indices = new uint256[](2); + indices[0] = 0; + indices[1] = 1; + + vm.prank(account2); + election.forceDecrementVotes(voter, slashedValue, lessers, greaters, indices); + } + + function test_ShouldDecrementGroup1Votes_WhenWeSlashAllOfGroup1VotesAndSomeOfGroup2__WhenWeSlash1MoreVoteThanGroup1PendingVoteTotal_WhenAccountHasVotedForMoreThanOneGroupInequally() + public + { + WhenWeSlashAllOfGroup1VotesAndSomeOfGroup2__WhenWeSlash1MoreVoteThanGroup1PendingVoteTotal_WhenAccountHasVotedForMoreThanOneGroupInequally(); + assertEq(election.getTotalVotesForGroupByAccount(group, voter), group1Remaining); + assertEq(election.getTotalVotesForGroup(group), group1Remaining); + assertEq(election.getPendingVotesForGroupByAccount(group, voter), group1Remaining); + assertEq(election.getActiveVotesForGroupByAccount(group, voter), group1Remaining); + } + + function test_ShouldDecrementGroup2Votes_WhenWeSlashAllOfGroup1VotesAndSomeOfGroup2__WhenWeSlash1MoreVoteThanGroup1PendingVoteTotal_WhenAccountHasVotedForMoreThanOneGroupInequally() + public + { + WhenWeSlashAllOfGroup1VotesAndSomeOfGroup2__WhenWeSlash1MoreVoteThanGroup1PendingVoteTotal_WhenAccountHasVotedForMoreThanOneGroupInequally(); + assertEq(election.getTotalVotesForGroupByAccount(group2, voter), group2TotalRemaining); + assertEq(election.getTotalVotesByAccount(voter), totalRemaining); + assertEq(election.getPendingVotesForGroupByAccount(group2, voter), group2PendingRemaining); + assertEq(election.getActiveVotesForGroupByAccount(group2, voter), group2ActiveRemaining); + } + + uint256 group1RemainingActiveVotes; + address[] initialOrdering; + + function WhenSlashAffectsElectionOrder() public { + WhenAccountHasVotedForMoreThanOneGroupInequally(); + + slashedValue = value / 4; + group1RemainingActiveVotes = value - slashedValue; + + election.vote(group, value / 2, group2, address(0)); + vm.roll(EPOCH_SIZE + 1); + election.activate(group); + vm.roll(2 * EPOCH_SIZE + 2); + election.activate(group2); + + (initialOrdering, ) = election.getTotalVotesForEligibleValidatorGroups(); + registry.setAddressFor("LockedGold", account2); + + address[] memory lessers = new address[](2); + lessers[0] = group; + lessers[1] = address(0); + address[] memory greaters = new address[](2); + greaters[0] = address(0); + greaters[1] = group2; + uint256[] memory indices = new uint256[](2); + indices[0] = 0; + indices[1] = 1; + + vm.prank(account2); + election.forceDecrementVotes(voter, slashedValue, lessers, greaters, indices); + } + + function test_ShouldDecrementGroup1TotalVotesByOneQuarter_WhenSlashAffectsElectionOrder() public { + WhenSlashAffectsElectionOrder(); + assertEq(election.getTotalVotesForGroupByAccount(group, voter), group1RemainingActiveVotes); + assertEq(election.getTotalVotesForGroup(group), group1RemainingActiveVotes); + } + + function test_ShouldChangeTheOrderingOfTheElection_WhenSlashAffectsElectionOrder() public { + WhenSlashAffectsElectionOrder(); + (address[] memory newOrdering, ) = election.getTotalVotesForEligibleValidatorGroups(); + assertEq(newOrdering[0], initialOrdering[1]); + assertEq(newOrdering[1], initialOrdering[0]); + } + + function test_ShouldRevert_WhenCalledToSlashMoreValueThanGroupsHave_WhenCalledWithMalformedInputs() + public + { + WhenAccountHasVotedForMoreThanOneGroupInequally(); + slashedValue = value + value2 + 1; + address[] memory lessers = new address[](2); + lessers[0] = group; + lessers[1] = address(0); + address[] memory greaters = new address[](2); + greaters[0] = address(0); + greaters[1] = group2; + uint256[] memory indices = new uint256[](2); + indices[0] = 0; + indices[1] = 1; + + registry.setAddressFor("LockedGold", account2); + vm.prank(account2); + vm.expectRevert("Failure to decrement all votes."); + election.forceDecrementVotes(voter, slashedValue, lessers, greaters, indices); + } + + function test_ShouldRevert_WhenCalledToSlashWithIncorrectLessersGreaters_WhenCalledWithMalformedInputs() + public + { + WhenAccountHasVotedForMoreThanOneGroupInequally(); + slashedValue = value; + address[] memory lessers = new address[](2); + lessers[0] = address(0); + lessers[1] = address(0); + address[] memory greaters = new address[](2); + greaters[0] = address(0); + greaters[1] = group2; + uint256[] memory indices = new uint256[](2); + indices[0] = 0; + indices[1] = 1; + + registry.setAddressFor("LockedGold", account2); + vm.prank(account2); + vm.expectRevert("greater and lesser key zero"); + election.forceDecrementVotes(voter, slashedValue, lessers, greaters, indices); + } + + function test_ShouldRevert_WhenCalledToSlashWithIncorrectIndices_WhenCalledWithMalformedInputs() + public + { + WhenAccountHasVotedForMoreThanOneGroupInequally(); + slashedValue = value; + address[] memory lessers = new address[](2); + lessers[0] = address(0); + lessers[1] = address(0); + address[] memory greaters = new address[](2); + greaters[0] = address(0); + greaters[1] = group2; + uint256[] memory indices = new uint256[](2); + indices[0] = 0; + indices[1] = 0; + + registry.setAddressFor("LockedGold", account2); + vm.prank(account2); + vm.expectRevert("Bad index"); + election.forceDecrementVotes(voter, slashedValue, lessers, greaters, indices); + } + + function test_ShouldRevert_WhenCalledByAnyoneElseThanLockedGoldContract_WhenCalledWithMalformedInputs() + public + { + WhenAccountHasVotedForMoreThanOneGroupInequally(); + slashedValue = value; + address[] memory lessers = new address[](2); + lessers[0] = address(0); + lessers[1] = address(0); + address[] memory greaters = new address[](2); + greaters[0] = address(0); + greaters[1] = group2; + uint256[] memory indices = new uint256[](2); + indices[0] = 0; + indices[1] = 0; + + vm.expectRevert("only registered contract"); + election.forceDecrementVotes(voter, slashedValue, lessers, greaters, indices); + } +} + +contract Election_ConsistencyChecks is ElectionTestFoundry { + address voter = address(this); + address group = account2; + uint256 rewardValue2 = 10000000; + + function setUp() public { + super.setUp(); + + } + + function checkVoterInvariants() public {} +} From 96c3a54630dad73090ce100fe88bc5894ab348a4 Mon Sep 17 00:00:00 2001 From: pahor167 <47992132+pahor167@users.noreply.github.com> Date: Mon, 8 Jan 2024 17:43:16 +0100 Subject: [PATCH 05/25] All tests are working --- .../protocol/test-sol/voting/Election.t.sol | 202 +++++++++++++++++- 1 file changed, 201 insertions(+), 1 deletion(-) diff --git a/packages/protocol/test-sol/voting/Election.t.sol b/packages/protocol/test-sol/voting/Election.t.sol index 2d61f304c18..1a008f4801f 100644 --- a/packages/protocol/test-sol/voting/Election.t.sol +++ b/packages/protocol/test-sol/voting/Election.t.sol @@ -2361,10 +2361,210 @@ contract Election_ConsistencyChecks is ElectionTestFoundry { address group = account2; uint256 rewardValue2 = 10000000; + AccountStruct[] accounts; + + struct AccountStruct { + address account; + uint256 active; + uint256 pending; + uint256 nonVoting; + } + + enum VoteActionType { Vote, Activate, RevokePending, RevokeActive } + function setUp() public { super.setUp(); + // 50M gives us 500M total locked gold + uint256 voterStartBalance = 50000000 ether; + address[] memory members = new address[](1); + members[0] = account9; + validators.setMembers(group, members); + registry.setAddressFor("Validators", address(this)); + election.markGroupEligible(group, address(0), address(0)); + registry.setAddressFor("Validators", address(validators)); + lockedGold.setTotalLockedGold(voterStartBalance * accountsArray.length); + validators.setNumRegisteredValidators(1); + for (uint256 i = 0; i < accountsArray.length; i++) { + lockedGold.incrementNonvotingAccountBalance(accountsArray[i], voterStartBalance); + + accounts.push( + AccountStruct( + accountsArray[i], + election.getActiveVotesForGroupByAccount(group, accountsArray[i]), + election.getPendingVotesForGroupByAccount(group, accountsArray[i]), + lockedGold.nonvotingAccountBalance(accountsArray[i]) + ) + ); + } + } + + function makeRandomAction(AccountStruct storage account, uint256 salt) internal { + VoteActionType[] memory actions = new VoteActionType[](4); + uint256 actionCount = 0; + + if (account.nonVoting > 0) { + actions[actionCount++] = VoteActionType.Vote; + } + if (election.hasActivatablePendingVotes(account.account, group)) { + // Assuming this is a view function + actions[actionCount++] = VoteActionType.Activate; + } + if (account.pending > 0) { + actions[actionCount++] = VoteActionType.RevokePending; + } + if (account.active > 0) { + actions[actionCount++] = VoteActionType.RevokeActive; + } + + VoteActionType action = actions[generatePRN(0, actionCount - 1, uint256(account.account))]; + uint256 value; + + vm.startPrank(account.account); + if (action == VoteActionType.Vote) { + value = generatePRN(0, account.nonVoting, uint256(account.account) + salt); + election.vote(group, value, address(0), address(0)); + account.nonVoting -= value; + account.pending += value; + } else if (action == VoteActionType.Activate) { + value = account.pending; + election.activate(group); + account.pending -= value; + account.active += value; + } else if (action == VoteActionType.RevokePending) { + value = generatePRN(0, account.pending, uint256(account.account) + salt); + election.revokePending(group, value, address(0), address(0), 0); + account.pending -= value; + account.nonVoting += value; + } else if (action == VoteActionType.RevokeActive) { + value = generatePRN(0, account.active, uint256(account.account) + salt); + election.revokeActive(group, value, address(0), address(0), 0); + account.active -= value; + account.nonVoting += value; + } + vm.stopPrank(); } - function checkVoterInvariants() public {} + function checkVoterInvariants(AccountStruct memory account, uint256 delta) public { + assertAlmostEqual( + election.getPendingVotesForGroupByAccount(group, account.account), + account.pending, + delta + ); + assertAlmostEqual( + election.getActiveVotesForGroupByAccount(group, account.account), + account.active, + delta + ); + assertAlmostEqual( + election.getTotalVotesForGroupByAccount(group, account.account), + account.active + account.pending, + delta + ); + assertAlmostEqual( + lockedGold.nonvotingAccountBalance(account.account), + account.nonVoting, + delta + ); + } + + function checkGroupInvariants(uint256 delta) public { + uint256 pendingTotal; + + for (uint256 i = 0; i < accounts.length; i++) { + pendingTotal += accounts[i].pending; + } + + uint256 activateTotal; + + for (uint256 i = 0; i < accounts.length; i++) { + activateTotal += accounts[i].active; + } + + assertAlmostEqual(election.getPendingVotesForGroup(group), pendingTotal, delta); + assertAlmostEqual(election.getActiveVotesForGroup(group), activateTotal, delta); + assertAlmostEqual(election.getTotalVotesForGroup(group), pendingTotal + activateTotal, delta); + + assertAlmostEqual(election.getTotalVotes(), election.getTotalVotesForGroup(group), delta); + } + + function revokeAllAndCheckInvariants(uint256 delta) public { + for (uint256 i = 0; i < accounts.length; i++) { + AccountStruct storage account = accounts[i]; + + checkVoterInvariants(account, delta); + + uint256 active = election.getActiveVotesForGroupByAccount(group, account.account); + if (active > 0) { + vm.prank(account.account); + election.revokeActive(group, active, address(0), address(0), 0); + account.active = 0; + account.nonVoting += active; + } + + uint256 pending = account.pending; + if (pending > 0) { + vm.prank(account.account); + election.revokePending(group, pending, address(0), address(0), 0); + account.pending = 0; + account.nonVoting += pending; + } + + assertEq(election.getActiveVotesForGroupByAccount(group, account.account), 0); + assertEq(election.getPendingVotesForGroupByAccount(group, account.account), 0); + assertEq(lockedGold.nonvotingAccountBalance(account.account), account.nonVoting); + } + } + + function test_ActualAndExpectedShouldAlwaysMatchExactly_WhenNoEpochRewardsAreDistributed() + public + { + for (uint256 i = 0; i < 10; i++) { + for (uint256 j = 0; j < accounts.length; j++) { + makeRandomAction(accounts[j], j); + checkVoterInvariants(accounts[j], 0); + checkGroupInvariants(0); + vm.roll((i + 1) * EPOCH_SIZE + (i + 1)); + } + } + revokeAllAndCheckInvariants(0); + } + + function distributeEpochRewards(uint256 salt) public { + // 1% compounded 100x gives up to a 2.7x multiplier. + uint256 reward = generatePRN(0, election.getTotalVotes() / 100, salt); + uint256 activeTotal; + + for (uint256 i = 0; i < accounts.length; i++) { + activeTotal += accounts[i].active; + } + + if (reward > 0 && activeTotal > 0) { + election.distributeEpochRewards(group, reward, address(0), address(0)); + + for (uint256 i = 0; i < accounts.length; i++) { + AccountStruct storage account = accounts[i]; + account.active = ((activeTotal + reward) * accounts[i].active) / activeTotal; + } + } + } + + function test_ActualAndExpectedShouldAlwaysMatchWithinSmallDelta() public { + for (uint256 i = 0; i < 30; i++) { + for (uint256 j = 0; j < accounts.length; j++) { + makeRandomAction(accounts[j], j); + checkVoterInvariants(accounts[j], 100); + checkGroupInvariants(100); + } + + distributeEpochRewards(i); + vm.roll((i + 1) * EPOCH_SIZE + (i + 1)); + + for (uint256 j = 0; j < accounts.length; j++) { + checkVoterInvariants(accounts[j], 100); + checkGroupInvariants(100); + } + } + revokeAllAndCheckInvariants(100); + } } From 76b15e735d2afee61e431186892a212104da2c86 Mon Sep 17 00:00:00 2001 From: pahor167 <47992132+pahor167@users.noreply.github.com> Date: Mon, 8 Jan 2024 17:44:17 +0100 Subject: [PATCH 06/25] removal of election ts tests --- .../test/governance/voting/election.ts | 2580 ----------------- 1 file changed, 2580 deletions(-) delete mode 100644 packages/protocol/test/governance/voting/election.ts diff --git a/packages/protocol/test/governance/voting/election.ts b/packages/protocol/test/governance/voting/election.ts deleted file mode 100644 index 448071bbfdc..00000000000 --- a/packages/protocol/test/governance/voting/election.ts +++ /dev/null @@ -1,2580 +0,0 @@ -import { NULL_ADDRESS } from '@celo/base/lib/address' -import { CeloContractName } from '@celo/protocol/lib/registry-utils' -import { - assertAlmostEqualBN, - assertContainSubset, - assertEqualBN, - assertRevert, - assertTransactionRevertWithReason, - mineBlocks, -} from '@celo/protocol/lib/test-utils' -import { normalizeAddressWith0x } from '@celo/utils/lib/address' -import { fixed1, toFixed } from '@celo/utils/lib/fixidity' -import BigNumber from 'bignumber.js' -import { - AccountsContract, - AccountsInstance, - ElectionTestContract, - ElectionTestInstance, - FreezerContract, - FreezerInstance, - MockLockedGoldContract, - MockLockedGoldInstance, - MockRandomContract, - MockRandomInstance, - MockValidatorsContract, - MockValidatorsInstance, - RegistryContract, - RegistryInstance, -} from 'types' - -const Accounts: AccountsContract = artifacts.require('Accounts') -const ElectionTest: ElectionTestContract = artifacts.require('ElectionTest') -const Freezer: FreezerContract = artifacts.require('Freezer') -const MockLockedGold: MockLockedGoldContract = artifacts.require('MockLockedGold') -const MockValidators: MockValidatorsContract = artifacts.require('MockValidators') -const MockRandom: MockRandomContract = artifacts.require('MockRandom') -const Registry: RegistryContract = artifacts.require('Registry') - -// @ts-ignore -// TODO(mcortesi): Use BN -ElectionTest.numberFormat = 'BigNumber' -// @ts-ignoree -MockLockedGold.numberFormat = 'BigNumber' - -// Hard coded in ganache. -const EPOCH = 100 - -contract('Election', (accounts: string[]) => { - let accountsInstance: AccountsInstance - let election: ElectionTestInstance - let freezer: FreezerInstance - let registry: RegistryInstance - let mockLockedGold: MockLockedGoldInstance - let mockValidators: MockValidatorsInstance - - const nonOwner = accounts[1] - const electableValidators = { - min: new BigNumber(4), - max: new BigNumber(6), - } - const maxNumGroupsVotedFor = new BigNumber(3) - const electabilityThreshold = toFixed(1 / 100) - - beforeEach(async () => { - accountsInstance = await Accounts.new(true) - await Promise.all(accounts.map((account) => accountsInstance.createAccount({ from: account }))) - election = await ElectionTest.new() - freezer = await Freezer.new(true) - mockLockedGold = await MockLockedGold.new() - mockValidators = await MockValidators.new() - registry = await Registry.new(true) - await registry.setAddressFor(CeloContractName.Accounts, accountsInstance.address) - await registry.setAddressFor(CeloContractName.Freezer, freezer.address) - await registry.setAddressFor(CeloContractName.LockedGold, mockLockedGold.address) - await registry.setAddressFor(CeloContractName.Validators, mockValidators.address) - await election.initialize( - registry.address, - electableValidators.min, - electableValidators.max, - maxNumGroupsVotedFor, - electabilityThreshold - ) - }) - - async function setupGroupAndVote( - newGroup: string, - oldGroup: string, - members: string[], - vote = true - ) { - await mockValidators.setMembers(newGroup, members) - await registry.setAddressFor(CeloContractName.Validators, accounts[0]) - await election.markGroupEligible(newGroup, oldGroup, NULL_ADDRESS) - await registry.setAddressFor(CeloContractName.Validators, mockValidators.address) - if (vote) { - await election.vote(newGroup, 1, oldGroup, NULL_ADDRESS) - } - } - - describe('#initialize()', () => { - it('should have set the owner', async () => { - const owner: string = await election.owner() - assert.equal(owner, accounts[0]) - }) - - 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 () => { - const actualMaxNumGroupsVotedFor = await election.maxNumGroupsVotedFor() - 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 assertTransactionRevertWithReason( - election.initialize( - registry.address, - electableValidators.min, - electableValidators.max, - maxNumGroupsVotedFor, - electabilityThreshold - ), - 'contract already initialized' - ) - }) - }) - - describe('#setElectabilityThreshold', () => { - it('should set the electability threshold', async () => { - const threshold = toFixed(1 / 10) - await election.setElectabilityThreshold(threshold) - const result = await election.getElectabilityThreshold() - assertEqualBN(result, threshold) - }) - - it('should revert when the threshold is larger than 100%', async () => { - const threshold = toFixed(new BigNumber('2')) - await assertTransactionRevertWithReason( - election.setElectabilityThreshold(threshold), - 'Electability threshold must be lower than 100%' - ) - }) - }) - - describe('#setElectableValidators', () => { - const newElectableValidators = { - min: electableValidators.min.plus(1), - max: electableValidators.max.plus(1), - } - - it('should set the minimum electable valdiators', async () => { - 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 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: 'ElectableValidatorsSet', - args: { - min: newElectableValidators.min, - max: newElectableValidators.max, - }, - }) - }) - - it('should revert when the minElectableValidators is zero', async () => { - await assertTransactionRevertWithReason( - election.setElectableValidators(0, newElectableValidators.max), - 'Minimum electable validators cannot be zero' - ) - }) - - it('should revert when the min is greater than max', async () => { - await assertTransactionRevertWithReason( - election.setElectableValidators( - newElectableValidators.max.plus(1), - newElectableValidators.max - ), - 'Maximum electable validators cannot be smaller than minimum' - ) - }) - - it('should revert when the values are unchanged', async () => { - await assertTransactionRevertWithReason( - election.setElectableValidators(electableValidators.min, electableValidators.max), - 'Electable validators not changed' - ) - }) - - it('should revert when called by anyone other than the owner', async () => { - await assertTransactionRevertWithReason( - election.setElectableValidators(newElectableValidators.min, newElectableValidators.max, { - from: nonOwner, - }), - 'Ownable: caller is not the owner' - ) - }) - }) - - describe('#setMaxNumGroupsVotedFor', () => { - const newMaxNumGroupsVotedFor = maxNumGroupsVotedFor.plus(1) - it('should set the max electable validators', async () => { - await election.setMaxNumGroupsVotedFor(newMaxNumGroupsVotedFor) - assertEqualBN(await election.maxNumGroupsVotedFor(), newMaxNumGroupsVotedFor) - }) - - 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: 'MaxNumGroupsVotedForSet', - args: { - maxNumGroupsVotedFor: new BigNumber(newMaxNumGroupsVotedFor), - }, - }) - }) - - it('should revert when the maxNumGroupsVotedFor is unchanged', async () => { - await assertTransactionRevertWithReason( - election.setMaxNumGroupsVotedFor(maxNumGroupsVotedFor), - 'Max groups voted for not changed' - ) - }) - - it('should revert when called by anyone other than the owner', async () => { - await assertTransactionRevertWithReason( - election.setMaxNumGroupsVotedFor(newMaxNumGroupsVotedFor, { from: nonOwner }), - 'Ownable: caller is not the owner' - ) - }) - }) - - describe('#setAllowedToVoteOverMaxNumberOfGroups', () => { - it('should set Allowed To Vote Over Max Number Of Groups', async () => { - await election.setAllowedToVoteOverMaxNumberOfGroups(true) - assert.equal(await election.allowedToVoteOverMaxNumberOfGroups(accounts[0]), true) - }) - - it('should revert when vote over max number of groups set to true', async () => { - await mockValidators.setValidator(accounts[0]) - await assertTransactionRevertWithReason( - election.setAllowedToVoteOverMaxNumberOfGroups(true), - 'Validators cannot vote for more than max number of groups' - ) - }) - - it('should revert when vote over max number of groups set to true', async () => { - await mockValidators.setValidatorGroup(accounts[0]) - await assertTransactionRevertWithReason( - election.setAllowedToVoteOverMaxNumberOfGroups(true), - 'Validator groups cannot vote for more than max number of groups' - ) - }) - - it('should emit the AllowedToVoteOverMaxNumberOfGroups event', async () => { - const resp = await election.setAllowedToVoteOverMaxNumberOfGroups(true) - assert.equal(resp.logs.length, 1) - const log = resp.logs[0] - assertContainSubset(log, { - event: 'AllowedToVoteOverMaxNumberOfGroups', - args: { - account: accounts[0], - flag: true, - }, - }) - }) - - describe('When AllowedToVoteOverMaxNumberOfGroups on', () => { - beforeEach(async () => { - await election.setAllowedToVoteOverMaxNumberOfGroups(true) - }) - - it('should turn AllowedToVoteOverMaxNumberOfGroups off', async () => { - await election.setAllowedToVoteOverMaxNumberOfGroups(false) - assert.equal(await election.allowedToVoteOverMaxNumberOfGroups(accounts[0]), false) - }) - - it('should emit the AllowedToVoteOverMaxNumberOfGroups event', async () => { - const resp = await election.setAllowedToVoteOverMaxNumberOfGroups(false) - assert.equal(resp.logs.length, 1) - const log = resp.logs[0] - assertContainSubset(log, { - event: 'AllowedToVoteOverMaxNumberOfGroups', - args: { - account: accounts[0], - flag: false, - }, - }) - }) - }) - }) - - describe('#markGroupEligible', () => { - const group = accounts[1] - describe('when called by the registered validators contract', () => { - beforeEach(async () => { - await registry.setAddressFor(CeloContractName.Validators, accounts[0]) - }) - - describe('when the group has no votes', () => { - let resp: any - beforeEach(async () => { - resp = await election.markGroupEligible(group, NULL_ADDRESS, NULL_ADDRESS) - }) - - it('should add the group to the list of eligible groups', async () => { - assert.deepEqual(await election.getEligibleValidatorGroups(), [group]) - }) - - it('should emit the ValidatorGroupMarkedEligible event', async () => { - assert.equal(resp.logs.length, 1) - const log = resp.logs[0] - assertContainSubset(log, { - event: 'ValidatorGroupMarkedEligible', - args: { - group, - }, - }) - }) - - describe('when the group has already been marked eligible', () => { - it('should revert', async () => { - await assertTransactionRevertWithReason( - election.markGroupEligible(group, NULL_ADDRESS, NULL_ADDRESS), - 'invalid key' - ) - }) - }) - }) - }) - - describe('not called by the registered validators contract', () => { - it('should revert', async () => { - await assertTransactionRevertWithReason( - election.markGroupEligible(group, NULL_ADDRESS, NULL_ADDRESS), - 'only registered contract' - ) - }) - }) - }) - - describe('#markGroupIneligible', () => { - const group = accounts[1] - describe('when the group is eligible', () => { - beforeEach(async () => { - await mockValidators.setMembers(group, [accounts[9]]) - 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', () => { - let resp: any - beforeEach(async () => { - await registry.setAddressFor(CeloContractName.Validators, accounts[0]) - resp = await election.markGroupIneligible(group) - }) - - it('should remove the group from the list of eligible groups', async () => { - assert.deepEqual(await election.getEligibleValidatorGroups(), []) - }) - - it('should emit the ValidatorGroupMarkedIneligible event', async () => { - assert.equal(resp.logs.length, 1) - const log = resp.logs[0] - assertContainSubset(log, { - event: 'ValidatorGroupMarkedIneligible', - args: { - group, - }, - }) - }) - }) - - describe('when not called by the registered Validators contract', () => { - it('should revert', async () => { - await assertTransactionRevertWithReason( - election.markGroupIneligible(group), - 'only registered contract' - ) - }) - }) - }) - - 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', async () => { - await assertTransactionRevertWithReason( - election.markGroupIneligible(group), - 'key not in list' - ) - }) - }) - }) - }) - - describe('#vote', () => { - const voter = accounts[0] - const group = accounts[1] - const value = new BigNumber(1000) - describe('when the group is eligible', () => { - beforeEach(async () => { - await mockValidators.setMembers(group, [accounts[9]]) - 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', () => { - beforeEach(async () => { - await mockLockedGold.setTotalLockedGold(value) - await mockValidators.setNumRegisteredValidators(1) - }) - - describe('when the voter can vote for an additional group', () => { - describe('when the voter has sufficient non-voting balance', () => { - beforeEach(async () => { - await mockLockedGold.incrementNonvotingAccountBalance(voter, value) - }) - - describe('when the voter has not already voted for this group', () => { - let resp: any - beforeEach(async () => { - resp = await election.vote(group, value, NULL_ADDRESS, NULL_ADDRESS) - }) - - it('should add the group to the list of groups the account has voted for', async () => { - assert.deepEqual(await election.getGroupsVotedForByAccount(voter), [group]) - }) - - it("should increment the account's pending votes for the group", async () => { - assertEqualBN(await election.getPendingVotesForGroupByAccount(group, voter), value) - }) - - it("should increment the account's total votes for the group", async () => { - assertEqualBN(await election.getTotalVotesForGroupByAccount(group, voter), value) - }) - - it("should increment the account's total votes", async () => { - assertEqualBN(await election.getTotalVotesByAccount(voter), value) - }) - - it('should increment the total votes for the group', async () => { - assertEqualBN(await election.getTotalVotesForGroup(group), value) - }) - - it('should increment the total votes', async () => { - assertEqualBN(await election.getTotalVotes(), value) - }) - - it("should decrement the account's nonvoting locked gold balance", async () => { - assertEqualBN(await mockLockedGold.nonvotingAccountBalance(voter), 0) - }) - - 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), - }, - }) - }) - - describe('when the voter has already voted for this group', () => { - let response: any - beforeEach(async () => { - await mockLockedGold.incrementNonvotingAccountBalance(voter, value) - response = await election.vote(group, value, NULL_ADDRESS, NULL_ADDRESS) - }) - - it('should not change the list of groups the account has voted for', async () => { - assert.deepEqual(await election.getGroupsVotedForByAccount(voter), [group]) - }) - - it("should increment the account's pending votes for the group", async () => { - assertEqualBN( - await election.getPendingVotesForGroupByAccount(group, voter), - value.times(2) - ) - }) - - it("should increment the account's total votes for the group", async () => { - assertEqualBN( - await election.getTotalVotesForGroupByAccount(group, voter), - value.times(2) - ) - }) - - it("should increment the account's total votes", async () => { - assertEqualBN(await election.getTotalVotesByAccount(voter), value.times(2)) - }) - - it('should increment the total votes for the group', async () => { - assertEqualBN(await election.getTotalVotesForGroup(group), value.times(2)) - }) - - it('should increment the total votes', async () => { - assertEqualBN(await election.getTotalVotes(), value.times(2)) - }) - - it("should decrement the account's nonvoting locked gold balance", async () => { - assertEqualBN(await mockLockedGold.nonvotingAccountBalance(voter), 0) - }) - - it('should emit the ValidatorGroupVoteCast event', async () => { - assert.equal(response.logs.length, 1) - const log = response.logs[0] - assertContainSubset(log, { - event: 'ValidatorGroupVoteCast', - args: { - account: voter, - group, - value: new BigNumber(value), - }, - }) - }) - }) - }) - }) - - describe('when the voter does not have sufficient non-voting balance', () => { - beforeEach(async () => { - await mockLockedGold.incrementNonvotingAccountBalance(voter, value.minus(1)) - }) - - it('should revert', async () => { - await assertTransactionRevertWithReason( - election.vote(group, value, NULL_ADDRESS, NULL_ADDRESS), - 'SafeMath: subtraction overflow' - ) - }) - }) - }) - - 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 < maxNumGroupsVotedFor.toNumber(); i++) { - newGroup = accounts[i + 2] - await setupGroupAndVote(newGroup, group, [accounts[9]]) - } - }) - - it('should revert', async () => { - await assertTransactionRevertWithReason( - election.vote(group, value.minus(maxNumGroupsVotedFor), newGroup, NULL_ADDRESS), - 'Voted for too many groups' - ) - }) - }) - - describe('when the voter is over maxNumGroupsVotedFor but can vote for additional groups', () => { - let newGroup: string - beforeEach(async () => { - await mockLockedGold.incrementNonvotingAccountBalance(voter, value) - for (let i = 0; i < maxNumGroupsVotedFor.toNumber(); i++) { - newGroup = accounts[i + 2] - await setupGroupAndVote(newGroup, group, [accounts[9]]) - } - newGroup = accounts[maxNumGroupsVotedFor.toNumber() + 2] - await setupGroupAndVote(newGroup, group, [accounts[9]], false) - await election.setAllowedToVoteOverMaxNumberOfGroups(true) - }) - - it('should allow to vote for another group', async () => { - const valueToVoteFor = value.minus(maxNumGroupsVotedFor) - const resp = await election.vote(group, valueToVoteFor, newGroup, NULL_ADDRESS) - assert.equal(resp.logs.length, 1) - const log = resp.logs[0] - assertContainSubset(log, { - event: 'ValidatorGroupVoteCast', - args: { - account: voter, - group, - value: new BigNumber(valueToVoteFor), - }, - }) - }) - - it('should total votes by account since max number of groups was not reached', async () => { - const totalVotes = await election.getTotalVotesByAccount(accounts[0]) - assertEqualBN(totalVotes, maxNumGroupsVotedFor) - }) - - describe('When over maximum number of groups voted', () => { - const originallyNotVotedWithAmount = 1 - const account0FirstGroupVote = value - .minus(maxNumGroupsVotedFor) - .minus(originallyNotVotedWithAmount) - beforeEach(async () => { - await election.vote(group, account0FirstGroupVote, newGroup, NULL_ADDRESS) - }) - - it('should revert when turning off of setAllowedToVoteOverMaxNumberOfGroups', async () => { - await assertTransactionRevertWithReason( - election.setAllowedToVoteOverMaxNumberOfGroups(false), - 'Too many groups voted for!' - ) - }) - - it('should return return only last voted with since votes were not manually counted', async () => { - const totalVotes = await election.getTotalVotesByAccount(accounts[0]) - assertEqualBN(totalVotes, account0FirstGroupVote) - }) - - describe('When total votes are manually counted on', () => { - beforeEach(async () => { - for (let i = 0; i < maxNumGroupsVotedFor.toNumber(); i++) { - newGroup = accounts[i + 2] - await election.updateTotalVotesByAccountForGroup(accounts[0], newGroup) - } - await election.updateTotalVotesByAccountForGroup(accounts[0], group) - }) - - it('should return total votes by account', async () => { - const totalVotes = await election.getTotalVotesByAccount(accounts[0]) - assertEqualBN(totalVotes, value.minus(originallyNotVotedWithAmount)) - }) - - describe('When votes revoked', () => { - const revokeDiff = 100 - const revokeValue = account0FirstGroupVote.minus(100) - - beforeEach(async () => { - await election.revokePending(group, revokeValue, accounts[4], NULL_ADDRESS, 3, { - from: accounts[0], - }) - }) - - it('should return lowered total number of votes', async () => { - const totalVotes = await election.getTotalVotesByAccount(accounts[0]) - assertEqualBN(totalVotes, maxNumGroupsVotedFor.plus(revokeDiff)) - }) - }) - }) - - describe('When votes are being activated', () => { - const rewardValue = new BigNumber(1000000) - beforeEach(async () => { - await mineBlocks(EPOCH, web3) - await election.activateForAccount(group, voter) - }) - - it("should increment the account's active votes for the group", async () => { - assertEqualBN( - await election.getActiveVotesForGroupByAccount(group, voter), - account0FirstGroupVote - ) - }) - - it('should return correct value when manually counted', async () => { - for (let i = 0; i < maxNumGroupsVotedFor.toNumber(); i++) { - newGroup = accounts[i + 2] - await election.updateTotalVotesByAccountForGroup(accounts[0], newGroup) - } - await election.updateTotalVotesByAccountForGroup(accounts[0], group) - - const totalVotes = await election.getTotalVotesByAccount(accounts[0]) - assertEqualBN(totalVotes, value.minus(originallyNotVotedWithAmount)) - }) - - describe('When awards are distributed', () => { - beforeEach(async () => { - await election.distributeEpochRewards(group, rewardValue, newGroup, NULL_ADDRESS) - }) - - it('should revoke active votes (more then original votes without rewards)', async () => { - await election.revokeActive(group, value, newGroup, NULL_ADDRESS, 3) - assertEqualBN( - await election.getActiveVotesForGroupByAccount(group, voter), - rewardValue.minus(maxNumGroupsVotedFor).minus(originallyNotVotedWithAmount) - ) - }) - - describe('When more votes than active is revoked', () => { - beforeEach(async () => { - await election.revokeActive(group, value, newGroup, NULL_ADDRESS, 3) - }) - - it('should return correct value when manually counted', async () => { - for (let i = 0; i < maxNumGroupsVotedFor.toNumber(); i++) { - newGroup = accounts[i + 2] - await election.updateTotalVotesByAccountForGroup(accounts[0], newGroup) - } - await election.updateTotalVotesByAccountForGroup(accounts[0], group) - - const totalVotes = await election.getTotalVotesByAccount(accounts[0]) - assertEqualBN(totalVotes, rewardValue.minus(originallyNotVotedWithAmount)) - }) - }) - - describe('When total votes are manually counted on rewards are being distributed', () => { - beforeEach(async () => { - for (let i = 0; i < maxNumGroupsVotedFor.toNumber(); i++) { - newGroup = accounts[i + 2] - await election.updateTotalVotesByAccountForGroup(accounts[0], newGroup) - } - await election.updateTotalVotesByAccountForGroup(accounts[0], group) - }) - - it('should return total votes by account', async () => { - const totalVotes = await election.getTotalVotesByAccount(accounts[0]) - assertEqualBN( - totalVotes, - value.plus(rewardValue).minus(originallyNotVotedWithAmount) - ) - }) - - it('should increase total votes count once voted', async () => { - await election.vote( - newGroup, - originallyNotVotedWithAmount, - accounts[3], - group, - { from: accounts[0] } - ) - - const totalVotes = await election.getTotalVotesByAccount(accounts[0]) - assertEqualBN(totalVotes, value.plus(rewardValue)) - }) - }) - }) - }) - }) - }) - }) - - 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)) - }) - - it('should revert', async () => { - await assertTransactionRevertWithReason( - election.vote(group, value, NULL_ADDRESS, NULL_ADDRESS), - 'Group cannot receive votes' - ) - }) - }) - }) - - describe('when the group is not eligible', () => { - it('should revert', async () => { - await assertTransactionRevertWithReason( - election.vote(group, value, NULL_ADDRESS, NULL_ADDRESS), - 'Group not eligible' - ) - }) - }) - }) - - describe('#activate', () => { - const voter = accounts[0] - const group = accounts[1] - const value = 1000 - beforeEach(async () => { - await mockValidators.setMembers(group, [accounts[9]]) - 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) - }) - - describe('when the voter has pending votes', () => { - beforeEach(async () => { - await election.vote(group, value, NULL_ADDRESS, NULL_ADDRESS) - }) - - 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) - }) - - it("should decrement the account's pending votes for the group", async () => { - assertEqualBN(await election.getPendingVotesForGroupByAccount(group, voter), 0) - }) - - it("should increment the account's active votes for the group", async () => { - assertEqualBN(await election.getActiveVotesForGroupByAccount(group, voter), value) - }) - - it("should not modify the account's total votes for the group", async () => { - assertEqualBN(await election.getTotalVotesForGroupByAccount(group, voter), value) - }) - - it("should not modify the account's total votes", async () => { - assertEqualBN(await election.getTotalVotesByAccount(voter), value) - }) - - it('should not modify the total votes for the group', async () => { - assertEqualBN(await election.getTotalVotesForGroup(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 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 first account's active votes for the group", async () => { - assertEqualBN(await election.getActiveVotesForGroupByAccount(group, voter), value) - }) - - it("should not modify the first account's total votes for the group", async () => { - assertEqualBN(await election.getTotalVotesForGroupByAccount(group, voter), value) - }) - - it("should not modify the first account's total votes", async () => { - assertEqualBN(await election.getTotalVotesByAccount(voter), value) - }) - - it("should decrement the second account's pending votes for the group", async () => { - assertEqualBN(await election.getPendingVotesForGroupByAccount(group, voter2), 0) - }) - - it("should increment the second account's active votes for the group", async () => { - assertEqualBN(await election.getActiveVotesForGroupByAccount(group, voter2), value2) - }) - - it("should not modify the second account's total votes for the group", async () => { - assertEqualBN(await election.getTotalVotesForGroupByAccount(group, voter2), value2) - }) - - it("should not modify the second account's total votes", async () => { - assertEqualBN(await election.getTotalVotesByAccount(voter2), value2) - }) - - it('should not modify the total votes for the group', async () => { - assertEqualBN(await election.getTotalVotesForGroup(group), value + value2) - }) - - it('should not modify the total votes', async () => { - assertEqualBN(await election.getTotalVotes(), value + value2) - }) - }) - }) - - describe('when an epoch boundary has not passed since the pending votes were made', () => { - it('should revert', async () => { - await assertTransactionRevertWithReason( - election.activate(group), - 'Pending vote epoch not passed' - ) - }) - }) - }) - - describe('when the voter does not have pending votes', () => { - it('should revert', async () => { - await assertTransactionRevertWithReason( - election.activate(group), - 'Vote value cannot be zero' - ) - }) - }) - }) - - describe('#activateForAccount', () => { - const voter = accounts[0] - const group = accounts[1] - const value = 1000 - beforeEach(async () => { - await mockValidators.setMembers(group, [accounts[9]]) - 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) - }) - - describe('when the voter has pending votes', () => { - beforeEach(async () => { - await election.vote(group, value, NULL_ADDRESS, NULL_ADDRESS) - }) - - 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.activateForAccount(group, voter) - }) - - it("should decrement the account's pending votes for the group", async () => { - assertEqualBN(await election.getPendingVotesForGroupByAccount(group, voter), 0) - }) - - it("should increment the account's active votes for the group", async () => { - assertEqualBN(await election.getActiveVotesForGroupByAccount(group, voter), value) - }) - - it("should not modify the account's total votes for the group", async () => { - assertEqualBN(await election.getTotalVotesForGroupByAccount(group, voter), value) - }) - - it("should not modify the account's total votes", async () => { - assertEqualBN(await election.getTotalVotesByAccount(voter), value) - }) - - it('should not modify the total votes for the group', async () => { - assertEqualBN(await election.getTotalVotesForGroup(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 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.activateForAccount(group, voter2, { from: voter }) - }) - - it("should not modify the first account's active votes for the group", async () => { - assertEqualBN(await election.getActiveVotesForGroupByAccount(group, voter), value) - }) - - it("should not modify the first account's total votes for the group", async () => { - assertEqualBN(await election.getTotalVotesForGroupByAccount(group, voter), value) - }) - - it("should not modify the first account's total votes", async () => { - assertEqualBN(await election.getTotalVotesByAccount(voter), value) - }) - - it("should decrement the second account's pending votes for the group", async () => { - assertEqualBN(await election.getPendingVotesForGroupByAccount(group, voter2), 0) - }) - - it("should increment the second account's active votes for the group", async () => { - assertEqualBN(await election.getActiveVotesForGroupByAccount(group, voter2), value2) - }) - - it("should not modify the second account's total votes for the group", async () => { - assertEqualBN(await election.getTotalVotesForGroupByAccount(group, voter2), value2) - }) - - it("should not modify the second account's total votes", async () => { - assertEqualBN(await election.getTotalVotesByAccount(voter2), value2) - }) - - it('should not modify the total votes for the group', async () => { - assertEqualBN(await election.getTotalVotesForGroup(group), value + value2) - }) - - it('should not modify the total votes', async () => { - assertEqualBN(await election.getTotalVotes(), value + value2) - }) - }) - }) - - describe('when an epoch boundary has not passed since the pending votes were made', () => { - it('should revert', async () => { - await assertTransactionRevertWithReason( - election.activateForAccount(group, voter), - 'Pending vote epoch not passed' - ) - }) - }) - }) - - describe('when the voter does not have pending votes', () => { - it('should revert', async () => { - await assertTransactionRevertWithReason( - election.activateForAccount(group, voter), - 'Vote value cannot be zero' - ) - }) - }) - }) - - describe('#revokePending', () => { - const voter = accounts[0] - const group = accounts[1] - const value = 1000 - describe('when the voter has pending votes', () => { - beforeEach(async () => { - await mockValidators.setMembers(group, [accounts[9]]) - 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) - await election.vote(group, value, NULL_ADDRESS, NULL_ADDRESS) - }) - - describe('when the validator group has votes but is ineligible', () => { - const index = 0 - const revokedValue = value - 1 - const remaining = value - revokedValue - let resp: any - beforeEach(async () => { - await registry.setAddressFor(CeloContractName.Validators, accounts[0]) - await election.markGroupIneligible(group) - resp = await election.revokePending( - group, - revokedValue, - NULL_ADDRESS, - NULL_ADDRESS, - index - ) - }) - - it("should decrement the account's pending votes for the group", async () => { - assertEqualBN(await election.getPendingVotesForGroupByAccount(group, voter), remaining) - }) - - it("should decrement the account's total votes for the group", async () => { - assertEqualBN(await election.getTotalVotesForGroupByAccount(group, voter), remaining) - }) - - it("should decrement the account's total votes", async () => { - assertEqualBN(await election.getTotalVotesByAccount(voter), remaining) - }) - - it('should decrement the total votes for the group', async () => { - assertEqualBN(await election.getTotalVotesForGroup(group), remaining) - }) - - it('should decrement the total votes', async () => { - assertEqualBN(await election.getTotalVotes(), remaining) - }) - - it("should increment the account's nonvoting locked gold balance", async () => { - assertEqualBN(await mockLockedGold.nonvotingAccountBalance(voter), revokedValue) - }) - - it('should emit the ValidatorGroupPendingVoteRevoked event', async () => { - assert.equal(resp.logs.length, 1) - const log = resp.logs[0] - assertContainSubset(log, { - event: 'ValidatorGroupPendingVoteRevoked', - args: { - account: voter, - group, - value: new BigNumber(revokedValue), - }, - }) - }) - }) - - 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 decrement the account's pending votes for the group", async () => { - assertEqualBN(await election.getPendingVotesForGroupByAccount(group, voter), remaining) - }) - - it("should decrement the account's total votes for the group", async () => { - assertEqualBN(await election.getTotalVotesForGroupByAccount(group, voter), remaining) - }) - - it("should decrement the account's total votes", async () => { - assertEqualBN(await election.getTotalVotesByAccount(voter), remaining) - }) - - it('should decrement the total votes for the group', async () => { - assertEqualBN(await election.getTotalVotesForGroup(group), remaining) - }) - - it('should decrement the total votes', async () => { - assertEqualBN(await election.getTotalVotes(), remaining) - }) - - it("should increment the account's nonvoting locked gold balance", async () => { - assertEqualBN(await mockLockedGold.nonvotingAccountBalance(voter), revokedValue) - }) - - it('should emit the ValidatorGroupPendingVoteRevoked event', async () => { - assert.equal(resp.logs.length, 1) - const log = resp.logs[0] - assertContainSubset(log, { - event: 'ValidatorGroupPendingVoteRevoked', - args: { - account: voter, - group, - value: new BigNumber(revokedValue), - }, - }) - }) - }) - - 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) - }) - - it('should remove the group to the list of groups the account has voted for', async () => { - assert.deepEqual(await election.getGroupsVotedForByAccount(voter), []) - }) - }) - - describe('when the wrong index is provided', () => { - const index = 1 - it('should revert', async () => { - await assertTransactionRevertWithReason( - election.revokePending(group, value, NULL_ADDRESS, NULL_ADDRESS, index), - 'Bad index' - ) - }) - }) - }) - - describe('when the revoked value is greater than the pending votes', () => { - const index = 0 - it('should revert', async () => { - await assertTransactionRevertWithReason( - election.revokePending(group, value + 1, NULL_ADDRESS, NULL_ADDRESS, index), - 'Vote value larger than pending votes' - ) - }) - }) - }) - }) - - describe('#revokeActive', () => { - const voter0 = accounts[0] - const voter1 = accounts[1] - const group = accounts[2] - const voteValue0 = 1000 - const reward0 = 111 - const voteValue1 = 1000 - describe('when the voter has active votes', () => { - const assertConsistentSums = async () => { - const active0 = await election.getActiveVotesForGroupByAccount(group, voter0) - const active1 = await election.getActiveVotesForGroupByAccount(group, voter1) - const activeTotal = await election.getActiveVotesForGroup(group) - // This can vary by up to 1 wei due to rounding errors. - assertAlmostEqualBN(activeTotal, active0.plus(active1), 1) - const pending0 = await election.getPendingVotesForGroupByAccount(group, voter0) - const pending1 = await election.getPendingVotesForGroupByAccount(group, voter1) - const pendingTotal = pending0.plus(pending1) - const totalGroup = await election.getTotalVotesForGroup(group) - // This can vary by up to 1 wei due to rounding errors. - assertAlmostEqualBN(totalGroup, activeTotal.plus(pendingTotal), 1) - const total = await election.getTotalVotes() - assertEqualBN(total, totalGroup) - } - - beforeEach(async () => { - await mockValidators.setMembers(group, [accounts[9]]) - 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(voteValue0 + voteValue1) - await mockValidators.setNumRegisteredValidators(1) - await mockLockedGold.incrementNonvotingAccountBalance(voter0, voteValue0) - await mockLockedGold.incrementNonvotingAccountBalance(voter1, voteValue1) - // Gives 1000 units to voter 0 - await election.vote(group, voteValue0, NULL_ADDRESS, NULL_ADDRESS) - await assertConsistentSums() - await mineBlocks(EPOCH, web3) - await election.activate(group) - await assertConsistentSums() - - // Makes those 1000 units represent 1111 votes. - await election.distributeEpochRewards(group, reward0, NULL_ADDRESS, NULL_ADDRESS) - await assertConsistentSums() - - // Gives 900 units to voter 1. - await election.vote(group, voteValue1, NULL_ADDRESS, NULL_ADDRESS, { from: voter1 }) - await assertConsistentSums() - await mineBlocks(EPOCH, web3) - await election.activate(group, { from: voter1 }) - await assertConsistentSums() - }) - - describe('when the validator group has votes but is ineligible', () => { - const index = 0 - const remaining = 1 - const revokedValue = voteValue0 + reward0 - remaining - let resp: any - - beforeEach(async () => { - await registry.setAddressFor(CeloContractName.Validators, accounts[0]) - await election.markGroupIneligible(group) - resp = await election.revokeActive(group, revokedValue, accounts[1], accounts[3], index) - }) - - it('should be consistent', async () => { - await assertConsistentSums() - }) - - it("should decrement the account's active votes for the group", async () => { - assertEqualBN(await election.getActiveVotesForGroupByAccount(group, voter0), remaining) - }) - - it("should decrement the account's total votes for the group", async () => { - assertEqualBN(await election.getTotalVotesForGroupByAccount(group, voter0), remaining) - }) - - it("should decrement the account's total votes", async () => { - assertEqualBN(await election.getTotalVotesByAccount(voter0), remaining) - }) - - it('should decrement the total votes for the group', async () => { - assertEqualBN( - await election.getTotalVotesForGroup(group), - voteValue0 + reward0 + voteValue1 - revokedValue - ) - }) - - it('should decrement the total votes', async () => { - assertEqualBN( - await election.getTotalVotes(), - voteValue0 + reward0 + voteValue1 - revokedValue - ) - }) - - it("should increment the account's nonvoting locked gold balance", async () => { - assertEqualBN(await mockLockedGold.nonvotingAccountBalance(voter0), revokedValue) - }) - - it('should emit the ValidatorGroupActiveVoteRevoked event', async () => { - assert.equal(resp.logs.length, 1) - const log = resp.logs[0] - assertContainSubset(log, { - event: 'ValidatorGroupActiveVoteRevoked', - args: { - account: voter0, - group, - value: new BigNumber(revokedValue), - }, - }) - }) - }) - - describe('when the revoked value is less than the active votes', () => { - const index = 0 - const remaining = 1 - const revokedValue = voteValue0 + reward0 - remaining - let resp: any - beforeEach(async () => { - resp = await election.revokeActive(group, revokedValue, NULL_ADDRESS, NULL_ADDRESS, index) - }) - - it('should be consistent', async () => { - await assertConsistentSums() - }) - - it("should decrement the account's active votes for the group", async () => { - assertEqualBN(await election.getActiveVotesForGroupByAccount(group, voter0), remaining) - }) - - it("should decrement the account's total votes for the group", async () => { - assertEqualBN(await election.getTotalVotesForGroupByAccount(group, voter0), remaining) - }) - - it("should decrement the account's total votes", async () => { - assertEqualBN(await election.getTotalVotesByAccount(voter0), remaining) - }) - - it('should decrement the total votes for the group', async () => { - assertEqualBN( - await election.getTotalVotesForGroup(group), - voteValue0 + reward0 + voteValue1 - revokedValue - ) - }) - - it('should decrement the total votes', async () => { - assertEqualBN( - await election.getTotalVotes(), - voteValue0 + reward0 + voteValue1 - revokedValue - ) - }) - - it("should increment the account's nonvoting locked gold balance", async () => { - assertEqualBN(await mockLockedGold.nonvotingAccountBalance(voter0), revokedValue) - }) - - it('should emit the ValidatorGroupActiveVoteRevoked event', async () => { - assert.equal(resp.logs.length, 1) - const log = resp.logs[0] - assertContainSubset(log, { - event: 'ValidatorGroupActiveVoteRevoked', - args: { - account: voter0, - group, - value: new BigNumber(revokedValue), - }, - }) - }) - }) - - describe('when the revoked value is equal to the active votes', () => { - describe('#revokeAllActive', () => { - const index = 0 - beforeEach(async () => { - await election.revokeAllActive(group, NULL_ADDRESS, NULL_ADDRESS, index) - }) - - it('should be consistent', async () => { - await assertConsistentSums() - }) - - it("should decrement all of the account's active votes for the group", async () => { - assertEqualBN(await election.getActiveVotesForGroupByAccount(group, voter0), 0) - }) - }) - - describe('when the correct index is provided', () => { - const index = 0 - const revokedValue = voteValue0 + reward0 - beforeEach(async () => { - await election.revokeActive(group, revokedValue, NULL_ADDRESS, NULL_ADDRESS, index) - }) - - it('should be consistent', async () => { - await assertConsistentSums() - }) - - it("should decrement the account's active votes for the group", async () => { - assertEqualBN(await election.getActiveVotesForGroupByAccount(group, voter0), 0) - }) - - it("should decrement the account's total votes for the group", async () => { - assertEqualBN(await election.getTotalVotesForGroupByAccount(group, voter0), 0) - }) - - it("should decrement the account's total votes", async () => { - assertEqualBN(await election.getTotalVotesByAccount(voter0), 0) - }) - - it('should decrement the total votes for the group', async () => { - assertEqualBN(await election.getTotalVotesForGroup(group), voteValue1) - }) - - it('should decrement the total votes', async () => { - assertEqualBN(await election.getTotalVotes(), voteValue1) - }) - - it("should increment the account's nonvoting locked gold balance", async () => { - assertEqualBN(await mockLockedGold.nonvotingAccountBalance(voter0), revokedValue) - }) - - it('should remove the group to the list of groups the account has voted for', async () => { - assert.deepEqual(await election.getGroupsVotedForByAccount(voter0), []) - }) - }) - - describe('when the wrong index is provided', () => { - const index = 1 - it('should revert', async () => { - await assertTransactionRevertWithReason( - election.revokeActive(group, voteValue0 + reward0, NULL_ADDRESS, NULL_ADDRESS, index), - 'Bad index' - ) - }) - }) - }) - - describe('when the revoked value is greater than the active votes', () => { - const index = 0 - it('should revert', async () => { - await assertTransactionRevertWithReason( - election.revokeActive( - group, - voteValue0 + reward0 + 1, - NULL_ADDRESS, - NULL_ADDRESS, - index - ), - 'Vote value larger than active votes' - ) - }) - }) - }) - }) - - describe('#electValidatorSigners', () => { - let random: MockRandomInstance - let totalLockedGold: number - 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' - - // 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 } - 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()) - ) - } - - const setRandomness = async (hash: string) => - random.addTestRandomness((await web3.eth.getBlockNumber()) + 1, hash) - - beforeEach(async () => { - random = await MockRandom.new() - await registry.setAddressFor(CeloContractName.Random, random.address) - }) - - describe('when there is a large number of groups', () => { - const numbers: any = {} - beforeEach(async () => { - await mockLockedGold.setTotalLockedGold(new BigNumber(1e25)) - await mockValidators.setNumRegisteredValidators(400) - await mockLockedGold.incrementNonvotingAccountBalance(voter1.address, new BigNumber(1e25)) - await election.setElectabilityThreshold(0) - await election.setElectableValidators(10, 100) - - await election.setMaxNumGroupsVotedFor(200) - let prev = NULL_ADDRESS - let randomVotes = [] - for (let i = 0; i < 100; i++) { - randomVotes.push(Math.floor(Math.random() * 1e14)) - } - const pad = (a: string) => { - let res = a - while (res.length < 42) { - res = res + 'f' - } - return res - } - randomVotes = randomVotes.sort((a, b) => b - a) - for (let i = 0; i < 100; i++) { - await mockValidators.setMembers(pad('0x00' + i), [ - pad('0x1a' + i), - pad('0x2a' + i), - pad('0x3a' + i), - pad('0x4a' + i), - ]) - numbers[pad('0x1a' + i)] = randomVotes[i] - numbers[pad('0x2a' + i)] = randomVotes[i] / 2 - numbers[pad('0x3a' + i)] = randomVotes[i] / 3 - numbers[pad('0x4a' + i)] = randomVotes[i] / 4 - await registry.setAddressFor(CeloContractName.Validators, accounts[0]) - await election.markGroupEligible(pad('0x00' + i), NULL_ADDRESS, prev) - await registry.setAddressFor(CeloContractName.Validators, mockValidators.address) - await election.vote(pad('0x00' + i), randomVotes[i], NULL_ADDRESS, prev, { - from: voter1.address, - }) - prev = pad('0x00' + i) - } - }) - it('can elect correct validators', async () => { - const lst = await election.electValidatorSigners() - const smallest = lst - .map(normalizeAddressWith0x) - .map((a) => numbers[a]) - .sort((a, b) => a - b)[0] - // TODO fix types - const number100th = (Object as any).values(numbers).sort((a: any, b: any) => b - a)[99] - assert.equal(smallest, number100th) - }) - }) - - describe('when there are some groups', () => { - beforeEach(async () => { - await mockValidators.setMembers(group1, [validator1, validator2, validator3, validator4]) - await mockValidators.setMembers(group2, [validator5, validator6]) - await mockValidators.setMembers(group3, [validator7]) - - 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) - } - await mockLockedGold.setTotalLockedGold(totalLockedGold) - await mockValidators.setNumRegisteredValidators(7) - }) - - describe('when a single group has >= minElectableValidators as members and received votes', () => { - beforeEach(async () => { - await election.vote(group1, voter1.weight, group2, NULL_ADDRESS, { from: voter1.address }) - }) - - it("should return that group's member list", async () => { - await setRandomness(hash1) - assertSameAddresses(await election.electValidatorSigners(), [ - validator1, - validator2, - validator3, - validator4, - ]) - }) - }) - - 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 }) - await election.vote(group3, voter3.weight, NULL_ADDRESS, group2, { from: voter3.address }) - }) - - it('should return maxElectableValidators elected validators', async () => { - await setRandomness(hash1) - assertSameAddresses(await election.electValidatorSigners(), [ - validator1, - validator2, - validator3, - validator5, - validator6, - validator7, - ]) - }) - }) - - describe('when a group receives enough votes for > n seats but only has n members', () => { - beforeEach(async () => { - // 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 () => { - await setRandomness(hash1) - assertSameAddresses(await election.electValidatorSigners(), [ - validator7, - validator1, - validator2, - validator3, - validator5, - validator6, - ]) - }) - }) - - 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 }) - }) - - it('should not elect any members from that group', async () => { - await setRandomness(hash1) - assertSameAddresses(await election.electValidatorSigners(), [ - 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 }) - await election.vote(group3, voter3.weight, NULL_ADDRESS, group2, { from: voter3.address }) - }) - - it('should revert', async () => { - await setRandomness(hash1) - await assertRevert(election.electValidatorSigners()) - }) - }) - }) - }) - - describe('#getGroupEpochRewards', () => { - const voter = accounts[0] - const group1 = accounts[1] - const group2 = accounts[2] - const voteValue1 = new BigNumber(2000000000) - const voteValue2 = new BigNumber(1000000000) - const totalRewardValue = new BigNumber(3000000000) - 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) - }) - - describe('when one group has active votes', () => { - beforeEach(async () => { - await mineBlocks(EPOCH, web3) - await election.activate(group1) - }) - - describe('when the group meets the locked gold requirements ', () => { - describe('when group uptime is 100%', () => { - it('should return the total reward value', async () => { - assertEqualBN( - await election.getGroupEpochRewards(group1, totalRewardValue, [fixed1]), - totalRewardValue - ) - }) - }) - - describe('when group uptime is less than 100%', () => { - it('should return part of the total reward value', async () => { - assertEqualBN( - await election.getGroupEpochRewards(group1, totalRewardValue, [toFixed(0.5)]), - totalRewardValue.idiv(2) - ) - }) - }) - - describe('when group uptime is zero', () => { - it('should return zero', async () => { - assertEqualBN(await election.getGroupEpochRewards(group1, totalRewardValue, [0]), 0) - }) - }) - }) - - describe('when the group does not meet the locked gold requirements ', () => { - beforeEach(async () => { - await mockValidators.setDoesNotMeetAccountLockedGoldRequirements(group1) - }) - - it('should return zero', async () => { - assertEqualBN(await election.getGroupEpochRewards(group1, totalRewardValue, [fixed1]), 0) - }) - }) - }) - - describe('when two groups have active votes', () => { - const expectedGroup1EpochRewards = voteValue1 - .div(voteValue1.plus(voteValue2)) - .times(totalRewardValue) - .dp(0) - .minus(1) // minus 1 wei for rounding errors. - beforeEach(async () => { - await mineBlocks(EPOCH, web3) - await election.activate(group1) - await election.activate(group2) - }) - - describe('when one group does not meet the locked gold requirements ', () => { - beforeEach(async () => { - await mockValidators.setDoesNotMeetAccountLockedGoldRequirements(group2) - }) - - it('should return zero for that group', async () => { - assertEqualBN(await election.getGroupEpochRewards(group2, totalRewardValue, [fixed1]), 0) - }) - - it('should return the proportional reward value for the other group', async () => { - assertEqualBN( - await election.getGroupEpochRewards(group1, totalRewardValue, [fixed1]), - expectedGroup1EpochRewards - ) - }) - }) - }) - - describe('when the group does not have active votes', () => { - describe('when the group meets the locked gold requirements ', () => { - it('should return zero', async () => { - assertEqualBN(await election.getGroupEpochRewards(group1, totalRewardValue, [fixed1]), 0) - }) - }) - }) - }) - - describe('#distributeEpochRewards', () => { - const voter = accounts[0] - const group = accounts[1] - const voteValue = new BigNumber(1000000) - const rewardValue = new BigNumber(1000000) - beforeEach(async () => { - 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) - await mineBlocks(EPOCH, web3) - 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.getActiveVotesForGroupByAccount(group, voter), - voteValue.plus(rewardValue) - ) - }) - - it("should increment the account's total votes for the group", async () => { - assertEqualBN( - await election.getTotalVotesForGroupByAccount(group, voter), - voteValue.plus(rewardValue) - ) - }) - - it("should increment account's total votes", async () => { - assertEqualBN(await election.getTotalVotesByAccount(voter), voteValue.plus(rewardValue)) - }) - - it('should increment the total votes for the group', async () => { - assertEqualBN(await election.getTotalVotesForGroup(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 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) - // 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 }) - }) - - 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.getActiveVotesForGroupByAccount(group, voter), - expectedVoterActiveVotesForGroup - ) - assertEqualBN( - await election.getActiveVotesForGroupByAccount(group, voter2), - expectedVoter2ActiveVotesForGroup - ) - assertEqualBN( - await election.getActiveVotesForGroupByAccount(group2, voter2), - expectedVoter2ActiveVotesForGroup2 - ) - }) - - it("should increment the accounts' total votes for both groups", async () => { - assertEqualBN( - await election.getTotalVotesForGroupByAccount(group, voter), - expectedVoterActiveVotesForGroup - ) - assertEqualBN( - await election.getTotalVotesForGroupByAccount(group, voter2), - expectedVoter2ActiveVotesForGroup - ) - assertEqualBN( - await election.getTotalVotesForGroupByAccount(group2, voter2), - expectedVoter2ActiveVotesForGroup2 - ) - }) - - it("should increment the accounts' total votes", async () => { - assertEqualBN( - await election.getTotalVotesByAccount(voter), - expectedVoterActiveVotesForGroup - ) - assertEqualBN( - await election.getTotalVotesByAccount(voter2), - expectedVoter2ActiveVotesForGroup.plus(expectedVoter2ActiveVotesForGroup2) - ) - }) - - it('should increment the total votes for the groups', async () => { - assertEqualBN(await election.getTotalVotesForGroup(group), expectedGroupTotalActiveVotes) - assertEqualBN( - await election.getTotalVotesForGroup(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]) - }) - }) - }) - }) - - describe('#forceDecrementVotes', () => { - const voter = accounts[0] - const group = accounts[1] - const value = 1000 - - describe('when the account has voted for one group', () => { - beforeEach(async () => { - await mockValidators.setMembers(group, [accounts[9]]) - 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) - await election.vote(group, value, NULL_ADDRESS, NULL_ADDRESS) - await registry.setAddressFor(CeloContractName.LockedGold, accounts[2]) - }) - - describe('when the account only has pending votes', () => { - describe('when the account is slashed for the total pending voted gold', () => { - const index = 0 - const slashedValue = value - const remaining = value - slashedValue - beforeEach(async () => { - await election.forceDecrementVotes( - voter, - slashedValue, - [NULL_ADDRESS], - [NULL_ADDRESS], - [index], - { - from: accounts[2], - } - ) - }) - - it('should decrement pending votes to zero', async () => { - assertEqualBN(await election.getPendingVotesForGroupByAccount(group, voter), remaining) - }) - - it('should decrement total votes to zero', async () => { - assertEqualBN(await election.getTotalVotesForGroupByAccount(group, voter), remaining) - assertEqualBN(await election.getTotalVotesByAccount(voter), remaining) - assertEqualBN(await election.getTotalVotesForGroup(group), remaining) - assertEqualBN(await election.getTotalVotes(), remaining) - }) - - it("should remove the group from the voter's voted set", async () => { - assert.deepEqual(await election.getGroupsVotedForByAccount(voter), []) - }) - }) - }) - - describe('when the account only has active votes', () => { - beforeEach(async () => { - await mineBlocks(EPOCH, web3) - await election.activate(group) - }) - - describe('when the account is slashed for the total active voting gold', () => { - const index = 0 - const slashedValue = value - const remaining = value - slashedValue - beforeEach(async () => { - await election.forceDecrementVotes( - voter, - slashedValue, - [NULL_ADDRESS], - [NULL_ADDRESS], - [index], - { - from: accounts[2], - } - ) - }) - - it('should decrement active voted gold to zero', async () => { - assertEqualBN(await election.getActiveVotesForGroupByAccount(group, voter), remaining) - }) - - it('should decrement total voted gold to zero', async () => { - assertEqualBN(await election.getTotalVotesForGroupByAccount(group, voter), remaining) - assertEqualBN(await election.getTotalVotesByAccount(voter), remaining) - assertEqualBN(await election.getTotalVotesForGroup(group), remaining) - assertEqualBN(await election.getTotalVotes(), remaining) - }) - - it("should remove the group from the voter's voted set", async () => { - assert.deepEqual(await election.getGroupsVotedForByAccount(voter), []) - }) - }) - }) - }) - - describe('when the account has voted for more than one group equally', () => { - const group2 = accounts[7] - - beforeEach(async () => { - await mockValidators.setMembers(group, [accounts[9]]) - await mockValidators.setMembers(group2, [accounts[8]]) - await registry.setAddressFor(CeloContractName.Validators, accounts[0]) - await election.markGroupEligible(group, NULL_ADDRESS, NULL_ADDRESS) - await election.markGroupEligible(group2, NULL_ADDRESS, group) - await registry.setAddressFor(CeloContractName.Validators, mockValidators.address) - await mockLockedGold.setTotalLockedGold(value) - await mockValidators.setNumRegisteredValidators(2) - await mockLockedGold.incrementNonvotingAccountBalance(voter, value) - await election.vote(group, value / 2, group2, NULL_ADDRESS) - await election.vote(group2, value / 2, NULL_ADDRESS, group) - await registry.setAddressFor(CeloContractName.LockedGold, accounts[2]) - }) - - describe('when the accounts only have pending votes', () => { - describe('when both accounts are slashed for the total pending voted gold', () => { - const slashedValue = value - const remaining = value - slashedValue - - beforeEach(async () => { - await election.forceDecrementVotes( - voter, - slashedValue, - [group2, NULL_ADDRESS], - [NULL_ADDRESS, group], - [0, 1], - { from: accounts[2] } - ) - }) - - it("should decrement both group's pending votes to zero", async () => { - assertEqualBN(await election.getPendingVotesForGroupByAccount(group, voter), remaining) - assertEqualBN(await election.getPendingVotesForGroupByAccount(group2, voter), remaining) - }) - - it("should decrement both group's total votes to zero", async () => { - assertEqualBN(await election.getTotalVotesForGroupByAccount(group, voter), remaining) - assertEqualBN(await election.getTotalVotesByAccount(voter), remaining) - assertEqualBN(await election.getTotalVotesForGroup(group), remaining) - assertEqualBN(await election.getTotalVotesForGroup(group2), remaining) - assertEqualBN(await election.getTotalVotes(), remaining) - }) - - it("should remove the groups from the voter's voted set", async () => { - assert.deepEqual(await election.getGroupsVotedForByAccount(voter), []) - }) - }) - }) - }) - - describe('when the account has voted for more than one group inequally', () => { - const group2 = accounts[7] - const value2 = value * 1.5 - - beforeEach(async () => { - await mockValidators.setMembers(group, [accounts[9]]) - await mockValidators.setMembers(group2, [accounts[8]]) - await registry.setAddressFor(CeloContractName.Validators, accounts[0]) - await election.markGroupEligible(group, NULL_ADDRESS, NULL_ADDRESS) - await election.markGroupEligible(group2, group, NULL_ADDRESS) - await registry.setAddressFor(CeloContractName.Validators, mockValidators.address) - await mockLockedGold.setTotalLockedGold(value + value2) - await mockValidators.setNumRegisteredValidators(2) - await mockLockedGold.incrementNonvotingAccountBalance(voter, value + value2) - await election.vote(group2, value2 / 2, group, NULL_ADDRESS) - await election.vote(group, value / 2, NULL_ADDRESS, group2) - }) - - describe('when both groups have both pending and active votes', async () => { - beforeEach(async () => { - await mineBlocks(EPOCH, web3) - await election.activate(group) - await mineBlocks(EPOCH, web3) - await election.activate(group2) - await election.vote(group2, value2 / 2, group, NULL_ADDRESS) - await election.vote(group, value / 2, NULL_ADDRESS, group2) - await registry.setAddressFor(CeloContractName.LockedGold, accounts[2]) - }) - - describe("when we slash 1 more vote than group 1's pending vote total", async () => { - const slashedValue = value / 2 + 1 - const remaining = value - slashedValue - beforeEach(async () => { - await election.forceDecrementVotes( - voter, - slashedValue, - [NULL_ADDRESS, NULL_ADDRESS], - [group, group2], - [0, 1], - { from: accounts[2] } - ) - }) - - it('should not affect group 2', async () => { - assertEqualBN(await election.getTotalVotesForGroupByAccount(group2, voter), value2) - assertEqualBN(await election.getTotalVotesForGroup(group2), value2) - }) - - it("should reduce group 1's votes", async () => { - assertEqualBN(await election.getTotalVotesForGroupByAccount(group, voter), remaining) - assertEqualBN(await election.getTotalVotesForGroup(group), remaining) - }) - - it("should reduce `voter`'s total votes", async () => { - assertEqualBN(await election.getTotalVotesByAccount(voter), value2 + remaining) - }) - - it("should reduce `group1`'s pending votes to 0", async () => { - assertEqualBN(await election.getPendingVotesForGroupByAccount(group, voter), 0) - }) - - it("should reduce `group1`'s' active votes by 1", async () => { - assertEqualBN(await election.getActiveVotesForGroupByAccount(group, voter), remaining) - }) - }) - - describe("when we slash all of group 1's votes and some of group 2's", async () => { - const slashedValue = value + 1 - const totalRemaining = value + value2 - slashedValue - const group1Remaining = 0 - const group2TotalRemaining = value2 - 1 - // 1 vote is removed from group2, pending is removed first - const group2PendingRemaining = value2 / 2 - 1 - const group2ActiveRemaining = value2 / 2 - beforeEach(async () => { - await election.forceDecrementVotes( - voter, - slashedValue, - [group, NULL_ADDRESS], - [NULL_ADDRESS, group2], - [0, 1], - { from: accounts[2] } - ) - }) - - it("should decrement group 1's votes to 0", async () => { - assertEqualBN( - await election.getTotalVotesForGroupByAccount(group, voter), - group1Remaining - ) - assertEqualBN(await election.getTotalVotesForGroup(group), group1Remaining) - assertEqualBN( - await election.getPendingVotesForGroupByAccount(group, voter), - group1Remaining - ) - assertEqualBN( - await election.getActiveVotesForGroupByAccount(group, voter), - group1Remaining - ) - }) - - it("should decrement group 2's total votes by 1", async () => { - assertEqualBN( - await election.getTotalVotesForGroupByAccount(group2, voter), - group2TotalRemaining - ) - assertEqualBN(await election.getTotalVotesForGroup(group2), group2TotalRemaining) - }) - - it("should reduce `voter`'s total votes", async () => { - assertEqualBN(await election.getTotalVotesByAccount(voter), totalRemaining) - }) - - it("should reduce `group2`'s pending votes by 1", async () => { - assertEqualBN( - await election.getPendingVotesForGroupByAccount(group2, voter), - group2PendingRemaining - ) - }) - - it("should not reduce `group2`'s active votes", async () => { - assertEqualBN( - await election.getActiveVotesForGroupByAccount(group2, voter), - group2ActiveRemaining - ) - }) - }) - }) - - describe('when a slash affects the election order', () => { - const slashedValue = value / 4 - const group1RemainingActiveVotes = value - slashedValue - let initialGroupOrdering = [] - - beforeEach(async () => { - await election.vote(group, value / 2, group2, NULL_ADDRESS) - await mineBlocks(EPOCH, web3) - await election.activate(group) - await mineBlocks(EPOCH, web3) - await election.activate(group2) - initialGroupOrdering = (await election.getTotalVotesForEligibleValidatorGroups())[0] - await registry.setAddressFor(CeloContractName.LockedGold, accounts[2]) - await election.forceDecrementVotes( - voter, - slashedValue, - [group, NULL_ADDRESS], - [NULL_ADDRESS, group2], - [0, 1], - { from: accounts[2] } - ) - }) - - it("should decrement group 1's total votes by 1/4", async () => { - assertEqualBN( - await election.getTotalVotesForGroupByAccount(group, voter), - group1RemainingActiveVotes - ) - assertEqualBN(await election.getTotalVotesForGroup(group), group1RemainingActiveVotes) - }) - - it('should change the ordering of the election', async () => { - const newGroupOrdering = (await election.getTotalVotesForEligibleValidatorGroups())[0] - assert.notEqual(initialGroupOrdering, newGroupOrdering) - assert.equal(initialGroupOrdering[0], newGroupOrdering[1]) - assert.equal(initialGroupOrdering[1], newGroupOrdering[0]) - }) - }) - - describe('when `forceDecrementVotes` is called with malformed inputs', () => { - describe('when called to slash more value than groups have', () => { - it('should revert', async () => { - await assertTransactionRevertWithReason( - election.forceDecrementVotes( - voter, - value + value2 + 1, - [group, NULL_ADDRESS], - [NULL_ADDRESS, group2], - [0, 1], - { from: accounts[2] } - ), - 'only registered contract' - ) - }) - }) - - describe('when called to slash with incorrect lessers/greaters', () => { - it('should revert', async () => { - const slashedValue = value - // `group` should be listed as a lesser for index 0 (group2's lesser) - await assertTransactionRevertWithReason( - election.forceDecrementVotes( - voter, - slashedValue, - [NULL_ADDRESS, NULL_ADDRESS], - [NULL_ADDRESS, group2], - [0, 1], - { from: accounts[2] } - ), - 'only registered contract' - ) - }) - }) - - describe('when called to slash with incorrect indices', () => { - it('should revert', async () => { - const slashedValue = value - await assertTransactionRevertWithReason( - election.forceDecrementVotes( - voter, - slashedValue, - [group, NULL_ADDRESS], - [NULL_ADDRESS, group2], - [0, 0], - { from: accounts[2] } - ), - 'only registered contract' - ) - }) - }) - - describe('when called from an address other than the locked gold contract', () => { - it('should revert', async () => { - await assertTransactionRevertWithReason( - election.forceDecrementVotes( - voter, - value, - [group, NULL_ADDRESS], - [NULL_ADDRESS, group2], - [0, 0] - ), - 'only registered contract' - ) - }) - }) - }) - }) - }) - - describe('#consistencyChecks', () => { - const debug = false - const group = accounts[0] - const voters = accounts.slice(1) - interface Account { - address: string - nonvoting: BigNumber - pending: BigNumber - active: BigNumber - } - - const debugLog = (s: string) => { - if (debug) { - // tslint:disable-next-line: no-console - console.log(s) - } - } - - const printAccount = async (account: Account) => { - if (debug) { - debugLog( - `Expected ${ - account.address - }:\n\tnonvoting: ${account.nonvoting.toFixed()}\n\tpending: ${account.pending.toFixed()}\n\tactive: ${account.active.toFixed()}` - ) - debugLog( - `Actual ${account.address}:\n\tnonvoting: ${( - await mockLockedGold.nonvotingAccountBalance(account.address) - ).toFixed()}\n\tpending: ${( - await election.getPendingVotesForGroupByAccount(group, account.address) - ).toFixed()}\n\tactive: ${( - await election.getActiveVotesForGroupByAccount(group, account.address) - ).toFixed()}\n\tunits: ${( - await election.getActiveVoteUnitsForGroupByAccount(group, account.address) - ).toFixed()}\n\ttotalunits: ${( - await election.getActiveVoteUnitsForGroup(group) - ).toFixed()}\n\ttotalVotes: ${(await election.getActiveVotesForGroup(group)).toFixed()}` - ) - } - } - - enum VoteActionType { - Vote = 1, - Activate, - RevokePending, - RevokeActive, - } - - const randomElement = (list: A[]): A => { - return list[Math.floor(BigNumber.random().times(list.length).toNumber())] - } - - const randomInteger = (max: BigNumber, min: BigNumber = new BigNumber(1)): BigNumber => { - return BigNumber.random().times(max.minus(min)).plus(min).dp(0) - } - - const makeRandomAction = async (account: Account) => { - await printAccount(account) - const actions = [] - if (account.nonvoting.gt(0)) { - actions.push(VoteActionType.Vote) - } - if (await election.hasActivatablePendingVotes(account.address, group)) { - actions.push(VoteActionType.Activate) - } - if (account.pending.gt(0)) { - actions.push(VoteActionType.RevokePending) - } - if (account.active.gt(0)) { - actions.push(VoteActionType.RevokeActive) - } - const action = randomElement(actions) - let value: string - switch (action) { - case VoteActionType.Vote: - value = randomInteger(account.nonvoting).toFixed() - debugLog(`${account.address} voting with value ${value}`) - await election.vote(group, value, NULL_ADDRESS, NULL_ADDRESS, { from: account.address }) - account.nonvoting = account.nonvoting.minus(value) - account.pending = account.pending.plus(value) - break - case VoteActionType.Activate: - value = account.pending.toFixed() - debugLog(`${account.address} activating with value ${value}`) - await election.activate(group, { from: account.address }) - account.active = account.active.plus(value) - account.pending = account.pending.minus(value) - break - case VoteActionType.RevokePending: - value = randomInteger(account.pending).toFixed() - debugLog(`${account.address} revoking pending with value ${value}`) - await election.revokePending(group, value, NULL_ADDRESS, NULL_ADDRESS, 0, { - from: account.address, - }) - account.pending = account.pending.minus(value) - account.nonvoting = account.nonvoting.plus(value) - break - case VoteActionType.RevokeActive: - value = randomInteger(account.active).toFixed() - debugLog(`${account.address} revoking active with value ${value}`) - await election.revokeActive(group, value, NULL_ADDRESS, NULL_ADDRESS, 0, { - from: account.address, - }) - account.active = account.active.minus(value) - account.nonvoting = account.nonvoting.plus(value) - break - } - return account - } - - const checkVoterInvariants = async (account: Account, delta: number = 0) => { - await printAccount(account) - debugLog(`Checking pending vote invariant for ${account.address}`) - assertEqualBN( - await election.getPendingVotesForGroupByAccount(group, account.address), - account.pending - ) - debugLog(`Checking active vote invariant for ${account.address}`) - assertAlmostEqualBN( - await election.getActiveVotesForGroupByAccount(group, account.address), - account.active, - delta - ) - debugLog(`Checking total vote invariant for ${account.address}`) - assertAlmostEqualBN( - await election.getTotalVotesForGroupByAccount(group, account.address), - account.active.plus(account.pending), - delta - ) - debugLog(`Checking nonvoting invariant for ${account.address}`) - assertAlmostEqualBN( - await mockLockedGold.nonvotingAccountBalance(account.address), - account.nonvoting, - delta - ) - } - - const checkGroupInvariants = async (vAccounts: Account[], delta: number = 0) => { - const pendingTotal = vAccounts.reduce((a, b) => a.plus(b.pending), new BigNumber(0)) - const activeTotal = vAccounts.reduce((a, b) => a.plus(b.active), new BigNumber(0)) - debugLog(`Checking pending vote invariant for group`) - assertEqualBN(await election.getPendingVotesForGroup(group), pendingTotal) - debugLog(`Checking active vote invariant for group`) - assertAlmostEqualBN(await election.getActiveVotesForGroup(group), activeTotal, delta) - debugLog(`Checking total vote invariant for group`) - assertEqualBN( - await election.getTotalVotesForGroup(group), - pendingTotal.plus(await election.getActiveVotesForGroup(group)) - ) - assertEqualBN(await election.getTotalVotes(), await election.getTotalVotesForGroup(group)) - } - - const revokeAllAndCheckInvariants = async (delta: number = 0) => { - const vAccounts = await Promise.all( - voters.map(async (v) => { - return { - address: v, - active: await election.getActiveVotesForGroupByAccount(group, v), - pending: await election.getPendingVotesForGroupByAccount(group, v), - nonvoting: await mockLockedGold.nonvotingAccountBalance(v), - } - }) - ) - - for (const account of vAccounts) { - await checkVoterInvariants(account, delta) - const address = account.address - // Need to fetch actual number due to potential rounding errors. - const active = await election.getActiveVotesForGroupByAccount(group, address) - if (active.gt(0)) { - await election.revokeActive(group, active.toFixed(), NULL_ADDRESS, NULL_ADDRESS, 0, { - from: address, - }) - account.active = new BigNumber(0) - account.nonvoting = account.nonvoting.plus(active) - } - const pending = account.pending - if (pending.gt(0)) { - await election.revokePending(group, pending.toFixed(), NULL_ADDRESS, NULL_ADDRESS, 0, { - from: address, - }) - account.pending = new BigNumber(0) - account.nonvoting = account.nonvoting.plus(pending) - } - assertEqualBN(await election.getActiveVotesForGroupByAccount(group, address), 0) - assertEqualBN(await election.getPendingVotesForGroupByAccount(group, address), 0) - assertEqualBN(await mockLockedGold.nonvotingAccountBalance(address), account.nonvoting) - } - } - - let voterAccounts: Account[] - beforeEach(async () => { - // 50M gives us 450M total locked gold - const voterStartBalance = new BigNumber(web3.utils.toWei('50000000')) - await mockValidators.setMembers(group, [accounts[9]]) - 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(voterStartBalance.times(voters.length)) - await mockValidators.setNumRegisteredValidators(1) - await Promise.all( - voters.map((voter) => - mockLockedGold.incrementNonvotingAccountBalance(voter, voterStartBalance) - ) - ) - voterAccounts = voters.map((v) => { - return { - address: v, - nonvoting: voterStartBalance, - pending: new BigNumber(0), - active: new BigNumber(0), - } - }) - }) - - describe('when placing, activating, and revoking votes randomly', function (this: any) { - this.timeout(0) - describe('when no epoch rewards are distributed', () => { - it('actual and expected should always match exactly', async () => { - for (let i = 0; i < 10; i++) { - voterAccounts = await Promise.all(voterAccounts.map(makeRandomAction)) - await Promise.all(voterAccounts.map(checkVoterInvariants)) - await checkGroupInvariants(voterAccounts) - await mineBlocks(EPOCH, web3) - } - await revokeAllAndCheckInvariants() - }) - }) - - describe('when epoch rewards are distributed', () => { - it('actual and expected should always match within a small delta', async () => { - const distributeEpochRewards = async (vAccounts: Account[]) => { - // 1% compounded 100x gives up to a 2.7x multiplier. - const reward = randomInteger((await election.getTotalVotes()).times(0.01).dp(0)) - const activeTotal = vAccounts.reduce((a, b) => a.plus(b.active), new BigNumber(0)) - if (!reward.isZero() && !activeTotal.isZero()) { - debugLog(`Distributing ${reward.toFixed()} in rewards to voters`) - await election.distributeEpochRewards( - group, - reward.toFixed(), - NULL_ADDRESS, - NULL_ADDRESS - ) - // tslint:disable-next-line - for (let i = 0; i < vAccounts.length; i++) { - vAccounts[i].active = activeTotal - .plus(reward) - .times(vAccounts[i].active) - .div(activeTotal) - .dp(0) - await printAccount(vAccounts[i]) - } - } - return vAccounts - } - - for (let i = 0; i < 30; i++) { - debugLog(`Starting iteration ${i}`) - voterAccounts = await Promise.all(voterAccounts.map(makeRandomAction)) - await Promise.all(voterAccounts.map((v) => checkVoterInvariants(v, 10))) - await checkGroupInvariants(voterAccounts, 10) - - await mineBlocks(EPOCH, web3) - voterAccounts = await distributeEpochRewards(voterAccounts) - await Promise.all(voterAccounts.map((v) => checkVoterInvariants(v, 10))) - await checkGroupInvariants(voterAccounts, 10) - } - await revokeAllAndCheckInvariants(10) - }) - }) - }) - }) -}) From e2d9d8f9eeed8ae364fa9755f4046b2031e12a4a Mon Sep 17 00:00:00 2001 From: pahor167 <47992132+pahor167@users.noreply.github.com> Date: Mon, 8 Jan 2024 17:48:10 +0100 Subject: [PATCH 07/25] compilation CI fix --- packages/protocol/test-sol/voting/Election.t.sol | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/protocol/test-sol/voting/Election.t.sol b/packages/protocol/test-sol/voting/Election.t.sol index 1a008f4801f..e2382e707d4 100644 --- a/packages/protocol/test-sol/voting/Election.t.sol +++ b/packages/protocol/test-sol/voting/Election.t.sol @@ -3,7 +3,6 @@ pragma solidity ^0.5.13; pragma experimental ABIEncoderV2; import { Test } from "celo-foundry/Test.sol"; -import { TestSortedLinkedList } from "../../contracts/stability/TestSortedLinkedList.sol"; import "../../contracts/common/FixidityLib.sol"; import "../../contracts/governance/Election.sol"; import "../../contracts/governance/test/MockLockedGold.sol"; From 50a8a808393b15ae31ef7b825a03b28e4e622b7f Mon Sep 17 00:00:00 2001 From: pahor167 <47992132+pahor167@users.noreply.github.com> Date: Tue, 9 Jan 2024 10:12:47 +0100 Subject: [PATCH 08/25] utils added --- packages/protocol/test-sol/utils.sol | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packages/protocol/test-sol/utils.sol b/packages/protocol/test-sol/utils.sol index 8bd1a23b47f..05d29fa1c82 100644 --- a/packages/protocol/test-sol/utils.sol +++ b/packages/protocol/test-sol/utils.sol @@ -64,4 +64,11 @@ contract Utils is Test { addressSet.remove(arr1[i]); } } + + function generatePRN(uint256 min, uint256 max, uint256 salt) public view returns (uint256) { + return + (uint256(keccak256(abi.encodePacked(block.timestamp, block.difficulty, msg.sender, salt))) % + (max - min + 1)) + + min; + } } From 2f4767d863efa3b85231acaf21e41c8f28ce3c47 Mon Sep 17 00:00:00 2001 From: pahor167 <47992132+pahor167@users.noreply.github.com> Date: Tue, 9 Jan 2024 10:14:03 +0100 Subject: [PATCH 09/25] constants added --- packages/protocol/test-sol/constants.sol | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/protocol/test-sol/constants.sol b/packages/protocol/test-sol/constants.sol index 3db2fae475a..c05f119e1cc 100644 --- a/packages/protocol/test-sol/constants.sol +++ b/packages/protocol/test-sol/constants.sol @@ -3,7 +3,11 @@ pragma solidity ^0.5.13; // This contract is only required for Solidity 0.5 contract Constants { uint256 public constant FIXED1 = 1e24; - uint256 constant YEAR = 365 * 24 * 60 * 60; + uint256 public constant DAY = 24 * 60 * 60; + uint256 public constant WEEK = DAY * 7; + uint256 public constant YEAR = 365 * DAY; + + uint256 public constant EPOCH_SIZE = 17280; // contract names string constant ElectionContract = "Election"; From a78ac6cad11370a012e21248f6bd61eece273702 Mon Sep 17 00:00:00 2001 From: pahor167 <47992132+pahor167@users.noreply.github.com> Date: Tue, 9 Jan 2024 10:34:00 +0100 Subject: [PATCH 10/25] minor refactor --- packages/protocol/test-sol/utils.sol | 3 +- .../protocol/test-sol/voting/Election.t.sol | 98 +++++++------------ 2 files changed, 38 insertions(+), 63 deletions(-) diff --git a/packages/protocol/test-sol/utils.sol b/packages/protocol/test-sol/utils.sol index 9db8b8c3a16..971567dcd93 100644 --- a/packages/protocol/test-sol/utils.sol +++ b/packages/protocol/test-sol/utils.sol @@ -65,14 +65,15 @@ contract Utils is Test { } } + // Generates pseudo random number in the range [min, max] using block attributes function generatePRN(uint256 min, uint256 max, uint256 salt) public view returns (uint256) { return (uint256(keccak256(abi.encodePacked(block.timestamp, block.difficulty, msg.sender, salt))) % (max - min + 1)) + min; } + function blockTravel(uint256 blockTravel) public { vm.roll(block.number + blockTravel); } - } diff --git a/packages/protocol/test-sol/voting/Election.t.sol b/packages/protocol/test-sol/voting/Election.t.sol index e2382e707d4..9a92e456282 100644 --- a/packages/protocol/test-sol/voting/Election.t.sol +++ b/packages/protocol/test-sol/voting/Election.t.sol @@ -552,7 +552,7 @@ contract Election_Vote is ElectionTestFoundry { function WhenVotesAreBeingActivated() public returns (address newGroup) { newGroup = WhenVotedForMoreThanMaxNumberOfGroups(); - vm.roll(EPOCH_SIZE + 1); + blockTravel(EPOCH_SIZE + 1); election.activateForAccount(group, voter); } @@ -664,7 +664,7 @@ contract Election_Activate is ElectionTestFoundry { function WhenEpochBoundaryHasPassed() public { WhenVoterHasPendingVotes(); - vm.roll(EPOCH_SIZE + 1); + blockTravel(EPOCH_SIZE + 1); election.activate(group); } @@ -706,7 +706,7 @@ contract Election_Activate is ElectionTestFoundry { function test_ShouldEmitValidatorGroupVoteActivatedEvent_WhenEpochBoundaryHasPassed() public { WhenVoterHasPendingVotes(); - vm.roll(EPOCH_SIZE + 1); + blockTravel(EPOCH_SIZE + 1); vm.expectEmit(true, true, true, false); emit ValidatorGroupVoteActivated(voter, group, value, value * 100000000000000000000); election.activate(group); @@ -720,7 +720,7 @@ contract Election_Activate is ElectionTestFoundry { lockedGold.incrementNonvotingAccountBalance(voter2, value2); vm.prank(voter2); election.vote(group, value2, address(0), address(0)); - vm.roll(2 * EPOCH_SIZE + 2); + blockTravel(EPOCH_SIZE + 1); vm.prank(voter2); election.activate(group); } @@ -820,7 +820,7 @@ contract Election_ActivateForAccount is ElectionTestFoundry { function WhenEpochBoundaryHasPassed() public { WhenVoterHasPendingVotes(); - vm.roll(EPOCH_SIZE + 1); + blockTravel(EPOCH_SIZE + 1); election.activateForAccount(group, voter); } @@ -862,7 +862,7 @@ contract Election_ActivateForAccount is ElectionTestFoundry { function test_ShouldEmitValidatorGroupVoteActivatedEvent_WhenEpochBoundaryHasPassed() public { WhenVoterHasPendingVotes(); - vm.roll(EPOCH_SIZE + 1); + blockTravel(EPOCH_SIZE + 1); vm.expectEmit(true, true, true, false); emit ValidatorGroupVoteActivated(voter, group, value, value * 100000000000000000000); election.activate(group); @@ -876,7 +876,7 @@ contract Election_ActivateForAccount is ElectionTestFoundry { lockedGold.incrementNonvotingAccountBalance(voter2, value2); vm.prank(voter2); election.vote(group, value2, address(0), address(0)); - vm.roll(2 * EPOCH_SIZE + 2); + blockTravel(EPOCH_SIZE + 1); election.activateForAccount(group, voter2); } @@ -1147,7 +1147,7 @@ contract Election_RevokeActive is ElectionTestFoundry { // Gives 1000 units to voter 0 election.vote(group, voteValue0, address(0), address(0)); assertConsistentSums(); - vm.roll(EPOCH_SIZE + 1); + blockTravel(EPOCH_SIZE + 1); election.activate(group); assertConsistentSums(); @@ -1159,7 +1159,7 @@ contract Election_RevokeActive is ElectionTestFoundry { vm.prank(voter1); election.vote(group, voteValue1, address(0), address(0)); assertConsistentSums(); - vm.roll(2 * EPOCH_SIZE + 2); + blockTravel(EPOCH_SIZE + 1); vm.prank(voter1); election.activate(group); assertConsistentSums(); @@ -1669,7 +1669,7 @@ contract Election_GetGroupEpochRewards is ElectionTestFoundry { } function WhenOneGroupHasActiveVotes() public { - vm.roll(EPOCH_SIZE + 1); + blockTravel(EPOCH_SIZE + 1); election.activate(group1); } @@ -1708,7 +1708,7 @@ contract Election_GetGroupEpochRewards is ElectionTestFoundry { } function WhenTwoGroupsHaveActiveVotes() public { - vm.roll(EPOCH_SIZE + 1); + blockTravel(EPOCH_SIZE + 1); election.activate(group1); election.activate(group2); } @@ -1780,7 +1780,7 @@ contract Election_DistributeEpochRewards is ElectionTestFoundry { lockedGold.incrementNonvotingAccountBalance(voter, voteValue); election.vote(group, voteValue, address(0), address(0)); - vm.roll(EPOCH_SIZE + 1); + blockTravel(EPOCH_SIZE + 1); election.activate(group); } @@ -1835,7 +1835,7 @@ contract Election_DistributeEpochRewards is ElectionTestFoundry { // Split voter2's vote between the two groups. election.vote(group, voteValue2 / 2, group2, address(0)); election.vote(group2, voteValue2 / 2, address(0), group); - vm.roll(2 * EPOCH_SIZE + 2); + blockTravel(EPOCH_SIZE + 1); election.activate(group); election.activate(group2); vm.stopPrank(); @@ -1980,7 +1980,7 @@ contract Election_ForceDecrementVotes is ElectionTestFoundry { function WhenAccountHasOnlyActiveVotes() public { WhenAccountHasVotedForOneGroup(); - vm.roll(EPOCH_SIZE + 1); + blockTravel(EPOCH_SIZE + 1); election.activate(group); vm.prank(account2); @@ -2033,14 +2033,18 @@ contract Election_ForceDecrementVotes is ElectionTestFoundry { registry.setAddressFor("LockedGold", account2); } - function WhenAccountsOnlyHavePendingVotes() public { - WhenAccountHasVotedForMoreThanOneGroupEqually(); + function forceDecrementVotes2Groups( + address lesser0, + address lesser1, + address greater0, + address greater1 + ) public { address[] memory lessers = new address[](2); - lessers[0] = group2; - lessers[1] = address(0); + lessers[0] = lesser0; + lessers[1] = lesser1; address[] memory greaters = new address[](2); - greaters[0] = address(0); - greaters[1] = group; + greaters[0] = greater0; + greaters[1] = greater1; uint256[] memory indices = new uint256[](2); indices[0] = 0; indices[1] = 1; @@ -2049,6 +2053,11 @@ contract Election_ForceDecrementVotes is ElectionTestFoundry { election.forceDecrementVotes(voter, slashedValue, lessers, greaters, indices); } + function WhenAccountsOnlyHavePendingVotes() public { + WhenAccountHasVotedForMoreThanOneGroupEqually(); + forceDecrementVotes2Groups(group2, address(0), address(0), group); + } + function test_ShouldDecrementBothGroupPendingVotesToZero_WhenAccountsOnlyHavePendingVotes_WhenAccountHasVotedForMoreThanOneGroupEqually() public { @@ -2100,9 +2109,9 @@ contract Election_ForceDecrementVotes is ElectionTestFoundry { public { WhenAccountHasVotedForMoreThanOneGroupInequally(); - vm.roll(EPOCH_SIZE + 1); + blockTravel(EPOCH_SIZE + 1); election.activate(group); - vm.roll(2 * EPOCH_SIZE + 2); + blockTravel(EPOCH_SIZE + 1); election.activate(group2); election.vote(group2, value2 / 2, group, address(0)); @@ -2118,19 +2127,7 @@ contract Election_ForceDecrementVotes is ElectionTestFoundry { public { WhenBothGroupsHaveBothPendingAndActiveVotes_WhenAccountHasVotedForMoreThanOneGroupInequally(); - - address[] memory lessers = new address[](2); - lessers[0] = address(0); - lessers[1] = address(0); - address[] memory greaters = new address[](2); - greaters[0] = group; - greaters[1] = group2; - uint256[] memory indices = new uint256[](2); - indices[0] = 0; - indices[1] = 1; - - vm.prank(account2); - election.forceDecrementVotes(voter, slashedValue, lessers, greaters, indices); + forceDecrementVotes2Groups(address(0), address(0), group, group2); } function test_ShouldNotAffectGroup2_WhenWeSlash1MoreVoteThanGroup1PendingVoteTotal_WhenAccountHasVotedForMoreThanOneGroupInequally() @@ -2191,20 +2188,8 @@ contract Election_ForceDecrementVotes is ElectionTestFoundry { group2TotalRemaining = value2 - 1; group2PendingRemaining = value2 / 2 - 1; group2ActiveRemaining = value2 / 2; - console.log("slashedValue", slashedValue); - address[] memory lessers = new address[](2); - lessers[0] = group; - lessers[1] = address(0); - address[] memory greaters = new address[](2); - greaters[0] = address(0); - greaters[1] = group2; - uint256[] memory indices = new uint256[](2); - indices[0] = 0; - indices[1] = 1; - - vm.prank(account2); - election.forceDecrementVotes(voter, slashedValue, lessers, greaters, indices); + forceDecrementVotes2Groups(group, address(0), address(0), group2); } function test_ShouldDecrementGroup1Votes_WhenWeSlashAllOfGroup1VotesAndSomeOfGroup2__WhenWeSlash1MoreVoteThanGroup1PendingVoteTotal_WhenAccountHasVotedForMoreThanOneGroupInequally() @@ -2237,26 +2222,15 @@ contract Election_ForceDecrementVotes is ElectionTestFoundry { group1RemainingActiveVotes = value - slashedValue; election.vote(group, value / 2, group2, address(0)); - vm.roll(EPOCH_SIZE + 1); + blockTravel(EPOCH_SIZE + 1); election.activate(group); - vm.roll(2 * EPOCH_SIZE + 2); + blockTravel(EPOCH_SIZE + 1); election.activate(group2); (initialOrdering, ) = election.getTotalVotesForEligibleValidatorGroups(); registry.setAddressFor("LockedGold", account2); - address[] memory lessers = new address[](2); - lessers[0] = group; - lessers[1] = address(0); - address[] memory greaters = new address[](2); - greaters[0] = address(0); - greaters[1] = group2; - uint256[] memory indices = new uint256[](2); - indices[0] = 0; - indices[1] = 1; - - vm.prank(account2); - election.forceDecrementVotes(voter, slashedValue, lessers, greaters, indices); + forceDecrementVotes2Groups(group, address(0), address(0), group2); } function test_ShouldDecrementGroup1TotalVotesByOneQuarter_WhenSlashAffectsElectionOrder() public { From f7126daf6d42a978eccbced611c8e769b53d40e2 Mon Sep 17 00:00:00 2001 From: pahor167 <47992132+pahor167@users.noreply.github.com> Date: Tue, 30 Jan 2024 11:06:32 +0100 Subject: [PATCH 11/25] Update packages/protocol/test-sol/voting/Election.t.sol Co-authored-by: soloseng <102702451+soloseng@users.noreply.github.com> --- packages/protocol/test-sol/voting/Election.t.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/protocol/test-sol/voting/Election.t.sol b/packages/protocol/test-sol/voting/Election.t.sol index 9a92e456282..8f894f72459 100644 --- a/packages/protocol/test-sol/voting/Election.t.sol +++ b/packages/protocol/test-sol/voting/Election.t.sol @@ -15,7 +15,7 @@ import { Constants } from "../constants.sol"; import "../utils.sol"; import "forge-std/console.sol"; -contract ElectionTest is Election(true) { +contract ElectionMock is Election(true) { function distributeEpochRewards(address group, uint256 value, address lesser, address greater) external { From 574d3a65085a826c524ffffa3bc4242b29190f91 Mon Sep 17 00:00:00 2001 From: pahor167 <47992132+pahor167@users.noreply.github.com> Date: Tue, 30 Jan 2024 11:06:40 +0100 Subject: [PATCH 12/25] Update packages/protocol/test-sol/voting/Election.t.sol Co-authored-by: soloseng <102702451+soloseng@users.noreply.github.com> --- packages/protocol/test-sol/voting/Election.t.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/protocol/test-sol/voting/Election.t.sol b/packages/protocol/test-sol/voting/Election.t.sol index 8f894f72459..fa56cf308c2 100644 --- a/packages/protocol/test-sol/voting/Election.t.sol +++ b/packages/protocol/test-sol/voting/Election.t.sol @@ -23,7 +23,7 @@ contract ElectionMock is Election(true) { } } -contract ElectionTestFoundry is Utils, Constants { +contract ElectionTest is Utils, Constants { using FixidityLib for FixidityLib.Fraction; event ElectableValidatorsSet(uint256 min, uint256 max); From 4d19543c6822a02f8cb23389e581f4153e8ac256 Mon Sep 17 00:00:00 2001 From: pahor167 <47992132+pahor167@users.noreply.github.com> Date: Tue, 30 Jan 2024 11:06:47 +0100 Subject: [PATCH 13/25] Update packages/protocol/test-sol/voting/Election.t.sol Co-authored-by: soloseng <102702451+soloseng@users.noreply.github.com> --- packages/protocol/test-sol/voting/Election.t.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/protocol/test-sol/voting/Election.t.sol b/packages/protocol/test-sol/voting/Election.t.sol index fa56cf308c2..fec96a395c9 100644 --- a/packages/protocol/test-sol/voting/Election.t.sol +++ b/packages/protocol/test-sol/voting/Election.t.sol @@ -146,7 +146,7 @@ contract ElectionTest is Utils, Constants { } } -contract Election_Initialize is ElectionTestFoundry { +contract ElectionTest_Initialize is ElectionTestFoundry { function test_shouldHaveSetOwner() public { assertEq(election.owner(), owner); } From 665a86221158b3acf95ddd71407db9354de51f37 Mon Sep 17 00:00:00 2001 From: pahor167 <47992132+pahor167@users.noreply.github.com> Date: Tue, 30 Jan 2024 11:07:00 +0100 Subject: [PATCH 14/25] Update packages/protocol/test-sol/voting/Election.t.sol Co-authored-by: soloseng <102702451+soloseng@users.noreply.github.com> --- packages/protocol/test-sol/voting/Election.t.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/protocol/test-sol/voting/Election.t.sol b/packages/protocol/test-sol/voting/Election.t.sol index fec96a395c9..380bc8736fb 100644 --- a/packages/protocol/test-sol/voting/Election.t.sol +++ b/packages/protocol/test-sol/voting/Election.t.sol @@ -510,7 +510,7 @@ contract Election_Vote is ElectionTestFoundry { election.vote(group, voterFirstGroupVote, newGroup, address(0)); } - function test_ShouldRevert_WhenTurningOffSetAllowedToVoteOverMaxNUmberOfGroups() public { + function test_ShouldRevert_WhenTurningOffSetAllowedToVoteOverMaxNUmberOfGroups_WhenOverMaximumNumberOfGroupsVoted() public { WhenVotedForMoreThanMaxNumberOfGroups(); vm.expectRevert("Too many groups voted for!"); From 957df6b3d999b894e409cf440d58f8834ccb081b Mon Sep 17 00:00:00 2001 From: pahor167 <47992132+pahor167@users.noreply.github.com> Date: Tue, 30 Jan 2024 11:07:11 +0100 Subject: [PATCH 15/25] Update packages/protocol/test-sol/voting/Election.t.sol Co-authored-by: soloseng <102702451+soloseng@users.noreply.github.com> --- packages/protocol/test-sol/voting/Election.t.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/protocol/test-sol/voting/Election.t.sol b/packages/protocol/test-sol/voting/Election.t.sol index 380bc8736fb..56041ac1a46 100644 --- a/packages/protocol/test-sol/voting/Election.t.sol +++ b/packages/protocol/test-sol/voting/Election.t.sol @@ -2522,7 +2522,7 @@ contract Election_ConsistencyChecks is ElectionTestFoundry { } } - function test_ActualAndExpectedShouldAlwaysMatchWithinSmallDelta() public { + function test_ActualAndExpectedShouldAlwaysMatchWithinSmallDelta_WhenEpochRewardsAreDistributed() public { for (uint256 i = 0; i < 30; i++) { for (uint256 j = 0; j < accounts.length; j++) { makeRandomAction(accounts[j], j); From 97d70a86e59b25d1b398472196e77c591400b5d6 Mon Sep 17 00:00:00 2001 From: pahor167 <47992132+pahor167@users.noreply.github.com> Date: Tue, 30 Jan 2024 11:07:28 +0100 Subject: [PATCH 16/25] Update packages/protocol/test-sol/voting/Election.t.sol Co-authored-by: soloseng <102702451+soloseng@users.noreply.github.com> --- packages/protocol/test-sol/voting/Election.t.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/protocol/test-sol/voting/Election.t.sol b/packages/protocol/test-sol/voting/Election.t.sol index 56041ac1a46..32350ca1775 100644 --- a/packages/protocol/test-sol/voting/Election.t.sol +++ b/packages/protocol/test-sol/voting/Election.t.sol @@ -200,7 +200,7 @@ contract Election_SetElectableValidators is ElectionTestFoundry { assertEq(max, newElectableValidatorsMax); } - function test_ShouldEmitTHeElectableValidatorsSetEvent() public { + function test_ShouldEmitTheElectableValidatorsSetEvent() public { uint256 newElectableValidatorsMin = 2; uint256 newElectableValidatorsMax = 4; vm.expectEmit(true, false, false, false); From 2ec7d464177bfb8a2516e555098ce45ea9795e02 Mon Sep 17 00:00:00 2001 From: pahor167 <47992132+pahor167@users.noreply.github.com> Date: Tue, 30 Jan 2024 11:07:41 +0100 Subject: [PATCH 17/25] Update packages/protocol/test-sol/voting/Election.t.sol Co-authored-by: soloseng <102702451+soloseng@users.noreply.github.com> --- packages/protocol/test-sol/voting/Election.t.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/protocol/test-sol/voting/Election.t.sol b/packages/protocol/test-sol/voting/Election.t.sol index 32350ca1775..80808601db9 100644 --- a/packages/protocol/test-sol/voting/Election.t.sol +++ b/packages/protocol/test-sol/voting/Election.t.sol @@ -275,7 +275,7 @@ contract Election_SetAllowedToVoteOverMaxNumberOfGroups is ElectionTestFoundry { election.setAllowedToVoteOverMaxNumberOfGroups(true); } - function test_ShouldSwitchAllowedToVoteOVerMaxNumberOfGroupsOff_WhenTurnedOn() public { + function test_ShouldSwitchAllowedToVoteOverMaxNumberOfGroupsOff_WhenTurnedOn() public { election.setAllowedToVoteOverMaxNumberOfGroups(true); assertEq(election.allowedToVoteOverMaxNumberOfGroups(address(this)), true); election.setAllowedToVoteOverMaxNumberOfGroups(false); From a52f6e36927dd70629e6a5a6410d4f05d55e0d28 Mon Sep 17 00:00:00 2001 From: pahor167 <47992132+pahor167@users.noreply.github.com> Date: Tue, 30 Jan 2024 11:07:54 +0100 Subject: [PATCH 18/25] Update packages/protocol/test-sol/voting/Election.t.sol Co-authored-by: soloseng <102702451+soloseng@users.noreply.github.com> --- packages/protocol/test-sol/voting/Election.t.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/protocol/test-sol/voting/Election.t.sol b/packages/protocol/test-sol/voting/Election.t.sol index 80808601db9..54ffd5a9c78 100644 --- a/packages/protocol/test-sol/voting/Election.t.sol +++ b/packages/protocol/test-sol/voting/Election.t.sol @@ -444,7 +444,7 @@ contract Election_Vote is ElectionTestFoundry { assertEq(election.getTotalVotes(), value); } - function test_ShouldDecrementTheACcountsNonVotingLockedGoldBalance_WhenTheVoterHasNotAlreadyVotedForThisGroup() + function test_ShouldDecrementTheAccountsNonVotingLockedGoldBalance_WhenTheVoterHasNotAlreadyVotedForThisGroup() public { WhenTheVoterHasNotAlreadyVotedForThisGroup(); From fdd46bce9598c04e97bf17f29b10c5fd0021eb17 Mon Sep 17 00:00:00 2001 From: pahor167 <47992132+pahor167@users.noreply.github.com> Date: Tue, 30 Jan 2024 11:08:06 +0100 Subject: [PATCH 19/25] Update packages/protocol/test-sol/voting/Election.t.sol Co-authored-by: soloseng <102702451+soloseng@users.noreply.github.com> --- packages/protocol/test-sol/voting/Election.t.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/protocol/test-sol/voting/Election.t.sol b/packages/protocol/test-sol/voting/Election.t.sol index 54ffd5a9c78..c6d2e25d51b 100644 --- a/packages/protocol/test-sol/voting/Election.t.sol +++ b/packages/protocol/test-sol/voting/Election.t.sol @@ -487,7 +487,7 @@ contract Election_Vote is ElectionTestFoundry { election.vote(group, value - maxNumGroupsVotedFor, newGroup, address(0)); } - function test_ShouldAllowToVoteForAnotherGroup_WhenTheVoterIsOVerMaxNumberGroupsVotedForButCanVoteForAdditionalGroup() + function test_ShouldAllowToVoteForAnotherGroup_WhenTheVoterIsOverMaxNumberGroupsVotedForButCanVoteForAdditionalGroup() public { address newGroup = WhenVotedForMaxNumberOfGroups(); From 0c3731e8bb668edff95a54e7283f4fcc54656a18 Mon Sep 17 00:00:00 2001 From: pahor167 <47992132+pahor167@users.noreply.github.com> Date: Tue, 30 Jan 2024 11:08:24 +0100 Subject: [PATCH 20/25] Update packages/protocol/test-sol/voting/Election.t.sol Co-authored-by: soloseng <102702451+soloseng@users.noreply.github.com> --- packages/protocol/test-sol/voting/Election.t.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/protocol/test-sol/voting/Election.t.sol b/packages/protocol/test-sol/voting/Election.t.sol index c6d2e25d51b..4f438f19042 100644 --- a/packages/protocol/test-sol/voting/Election.t.sol +++ b/packages/protocol/test-sol/voting/Election.t.sol @@ -461,7 +461,7 @@ contract Election_Vote is ElectionTestFoundry { election.vote(group, value, address(0), address(0)); } - function test_ShouldRevert_WhenTheVOterDoesNotHaveSufficientNonVotingBalance() public { + function test_ShouldRevert_WhenTheVoterDoesNotHaveSufficientNonVotingBalance() public { WhenGroupEligible(); lockedGold.incrementNonvotingAccountBalance(voter, value - 1); vm.expectRevert("SafeMath: subtraction overflow"); From 00b644909211c072653084a14efa9deaefe864b1 Mon Sep 17 00:00:00 2001 From: pahor167 <47992132+pahor167@users.noreply.github.com> Date: Tue, 30 Jan 2024 11:09:08 +0100 Subject: [PATCH 21/25] Update packages/protocol/test-sol/voting/Election.t.sol Co-authored-by: soloseng <102702451+soloseng@users.noreply.github.com> --- packages/protocol/test-sol/voting/Election.t.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/protocol/test-sol/voting/Election.t.sol b/packages/protocol/test-sol/voting/Election.t.sol index 4f438f19042..c51b0a2554b 100644 --- a/packages/protocol/test-sol/voting/Election.t.sol +++ b/packages/protocol/test-sol/voting/Election.t.sol @@ -402,7 +402,7 @@ contract Election_Vote is ElectionTestFoundry { election.vote(group, value, address(0), address(0)); } - function test_ShouldAddTheGroupToLIstOfGroupsTheAccountHasVotedFor_WhenTheVoterHasNotAlreadyVotedForThisGroup() + function test_ShouldAddTheGroupToListOfGroupsTheAccountHasVotedFor_WhenTheVoterHasNotAlreadyVotedForThisGroup() public { WhenTheVoterHasNotAlreadyVotedForThisGroup(); From bf64d49757aa7bbd6c398f3e5088bec23e65dcde Mon Sep 17 00:00:00 2001 From: pahor167 <47992132+pahor167@users.noreply.github.com> Date: Tue, 30 Jan 2024 11:28:56 +0100 Subject: [PATCH 22/25] buildable --- .../protocol/test-sol/voting/Election.t.sol | 55 +++++++++---------- 1 file changed, 27 insertions(+), 28 deletions(-) diff --git a/packages/protocol/test-sol/voting/Election.t.sol b/packages/protocol/test-sol/voting/Election.t.sol index c51b0a2554b..d1bd91b6a37 100644 --- a/packages/protocol/test-sol/voting/Election.t.sol +++ b/packages/protocol/test-sol/voting/Election.t.sol @@ -3,17 +3,16 @@ pragma solidity ^0.5.13; pragma experimental ABIEncoderV2; import { Test } from "celo-foundry/Test.sol"; -import "../../contracts/common/FixidityLib.sol"; -import "../../contracts/governance/Election.sol"; -import "../../contracts/governance/test/MockLockedGold.sol"; -import "../../contracts/governance/test/MockValidators.sol"; -import "../../contracts/common/Accounts.sol"; -import "../../contracts/common/linkedlists/AddressSortedLinkedList.sol"; -import "../../contracts/identity/test/MockRandom.sol"; -import "../../contracts/common/Freezer.sol"; +import "@celo-contracts/common/FixidityLib.sol"; +import "@celo-contracts/governance/Election.sol"; +import "@celo-contracts/governance/test/MockLockedGold.sol"; +import "@celo-contracts/governance/test/MockValidators.sol"; +import "@celo-contracts/common/Accounts.sol"; +import "@celo-contracts/common/linkedlists/AddressSortedLinkedList.sol"; +import "@celo-contracts/identity/test/MockRandom.sol"; +import "@celo-contracts/common/Freezer.sol"; import { Constants } from "../constants.sol"; import "../utils.sol"; -import "forge-std/console.sol"; contract ElectionMock is Election(true) { function distributeEpochRewards(address group, uint256 value, address lesser, address greater) @@ -53,7 +52,7 @@ contract ElectionTest is Utils, Constants { event EpochRewardsDistributedToVoters(address indexed group, uint256 value); Accounts accounts; - ElectionTest election; + ElectionMock election; Freezer freezer; MockLockedGold lockedGold; MockValidators validators; @@ -123,7 +122,7 @@ contract ElectionTest is Utils, Constants { createAccount(address(this)); - election = new ElectionTest(); + election = new ElectionMock(); freezer = new Freezer(true); lockedGold = new MockLockedGold(); validators = new MockValidators(); @@ -146,7 +145,7 @@ contract ElectionTest is Utils, Constants { } } -contract ElectionTest_Initialize is ElectionTestFoundry { +contract ElectionTest_Initialize is ElectionTest { function test_shouldHaveSetOwner() public { assertEq(election.owner(), owner); } @@ -177,7 +176,7 @@ contract ElectionTest_Initialize is ElectionTestFoundry { } } -contract Election_SetElectabilityThreshold is ElectionTestFoundry { +contract Election_SetElectabilityThreshold is ElectionTest { function test_shouldSetElectabilityThreshold() public { uint256 newElectabilityThreshold = FixidityLib.newFixedFraction(1, 200).unwrap(); election.setElectabilityThreshold(newElectabilityThreshold); @@ -190,7 +189,7 @@ contract Election_SetElectabilityThreshold is ElectionTestFoundry { } } -contract Election_SetElectableValidators is ElectionTestFoundry { +contract Election_SetElectableValidators is ElectionTest { function test_shouldSetElectableValidators() public { uint256 newElectableValidatorsMin = 2; uint256 newElectableValidatorsMax = 4; @@ -230,7 +229,7 @@ contract Election_SetElectableValidators is ElectionTestFoundry { } } -contract Election_SetMaxNumGroupsVotedFor is ElectionTestFoundry { +contract Election_SetMaxNumGroupsVotedFor is ElectionTest { function test_shouldSetMaxNumGroupsVotedFor() public { uint256 newMaxNumGroupsVotedFor = 4; election.setMaxNumGroupsVotedFor(newMaxNumGroupsVotedFor); @@ -251,7 +250,7 @@ contract Election_SetMaxNumGroupsVotedFor is ElectionTestFoundry { } } -contract Election_SetAllowedToVoteOverMaxNumberOfGroups is ElectionTestFoundry { +contract Election_SetAllowedToVoteOverMaxNumberOfGroups is ElectionTest { function test_shouldSetAllowedToVoteOverMaxNumberOfGroups() public { election.setAllowedToVoteOverMaxNumberOfGroups(true); assertEq(election.allowedToVoteOverMaxNumberOfGroups(address(this)), true); @@ -291,7 +290,7 @@ contract Election_SetAllowedToVoteOverMaxNumberOfGroups is ElectionTestFoundry { } -contract Election_MarkGroupEligible is ElectionTestFoundry { +contract Election_MarkGroupEligible is ElectionTest { function setUp() public { super.setUp(); @@ -327,7 +326,7 @@ contract Election_MarkGroupEligible is ElectionTestFoundry { } } -contract Election_MarkGroupInEligible is ElectionTestFoundry { +contract Election_MarkGroupInEligible is ElectionTest { function setUp() public { super.setUp(); @@ -363,7 +362,7 @@ contract Election_MarkGroupInEligible is ElectionTestFoundry { } } -contract Election_Vote is ElectionTestFoundry { +contract Election_Vote is ElectionTest { address voter = address(this); address group = account1; uint256 value = 1000; @@ -636,7 +635,7 @@ contract Election_Vote is ElectionTestFoundry { } -contract Election_Activate is ElectionTestFoundry { +contract Election_Activate is ElectionTest { address voter = address(this); address group = account1; uint256 value = 1000; @@ -792,7 +791,7 @@ contract Election_Activate is ElectionTestFoundry { } } -contract Election_ActivateForAccount is ElectionTestFoundry { +contract Election_ActivateForAccount is ElectionTest { address voter = address(this); address group = account1; uint256 value = 1000; @@ -948,7 +947,7 @@ contract Election_ActivateForAccount is ElectionTestFoundry { } -contract Election_RevokePending is ElectionTestFoundry { +contract Election_RevokePending is ElectionTest { address voter = address(this); address group = account1; uint256 value = 1000; @@ -1105,7 +1104,7 @@ contract Election_RevokePending is ElectionTestFoundry { } } -contract Election_RevokeActive is ElectionTestFoundry { +contract Election_RevokeActive is ElectionTest { address voter0 = address(this); address voter1 = account1; address group = account2; @@ -1379,7 +1378,7 @@ contract Election_RevokeActive is ElectionTestFoundry { } -contract Election_ElectionValidatorSigners is ElectionTestFoundry { +contract Election_ElectionValidatorSigners is ElectionTest { address group1 = address(this); address group2 = account1; address group3 = account2; @@ -1637,7 +1636,7 @@ contract Election_ElectionValidatorSigners is ElectionTestFoundry { } } -contract Election_GetGroupEpochRewards is ElectionTestFoundry { +contract Election_GetGroupEpochRewards is ElectionTest { address voter = address(this); address group1 = account2; address group2 = account3; @@ -1753,7 +1752,7 @@ contract Election_GetGroupEpochRewards is ElectionTestFoundry { } } -contract Election_DistributeEpochRewards is ElectionTestFoundry { +contract Election_DistributeEpochRewards is ElectionTest { address voter = address(this); address voter2 = account4; address group = account2; @@ -1915,7 +1914,7 @@ contract Election_DistributeEpochRewards is ElectionTestFoundry { } } -contract Election_ForceDecrementVotes is ElectionTestFoundry { +contract Election_ForceDecrementVotes is ElectionTest { address voter = address(this); address group = account2; address group2 = account7; @@ -2329,7 +2328,7 @@ contract Election_ForceDecrementVotes is ElectionTestFoundry { } } -contract Election_ConsistencyChecks is ElectionTestFoundry { +contract Election_ConsistencyChecks is ElectionTest { address voter = address(this); address group = account2; uint256 rewardValue2 = 10000000; From bf81648870e4dbdefd6c2d0789fafc87129af38f Mon Sep 17 00:00:00 2001 From: pahor167 <47992132+pahor167@users.noreply.github.com> Date: Tue, 30 Jan 2024 11:37:28 +0100 Subject: [PATCH 23/25] PR comments 2 --- .../protocol/test-sol/voting/Election.t.sol | 44 +++++++++---------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/packages/protocol/test-sol/voting/Election.t.sol b/packages/protocol/test-sol/voting/Election.t.sol index d1bd91b6a37..8b85b97c880 100644 --- a/packages/protocol/test-sol/voting/Election.t.sol +++ b/packages/protocol/test-sol/voting/Election.t.sol @@ -516,7 +516,7 @@ contract Election_Vote is ElectionTest { election.setAllowedToVoteOverMaxNumberOfGroups(false); } - function test_ShouldReturnOnlyLastVotedWithSinceVotesWereNotManuallyCounted() public { + function test_ShouldReturnOnlyLastVotedWith_WhenVotesWereNotManuallyCounted() public { WhenVotedForMoreThanMaxNumberOfGroups(); assertEq(election.getTotalVotesByAccount(voter), voterFirstGroupVote); } @@ -2333,7 +2333,7 @@ contract Election_ConsistencyChecks is ElectionTest { address group = account2; uint256 rewardValue2 = 10000000; - AccountStruct[] accounts; + AccountStruct[] _accounts; struct AccountStruct { address account; @@ -2360,7 +2360,7 @@ contract Election_ConsistencyChecks is ElectionTest { for (uint256 i = 0; i < accountsArray.length; i++) { lockedGold.incrementNonvotingAccountBalance(accountsArray[i], voterStartBalance); - accounts.push( + _accounts.push( AccountStruct( accountsArray[i], election.getActiveVotesForGroupByAccount(group, accountsArray[i]), @@ -2443,14 +2443,14 @@ contract Election_ConsistencyChecks is ElectionTest { function checkGroupInvariants(uint256 delta) public { uint256 pendingTotal; - for (uint256 i = 0; i < accounts.length; i++) { - pendingTotal += accounts[i].pending; + for (uint256 i = 0; i < _accounts.length; i++) { + pendingTotal += _accounts[i].pending; } uint256 activateTotal; - for (uint256 i = 0; i < accounts.length; i++) { - activateTotal += accounts[i].active; + for (uint256 i = 0; i < _accounts.length; i++) { + activateTotal += _accounts[i].active; } assertAlmostEqual(election.getPendingVotesForGroup(group), pendingTotal, delta); @@ -2461,8 +2461,8 @@ contract Election_ConsistencyChecks is ElectionTest { } function revokeAllAndCheckInvariants(uint256 delta) public { - for (uint256 i = 0; i < accounts.length; i++) { - AccountStruct storage account = accounts[i]; + for (uint256 i = 0; i < _accounts.length; i++) { + AccountStruct storage account = _accounts[i]; checkVoterInvariants(account, delta); @@ -2492,9 +2492,9 @@ contract Election_ConsistencyChecks is ElectionTest { public { for (uint256 i = 0; i < 10; i++) { - for (uint256 j = 0; j < accounts.length; j++) { - makeRandomAction(accounts[j], j); - checkVoterInvariants(accounts[j], 0); + for (uint256 j = 0; j < _accounts.length; j++) { + makeRandomAction(_accounts[j], j); + checkVoterInvariants(_accounts[j], 0); checkGroupInvariants(0); vm.roll((i + 1) * EPOCH_SIZE + (i + 1)); } @@ -2507,33 +2507,33 @@ contract Election_ConsistencyChecks is ElectionTest { uint256 reward = generatePRN(0, election.getTotalVotes() / 100, salt); uint256 activeTotal; - for (uint256 i = 0; i < accounts.length; i++) { - activeTotal += accounts[i].active; + for (uint256 i = 0; i < _accounts.length; i++) { + activeTotal += _accounts[i].active; } if (reward > 0 && activeTotal > 0) { election.distributeEpochRewards(group, reward, address(0), address(0)); - for (uint256 i = 0; i < accounts.length; i++) { - AccountStruct storage account = accounts[i]; - account.active = ((activeTotal + reward) * accounts[i].active) / activeTotal; + for (uint256 i = 0; i < _accounts.length; i++) { + AccountStruct storage account = _accounts[i]; + account.active = ((activeTotal + reward) * _accounts[i].active) / activeTotal; } } } function test_ActualAndExpectedShouldAlwaysMatchWithinSmallDelta_WhenEpochRewardsAreDistributed() public { for (uint256 i = 0; i < 30; i++) { - for (uint256 j = 0; j < accounts.length; j++) { - makeRandomAction(accounts[j], j); - checkVoterInvariants(accounts[j], 100); + for (uint256 j = 0; j < _accounts.length; j++) { + makeRandomAction(_accounts[j], j); + checkVoterInvariants(_accounts[j], 100); checkGroupInvariants(100); } distributeEpochRewards(i); vm.roll((i + 1) * EPOCH_SIZE + (i + 1)); - for (uint256 j = 0; j < accounts.length; j++) { - checkVoterInvariants(accounts[j], 100); + for (uint256 j = 0; j < _accounts.length; j++) { + checkVoterInvariants(_accounts[j], 100); checkGroupInvariants(100); } } From b5cabfa53f51d59fd22296eeb87ca791557d3c08 Mon Sep 17 00:00:00 2001 From: pahor167 <47992132+pahor167@users.noreply.github.com> Date: Tue, 30 Jan 2024 12:03:10 +0100 Subject: [PATCH 24/25] Missing tests added --- .../protocol/test-sol/voting/Election.t.sol | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/packages/protocol/test-sol/voting/Election.t.sol b/packages/protocol/test-sol/voting/Election.t.sol index 8b85b97c880..32c45123304 100644 --- a/packages/protocol/test-sol/voting/Election.t.sol +++ b/packages/protocol/test-sol/voting/Election.t.sol @@ -248,6 +248,11 @@ contract Election_SetMaxNumGroupsVotedFor is ElectionTest { vm.prank(nonOwner); election.setMaxNumGroupsVotedFor(1); } + + function test_ShouldRevert_WhenMaxNumGroupsVotedForIsUnchanged() public { + vm.expectRevert("Max groups voted for not changed"); + election.setMaxNumGroupsVotedFor(maxNumGroupsVotedFor); + } } contract Election_SetAllowedToVoteOverMaxNumberOfGroups is ElectionTest { @@ -460,6 +465,57 @@ contract Election_Vote is ElectionTest { election.vote(group, value, address(0), address(0)); } + function WhenTheVoterHasAlreadyVotedForThisGroup() public { + WhenTheVoterHasNotAlreadyVotedForThisGroup(); + lockedGold.incrementNonvotingAccountBalance(voter, value); + election.vote(group, value, address(0), address(0)); + } + + function test_ShouldNotChangeTheListOfGroupsTheAccountVotedFor_WhenTheVoterHasAlreadyVotedForThisGroup() public { + WhenTheVoterHasAlreadyVotedForThisGroup(); + address[] memory groupsVotedFor = election.getGroupsVotedForByAccount(voter); + assertEq(groupsVotedFor.length, 1); + assertEq(groupsVotedFor[0], group); + } + + function test_ShouldIncreaseAccountsPendingVotesForTheGroup_WhenTheVoterHasAlreadyVotedForThisGroup() public { + WhenTheVoterHasAlreadyVotedForThisGroup(); + assertEq(election.getPendingVotesForGroupByAccount(group, voter), value * 2); + } + + function test_ShouldIncrementAccountTotalVotesForTheGroup_WhenTheVoterHasAlreadyVotedForThisGroup() public { + WhenTheVoterHasAlreadyVotedForThisGroup(); + assertEq(election.getTotalVotesForGroupByAccount(group, voter), value * 2); + } + + function test_ShouldIncrementTheAccountsTotalVotes_WhenTheVoterHasAlreadyVotedForThisGroup() public { + WhenTheVoterHasAlreadyVotedForThisGroup(); + assertEq(election.getTotalVotesByAccount(voter), value * 2); + } + + function test_ShouldIncrementTotalVotesForTheGroup_WhenTheVoterHasAlreadyVotedForThisGroup() public { + WhenTheVoterHasAlreadyVotedForThisGroup(); + assertEq(election.getTotalVotesForGroup(group), value * 2); + } + + function test_ShouldIncrementTotalVotes_WhenTheVoterHasAlreadyVotedForThisGroup() public { + WhenTheVoterHasAlreadyVotedForThisGroup(); + assertEq(election.getTotalVotes(), value * 2); + } + + function test_ShouldDecrementAccountNonVotingBalance_WhenTheVoterHasAlreadyVotedForThisGroup() public { + WhenTheVoterHasAlreadyVotedForThisGroup(); + assertEq(lockedGold.nonvotingAccountBalance(voter), 0); + } + + function test_ShouldEmitValidatorGroupVoteCast_WhenTheVoterHasAlreadyVotedForThisGroup() public { + WhenTheVoterHasNotAlreadyVotedForThisGroup(); + lockedGold.incrementNonvotingAccountBalance(voter, value); + vm.expectEmit(true, true, false, false); + emit ValidatorGroupVoteCast(voter, group, value); + election.vote(group, value, address(0), address(0)); + } + function test_ShouldRevert_WhenTheVoterDoesNotHaveSufficientNonVotingBalance() public { WhenGroupEligible(); lockedGold.incrementNonvotingAccountBalance(voter, value - 1); From 0fd82215c6c331bbbc7c8482d068f6988566abeb Mon Sep 17 00:00:00 2001 From: pahor167 <47992132+pahor167@users.noreply.github.com> Date: Tue, 30 Jan 2024 12:14:06 +0100 Subject: [PATCH 25/25] Split of the tests --- .../protocol/test-sol/voting/Election.t.sol | 317 ++++++++++-------- 1 file changed, 181 insertions(+), 136 deletions(-) diff --git a/packages/protocol/test-sol/voting/Election.t.sol b/packages/protocol/test-sol/voting/Election.t.sol index 32c45123304..e20c27546f5 100644 --- a/packages/protocol/test-sol/voting/Election.t.sol +++ b/packages/protocol/test-sol/voting/Election.t.sol @@ -366,8 +366,7 @@ contract Election_MarkGroupInEligible is ElectionTest { election.markGroupIneligible(address(this)); } } - -contract Election_Vote is ElectionTest { +contract Election_Vote_WhenGroupEligible is ElectionTest { address voter = address(this); address group = account1; uint256 value = 1000; @@ -382,149 +381,19 @@ contract Election_Vote is ElectionTest { address[] memory members = new address[](1); members[0] = account9; validators.setMembers(group, members); - } - function WhenGroupEligible() public { registry.setAddressFor("Validators", address(this)); election.markGroupEligible(group, address(0), address(0)); registry.setAddressFor("Validators", address(validators)); } - function WhenGroupCanReceiveVotes() public { - WhenGroupEligible(); - lockedGold.setTotalLockedGold(value); - validators.setNumRegisteredValidators(1); - } - - function WhenTheVoterCanVoteForAnAdditionalGroup() public { - lockedGold.incrementNonvotingAccountBalance(voter, value); - } - - function WhenTheVoterHasNotAlreadyVotedForThisGroup() public { - WhenGroupCanReceiveVotes(); - WhenTheVoterCanVoteForAnAdditionalGroup(); - election.vote(group, value, address(0), address(0)); - } - - function test_ShouldAddTheGroupToListOfGroupsTheAccountHasVotedFor_WhenTheVoterHasNotAlreadyVotedForThisGroup() - public - { - WhenTheVoterHasNotAlreadyVotedForThisGroup(); - address[] memory groupsVotedFor = election.getGroupsVotedForByAccount(voter); - assertEq(groupsVotedFor.length, 1); - assertEq(groupsVotedFor[0], group); - } - - function test_ShouldIncrementTheAccountsPendingVotesForTheGroup_WhenTheVoterHasNotAlreadyVotedForThisGroup() - public - { - WhenTheVoterHasNotAlreadyVotedForThisGroup(); - assertEq(election.getPendingVotesForGroupByAccount(group, voter), value); - } - - function test_ShouldIncrementTheAccountsTotalVotesForTheGroup_WhenTheVoterHasNotAlreadyVotedForThisGroup() - public - { - WhenTheVoterHasNotAlreadyVotedForThisGroup(); - assertEq(election.getTotalVotesForGroupByAccount(group, voter), value); - } - - function test_ShouldIncrementTheACcountsTotalVotes_WhenTheVoterHasNotAlreadyVotedForThisGroup() - public - { - WhenTheVoterHasNotAlreadyVotedForThisGroup(); - assertEq(election.getTotalVotesByAccount(voter), value); - } - - function test_ShouldIncrementTheTotalVotesForTheGroup_WhenTheVoterHasNotAlreadyVotedForThisGroup() - public - { - WhenTheVoterHasNotAlreadyVotedForThisGroup(); - assertEq(election.getTotalVotesForGroup(group), value); - } - - function test_ShouldIncrementTheTotalVotes_WhenTheVoterHasNotAlreadyVotedForThisGroup() public { - WhenTheVoterHasNotAlreadyVotedForThisGroup(); - assertEq(election.getTotalVotes(), value); - } - - function test_ShouldDecrementTheAccountsNonVotingLockedGoldBalance_WhenTheVoterHasNotAlreadyVotedForThisGroup() - public - { - WhenTheVoterHasNotAlreadyVotedForThisGroup(); - assertEq(lockedGold.nonvotingAccountBalance(voter), 0); - } - - function test_ShouldEmitTheValidatorGroupVoteCastEvent_WhenTheVoterHasNotAlreadyVotedForThisGroup() - public - { - WhenGroupCanReceiveVotes(); - WhenTheVoterCanVoteForAnAdditionalGroup(); - vm.expectEmit(true, false, false, false); - emit ValidatorGroupVoteCast(voter, group, value); - election.vote(group, value, address(0), address(0)); - } - - function WhenTheVoterHasAlreadyVotedForThisGroup() public { - WhenTheVoterHasNotAlreadyVotedForThisGroup(); - lockedGold.incrementNonvotingAccountBalance(voter, value); - election.vote(group, value, address(0), address(0)); - } - - function test_ShouldNotChangeTheListOfGroupsTheAccountVotedFor_WhenTheVoterHasAlreadyVotedForThisGroup() public { - WhenTheVoterHasAlreadyVotedForThisGroup(); - address[] memory groupsVotedFor = election.getGroupsVotedForByAccount(voter); - assertEq(groupsVotedFor.length, 1); - assertEq(groupsVotedFor[0], group); - } - - function test_ShouldIncreaseAccountsPendingVotesForTheGroup_WhenTheVoterHasAlreadyVotedForThisGroup() public { - WhenTheVoterHasAlreadyVotedForThisGroup(); - assertEq(election.getPendingVotesForGroupByAccount(group, voter), value * 2); - } - - function test_ShouldIncrementAccountTotalVotesForTheGroup_WhenTheVoterHasAlreadyVotedForThisGroup() public { - WhenTheVoterHasAlreadyVotedForThisGroup(); - assertEq(election.getTotalVotesForGroupByAccount(group, voter), value * 2); - } - - function test_ShouldIncrementTheAccountsTotalVotes_WhenTheVoterHasAlreadyVotedForThisGroup() public { - WhenTheVoterHasAlreadyVotedForThisGroup(); - assertEq(election.getTotalVotesByAccount(voter), value * 2); - } - - function test_ShouldIncrementTotalVotesForTheGroup_WhenTheVoterHasAlreadyVotedForThisGroup() public { - WhenTheVoterHasAlreadyVotedForThisGroup(); - assertEq(election.getTotalVotesForGroup(group), value * 2); - } - - function test_ShouldIncrementTotalVotes_WhenTheVoterHasAlreadyVotedForThisGroup() public { - WhenTheVoterHasAlreadyVotedForThisGroup(); - assertEq(election.getTotalVotes(), value * 2); - } - - function test_ShouldDecrementAccountNonVotingBalance_WhenTheVoterHasAlreadyVotedForThisGroup() public { - WhenTheVoterHasAlreadyVotedForThisGroup(); - assertEq(lockedGold.nonvotingAccountBalance(voter), 0); - } - - function test_ShouldEmitValidatorGroupVoteCast_WhenTheVoterHasAlreadyVotedForThisGroup() public { - WhenTheVoterHasNotAlreadyVotedForThisGroup(); - lockedGold.incrementNonvotingAccountBalance(voter, value); - vm.expectEmit(true, true, false, false); - emit ValidatorGroupVoteCast(voter, group, value); - election.vote(group, value, address(0), address(0)); - } - function test_ShouldRevert_WhenTheVoterDoesNotHaveSufficientNonVotingBalance() public { - WhenGroupEligible(); lockedGold.incrementNonvotingAccountBalance(voter, value - 1); vm.expectRevert("SafeMath: subtraction overflow"); election.vote(group, value, address(0), address(0)); } function WhenVotedForMaxNumberOfGroups() public returns (address newGroup) { - WhenGroupEligible(); lockedGold.incrementNonvotingAccountBalance(voter, value); for (uint256 i = 0; i < maxNumGroupsVotedFor; i++) { @@ -565,7 +434,9 @@ contract Election_Vote is ElectionTest { election.vote(group, voterFirstGroupVote, newGroup, address(0)); } - function test_ShouldRevert_WhenTurningOffSetAllowedToVoteOverMaxNUmberOfGroups_WhenOverMaximumNumberOfGroupsVoted() public { + function test_ShouldRevert_WhenTurningOffSetAllowedToVoteOverMaxNUmberOfGroups_WhenOverMaximumNumberOfGroupsVoted() + public + { WhenVotedForMoreThanMaxNumberOfGroups(); vm.expectRevert("Too many groups voted for!"); @@ -672,7 +543,6 @@ contract Election_Vote is ElectionTest { } function test_ShouldRevert_WhenTheGroupCannotReceiveVotes() public { - WhenGroupEligible(); lockedGold.setTotalLockedGold(value / 2 - 1); address[] memory members = new address[](1); members[0] = account9; @@ -683,12 +553,185 @@ contract Election_Vote is ElectionTest { vm.expectRevert("Group cannot receive votes"); election.vote(group, value, address(0), address(0)); } +} + +contract Election_Vote_WhenGroupEligible_WhenGroupCanReceiveVotes is ElectionTest { + address voter = address(this); + address group = account1; + uint256 value = 1000; + + uint256 originallyNotVotedWithAmount = 1; + uint256 voterFirstGroupVote = value - maxNumGroupsVotedFor - originallyNotVotedWithAmount; + uint256 rewardValue = 1000000; + + function setUp() public { + super.setUp(); + + address[] memory members = new address[](1); + members[0] = account9; + validators.setMembers(group, members); + + registry.setAddressFor("Validators", address(this)); + election.markGroupEligible(group, address(0), address(0)); + registry.setAddressFor("Validators", address(validators)); + + lockedGold.setTotalLockedGold(value); + validators.setNumRegisteredValidators(1); + } + + function WhenTheVoterCanVoteForAnAdditionalGroup() public { + lockedGold.incrementNonvotingAccountBalance(voter, value); + } + + function WhenTheVoterHasNotAlreadyVotedForThisGroup() public { + WhenTheVoterCanVoteForAnAdditionalGroup(); + election.vote(group, value, address(0), address(0)); + } + + function test_ShouldAddTheGroupToListOfGroupsTheAccountHasVotedFor_WhenTheVoterHasNotAlreadyVotedForThisGroup() + public + { + WhenTheVoterHasNotAlreadyVotedForThisGroup(); + address[] memory groupsVotedFor = election.getGroupsVotedForByAccount(voter); + assertEq(groupsVotedFor.length, 1); + assertEq(groupsVotedFor[0], group); + } + + function test_ShouldIncrementTheAccountsPendingVotesForTheGroup_WhenTheVoterHasNotAlreadyVotedForThisGroup() + public + { + WhenTheVoterHasNotAlreadyVotedForThisGroup(); + assertEq(election.getPendingVotesForGroupByAccount(group, voter), value); + } + + function test_ShouldIncrementTheAccountsTotalVotesForTheGroup_WhenTheVoterHasNotAlreadyVotedForThisGroup() + public + { + WhenTheVoterHasNotAlreadyVotedForThisGroup(); + assertEq(election.getTotalVotesForGroupByAccount(group, voter), value); + } + + function test_ShouldIncrementTheACcountsTotalVotes_WhenTheVoterHasNotAlreadyVotedForThisGroup() + public + { + WhenTheVoterHasNotAlreadyVotedForThisGroup(); + assertEq(election.getTotalVotesByAccount(voter), value); + } + + function test_ShouldIncrementTheTotalVotesForTheGroup_WhenTheVoterHasNotAlreadyVotedForThisGroup() + public + { + WhenTheVoterHasNotAlreadyVotedForThisGroup(); + assertEq(election.getTotalVotesForGroup(group), value); + } + + function test_ShouldIncrementTheTotalVotes_WhenTheVoterHasNotAlreadyVotedForThisGroup() public { + WhenTheVoterHasNotAlreadyVotedForThisGroup(); + assertEq(election.getTotalVotes(), value); + } + + function test_ShouldDecrementTheAccountsNonVotingLockedGoldBalance_WhenTheVoterHasNotAlreadyVotedForThisGroup() + public + { + WhenTheVoterHasNotAlreadyVotedForThisGroup(); + assertEq(lockedGold.nonvotingAccountBalance(voter), 0); + } + + function test_ShouldEmitTheValidatorGroupVoteCastEvent_WhenTheVoterHasNotAlreadyVotedForThisGroup() + public + { + WhenTheVoterCanVoteForAnAdditionalGroup(); + vm.expectEmit(true, false, false, false); + emit ValidatorGroupVoteCast(voter, group, value); + election.vote(group, value, address(0), address(0)); + } + + function WhenTheVoterHasAlreadyVotedForThisGroup() public { + WhenTheVoterHasNotAlreadyVotedForThisGroup(); + lockedGold.incrementNonvotingAccountBalance(voter, value); + election.vote(group, value, address(0), address(0)); + } + + function test_ShouldNotChangeTheListOfGroupsTheAccountVotedFor_WhenTheVoterHasAlreadyVotedForThisGroup() + public + { + WhenTheVoterHasAlreadyVotedForThisGroup(); + address[] memory groupsVotedFor = election.getGroupsVotedForByAccount(voter); + assertEq(groupsVotedFor.length, 1); + assertEq(groupsVotedFor[0], group); + } + + function test_ShouldIncreaseAccountsPendingVotesForTheGroup_WhenTheVoterHasAlreadyVotedForThisGroup() + public + { + WhenTheVoterHasAlreadyVotedForThisGroup(); + assertEq(election.getPendingVotesForGroupByAccount(group, voter), value * 2); + } + + function test_ShouldIncrementAccountTotalVotesForTheGroup_WhenTheVoterHasAlreadyVotedForThisGroup() + public + { + WhenTheVoterHasAlreadyVotedForThisGroup(); + assertEq(election.getTotalVotesForGroupByAccount(group, voter), value * 2); + } + + function test_ShouldIncrementTheAccountsTotalVotes_WhenTheVoterHasAlreadyVotedForThisGroup() + public + { + WhenTheVoterHasAlreadyVotedForThisGroup(); + assertEq(election.getTotalVotesByAccount(voter), value * 2); + } + + function test_ShouldIncrementTotalVotesForTheGroup_WhenTheVoterHasAlreadyVotedForThisGroup() + public + { + WhenTheVoterHasAlreadyVotedForThisGroup(); + assertEq(election.getTotalVotesForGroup(group), value * 2); + } + + function test_ShouldIncrementTotalVotes_WhenTheVoterHasAlreadyVotedForThisGroup() public { + WhenTheVoterHasAlreadyVotedForThisGroup(); + assertEq(election.getTotalVotes(), value * 2); + } + + function test_ShouldDecrementAccountNonVotingBalance_WhenTheVoterHasAlreadyVotedForThisGroup() + public + { + WhenTheVoterHasAlreadyVotedForThisGroup(); + assertEq(lockedGold.nonvotingAccountBalance(voter), 0); + } + + function test_ShouldEmitValidatorGroupVoteCast_WhenTheVoterHasAlreadyVotedForThisGroup() public { + WhenTheVoterHasNotAlreadyVotedForThisGroup(); + lockedGold.incrementNonvotingAccountBalance(voter, value); + vm.expectEmit(true, true, false, false); + emit ValidatorGroupVoteCast(voter, group, value); + election.vote(group, value, address(0), address(0)); + } + +} + +contract Election_Vote_GroupNotEligible is ElectionTest { + address voter = address(this); + address group = account1; + uint256 value = 1000; + + uint256 originallyNotVotedWithAmount = 1; + uint256 voterFirstGroupVote = value - maxNumGroupsVotedFor - originallyNotVotedWithAmount; + uint256 rewardValue = 1000000; + + function setUp() public { + super.setUp(); + + address[] memory members = new address[](1); + members[0] = account9; + validators.setMembers(group, members); + } function test_ShouldRevert_WhenTheGroupIsNotEligible() public { vm.expectRevert("Group not eligible"); election.vote(group, value, address(0), address(0)); } - } contract Election_Activate is ElectionTest { @@ -2577,7 +2620,9 @@ contract Election_ConsistencyChecks is ElectionTest { } } - function test_ActualAndExpectedShouldAlwaysMatchWithinSmallDelta_WhenEpochRewardsAreDistributed() public { + function test_ActualAndExpectedShouldAlwaysMatchWithinSmallDelta_WhenEpochRewardsAreDistributed() + public + { for (uint256 i = 0; i < 30; i++) { for (uint256 j = 0; j < _accounts.length; j++) { makeRandomAction(_accounts[j], j);