From 032f5432327abf6606e778c9f5605c601b7b77bc Mon Sep 17 00:00:00 2001 From: soloseng <102702451+soloseng@users.noreply.github.com> Date: Fri, 20 Sep 2024 16:34:28 -0400 Subject: [PATCH] Epoch manager enabler tests (#11213) * ++ basic test * -- celoToken balance check * cleanup comments * use celoToken instead of native token for `initializeSystem` balance check * ++ more test * removed additional epochs --- .../contracts-0.8/common/EpochManager.sol | 4 - .../common/EpochManagerEnabler.sol | 7 + .../contracts-0.8/common/UsingPrecompiles.sol | 8 +- .../common/test/MockCeloToken.sol | 13 -- .../mocks/EpochManagerEnablerMock.sol | 33 ++++ .../unit/common/EpochManagerEnabler.t.sol | 171 ++++++++++++++++-- 6 files changed, 197 insertions(+), 39 deletions(-) create mode 100644 packages/protocol/test-sol/mocks/EpochManagerEnablerMock.sol diff --git a/packages/protocol/contracts-0.8/common/EpochManager.sol b/packages/protocol/contracts-0.8/common/EpochManager.sol index aa6fa57f02..c8de3995ea 100644 --- a/packages/protocol/contracts-0.8/common/EpochManager.sol +++ b/packages/protocol/contracts-0.8/common/EpochManager.sol @@ -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, diff --git a/packages/protocol/contracts-0.8/common/EpochManagerEnabler.sol b/packages/protocol/contracts-0.8/common/EpochManagerEnabler.sol index 2eafa24f02..61506ec787 100644 --- a/packages/protocol/contracts-0.8/common/EpochManagerEnabler.sol +++ b/packages/protocol/contracts-0.8/common/EpochManagerEnabler.sol @@ -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. diff --git a/packages/protocol/contracts-0.8/common/UsingPrecompiles.sol b/packages/protocol/contracts-0.8/common/UsingPrecompiles.sol index 66452d136a..261c7f59d5 100644 --- a/packages/protocol/contracts-0.8/common/UsingPrecompiles.sol +++ b/packages/protocol/contracts-0.8/common/UsingPrecompiles.sol @@ -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))); @@ -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))); @@ -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)); diff --git a/packages/protocol/contracts-0.8/common/test/MockCeloToken.sol b/packages/protocol/contracts-0.8/common/test/MockCeloToken.sol index 74baea164c..b412f134a2 100644 --- a/packages/protocol/contracts-0.8/common/test/MockCeloToken.sol +++ b/packages/protocol/contracts-0.8/common/test/MockCeloToken.sol @@ -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; } diff --git a/packages/protocol/test-sol/mocks/EpochManagerEnablerMock.sol b/packages/protocol/test-sol/mocks/EpochManagerEnablerMock.sol new file mode 100644 index 0000000000..137d910452 --- /dev/null +++ b/packages/protocol/test-sol/mocks/EpochManagerEnablerMock.sol @@ -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]; + } +} diff --git a/packages/protocol/test-sol/unit/common/EpochManagerEnabler.t.sol b/packages/protocol/test-sol/unit/common/EpochManagerEnabler.t.sol index b4273fb6c0..c3b4c1adc4 100644 --- a/packages/protocol/test-sol/unit/common/EpochManagerEnabler.t.sol +++ b/packages/protocol/test-sol/unit/common/EpochManagerEnabler.t.sol @@ -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(); } }