From 26ddf3c014127734a9d72898280e0b35b97bf882 Mon Sep 17 00:00:00 2001 From: soloseng <102702451+soloseng@users.noreply.github.com> Date: Tue, 18 Jun 2024 10:27:42 +0200 Subject: [PATCH] L2 Staking and Voting (#11034) * test: proof of concept 2e2 test using anvil devchain (#11020) * chore(test-sol/FeeCurrencyDirectory)): use `@celo-...` remapping not `../..` * test(test-sol/FeeCurrencyDirectory)): MVP e2e test MVP demo of an e2e test using the devchain. This is not the pattern I'll suggest, but good to see the test passes end-to-end. * chore(foundry.toml): adds `@test-sol` remapping * chore(test-sol/e2e): adds MVP `utils.sol` Idea is to have a Devchain class that can be inherited by Test contracts to have access to the deployed contracts on the devchain. * test(FeeCurrencyDirectory): MVP test using `Devchain` class * chore(migrations_sol): adds MVP script to run e2e tests * style(test-sol/e2e): linting * refactor(test-sol/FeeCurrencyDirectory): moves file to `.../e2e/` * chore(migrations_sol): use `test-sol/e2e/*` path * chore(test-sol/FeeCurrencyDirectory): match contract name in unit and e2e test * style(test-sol/FeeCurrencyDirectory): linting * chore(e2e/utils): removes unused imports and more Cleans up Adds `TODO` comments * test(test-sol/e2e): renames contract with "E2E..." I'm temporarily adding the "E2E..." prefix to the contract so I can exclude this test and the integration tests during the CI run. In a separate PR, I'll refactor the tests into a directory structure like: ``` test-sol/unit/ test-sol/e2e/ test-sol/integration/ ``` That way we could run tests with something like this: ``` # unit tests forge test --match-path "*test-sol/unit/*" # e2e tests forge test --match-path "*test-sol/e2e/*" # integration tests forge test --match-path "*test-sol/integration/*" ``` * chore(workflows/protocol_tests): excludes e2e test from workflow I'm temporarily adding the "E2E..." prefix to the contract so I can exclude this test and the integration tests during the CI run. In a separate PR, I'll refactor the tests into a directory structure like: ``` test-sol/unit/ test-sol/e2e/ test-sol/integration/ ``` That way we could run tests with something like this: ``` # unit tests forge test --match-path "*test-sol/unit/*" # e2e tests forge test --match-path "*test-sol/e2e/*" # integration tests forge test --match-path "*test-sol/integration/*" ``` * style(test-sol/e2e): linting * chore(e2e): temporarily renames contract with "E2E..." In subsequent PRs, I'll rename this more accurately. * set EpochSize on L2 * allow voting and activating * move election test contract to vote dir * updated test to allow testing of L2 with no reward distribution * feat: add ReserveSpenderMultiSig to anvil migrations (#11028) * feat(migrations_sol/migrationsConfig): adds `reserveSpenderMultiSig` configs * feat(migrations_sol): adds `migrateReserveSpenderMultiSig` Also adds contract calls in `migrateReserve` function. Not tested, and not sure this works yet. I might be missing some changes. * chore(test-sol/utils): adds `ReserveSpenderMultiSig.t.sol` From the code comment: The purpose of this file is not to provide test coverage for `ReserveSpenderMultiSig.sol`. This is an empty test to force foundry to compile `ReserveSpenderMultiSig.sol`, and include its artifacts in the `/out` directory. `ReserveSpenderMultiSig.sol` is needed in the migrations script, but because it's on Solidity 0.5 it can't be imported there directly. If there is a better way to force foundry to compile `ReserveSpenderMultiSig.sol` without this file, this file can confidently be deleted. * feat(migrations_sol/HelperInterFaces): adds `IReserveSpenderMultiSig` The helper interface is used as a workaround to allow the migrations script (`Migrations.s.sol`) to be on Solidity 0.8, while the ReserveSpenderMultiSig contract is on Solidity 0.5. The migration script only needs to initialize the contract, which is why the helper interface only defines an `initialize` function. * chore(migrations_sol): initialize `ReserveSpenderMultiSig` * chore(migrations_sol): adds code comment for readability * chore(migrations_sol): improves code comment, moves code block up * chore(migrations_sol): fix typo * style(migrations_sol): linting * test: refactor foundry test directory into unit, e2e, and integration tests (#11023) * chore(foundry.toml): adds `@mento-core/...` remapping Also moves existing mappings into groups for better readability * refactor(test-sol/common): moves files to `unit/common/` Also updates imports respectively using remappings. * refactor(test-sol/identity): moves files to `unit/identity/` Also updates imports using remappings where necessary. * refactor(test-sol/stability): moves files to `unit/stability/` Also updates imports using remappings where necessary. * refactor(test-sol/voting): moves files to `unit/voting/` Also updates imports using remappings where necessary. * refactor(test-sol/governance): moves files to `unit/governance/` Also updates imports using remappings where necessary. * chore(test-sol): update missing remappings With these changes all contracts compile and resolve correctly. * chore(workflows/protocol_tests): updates paths to `test-sol/unit` * chore(workflows/protocol_tests): adds integration and e2e tests * style(workflows/protocol_tests): refactors `forge test` for better readability * chore(migrations_sol): moves scripts to `scripts/foundry/ Moves all bash scripts from `migrations_sol` to `scripts/foundry/`, and updates paths where necessary. We already have a directory for `scripts/truffle/` and `scripts/bash/`, so this makes it easier to find foundry-related bash scripts. * test(governance/mock): move `MockGovernance` to `unit/` folder * refactor(foundry.toml): rename `migrations-sol/` remapping * refactor(workflows): refactor "run everything" step Runs all tests in the `unit/` directory instead of all test files in the repo. This makes sense from my perspective, because e2e tests (in the `e2e/` directory) and integration tests (in the `integration/` directory) require a connection to an anvil devchain serving at localhost. The intent of this command is to ensure that no unit tests are forgotten, since unit tests are run explicitly by going through the directories above. But, the intention is not to run all tests in the repo generally. * style(workflows/protocol-devchain-anvil): splits migration into 2 steps This helps the script becoming more readable, and ensure error logs are clearly associated with a workflow step rather than a single bash script. * chore(workflows): defines `ANVIL_PORT` env variable in workflow Previously, the `$ANVIL_PORT` variable was exported and passed around as an env variable in a bash script. But that required generating anvil migrations and running a devchain repeatedly. Instead, the workflow does that once and different steps can access the devchain. But, in that case the env variable is not passed around, so it has to be defined at the workflow level. Source: https://docs.github.com/en/actions/learn-github-actions/variables * chore(workflows/protocol_tests): removes code comment * feat(scripts/foundry): adds `stop_anvil.sh` * refactor(scripts/foundry): use `stop_anvil.sh` to stop anvil * feat(protocol/package.json): adds `anvil-devchain:...` (`start` and `stop`) * refactor(scripts/foundry): use `stop_anvil.sh` to stop anvil * refactor(migrations_sol): update `@migrations-sol` remapping Previous the remapping was called `@celo-migrations`. * docs(migrations_sol/README): renames README file Also changes title to match NPM package name. This is a matter of personal preference. IMO this is a small improvement in readability for 3rd party users. * docs(scripts/foundry): adds more links to `package.json` Adds `repository` and `directory` to `package.json`. This ensures npmjs.org displays hyperlinks to the github repository. * docs(migrations_sol/CONTRIBUTING): adds MVP dev docs We can complete this doc with additional information regarding the anvil devchain and migrations going forward. * docs(migrations_sol/README): adds "how we work" section This is helpful for 3rd party developers that found this package on npmjs.org. The links help developers take action and help improve the anvil devchain. * lint: function order * Soloseng/CHORE-update-workflow (#11029) * update workflow to run on push to release branches * ++ mintgoldschedule to migration test constants * initial validators contract review * Remove block gas limit flag for `governance/voting` CI tests * bump version * ++ tests && format * removed comments * getValidatorGroupSlashingMultiplier allowed only on L1 * reverting changes to test contract size issue * minimal changes * allow slashing multiplier reset on L2 --------- Co-authored-by: Arthur Gousset <46296830+arthurgousset@users.noreply.github.com> --- .github/workflows/protocol_tests.yml | 3 +- .../contracts/common/UsingPrecompiles.sol | 18 +- .../governance/BlockchainParameters.sol | 4 +- .../contracts/governance/Election.sol | 34 +- .../contracts/governance/EpochRewards.sol | 4 +- .../contracts/governance/Governance.sol | 2 +- .../contracts/governance/SlasherUtil.sol | 3 +- .../contracts/governance/Validators.sol | 10 +- .../protocol/contracts/identity/Random.sol | 4 +- .../governance/validators/Validators.t.sol | 265 +++-- .../{ => governance}/voting/Election.t.sol | 1038 +++++++++++++---- 11 files changed, 1046 insertions(+), 339 deletions(-) rename packages/protocol/test-sol/unit/{ => governance}/voting/Election.t.sol (79%) diff --git a/.github/workflows/protocol_tests.yml b/.github/workflows/protocol_tests.yml index 874fa50f66b..dd683c81f2e 100644 --- a/.github/workflows/protocol_tests.yml +++ b/.github/workflows/protocol_tests.yml @@ -88,8 +88,7 @@ jobs: if: success() || failure() run: | forge test -vvv \ - --match-path "test-sol/unit/governance/voting/*" \ - --block-gas-limit 50000000 + --match-path "test-sol/unit/governance/voting/*" - name: Run unit tests stability if: success() || failure() diff --git a/packages/protocol/contracts/common/UsingPrecompiles.sol b/packages/protocol/contracts/common/UsingPrecompiles.sol index a09eea3410b..017ad69bd33 100644 --- a/packages/protocol/contracts/common/UsingPrecompiles.sol +++ b/packages/protocol/contracts/common/UsingPrecompiles.sol @@ -2,8 +2,9 @@ pragma solidity ^0.5.13; import "openzeppelin-solidity/contracts/math/SafeMath.sol"; import "../common/interfaces/ICeloVersionedContract.sol"; +import "../../contracts-0.8/common/IsL2Check.sol"; -contract UsingPrecompiles { +contract UsingPrecompiles is IsL2Check { using SafeMath for uint256; address constant TRANSFER = address(0xff - 2); @@ -16,6 +17,7 @@ contract UsingPrecompiles { address constant HASH_HEADER = address(0xff - 9); address constant GET_PARENT_SEAL_BITMAP = address(0xff - 10); address constant GET_VERIFIED_SEAL_BITMAP = address(0xff - 11); + uint256 constant DAY = 86400; /** * @notice calculate a * b^x for fractions a, b to `decimals` precision @@ -55,11 +57,15 @@ contract UsingPrecompiles { * @return The current epoch size in blocks. */ function getEpochSize() public view returns (uint256) { - bytes memory out; - bool success; - (success, out) = EPOCH_SIZE.staticcall(abi.encodePacked(true)); - require(success, "error calling getEpochSize precompile"); - return getUint256FromBytes(out, 0); + if (isL2()) { + return DAY.div(5); + } else { + bytes memory out; + bool success; + (success, out) = EPOCH_SIZE.staticcall(abi.encodePacked(true)); + require(success, "error calling getEpochSize precompile"); + return getUint256FromBytes(out, 0); + } } /** diff --git a/packages/protocol/contracts/governance/BlockchainParameters.sol b/packages/protocol/contracts/governance/BlockchainParameters.sol index 8d358602344..7162d7e5ab4 100644 --- a/packages/protocol/contracts/governance/BlockchainParameters.sol +++ b/packages/protocol/contracts/governance/BlockchainParameters.sol @@ -5,12 +5,10 @@ import "openzeppelin-solidity/contracts/ownership/Ownable.sol"; import "../common/Initializable.sol"; import "../common/UsingPrecompiles.sol"; -import "../../contracts-0.8/common/IsL2Check.sol"; - /** * @title Contract for storing blockchain parameters that can be set by governance. */ -contract BlockchainParameters is Ownable, Initializable, UsingPrecompiles, IsL2Check { +contract BlockchainParameters is Ownable, Initializable, UsingPrecompiles { using SafeMath for uint256; // obsolete diff --git a/packages/protocol/contracts/governance/Election.sol b/packages/protocol/contracts/governance/Election.sol index 33c05c82ca6..8a7633599cd 100644 --- a/packages/protocol/contracts/governance/Election.sol +++ b/packages/protocol/contracts/governance/Election.sol @@ -15,7 +15,6 @@ import "../common/UsingRegistry.sol"; import "../common/interfaces/ICeloVersionedContract.sol"; import "../common/libraries/Heap.sol"; import "../common/libraries/ReentrancyGuard.sol"; -import "../../contracts-0.8/common/IsL2Check.sol"; contract Election is IElection, @@ -25,8 +24,7 @@ contract Election is Initializable, UsingRegistry, UsingPrecompiles, - CalledByVm, - IsL2Check + CalledByVm { using AddressSortedLinkedList for SortedLinkedList.List; using FixidityLib for FixidityLib.Fraction; @@ -198,7 +196,7 @@ contract Election is uint256 value, address lesser, address greater - ) external nonReentrant onlyL1 returns (bool) { + ) external nonReentrant returns (bool) { require(votes.total.eligible.contains(group), "Group not eligible"); require(0 < value, "Vote value cannot be zero"); require(canReceiveVotes(group, value), "Group cannot receive votes"); @@ -231,7 +229,7 @@ contract Election is * @return True upon success. * @dev Pending votes cannot be activated until an election has been held. */ - function activate(address group) external nonReentrant onlyL1 returns (bool) { + function activate(address group) external nonReentrant returns (bool) { address account = getAccounts().voteSignerToAccount(msg.sender); return _activate(group, account); } @@ -243,10 +241,7 @@ contract Election is * @return True upon success. * @dev Pending votes cannot be activated until an election has been held. */ - function activateForAccount( - address group, - address account - ) external nonReentrant onlyL1 returns (bool) { + function activateForAccount(address group, address account) external nonReentrant returns (bool) { return _activate(group, account); } @@ -369,7 +364,7 @@ contract Election is address group, address lesser, address greater - ) external onlyL1 onlyRegisteredContract(VALIDATORS_REGISTRY_ID) { + ) external onlyRegisteredContract(VALIDATORS_REGISTRY_ID) { uint256 value = getTotalVotesForGroup(group); votes.total.eligible.insert(group, value, lesser, greater); emit ValidatorGroupMarkedEligible(group); @@ -544,7 +539,7 @@ contract Election is address group, uint256 totalEpochRewards, uint256[] calldata uptimes - ) external view returns (uint256) { + ) external view onlyL1 returns (uint256) { IValidators validators = getValidators(); // The group must meet the balance requirements for their voters to receive epoch rewards. if (!validators.meetsAccountLockedGoldRequirements(group) || votes.active.total <= 0) { @@ -577,10 +572,7 @@ contract Election is * @return Whether or not `account` has activatable votes for `group`. * @dev Pending votes cannot be activated until an election has been held. */ - function hasActivatablePendingVotes( - address account, - address group - ) external view onlyL1 returns (bool) { + function hasActivatablePendingVotes(address account, address group) external view returns (bool) { PendingVote storage pendingVote = votes.pending.forGroup[group].byAccount[account]; return pendingVote.epoch < getEpochNumber() && pendingVote.value > 0; } @@ -619,7 +611,7 @@ contract Election is * @param max The maximum number of validators that can be elected. * @return True upon success. */ - function setElectableValidators(uint256 min, uint256 max) public onlyOwner onlyL1 returns (bool) { + function setElectableValidators(uint256 min, uint256 max) public onlyOwner returns (bool) { require(0 < min, "Minimum electable validators cannot be zero"); require(min <= max, "Maximum electable validators cannot be smaller than minimum"); require( @@ -636,9 +628,7 @@ contract Election is * @param _maxNumGroupsVotedFor The maximum number of groups an account can vote for. * @return True upon success. */ - function setMaxNumGroupsVotedFor( - uint256 _maxNumGroupsVotedFor - ) public onlyOwner onlyL1 returns (bool) { + function setMaxNumGroupsVotedFor(uint256 _maxNumGroupsVotedFor) public onlyOwner returns (bool) { require(_maxNumGroupsVotedFor != maxNumGroupsVotedFor, "Max groups voted for not changed"); maxNumGroupsVotedFor = _maxNumGroupsVotedFor; emit MaxNumGroupsVotedForSet(_maxNumGroupsVotedFor); @@ -650,7 +640,7 @@ contract Election is * @param threshold Electability threshold as unwrapped Fraction. * @return True upon success. */ - function setElectabilityThreshold(uint256 threshold) public onlyOwner onlyL1 returns (bool) { + function setElectabilityThreshold(uint256 threshold) public onlyOwner returns (bool) { electabilityThreshold = FixidityLib.wrap(threshold); require( electabilityThreshold.lt(FixidityLib.fixed1()), @@ -681,7 +671,7 @@ contract Election is * If not run, voting power of account will not reflect rewards awarded. * @param flag The on/off flag. */ - function setAllowedToVoteOverMaxNumberOfGroups(bool flag) public onlyL1 { + function setAllowedToVoteOverMaxNumberOfGroups(bool flag) public { address account = getAccounts().voteSignerToAccount(msg.sender); IValidators validators = getValidators(); require( @@ -822,7 +812,7 @@ contract Election is * @notice Returns get current validator signers using the precompiles. * @return List of current validator signers. */ - function getCurrentValidatorSigners() public view returns (address[] memory) { + function getCurrentValidatorSigners() public view onlyL1 returns (address[] memory) { uint256 n = numberValidatorsInCurrentSet(); address[] memory res = new address[](n); for (uint256 i = 0; i < n; i = i.add(1)) { diff --git a/packages/protocol/contracts/governance/EpochRewards.sol b/packages/protocol/contracts/governance/EpochRewards.sol index fc9135e3f39..b7e398d8ffa 100644 --- a/packages/protocol/contracts/governance/EpochRewards.sol +++ b/packages/protocol/contracts/governance/EpochRewards.sol @@ -10,7 +10,6 @@ import "../common/Initializable.sol"; import "../common/UsingRegistry.sol"; import "../common/UsingPrecompiles.sol"; import "../common/interfaces/ICeloVersionedContract.sol"; -import "../../contracts-0.8/common/IsL2Check.sol"; /** * @title Contract for calculating epoch rewards. @@ -22,8 +21,7 @@ contract EpochRewards is UsingPrecompiles, UsingRegistry, Freezable, - CalledByVm, - IsL2Check + CalledByVm { using FixidityLib for FixidityLib.Fraction; using SafeMath for uint256; diff --git a/packages/protocol/contracts/governance/Governance.sol b/packages/protocol/contracts/governance/Governance.sol index 7d3a50e81e8..e4860e60a7a 100644 --- a/packages/protocol/contracts/governance/Governance.sol +++ b/packages/protocol/contracts/governance/Governance.sol @@ -941,7 +941,7 @@ contract Governance is * @return Patch version of the contract. */ function getVersionNumber() external pure returns (uint256, uint256, uint256, uint256) { - return (1, 4, 1, 1); + return (1, 4, 2, 0); } /** diff --git a/packages/protocol/contracts/governance/SlasherUtil.sol b/packages/protocol/contracts/governance/SlasherUtil.sol index 066bed0dc6a..9884d15e082 100644 --- a/packages/protocol/contracts/governance/SlasherUtil.sol +++ b/packages/protocol/contracts/governance/SlasherUtil.sol @@ -7,9 +7,8 @@ import "../common/Initializable.sol"; import "../common/UsingRegistry.sol"; import "../common/UsingPrecompiles.sol"; import "../common/interfaces/ICeloVersionedContract.sol"; -import "../../contracts-0.8/common/IsL2Check.sol"; -contract SlasherUtil is Ownable, Initializable, UsingRegistry, UsingPrecompiles, IsL2Check { +contract SlasherUtil is Ownable, Initializable, UsingRegistry, UsingPrecompiles { using SafeMath for uint256; struct SlashingIncentives { diff --git a/packages/protocol/contracts/governance/Validators.sol b/packages/protocol/contracts/governance/Validators.sol index 4bce9ac487e..2f5ce7b2af5 100644 --- a/packages/protocol/contracts/governance/Validators.sol +++ b/packages/protocol/contracts/governance/Validators.sol @@ -15,7 +15,6 @@ import "../common/UsingRegistry.sol"; import "../common/UsingPrecompiles.sol"; import "../common/interfaces/ICeloVersionedContract.sol"; import "../common/libraries/ReentrancyGuard.sol"; -import "../../contracts-0.8/common/IsL2Check.sol"; /** * @title A contract for registering and electing Validator Groups and Validators. @@ -28,8 +27,7 @@ contract Validators is Initializable, UsingRegistry, UsingPrecompiles, - CalledByVm, - IsL2Check + CalledByVm { using FixidityLib for FixidityLib.Fraction; using AddressLinkedList for LinkedList.List; @@ -531,7 +529,6 @@ contract Validators is address lesserMember, address greaterMember ) external nonReentrant returns (bool) { - allowOnlyL1(); address account = getAccounts().validatorSignerToAccount(msg.sender); require(isValidatorGroup(account), "Not a group"); require(isValidator(validator), "Not a validator"); @@ -560,6 +557,7 @@ contract Validators is group.nextCommissionBlock = block.number.add(commissionUpdateDelay); emit ValidatorGroupCommissionUpdateQueued(account, commission, group.nextCommissionBlock); } + /** * @notice Updates a validator group's commission based on the previously queued update */ @@ -583,7 +581,6 @@ contract Validators is * @param validatorAccount The validator to deaffiliate from their affiliated validator group. */ function forceDeaffiliateIfValidator(address validatorAccount) external nonReentrant onlySlasher { - allowOnlyL1(); if (isValidator(validatorAccount)) { Validator storage validator = validators[validatorAccount]; if (validator.affiliation != address(0)) { @@ -597,7 +594,6 @@ contract Validators is * the last time the group was slashed. */ function resetSlashingMultiplier() external nonReentrant { - allowOnlyL1(); address account = getAccounts().validatorSignerToAccount(msg.sender); require(isValidatorGroup(account), "Not a validator group"); ValidatorGroup storage group = groups[account]; @@ -755,7 +751,6 @@ contract Validators is * @param account The group to fetch slashing multiplier for. */ function getValidatorGroupSlashingMultiplier(address account) external view returns (uint256) { - allowOnlyL1(); require(isValidatorGroup(account), "Not a validator group"); ValidatorGroup storage group = groups[account]; return group.slashInfo.multiplier.unwrap(); @@ -1032,6 +1027,7 @@ contract Validators is * @return Fixidity representation of the epoch score between 0 and 1. */ function calculateEpochScore(uint256 uptime) public view returns (uint256) { + allowOnlyL1(); require(uptime <= FixidityLib.fixed1().unwrap(), "Uptime cannot be larger than one"); uint256 numerator; uint256 denominator; diff --git a/packages/protocol/contracts/identity/Random.sol b/packages/protocol/contracts/identity/Random.sol index 81249f17540..8e67853ed1d 100644 --- a/packages/protocol/contracts/identity/Random.sol +++ b/packages/protocol/contracts/identity/Random.sol @@ -8,7 +8,6 @@ import "../common/CalledByVm.sol"; import "../common/Initializable.sol"; import "../common/UsingPrecompiles.sol"; import "../common/interfaces/ICeloVersionedContract.sol"; -import "../../contracts-0.8/common/IsL2Check.sol"; /** * @title Provides randomness for verifier selection @@ -19,8 +18,7 @@ contract Random is Ownable, Initializable, UsingPrecompiles, - CalledByVm, - IsL2Check + CalledByVm { using SafeMath for uint256; diff --git a/packages/protocol/test-sol/unit/governance/validators/Validators.t.sol b/packages/protocol/test-sol/unit/governance/validators/Validators.t.sol index 737cc83eef1..2978ec2f183 100644 --- a/packages/protocol/test-sol/unit/governance/validators/Validators.t.sol +++ b/packages/protocol/test-sol/unit/governance/validators/Validators.t.sol @@ -27,6 +27,21 @@ contract ValidatorsTest is Test, Constants, Utils, ECDSAHelper { using FixidityLib for FixidityLib.Fraction; using SafeMath for uint256; + struct ValidatorLockedGoldRequirements { + uint256 value; + uint256 duration; + } + + struct GroupLockedGoldRequirements { + uint256 value; + uint256 duration; + } + + struct ValidatorScoreParameters { + uint256 exponent; + FixidityLib.Fraction adjustmentSpeed; + } + address constant proxyAdminAddress = 0x4200000000000000000000000000000000000018; Registry registry; @@ -68,28 +83,6 @@ contract ValidatorsTest is Test, Constants, Utils, ECDSAHelper { FixidityLib.Fraction public commission = FixidityLib.newFixedFraction(1, 100); - event AccountSlashed( - address indexed slashed, - uint256 penalty, - address indexed reporter, - uint256 reward - ); - - struct ValidatorLockedGoldRequirements { - uint256 value; - uint256 duration; - } - - struct GroupLockedGoldRequirements { - uint256 value; - uint256 duration; - } - - struct ValidatorScoreParameters { - uint256 exponent; - FixidityLib.Fraction adjustmentSpeed; - } - ValidatorLockedGoldRequirements public originalValidatorLockedGoldRequirements; GroupLockedGoldRequirements public originalGroupLockedGoldRequirements; ValidatorScoreParameters public originalValidatorScoreParameters; @@ -103,6 +96,12 @@ contract ValidatorsTest is Test, Constants, Utils, ECDSAHelper { ValidatorsMockTunnel.InitParams public initParams; ValidatorsMockTunnel.InitParams2 public initParams2; + event AccountSlashed( + address indexed slashed, + uint256 penalty, + address indexed reporter, + uint256 reward + ); event MaxGroupSizeSet(uint256 size); event CommissionUpdateDelaySet(uint256 delay); event ValidatorScoreParametersSet(uint256 exponent, uint256 adjustmentSpeed); @@ -212,6 +211,38 @@ contract ValidatorsTest is Test, Constants, Utils, ECDSAHelper { accounts.createAccount(); } + function _whenL2() public { + deployCodeTo("Registry.sol", abi.encode(false), proxyAdminAddress); + } + + function _registerValidatorGroupWithMembers(address _group, uint256 _numMembers) public { + _registerValidatorGroupHelper(_group, _numMembers); + + for (uint256 i = 0; i < _numMembers; i++) { + if (i == 0) { + _registerValidatorHelper(validator, validatorPk); + + vm.prank(validator); + validators.affiliate(group); + + vm.prank(group); + validators.addFirstMember(validator, address(0), address(0)); + } else { + uint256 _validator1Pk = i; + address _validator1 = vm.addr(_validator1Pk); + + vm.prank(_validator1); + accounts.createAccount(); + _registerValidatorHelper(_validator1, _validator1Pk); + vm.prank(_validator1); + validators.affiliate(group); + + vm.prank(group); + validators.addMember(_validator1); + } + } + } + function getParsedSignatureOfAddress( address _address, uint256 privateKey @@ -296,34 +327,6 @@ contract ValidatorsTest is Test, Constants, Utils, ECDSAHelper { validators.registerValidatorGroup(commission.unwrap()); } - function _registerValidatorGroupWithMembers(address _group, uint256 _numMembers) public { - _registerValidatorGroupHelper(_group, _numMembers); - - for (uint256 i = 0; i < _numMembers; i++) { - if (i == 0) { - _registerValidatorHelper(validator, validatorPk); - - vm.prank(validator); - validators.affiliate(group); - - vm.prank(group); - validators.addFirstMember(validator, address(0), address(0)); - } else { - uint256 _validator1Pk = i; - address _validator1 = vm.addr(_validator1Pk); - - vm.prank(_validator1); - accounts.createAccount(); - _registerValidatorHelper(_validator1, _validator1Pk); - vm.prank(_validator1); - validators.affiliate(group); - - vm.prank(group); - validators.addMember(_validator1); - } - } - } - function _removeMemberAndTimeTravel( address _group, address _validator, @@ -334,6 +337,14 @@ contract ValidatorsTest is Test, Constants, Utils, ECDSAHelper { timeTravel(_duration); } + function _calculateScore(uint256 _uptime, uint256 _gracePeriod) internal view returns (uint256) { + return + _safeExponent( + _max1(_uptime.add(_gracePeriod)), + FixidityLib.wrap(originalValidatorScoreParameters.exponent) + ); + } + function _max1(uint256 num) internal pure returns (FixidityLib.Fraction memory) { return num > FixidityLib.fixed1().unwrap() ? FixidityLib.fixed1() : FixidityLib.wrap(num); } @@ -354,18 +365,6 @@ contract ValidatorsTest is Test, Constants, Utils, ECDSAHelper { } return result.unwrap(); } - - function _calculateScore(uint256 _uptime, uint256 _gracePeriod) internal view returns (uint256) { - return - _safeExponent( - _max1(_uptime.add(_gracePeriod)), - FixidityLib.wrap(originalValidatorScoreParameters.exponent) - ); - } - - function _whenL2() public { - deployCodeTo("Registry.sol", abi.encode(false), proxyAdminAddress); - } } contract ValidatorsTest_Initialize is ValidatorsTest { @@ -822,11 +821,6 @@ contract ValidatorsTest_DeregisterValidator_WhenAccountHasNeverBeenMemberOfValid timeTravel(originalValidatorLockedGoldRequirements.duration); } - function _deregisterValidator(address _validator) internal { - vm.prank(_validator); - validators.deregisterValidator(INDEX); - } - function test_ShouldMarkAccountAsNotValidator_WhenAccountHasNeverBeenMemberOfValidatorGroup() public { @@ -875,6 +869,10 @@ contract ValidatorsTest_DeregisterValidator_WhenAccountHasNeverBeenMemberOfValid vm.prank(validator); validators.deregisterValidator(INDEX + 1); } + function _deregisterValidator(address _validator) internal { + vm.prank(_validator); + validators.deregisterValidator(INDEX); + } } contract ValidatorsTest_DeregisterValidator_WhenAccountHasBeenMemberOfValidatorGroup is @@ -896,11 +894,6 @@ contract ValidatorsTest_DeregisterValidator_WhenAccountHasBeenMemberOfValidatorG validators.addFirstMember(validator, address(0), address(0)); } - function _deregisterValidator(address _validator) internal { - vm.prank(_validator); - validators.deregisterValidator(INDEX); - } - function test_ShouldMarkAccountAsNotValidator_WhenValidatorNoLongerMemberOfValidatorGroup() public { @@ -970,6 +963,11 @@ contract ValidatorsTest_DeregisterValidator_WhenAccountHasBeenMemberOfValidatorG vm.expectRevert("Has been group member recently"); _deregisterValidator(validator); } + + function _deregisterValidator(address _validator) internal { + vm.prank(_validator); + validators.deregisterValidator(INDEX); + } } contract ValidatorsTest_Affiliate_WhenGroupAndValidatorMeetLockedGoldRequirements is @@ -1828,6 +1826,8 @@ contract ValidatorsTest_AddMember is ValidatorsTest { uint256 _registrationEpoch; uint256 _additionEpoch; + uint256[] expectedSizeHistory; + function setUp() public { super.setUp(); @@ -1935,8 +1935,6 @@ contract ValidatorsTest_AddMember is ValidatorsTest { validators.addMember(otherValidator); } - uint256[] expectedSizeHistory; - function test_ShouldUpdateGroupsSizeHistoryAndBalanceRequirements_WhenAddingManyValidatorsAffiliatedWithGroup() public { @@ -2041,6 +2039,9 @@ contract ValidatorsTest_AddMember is ValidatorsTest { } contract ValidatorsTest_RemoveMember is ValidatorsTest { + uint256 _registrationEpoch; + uint256 _additionEpoch; + function setUp() public { super.setUp(); _registerValidatorGroupWithMembers(group, 1); @@ -2058,9 +2059,6 @@ contract ValidatorsTest_RemoveMember is ValidatorsTest { assertEq(members.length, expectedMembersList.length); } - uint256 _registrationEpoch; - uint256 _additionEpoch; - function test_ShouldUpdateMemberMembershipHistory() public { vm.prank(group); validators.removeMember(validator); @@ -2148,15 +2146,53 @@ contract ValidatorsTest_ReorderMember is ValidatorsTest { assertEq(expectedMembersList.length, members.length); } - function test_Reverts_ReorderGroupMemberList_WhenL2() public { + function test_Emits_ValidatorGroupMemberReorderedEvent() public { + vm.expectEmit(true, true, true, true); + emit ValidatorGroupMemberReordered(group, vm.addr(1)); + + vm.prank(group); + validators.reorderMember(vm.addr(1), validator, address(0)); + } + + function test_Reverts_WhenAccountIsNotRegisteredValidatorGroup() public { + vm.expectRevert("Not a group"); + vm.prank(vm.addr(1)); + validators.reorderMember(vm.addr(1), validator, address(0)); + } + + function test_Reverts_WhenMemberNotRegisteredValidator() public { + vm.expectRevert("Not a validator"); + vm.prank(group); + validators.reorderMember(nonValidator, validator, address(0)); + } + + function test_Reverts_WhenValidatorNotMemberOfValidatorGroup() public { + vm.prank(vm.addr(1)); + validators.deaffiliate(); + + vm.expectRevert("Not a member of the group"); + vm.prank(group); + validators.reorderMember(vm.addr(1), validator, address(0)); + } +} +contract ValidatorsTest_ReorderMember_L2 is ValidatorsTest { + function setUp() public { + super.setUp(); + _registerValidatorGroupWithMembers(group, 2); _whenL2(); + } + + function test_ShouldReorderGroupMemberList() public { address[] memory expectedMembersList = new address[](2); expectedMembersList[0] = vm.addr(1); expectedMembersList[1] = validator; vm.prank(group); - vm.expectRevert("This method is no longer supported in L2."); validators.reorderMember(vm.addr(1), validator, address(0)); + (address[] memory members, , , , , , ) = validators.getValidatorGroup(group); + + assertEq(expectedMembersList, members); + assertEq(expectedMembersList.length, members.length); } function test_Emits_ValidatorGroupMemberReorderedEvent() public { @@ -2410,6 +2446,13 @@ contract ValidatorsTest_CalculateEpochScore is ValidatorsTest { vm.expectRevert("Uptime cannot be larger than one"); validators.calculateEpochScore(uptime.unwrap()); } + + function test_Reverts_WhenL2() public { + _whenL2(); + + vm.expectRevert("This method is no longer supported in L2."); + validators.calculateEpochScore(1); + } } contract ValidatorsTest_CalculateGroupEpochScore is ValidatorsTest { @@ -2555,6 +2598,19 @@ contract ValidatorsTest_CalculateGroupEpochScore is ValidatorsTest { vm.expectRevert("Uptime cannot be larger than one"); validators.calculateGroupEpochScore(unwrapedUptimes); } + + function test_Reverts_WhenL2() public { + _whenL2(); + FixidityLib.Fraction[] memory uptimes = new FixidityLib.Fraction[](5); + uptimes[0] = FixidityLib.newFixedFraction(9, 10); + uptimes[1] = FixidityLib.newFixedFraction(9, 10); + uptimes[3] = FixidityLib.newFixedFraction(9, 10); + uptimes[4] = FixidityLib.newFixedFraction(9, 10); + + (uint256[] memory unwrapedUptimes, ) = _computeGroupUptimeCalculation(uptimes); + vm.expectRevert("This method is no longer supported in L2."); + validators.calculateGroupEpochScore(unwrapedUptimes); + } } contract ValidatorsTest_UpdateValidatorScoreFromSigner is ValidatorsTest { @@ -2638,6 +2694,12 @@ contract ValidatorsTest_UpdateValidatorScoreFromSigner is ValidatorsTest { } contract ValidatorsTest_UpdateMembershipHistory is ValidatorsTest { + address[] public expectedMembershipHistoryGroups; + uint256[] public expectedMembershipHistoryEpochs; + + address[] public actualMembershipHistoryGroups; + uint256[] public actualMembershipHistoryEpochs; + function setUp() public { super.setUp(); _registerValidatorHelper(validator, validatorPk); @@ -2648,12 +2710,6 @@ contract ValidatorsTest_UpdateMembershipHistory is ValidatorsTest { } } - address[] public expectedMembershipHistoryGroups; - uint256[] public expectedMembershipHistoryEpochs; - - address[] public actualMembershipHistoryGroups; - uint256[] public actualMembershipHistoryEpochs; - function test_ShouldOverwritePreviousEntry_WhenChangingGroupsInSameEpoch() public { uint256 numTest = 10; @@ -3159,11 +3215,29 @@ contract ValidatorsTest_ForceDeaffiliateIfValidator is ValidatorsTest { assertEq(affiliation, address(0)); } - function test_Reverts_WhenSenderIsWhitelistedSlashingAddress_WhenL2() public { + function test_Reverts_WhenSenderNotApprovedAddress() public { + vm.expectRevert("Only registered slasher can call"); + validators.forceDeaffiliateIfValidator(validator); + } +} +contract ValidatorsTest_ForceDeaffiliateIfValidator_L2 is ValidatorsTest { + function setUp() public { + super.setUp(); + + _registerValidatorHelper(validator, validatorPk); + _registerValidatorGroupHelper(group, 1); + + vm.prank(validator); + validators.affiliate(group); _whenL2(); + lockedGold.addSlasherTest(paymentDelegatee); + } + + function test_ShouldSucceed_WhenSenderIsWhitelistedSlashingAddress() public { vm.prank(paymentDelegatee); - vm.expectRevert("This method is no longer supported in L2."); validators.forceDeaffiliateIfValidator(validator); + (, , address affiliation, , ) = validators.getValidator(validator); + assertEq(affiliation, address(0)); } function test_Reverts_WhenSenderNotApprovedAddress() public { @@ -3173,17 +3247,17 @@ contract ValidatorsTest_ForceDeaffiliateIfValidator is ValidatorsTest { } contract ValidatorsTest_GroupMembershipInEpoch is ValidatorsTest { + struct EpochInfo { + uint256 epochNumber; + address groupy; + } + uint256 totalEpochs = 24; uint256 gapSize = 3; uint256 contractIndex; EpochInfo[] public epochInfoList; - struct EpochInfo { - uint256 epochNumber; - address groupy; - } - function setUp() public { super.setUp(); @@ -3385,13 +3459,16 @@ contract ValidatorsTest_ResetSlashingMultiplier is ValidatorsTest { assertEq(actualMultiplier, FixidityLib.fixed1().unwrap()); } - function test_Reverts_WhenSlashingMultiplierIsResetAfterResetPeriod_WhenL2() public { + function test_ShouldReturnToDefault_WhenSlashingMultiplierIsResetAfterResetPeriod_WhenL2() + public + { _whenL2(); timeTravel(slashingMultiplierResetPeriod); vm.prank(group); - vm.expectRevert("This method is no longer supported in L2."); validators.resetSlashingMultiplier(); + (, , , , , uint256 actualMultiplier, ) = validators.getValidatorGroup(group); + assertEq(actualMultiplier, FixidityLib.fixed1().unwrap()); } function test_Reverts_WhenSlashingMultiplierIsResetBeforeResetPeriod() public { diff --git a/packages/protocol/test-sol/unit/voting/Election.t.sol b/packages/protocol/test-sol/unit/governance/voting/Election.t.sol similarity index 79% rename from packages/protocol/test-sol/unit/voting/Election.t.sol rename to packages/protocol/test-sol/unit/governance/voting/Election.t.sol index 86fe2a80076..4000d1a1b4d 100644 --- a/packages/protocol/test-sol/unit/voting/Election.t.sol +++ b/packages/protocol/test-sol/unit/governance/voting/Election.t.sol @@ -30,32 +30,6 @@ contract ElectionTest is Utils, Constants { address constant proxyAdminAddress = 0x4200000000000000000000000000000000000018; - 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; ElectionMock election; Freezer freezer; @@ -85,6 +59,32 @@ contract ElectionTest is Utils, Constants { address[] accountsArray; + 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); + function createAccount(address account) public { vm.prank(account); accounts.createAccount(); @@ -197,10 +197,19 @@ contract ElectionTest_SetElectabilityThreshold is ElectionTest { vm.expectRevert("Electability threshold must be lower than 100%"); election.setElectabilityThreshold(FixidityLib.fixed1().unwrap() + 1); } +} - function test_Revert_setElectabilityThreshold_WhenL2() public { +contract ElectionTest_SetElectabilityThreshold_L2 is ElectionTest { + function test_shouldSetElectabilityThreshold() public { _whenL2(); - vm.expectRevert("This method is no longer supported in L2."); + uint256 newElectabilityThreshold = FixidityLib.newFixedFraction(1, 200).unwrap(); + election.setElectabilityThreshold(newElectabilityThreshold); + assertEq(election.electabilityThreshold(), newElectabilityThreshold); + } + + function test_ShouldRevertWhenThresholdLargerThan100Percent() public { + _whenL2(); + vm.expectRevert("Electability threshold must be lower than 100%"); election.setElectabilityThreshold(FixidityLib.fixed1().unwrap() + 1); } } @@ -215,12 +224,48 @@ contract ElectionTest_SetElectableValidators is ElectionTest { assertEq(max, newElectableValidatorsMax); } - function test_Reverts_shouldSetElectableValidators_WhenL2() public { + 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 ElectionTest_SetElectableValidators_L2 is ElectionTest { + function setUp() public { + super.setUp(); _whenL2(); + } + function test_shouldSetElectableValidators() public { uint256 newElectableValidatorsMin = 2; uint256 newElectableValidatorsMax = 4; - vm.expectRevert("This method is no longer supported in L2."); election.setElectableValidators(newElectableValidatorsMin, newElectableValidatorsMax); + (uint256 min, uint256 max) = election.getElectableValidators(); + assertEq(min, newElectableValidatorsMin); + assertEq(max, newElectableValidatorsMax); } function test_ShouldEmitTheElectableValidatorsSetEvent() public { @@ -260,11 +305,35 @@ contract ElectionTest_SetMaxNumGroupsVotedFor is ElectionTest { assertEq(election.maxNumGroupsVotedFor(), newMaxNumGroupsVotedFor); } - function test_Revert_SetMaxNumGroupsVotedFor_WhenL2() public { + 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); + } + + function test_ShouldRevert_WhenMaxNumGroupsVotedForIsUnchanged() public { + vm.expectRevert("Max groups voted for not changed"); + election.setMaxNumGroupsVotedFor(maxNumGroupsVotedFor); + } +} + +contract ElectionTest_SetMaxNumGroupsVotedFor_L2 is ElectionTest { + function setUp() public { + super.setUp(); _whenL2(); + } + + function test_shouldSetMaxNumGroupsVotedFor() public { uint256 newMaxNumGroupsVotedFor = 4; - vm.expectRevert("This method is no longer supported in L2."); election.setMaxNumGroupsVotedFor(newMaxNumGroupsVotedFor); + assertEq(election.maxNumGroupsVotedFor(), newMaxNumGroupsVotedFor); } function test_ShouldEmitMaxNumGroupsVotedForSetEvent() public { @@ -292,10 +361,48 @@ contract ElectionTest_SetAllowedToVoteOverMaxNumberOfGroups is ElectionTest { assertEq(election.allowedToVoteOverMaxNumberOfGroups(address(this)), true); } - function test_Revert_SetAllowedToVoteOverMaxNumberOfGroups_WhenL2() public { + 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 ElectionTest_SetAllowedToVoteOverMaxNumberOfGroups_L2 is ElectionTest { + function setUp() public { + super.setUp(); _whenL2(); - vm.expectRevert("This method is no longer supported in L2."); + } + + function test_shouldSetAllowedToVoteOverMaxNumberOfGroups() public { election.setAllowedToVoteOverMaxNumberOfGroups(true); + assertEq(election.allowedToVoteOverMaxNumberOfGroups(address(this)), true); } function test_ShouldRevertWhenCalledByValidator() public { @@ -346,11 +453,40 @@ contract ElectionTest_MarkGroupEligible is ElectionTest { assertEq(eligibleGroups[0], group); } - function test_Revert_MarkGroupEligible_WhenL2() public { + 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 ElectionTest_MarkGroupEligible_L2 is ElectionTest { + function setUp() public { + super.setUp(); _whenL2(); + registry.setAddressFor("Validators", address(address(this))); + } + + function test_shouldMarkGroupEligible() public { address group = address(this); - vm.expectRevert("This method is no longer supported in L2."); election.markGroupEligible(group, address(0), address(0)); + address[] memory eligibleGroups = election.getEligibleValidatorGroups(); + assertEq(eligibleGroups.length, 1); + assertEq(eligibleGroups[0], group); } function test_ShouldEmitValidatorGroupMarkedEligibleEvent() public { @@ -409,6 +545,7 @@ contract ElectionTest_MarkGroupInEligible is ElectionTest { election.markGroupIneligible(address(this)); } } + contract ElectionTest_Vote_WhenGroupEligible is ElectionTest { address voter = address(this); address group = account1; @@ -466,13 +603,6 @@ contract ElectionTest_Vote_WhenGroupEligible is ElectionTest { assertEq(election.getPendingVotesForGroupByAccount(group, voter), value - maxNumGroupsVotedFor); } - function test_Revert_Vote_WhenL2() public { - _whenL2(); - - vm.expectRevert("This method is no longer supported in L2."); - election.vote(group, value - maxNumGroupsVotedFor, address(0), address(0)); - } - function test_ShouldSetTotalVotesByAccount_WhenMaxNumberOfGroupsWasNotReached() public { WhenVotedForMaxNumberOfGroups(); assertEq(election.getTotalVotesByAccount(voter), maxNumGroupsVotedFor); @@ -605,7 +735,7 @@ contract ElectionTest_Vote_WhenGroupEligible is ElectionTest { } } -contract ElectionTest_Vote_WhenGroupEligible_WhenGroupCanReceiveVotes is ElectionTest { +contract ElectionTest_Vote_WhenGroupEligible_L2 is ElectionTest { address voter = address(this); address group = account1; uint256 value = 1000; @@ -616,7 +746,7 @@ contract ElectionTest_Vote_WhenGroupEligible_WhenGroupCanReceiveVotes is Electio function setUp() public { super.setUp(); - + _whenL2(); address[] memory members = new address[](1); members[0] = account9; validators.setMembers(group, members); @@ -624,45 +754,238 @@ contract ElectionTest_Vote_WhenGroupEligible_WhenGroupCanReceiveVotes is Electio registry.setAddressFor("Validators", address(this)); election.markGroupEligible(group, address(0), address(0)); registry.setAddressFor("Validators", address(validators)); + } - lockedGold.setTotalLockedGold(value); - validators.setNumRegisteredValidators(1); + function test_ShouldRevert_WhenTheVoterDoesNotHaveSufficientNonVotingBalance() public { + lockedGold.incrementNonvotingAccountBalance(voter, value - 1); + vm.expectRevert("SafeMath: subtraction overflow"); + election.vote(group, value, address(0), address(0)); } - function WhenTheVoterCanVoteForAnAdditionalGroup() public { + function WhenVotedForMaxNumberOfGroups() public returns (address newGroup) { lockedGold.incrementNonvotingAccountBalance(voter, value); - } - function WhenTheVoterHasNotAlreadyVotedForThisGroup() public { - WhenTheVoterCanVoteForAnAdditionalGroup(); - election.vote(group, value, address(0), address(0)); + 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_ShouldAddTheGroupToListOfGroupsTheAccountHasVotedFor_WhenTheVoterHasNotAlreadyVotedForThisGroup() - public - { - WhenTheVoterHasNotAlreadyVotedForThisGroup(); - address[] memory groupsVotedFor = election.getGroupsVotedForByAccount(voter); - assertEq(groupsVotedFor.length, 1); - assertEq(groupsVotedFor[0], group); - } + function test_ShouldRevert_WhenTheVoterCannotVoteForAnAdditionalGroup() public { + address newGroup = WhenVotedForMaxNumberOfGroups(); - function test_ShouldIncrementTheAccountsPendingVotesForTheGroup_WhenTheVoterHasNotAlreadyVotedForThisGroup() - public - { - WhenTheVoterHasNotAlreadyVotedForThisGroup(); - assertEq(election.getPendingVotesForGroupByAccount(group, voter), value); + vm.expectRevert("Voted for too many groups"); + election.vote(group, value - maxNumGroupsVotedFor, newGroup, address(0)); } - function test_ShouldIncrementTheAccountsTotalVotesForTheGroup_WhenTheVoterHasNotAlreadyVotedForThisGroup() + function test_ShouldAllowToVoteForAnotherGroup_WhenTheVoterIsOverMaxNumberGroupsVotedForButCanVoteForAdditionalGroup() public { - WhenTheVoterHasNotAlreadyVotedForThisGroup(); - assertEq(election.getTotalVotesForGroupByAccount(group, voter), value); + 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_ShouldIncrementTheACcountsTotalVotes_WhenTheVoterHasNotAlreadyVotedForThisGroup() - public + 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_WhenOverMaximumNumberOfGroupsVoted() + public + { + WhenVotedForMoreThanMaxNumberOfGroups(); + + vm.expectRevert("Too many groups voted for!"); + election.setAllowedToVoteOverMaxNumberOfGroups(false); + } + + function test_ShouldReturnOnlyLastVotedWith_WhenVotesWereNotManuallyCounted() 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(); + blockTravel(ph.epochSize() + 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)); + } + + // TODO: Implement when validator L2 rewards mechanism is implemented. + function skip_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 + ); + } + + // TODO: Implement when validator L2 rewards mechanism is implemented. + function skip_test_ShouldReturnCorrectValueWhenManuallyCounted_WhenMoreVotesThanActiveIsRevoked_WhenAwardsAreDistributed() + public + { + address newGroup = WhenAwardsAreDistributed(); + election.revokeActive(group, value, newGroup, address(0), 3); + manuallyUpdateTotalVotesForAllGroups(voter); + + assertEq(election.getTotalVotesByAccount(voter), rewardValue - originallyNotVotedWithAmount); + } + + // TODO: Implement when validator L2 rewards mechanism is implemented. + function skip_test_ShouldReturnTotalVotesByAccount_WhenTotalVotesAreManuallyCountedOnReward_WhenAwardsAreDistributed() + public + { + WhenAwardsAreDistributed(); + manuallyUpdateTotalVotesForAllGroups(voter); + + assertEq( + election.getTotalVotesByAccount(voter), + value + rewardValue - originallyNotVotedWithAmount + ); + } + + // TODO: Implement when validator L2 rewards mechanism is implemented. + function skip_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 { + 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)); + } +} + +contract ElectionTest_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); @@ -788,6 +1111,9 @@ contract ElectionTest_Activate is ElectionTest { address group = account1; uint256 value = 1000; + address voter2 = account2; + uint256 value2 = 573; + function setUp() public { super.setUp(); @@ -859,16 +1185,162 @@ contract ElectionTest_Activate is ElectionTest { election.activate(group); } - function test_Revert_Activate_WhenL2() public { + function WhenAnotherVoterActivatesVotes() public { + WhenEpochBoundaryHasPassed(); + lockedGold.incrementNonvotingAccountBalance(voter2, value2); + vm.prank(voter2); + election.vote(group, value2, address(0), address(0)); + blockTravel(ph.epochSize() + 1); + 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(); - _whenL2(); - vm.expectRevert("This method is no longer supported in L2."); + 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); } +} + +contract ElectionTest_Activate_L2 is ElectionTest { + address voter = address(this); + address group = account1; + uint256 value = 1000; address voter2 = account2; uint256 value2 = 573; + function setUp() public { + super.setUp(); + _whenL2(); + 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(); + blockTravel(ph.epochSize() + 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(); + blockTravel(ph.epochSize() + 1); + vm.expectEmit(true, true, true, false); + emit ValidatorGroupVoteActivated(voter, group, value, value * 100000000000000000000); + election.activate(group); + } + function WhenAnotherVoterActivatesVotes() public { WhenEpochBoundaryHasPassed(); lockedGold.incrementNonvotingAccountBalance(voter2, value2); @@ -940,26 +1412,178 @@ contract ElectionTest_Activate is ElectionTest { election.activateForAccount(group, voter); } - function test_Revert_ActivateForAccount_WhenL2() public { - _whenL2(); - vm.expectRevert("This method is no longer supported in L2."); + function test_ShouldRevert_WhenTheVoterDoesNotHavePendingVotes() public { + vm.expectRevert("Vote value cannot be zero"); + election.activate(group); + } +} + +contract ElectionTest_ActivateForAccount is ElectionTest { + address voter = address(this); + address group = account1; + uint256 value = 1000; + + address voter2 = account2; + uint256 value2 = 573; + + 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(); + blockTravel(ph.epochSize() + 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(); + blockTravel(ph.epochSize() + 1); + vm.expectEmit(true, true, true, false); + emit ValidatorGroupVoteActivated(voter, group, value, value * 100000000000000000000); + election.activate(group); + } + + function WhenAnotherVoterActivatesVotes() public { + WhenEpochBoundaryHasPassed(); + lockedGold.incrementNonvotingAccountBalance(voter2, value2); + vm.prank(voter2); + election.vote(group, value2, address(0), address(0)); + blockTravel(ph.epochSize() + 1); + 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.activate(group); + election.activateForAccount(group, voter); } } -contract ElectionTest_ActivateForAccount is ElectionTest { +contract ElectionTest_ActivateForAccount_L2 is ElectionTest { address voter = address(this); address group = account1; uint256 value = 1000; + address voter2 = account2; + uint256 value2 = 573; + function setUp() public { super.setUp(); - + _whenL2(); address[] memory members = new address[](1); members[0] = account9; validators.setMembers(group, members); @@ -1028,9 +1652,6 @@ contract ElectionTest_ActivateForAccount is ElectionTest { election.activate(group); } - address voter2 = account2; - uint256 value2 = 573; - function WhenAnotherVoterActivatesVotes() public { WhenEpochBoundaryHasPassed(); lockedGold.incrementNonvotingAccountBalance(voter2, value2); @@ -1538,6 +2159,11 @@ contract ElectionTest_RevokeActive is ElectionTest { } contract ElectionTest_ElectionValidatorSigners is ElectionTest { + struct MemberWithVotes { + address member; + uint256 votes; + } + address group1 = address(this); address group2 = account1; address group3 = account2; @@ -1571,11 +2197,6 @@ contract ElectionTest_ElectionValidatorSigners is ElectionTest { uint256 totalLockedGold = voter1Weight + voter2Weight + voter3Weight; - struct MemberWithVotes { - address member; - uint256 votes; - } - mapping(address => uint256) votesConsideredForElection; MemberWithVotes[] membersWithVotes; @@ -1598,37 +2219,6 @@ contract ElectionTest_ElectionValidatorSigners is ElectionTest { 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); @@ -1791,6 +2381,37 @@ contract ElectionTest_ElectionValidatorSigners is ElectionTest { vm.expectRevert("Not enough elected validators"); election.electValidatorSigners(); } + + // 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; + } } contract ElectionTest_GetGroupEpochRewards is ElectionTest { @@ -1801,6 +2422,12 @@ contract ElectionTest_GetGroupEpochRewards is ElectionTest { uint256 voteValue2 = 1000000000; uint256 totalRewardValue = 3000000000; + uint256 expectedGroup1EpochRewards = + FixidityLib + .newFixedFraction(voteValue1, voteValue1 + voteValue2) + .multiply(FixidityLib.newFixed(totalRewardValue)) + .fromFixed(); + function setUp() public { super.setUp(); @@ -1880,12 +2507,6 @@ contract ElectionTest_GetGroupEpochRewards is ElectionTest { 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 { @@ -1920,6 +2541,13 @@ contract ElectionTest_DistributeEpochRewards is ElectionTest { uint256 rewardValue = 1000000; uint256 rewardValue2 = 10000000; + 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 setUp() public { super.setUp(); @@ -1977,13 +2605,6 @@ contract ElectionTest_DistributeEpochRewards is ElectionTest { 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); @@ -2087,6 +2708,15 @@ contract ElectionTest_ForceDecrementVotes is ElectionTest { uint256 slashedValue = value; uint256 remaining = value - slashedValue; + uint256 totalRemaining; + uint256 group1Remaining; + uint256 group2TotalRemaining; + uint256 group2PendingRemaining; + uint256 group2ActiveRemaining; + + uint256 group1RemainingActiveVotes; + address[] initialOrdering; + function setUp() public { super.setUp(); } @@ -2331,12 +2961,6 @@ contract ElectionTest_ForceDecrementVotes is ElectionTest { assertEq(election.getActiveVotesForGroupByAccount(group, voter), remaining); } - uint256 totalRemaining; - uint256 group1Remaining; - uint256 group2TotalRemaining; - uint256 group2PendingRemaining; - uint256 group2ActiveRemaining; - function WhenWeSlashAllOfGroup1VotesAndSomeOfGroup2__WhenWeSlash1MoreVoteThanGroup1PendingVoteTotal_WhenAccountHasVotedForMoreThanOneGroupInequally() public { @@ -2373,9 +2997,6 @@ contract ElectionTest_ForceDecrementVotes is ElectionTest { assertEq(election.getActiveVotesForGroupByAccount(group2, voter), group2ActiveRemaining); } - uint256 group1RemainingActiveVotes; - address[] initialOrdering; - function WhenSlashAffectsElectionOrder() public { WhenAccountHasVotedForMoreThanOneGroupInequally(); @@ -2491,12 +3112,6 @@ contract ElectionTest_ForceDecrementVotes is ElectionTest { } contract ElectionTest_ConsistencyChecks is ElectionTest { - address voter = address(this); - address group = account2; - uint256 rewardValue2 = 10000000; - - AccountStruct[] _accounts; - struct AccountStruct { address account; uint256 active; @@ -2511,6 +3126,12 @@ contract ElectionTest_ConsistencyChecks is ElectionTest { RevokeActive } + address voter = address(this); + address group = account2; + uint256 rewardValue2 = 10000000; + + AccountStruct[] _accounts; + function setUp() public { super.setUp(); @@ -2538,52 +3159,6 @@ contract ElectionTest_ConsistencyChecks is ElectionTest { } } - 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(AccountStruct memory account, uint256 delta) public { assertAlmostEqual( election.getPendingVotesForGroupByAccount(group, account.account), @@ -2708,13 +3283,84 @@ contract ElectionTest_ConsistencyChecks is ElectionTest { } revokeAllAndCheckInvariants(100); } + + 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(); + } } contract ElectionTest_HasActivatablePendingVotes is ElectionTest { - function test_Revert_hasActivatablePendingVotes_WhenL2() public { + 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); + election.vote(group, value, address(0), address(0)); + blockTravel(ph.epochSize() + 1); + } + function test_ReturnsTrue_WhenUserHasVoted() public { + assertTrue(election.hasActivatablePendingVotes(voter, group)); + } + + function test_ReturnsTrue_WhenUserHasVotedOnL2() public { _whenL2(); - vm.expectRevert("This method is no longer supported in L2."); - vm.prank(address(0)); - election.hasActivatablePendingVotes(address(0), address(0)); + + assertTrue(election.hasActivatablePendingVotes(voter, group)); } }