diff --git a/packages/protocol/contracts-0.8/common/EpochManager.sol b/packages/protocol/contracts-0.8/common/EpochManager.sol index 516fa66c182..c2d68869b28 100644 --- a/packages/protocol/contracts-0.8/common/EpochManager.sol +++ b/packages/protocol/contracts-0.8/common/EpochManager.sol @@ -54,14 +54,14 @@ contract EpochManager is uint256 public epochDuration; uint256 public firstKnownEpoch; - uint256 private currentEpochNumber; + uint256 internal currentEpochNumber; address public oracleAddress; address[] public elected; mapping(address => ProcessedGroup) public processedGroups; EpochProcessState public epochProcessing; - mapping(uint256 => Epoch) private epochs; + mapping(uint256 => Epoch) internal epochs; mapping(address => uint256) public validatorPendingPayments; /** @@ -215,7 +215,7 @@ contract EpochManager is address[] calldata groups, address[] calldata lessers, address[] calldata greaters - ) external nonReentrant { + ) external virtual nonReentrant { require(isOnEpochProcess(), "Epoch process is not started"); // finalize epoch // last block should be the block before and timestamp from previous block @@ -327,7 +327,11 @@ contract EpochManager is } /** - * @return The current epoch info. + * @notice Returns the info of the current epoch. + * @return firstEpoch The first block of the epoch. + * @return lastBlock The first block of the epoch. + * @return startTimestamp The starting timestamp of the epoch. + * @return rewardsBlock The reward block of the epoch. */ function getCurrentEpoch() external @@ -335,8 +339,7 @@ contract EpochManager is onlySystemAlreadyInitialized returns (uint256, uint256, uint256, uint256) { - Epoch storage _epoch = epochs[currentEpochNumber]; - return (_epoch.firstBlock, _epoch.lastBlock, _epoch.startTimestamp, _epoch.rewardsBlock); + return getEpochByNumber(currentEpochNumber); } /** @@ -374,7 +377,7 @@ contract EpochManager is } /** - * @return The list of elected validators. + * @return The list of currently elected validators. */ function getElected() external view returns (address[] memory) { return elected; @@ -457,6 +460,21 @@ contract EpochManager is return initialized && isSystemInitialized; } + /** + * @notice Returns the epoch info of a specified epoch. + * @param epochNumber Epoch number where epoch info is retreived. + * @return firstEpoch The first block of the epoch. + * @return lastBlock The first block of the epoch. + * @return startTimestamp The starting timestamp of the epoch. + * @return rewardsBlock The reward block of the epoch. + */ + function getEpochByNumber( + uint256 epochNumber + ) public view onlySystemAlreadyInitialized returns (uint256, uint256, uint256, uint256) { + Epoch storage _epoch = epochs[epochNumber]; + return (_epoch.firstBlock, _epoch.lastBlock, _epoch.startTimestamp, _epoch.rewardsBlock); + } + /** * @notice Allocates rewards to elected validator accounts. */ diff --git a/packages/protocol/contracts-0.8/common/mocks/EpochManager_WithMocks.sol b/packages/protocol/contracts-0.8/common/mocks/EpochManager_WithMocks.sol index 9ddda4aba92..56ae9282da1 100644 --- a/packages/protocol/contracts-0.8/common/mocks/EpochManager_WithMocks.sol +++ b/packages/protocol/contracts-0.8/common/mocks/EpochManager_WithMocks.sol @@ -7,4 +7,22 @@ contract EpochManager_WithMocks is EpochManager(true) { function _setPaymentAllocation(address validator, uint256 amount) external { validatorPendingPayments[validator] = amount; } + + // mocks finishNextEpochProcess to increment the epoch number. + function finishNextEpochProcess( + address[] calldata groups, + address[] calldata lessers, + address[] calldata greaters + ) external override nonReentrant { + require(isOnEpochProcess(), "Epoch process is not started"); + + epochs[currentEpochNumber].lastBlock = block.number - 1; + + currentEpochNumber++; + epochs[currentEpochNumber].firstBlock = block.number; + epochs[currentEpochNumber].startTimestamp = block.timestamp; + + EpochProcessState storage _epochProcessing = epochProcessing; + _epochProcessing.status = EpochProcessStatus.NotStarted; + } } diff --git a/packages/protocol/test-sol/constants.sol b/packages/protocol/test-sol/constants.sol index b3570fa3f2b..e6f6a0e15d3 100644 --- a/packages/protocol/test-sol/constants.sol +++ b/packages/protocol/test-sol/constants.sol @@ -10,6 +10,7 @@ contract TestConstants { uint256 public constant MONTH = 30 * DAY; uint256 constant WEEK = 7 * DAY; uint256 public constant YEAR = 365 * DAY; + uint256 public constant L2_BLOCK_IN_EPOCH = 43200; // Contract names string constant ElectionContract = "Election"; diff --git a/packages/protocol/test-sol/devchain/migration/Migration.t.sol b/packages/protocol/test-sol/devchain/migration/Migration.t.sol index d1d427cc539..14dafb5bd09 100644 --- a/packages/protocol/test-sol/devchain/migration/Migration.t.sol +++ b/packages/protocol/test-sol/devchain/migration/Migration.t.sol @@ -239,7 +239,7 @@ contract EpochManagerIntegrationTest is IntegrationTest, MigrationsConstants { function test_SetsCurrentRewardBlock() public { _MockL2Migration(validatorsList); - blockTravel(vm, 43200); + blockTravel(vm, L2_BLOCK_IN_EPOCH); timeTravel(vm, DAY); epochManager.startNextEpochProcess(); diff --git a/packages/protocol/test-sol/unit/common/EpochManager.t.sol b/packages/protocol/test-sol/unit/common/EpochManager.t.sol index 638af589794..87bbb2c5b32 100644 --- a/packages/protocol/test-sol/unit/common/EpochManager.t.sol +++ b/packages/protocol/test-sol/unit/common/EpochManager.t.sol @@ -124,8 +124,7 @@ contract EpochManagerTest is Test, TestConstants, Utils08 { vm.prank(epochManagerEnabler); epochManager.initializeSystem(firstEpochNumber, firstEpochBlock, firstElected); - blockTravel(vm, 43200); - timeTravel(vm, DAY); + travelEpochL2(vm); } } @@ -469,3 +468,93 @@ contract EpochManagerTest_sendValidatorPayment is EpochManagerTest { assertEq(epochManagerBalanceAfter, epochManagerBalanceBefore - paymentAmount); } } + +contract EpochManagerTest_getEpochByNumber is EpochManagerTest { + function _travelAndProcess_N_L2Epoch(uint256 n) public { + for (uint256 i = 0; i < n; i++) { + travelEpochL2(vm); + epochManager.startNextEpochProcess(); + + address[] memory groups = new address[](0); + address[] memory lessers = new address[](0); + address[] memory greaters = new address[](0); + epochManager.finishNextEpochProcess(groups, lessers, greaters); + } + } + + function test_shouldReturnTheEpochInfoOfSpecifiedEpoch() public { + uint256 numberOfEpochsToTravel = 9; + + initializeEpochManagerSystem(); + uint256 _startingEpochNumber = epochManager.getCurrentEpochNumber(); + + ( + uint256 startingEpochFirstBlock, + uint256 startingEpochLastBlock, + uint256 startingEpochStartTimestamp, + uint256 startingEpochRewardBlock + ) = epochManager.getCurrentEpoch(); + + _travelAndProcess_N_L2Epoch(numberOfEpochsToTravel); + + ( + uint256 _firstBlock, + uint256 _lastBlock, + uint256 _startTimestamp, + uint256 _rewardBlock + ) = epochManager.getEpochByNumber(_startingEpochNumber + numberOfEpochsToTravel); + + assertEq( + startingEpochFirstBlock + (L2_BLOCK_IN_EPOCH * (numberOfEpochsToTravel + 1)) + 1, + _firstBlock + ); + assertEq(_lastBlock, 0); + assertEq(startingEpochStartTimestamp + (DAY * (numberOfEpochsToTravel + 1)), _startTimestamp); + assertEq(_rewardBlock, 0); + } + + function test_ReturnsHistoricalEpochInfoAfter_N_Epochs() public { + initializeEpochManagerSystem(); + uint256 _startingEpochNumber = epochManager.getCurrentEpochNumber(); + uint256 numberOfEpochsToTravel = 7; + ( + uint256 _startingEpochFirstBlock, + uint256 _startingLastBlock, + uint256 _startingStartTimestamp, + uint256 _startingRewardBlock + ) = epochManager.getCurrentEpoch(); + + _travelAndProcess_N_L2Epoch(numberOfEpochsToTravel); + + ( + uint256 _initialFirstBlock, + uint256 _initialLastBlock, + uint256 _initialStartTimestamp, + uint256 _initialRewardBlock + ) = epochManager.getEpochByNumber(_startingEpochNumber); + + assertEq(_initialFirstBlock, _startingEpochFirstBlock); + assertEq(_initialLastBlock, _startingLastBlock + (L2_BLOCK_IN_EPOCH * 2) + firstEpochBlock); + assertEq(_initialStartTimestamp, _startingStartTimestamp); + assertEq( + _initialRewardBlock, + _startingRewardBlock + (L2_BLOCK_IN_EPOCH * 2) + firstEpochBlock + 1 + ); + } + + function test_ReturnsZeroForFutureEpochs() public { + initializeEpochManagerSystem(); + + ( + uint256 _firstBlock, + uint256 _lastBlock, + uint256 _startTimestamp, + uint256 _rewardBlock + ) = epochManager.getEpochByNumber(500); + + assertEq(_firstBlock, 0); + assertEq(_lastBlock, 0); + assertEq(_startTimestamp, 0); + assertEq(_rewardBlock, 0); + } +} diff --git a/packages/protocol/test-sol/utils08.sol b/packages/protocol/test-sol/utils08.sol index bb069cea58b..c3afd4ed64d 100644 --- a/packages/protocol/test-sol/utils08.sol +++ b/packages/protocol/test-sol/utils08.sol @@ -1,8 +1,9 @@ pragma solidity >=0.5.13 <0.9.0; import "celo-foundry-8/Test.sol"; +import { TestConstants } from "@test-sol/constants.sol"; -contract Utils08 { +contract Utils08 is TestConstants { uint256 public constant secondsInOneBlock = 5; function timeTravel(Vm vm, uint256 timeDelta) public { @@ -20,12 +21,20 @@ contract Utils08 { timeTravel(vm, timeDelta); } - // This function can be also found in OpenZeppelin's library, but in a newer version than the one - function compareStrings(string memory a, string memory b) public pure returns (bool) { - return (keccak256(abi.encodePacked((a))) == keccak256(abi.encodePacked((b)))); + // XXX: this function only increases the block number and timestamp, but does not actually change epoch. + // XXX: you must start and finish epoch processing to change epochs. + function travelEpochL2(Vm vm) public { + uint256 blocksInEpoch = L2_BLOCK_IN_EPOCH; + blockTravel(vm, blocksInEpoch); + timeTravel(vm, DAY); } function whenL2(Vm vm) public { vm.etch(0x4200000000000000000000000000000000000018, abi.encodePacked(bytes1(0x01))); } + + // This function can be also found in OpenZeppelin's library, but in a newer version than the one + function compareStrings(string memory a, string memory b) public pure returns (bool) { + return (keccak256(abi.encodePacked((a))) == keccak256(abi.encodePacked((b)))); + } }