Skip to content

Commit

Permalink
Epoch manager enabler tests (#11213)
Browse files Browse the repository at this point in the history
* ++ basic test

* -- celoToken balance check

* cleanup comments

* use celoToken instead of native token for `initializeSystem` balance check

* ++ more test

* removed additional epochs
  • Loading branch information
soloseng committed Sep 20, 2024
1 parent c85d594 commit 032f543
Show file tree
Hide file tree
Showing 6 changed files with 197 additions and 39 deletions.
4 changes: 0 additions & 4 deletions packages/protocol/contracts-0.8/common/EpochManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -126,10 +126,6 @@ contract EpochManager is
uint256 firstEpochBlock,
address[] memory firstElected
) external onlyEpochManagerEnabler {
require(
address(registry.getAddressForOrDie(CELO_UNRELEASED_TREASURE_REGISTRY_ID)).balance > 0,
"CeloUnreleasedTreasury not yet funded."
);
require(
getCeloToken().balanceOf(registry.getAddressForOrDie(CELO_UNRELEASED_TREASURE_REGISTRY_ID)) >
0,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,13 @@ contract EpochManagerEnabler is
emit LastKnownElectedAccountsSet();
}

/**
* @return a list of know elected validator accounts.
*/
function getlastKnownElectedAccounts() external view returns (address[] memory) {
return lastKnownElectedAccounts;
}

/**
* @notice Returns the storage, major, minor, and patch version of the contract.
* @return Storage version of the contract.
Expand Down
8 changes: 5 additions & 3 deletions packages/protocol/contracts-0.8/common/UsingPrecompiles.sol
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,9 @@ contract UsingPrecompiles is IsL2Check {
* @param index Index of requested validator in the validator set.
* @return Address of validator at the requested index.
*/
function validatorSignerAddressFromCurrentSet(uint256 index) public view returns (address) {
function validatorSignerAddressFromCurrentSet(
uint256 index
) public view virtual returns (address) {
bytes memory out;
bool success;
(success, out) = GET_VALIDATOR.staticcall(abi.encodePacked(index, uint256(block.number)));
Expand Down Expand Up @@ -118,7 +120,7 @@ contract UsingPrecompiles is IsL2Check {
* @notice Gets the size of the current elected validator set.
* @return Size of the current elected validator set.
*/
function numberValidatorsInCurrentSet() public view returns (uint256) {
function numberValidatorsInCurrentSet() public view virtual returns (uint256) {
bytes memory out;
bool success;
(success, out) = NUMBER_VALIDATORS.staticcall(abi.encodePacked(uint256(block.number)));
Expand All @@ -131,7 +133,7 @@ contract UsingPrecompiles is IsL2Check {
* @param blockNumber Block number to retrieve the validator set from.
* @return Size of the validator set.
*/
function numberValidatorsInSet(uint256 blockNumber) public view returns (uint256) {
function numberValidatorsInSet(uint256 blockNumber) public view virtual returns (uint256) {
bytes memory out;
bool success;
(success, out) = NUMBER_VALIDATORS.staticcall(abi.encodePacked(blockNumber));
Expand Down
13 changes: 0 additions & 13 deletions packages/protocol/contracts-0.8/common/test/MockCeloToken.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,6 @@ contract MockCeloToken08 {
uint8 public constant decimals = 18;
mapping(address => uint256) balances;

uint256 constant L1_MINTED_CELO_SUPPLY = 692702432463315819704447326; // as of May 15 2024

uint256 constant CELO_SUPPLY_CAP = 1000000000 ether; // 1 billion Celo
uint256 constant GENESIS_CELO_SUPPLY = 600000000 ether; // 600 million Celo

uint256 constant FIFTEEN_YEAR_LINEAR_REWARD = (CELO_SUPPLY_CAP - GENESIS_CELO_SUPPLY) / 2; // 200 million Celo

uint256 constant FIFTEEN_YEAR_CELO_SUPPLY = GENESIS_CELO_SUPPLY + FIFTEEN_YEAR_LINEAR_REWARD; // 800 million Celo (includes GENESIS_CELO_SUPPLY)

uint256 constant MAX_L2_DISTRIBUTION = FIFTEEN_YEAR_CELO_SUPPLY - L1_MINTED_CELO_SUPPLY; // 107.2 million Celo

uint256 constant L2_INITIAL_STASH_BALANCE = FIFTEEN_YEAR_LINEAR_REWARD + MAX_L2_DISTRIBUTION; // leftover from L1 target supply plus the 2nd 15 year term.

function setTotalSupply(uint256 value) external {
totalSupply_ = value;
}
Expand Down
33 changes: 33 additions & 0 deletions packages/protocol/test-sol/mocks/EpochManagerEnablerMock.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
pragma solidity ^0.8.0;

import "../../contracts-0.8/common/EpochManagerEnabler.sol";

/**
* @title A wrapper around EpochManagerEnabler that exposes internal functions for testing.
*/
contract EpochManagerEnablerMock is EpochManagerEnabler(true) {
address[] validatorSet;

function setFirstBlockOfEpoch() external {
return _setFirstBlockOfEpoch();
}

function addValidator(address validator) external {
validatorSet.push(validator);
}

// Minimally override core functions from UsingPrecompiles
function numberValidatorsInCurrentSet() public view override returns (uint256) {
return validatorSet.length;
}

function numberValidatorsInSet(uint256) public view override returns (uint256) {
return validatorSet.length;
}

function validatorSignerAddressFromCurrentSet(
uint256 index
) public view override returns (address) {
return validatorSet[index];
}
}
171 changes: 152 additions & 19 deletions packages/protocol/test-sol/unit/common/EpochManagerEnabler.t.sol
Original file line number Diff line number Diff line change
@@ -1,37 +1,170 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity >=0.8.7 <0.8.20;
pragma solidity >=0.8.0 <0.8.20;

import "celo-foundry-8/Test.sol";
import "@celo-contracts-8/common/EpochManagerEnabler.sol";
import "@celo-contracts/stability/test/MockSortedOracles.sol";
import "@celo-contracts-8/common/EpochManager.sol";

import { EpochManagerEnablerMock } from "@test-sol/mocks/EpochManagerEnablerMock.sol";

import { CeloUnreleasedTreasure } from "@celo-contracts-8/common/CeloUnreleasedTreasure.sol";
import { ICeloUnreleasedTreasure } from "@celo-contracts/common/interfaces/ICeloUnreleasedTreasure.sol";
import { IAccounts } from "@celo-contracts/common/interfaces/IAccounts.sol";

import { TestConstants } from "@test-sol/constants.sol";
import { Utils08 } from "@test-sol/utils08.sol";

import "@celo-contracts/common/interfaces/IRegistry.sol";

import { EPOCH_SIZEPRE_COMPILE_ADDRESS, EpochSizePrecompile } from "@test-sol/precompiles/EpochSizePrecompile.sol";
import { EpochRewardsMock08 } from "@celo-contracts-8/governance/test/EpochRewardsMock.sol";
import { ValidatorsMock } from "@test-sol/unit/governance/validators/mocks/ValidatorsMock.sol";
import { MockCeloUnreleasedTreasure } from "@celo-contracts-8/common/test/MockCeloUnreleasedTreasure.sol";
import "@celo-contracts-8/common/test/MockCeloToken.sol";

contract EpochManagerEnablerTest is Test, TestConstants, Utils08 {
EpochManager epochManager;
EpochManagerEnablerMock epochManagerEnabler;
MockCeloUnreleasedTreasure celoUnreleasedTreasure;
MockCeloToken08 celoToken;

IRegistry registry;
IAccounts accounts;

address accountsAddress;
address nonOwner;

uint256 epochDuration = DAY;
uint256 numberValidators = 100;

event LastKnownEpochNumberSet(uint256 lastKnownEpochNumber);
event LastKnownFirstBlockOfEpochSet(uint256 lastKnownFirstBlockOfEpoch);
event LastKnownElectedAccountsSet();

function setUp() public virtual {
ph.setEpochSize(17280);
epochManager = new EpochManager(true);
epochManagerEnabler = new EpochManagerEnablerMock();
celoToken = new MockCeloToken08();

celoUnreleasedTreasure = new MockCeloUnreleasedTreasure();

accountsAddress = actor("accountsAddress");

contract EpochManagerEnablerMock is EpochManagerEnabler {
constructor(bool test) public EpochManagerEnabler(test) {}
nonOwner = actor("nonOwner");

function setFirstBlockOfEpoch() external {
return _setFirstBlockOfEpoch();
deployCodeTo("MockRegistry.sol", abi.encode(false), REGISTRY_ADDRESS);
deployCodeTo("Accounts.sol", abi.encode(false), accountsAddress);

registry = IRegistry(REGISTRY_ADDRESS);
accounts = IAccounts(accountsAddress);

registry.setAddressFor(EpochManagerContract, address(epochManager));
registry.setAddressFor(EpochManagerEnablerContract, address(epochManagerEnabler));
registry.setAddressFor(AccountsContract, address(accounts));
registry.setAddressFor(CeloTokenContract, address(celoToken));
registry.setAddressFor(CeloUnreleasedTreasureContract, address(celoUnreleasedTreasure));

celoToken.setTotalSupply(CELO_SUPPLY_CAP);
celoToken.setBalanceOf(address(celoUnreleasedTreasure), L2_INITIAL_STASH_BALANCE);

epochManagerEnabler.initialize(REGISTRY_ADDRESS);
epochManager.initialize(REGISTRY_ADDRESS, epochDuration);

_setupValidators();
travelEpochL1(vm);
travelEpochL1(vm);
}

function _setupValidators() internal {
for (uint256 i = 0; i < numberValidators; i++) {
vm.prank(vm.addr(i + 1));
accounts.createAccount();

epochManagerEnabler.addValidator(vm.addr(i + 1));
}
}
}

contract EpochManagerEnablerTest is Test {
EpochManagerEnablerMock epochManagerEnabler;
uint256 EPOCH_SIZE_NEW = 17280;
contract EpochManagerEnablerTest_initialize is EpochManagerEnablerTest {
function test_initialize() public {
assertEq(address(epochManagerEnabler.registry()), REGISTRY_ADDRESS);
}

function setUp() public virtual {
deployCodeTo("EpochSizePrecompile", EPOCH_SIZEPRE_COMPILE_ADDRESS);
address payable payableAddress = payable(EPOCH_SIZEPRE_COMPILE_ADDRESS);
function test_Reverts_WhenAlreadyInitialized() public virtual {
vm.expectRevert("contract already initialized");
epochManagerEnabler.initialize(REGISTRY_ADDRESS);
}
}

EpochSizePrecompile(payableAddress).setEpochSize(EPOCH_SIZE_NEW);
contract EpochManagerEnablerTest_initEpochManager is EpochManagerEnablerTest {
function test_CanBeCalledByAnyone() public {
epochManagerEnabler.captureEpochAndValidators();

epochManagerEnabler = new EpochManagerEnablerMock(true);
whenL2(vm);
vm.prank(nonOwner);
epochManagerEnabler.initEpochManager();

assertGt(epochManager.getElected().length, 0);
assertTrue(epochManager.systemAlreadyInitialized());
}

function test_Reverts_ifEpochAndValidatorsAreNotCaptured() public {
whenL2(vm);
vm.expectRevert("lastKnownEpochNumber not set.");

epochManagerEnabler.initEpochManager();
}

function test_Reverts_whenL1() public {
vm.expectRevert("This method is not supported in L1.");

epochManagerEnabler.initEpochManager();
}
}

contract EpochManagerEnablerTest_captureEpochAndValidators is EpochManagerEnablerTest {
function test_Reverts_whenL2() public {
whenL2(vm);
vm.expectRevert("This method is no longer supported in L2.");
epochManagerEnabler.captureEpochAndValidators();
}

function test_shouldSetLastKnownElectedAccounts() public {
epochManagerEnabler.captureEpochAndValidators();

assertEq(epochManagerEnabler.getlastKnownElectedAccounts().length, numberValidators);
}

function test_precompilerWorks() public {
// Make sure epoch size is correct
assertEq(epochManagerEnabler.getEpochSize(), EPOCH_SIZE_NEW);
function test_shouldSetLastKnownEpochNumber() public {
epochManagerEnabler.captureEpochAndValidators();

assertEq(epochManagerEnabler.lastKnownEpochNumber(), 3);
}

function test_shouldSetLastKnownFirstBlockOfEpoch() public {
epochManagerEnabler.captureEpochAndValidators();

assertEq(epochManagerEnabler.lastKnownFirstBlockOfEpoch(), 17280 * 2);
}

function test_Emits_LastKnownEpochNumberSet() public {
vm.expectEmit(true, true, true, true);
emit LastKnownEpochNumberSet(3);

epochManagerEnabler.captureEpochAndValidators();
}

function test_Emits_LastKnownElectedAccountsSet() public {
vm.expectEmit(true, true, true, true);
emit LastKnownElectedAccountsSet();

epochManagerEnabler.captureEpochAndValidators();
}

function test_Emits_LastKnownFirstBlockOfEpochSet() public {
vm.expectEmit(true, true, true, true);
emit LastKnownFirstBlockOfEpochSet(34560);

epochManagerEnabler.captureEpochAndValidators();
}
}

Expand Down

0 comments on commit 032f543

Please sign in to comment.