Skip to content

Commit

Permalink
startNextEpochProcess unit & integration test (#11191)
Browse files Browse the repository at this point in the history
* unit test with mocks

* ++ integration tests

* clean up

* -- logging

* removed duplicate interface

* using `MockCeloToken` to get test to pass.

Fails when it hits a precompile in `EpochRewards.sol`

* removed endEpochTimestamp

* moved IEpochManager to 0.5 folder

* added L2 conditions for EpochRewards functions using precompiles

Still missing tests

* renamed EpochManagerInitializer due to name conflict

* ++ more unit test

* setup anvil migration
fix name conflict

* compiles

* ++ require fund in unreleased treasury

* Updated regex

* ++ registry 0.8 for testing only

* clean up

* ++ unit test

* initial integration test using L1 devchain

* ++ comment

* -- forge based integration test

* ++ to const

* happy linter

* update contract name

* ++ PR feedback

* ++ checks

* updated carbon address

* proxy stableToken mint call via Validators contract

* -- duplicate imports

* removed registry08. replaced with vm call

* PR feedback

* -- coment

* passing unit tests

* clean up

* ++ mintStable test

* -- TODO; compiles test when filtering

* PR feedback

* updated migration script to add more validators

* passing integration test

* removed test for zero amount

* yarn build fix

* clean up comments && TODO

* revert change as out of scope
  • Loading branch information
soloseng authored Sep 17, 2024
1 parent 4cbc6d9 commit 080b430
Show file tree
Hide file tree
Showing 40 changed files with 1,041 additions and 441 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,7 @@ import "./UsingRegistry.sol";
import "../common/IsL2Check.sol";

import "../../contracts/common/Initializable.sol";
import "../../contracts/common/interfaces/ICeloToken.sol";
import "./interfaces/ICeloUnreleasedTreasureInitializer.sol";
import "@openzeppelin/contracts8/token/ERC20/IERC20.sol";

/**
* @title Contract for unreleased Celo tokens.
Expand Down Expand Up @@ -49,8 +47,7 @@ contract CeloUnreleasedTreasure is UsingRegistry, ReentrancyGuard, Initializable
*/
function release(address to, uint256 amount) external onlyEpochManager {
require(address(this).balance >= amount, "Insufficient balance.");
IERC20 celoToken = IERC20(address(getCeloToken()));
celoToken.transfer(to, amount);
require(getCeloToken().transfer(to, amount), "CELO transfer failed.");
emit Released(to, amount);
}

Expand Down
64 changes: 39 additions & 25 deletions packages/protocol/contracts-0.8/common/EpochManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import "../common/UsingRegistry.sol";
import "../../contracts/common/Initializable.sol";
import "../../contracts/common/interfaces/IEpochManager.sol";
import "../../contracts/common/interfaces/ICeloVersionedContract.sol";
import "../../contracts/common/interfaces/IEpochManager.sol";

contract EpochManager is
Initializable,
Expand All @@ -23,7 +24,6 @@ contract EpochManager is
uint256 firstBlock;
uint256 lastBlock;
uint256 startTimestamp;
uint256 endTimestamp;
uint256 rewardsBlock;
}

Expand All @@ -33,7 +33,7 @@ contract EpochManager is
}

struct EpochProcessState {
EpochProcessStatus status; // TODO maybe a enum for future updates
EpochProcessStatus status;
uint256 perValidatorReward; // The per validator epoch reward.
uint256 totalRewardsVoter; // The total rewards to voters.
uint256 totalRewardsCommunity; // The total community reward.
Expand All @@ -59,7 +59,7 @@ contract EpochManager is
mapping(address => uint256) public validatorPendingPayments;

address public carbonOffsettingPartner;
address public epochManagerInitializer;
address public epochManagerEnabler;

/**
* @notice Event emited when epochProcessing has begun.
Expand All @@ -73,8 +73,8 @@ contract EpochManager is
*/
event EpochProcessingEnded(uint256 indexed epochNumber);

modifier onlyEpochManagerInitializer() {
require(msg.sender == epochManagerInitializer, "msg.sender is not Initializer");
modifier onlyEpochManagerEnabler() {
require(msg.sender == epochManagerEnabler, "msg.sender is not Initializer");
_;
}

Expand All @@ -93,14 +93,15 @@ contract EpochManager is
address registryAddress,
uint256 newEpochDuration,
address _carbonOffsettingPartner,
address _epochManagerInitializer
address _epochManagerEnabler
) external initializer {
require(_epochManagerInitializer != address(0), "EpochManagerInitializer address is required");
require(_carbonOffsettingPartner != address(0), "carbonOffsettingPartner address is required");
require(_epochManagerEnabler != address(0), "EpochManagerEnabler address is required");
_transferOwnership(msg.sender);
setRegistry(registryAddress);
setEpochDuration(newEpochDuration);
carbonOffsettingPartner = _carbonOffsettingPartner;
epochManagerInitializer = _epochManagerInitializer;
epochManagerEnabler = _epochManagerEnabler;
}

// DESIGNDESICION(XXX): we assume that the first epoch on the L2 starts as soon as the system is initialized
Expand All @@ -110,7 +111,16 @@ contract EpochManager is
uint256 firstEpochNumber,
uint256 firstEpochBlock,
address[] memory firstElected
) external onlyEpochManagerInitializer {
) 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,
"CeloUnreleasedTreasury not yet funded."
);
require(!systemAlreadyInitialized(), "Epoch system already initialized");
require(firstEpochNumber > 0, "First epoch number must be greater than 0");
require(firstEpochBlock > 0, "First epoch block must be greater than 0");
Expand All @@ -127,7 +137,7 @@ contract EpochManager is
_currentEpoch.startTimestamp = block.timestamp;

elected = firstElected;
epochManagerInitializer = address(0);
epochManagerEnabler = address(0);
}

// TODO maybe "freezeEpochRewards" "prepareForNextEpoch"
Expand Down Expand Up @@ -171,8 +181,6 @@ contract EpochManager is
// TODO complete this function
require(isOnEpochProcess(), "Epoch process is not started");
// finalize epoch
// TODO last block should be the block before and timestamp from previous block
epochs[currentEpochNumber].endTimestamp = block.timestamp;
epochs[currentEpochNumber].lastBlock = block.number - 1;
// start new epoch
currentEpochNumber++;
Expand Down Expand Up @@ -218,16 +226,9 @@ contract EpochManager is
}

/// returns the current epoch Info
function getCurrentEpoch() external view returns (uint256, uint256, uint256, uint256, uint256) {
function getCurrentEpoch() external view returns (uint256, uint256, uint256, uint256) {
Epoch storage _epoch = epochs[currentEpochNumber];

return (
_epoch.firstBlock,
_epoch.lastBlock,
_epoch.startTimestamp,
_epoch.endTimestamp,
_epoch.rewardsBlock
);
return (_epoch.firstBlock, _epoch.lastBlock, _epoch.startTimestamp, _epoch.rewardsBlock);
}

/// returns the current epoch number.
Expand All @@ -236,6 +237,21 @@ contract EpochManager is
return currentEpochNumber;
}

/// returns epoch processing state
function getEpochProcessingState()
external
view
returns (uint256, uint256, uint256, uint256, uint256)
{
return (
uint256(epochProcessing.status),
epochProcessing.perValidatorReward,
epochProcessing.totalRewardsVoter,
epochProcessing.totalRewardsCommunity,
epochProcessing.totalRewardsCarbonFund
);
}

function getElected() external view returns (address[] memory) {
return elected;
}
Expand Down Expand Up @@ -285,11 +301,10 @@ contract EpochManager is
}

function systemAlreadyInitialized() public view returns (bool) {
return initialized && epochManagerInitializer == address(0);
return initialized && epochManagerEnabler == address(0);
}

function allocateValidatorsRewards() internal {
// TODO complete this function
uint256 totalRewards = 0;
IScoreReader scoreReader = getScoreReader();
IValidators validators = getValidators();
Expand All @@ -305,15 +320,14 @@ contract EpochManager is
totalRewards += validatorReward;
}
// Mint all cUSD required for payment and the corresponding CELO
IStableToken(getStableToken()).mint(address(this), totalRewards);
validators.mintStableToEpochManager(totalRewards);
// this should have a setter for the oracle.

(uint256 numerator, uint256 denominator) = IOracle(address(getSortedOracles())).getExchangeRate(
address(getStableToken())
);

uint256 CELOequivalent = (numerator * totalRewards) / denominator;
// this is not a mint anymore
getCeloUnreleasedTreasure().release(
registry.getAddressForOrDie(RESERVE_REGISTRY_ID),
CELOequivalent
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@ import "../common/UsingPrecompiles.sol";

import "../../contracts/common/Initializable.sol";
import "../../contracts/common/interfaces/ICeloVersionedContract.sol";
import "../../contracts/common/interfaces/IEpochManagerEnabler.sol";
import "../../contracts/governance/interfaces/IEpochRewards.sol";

contract EpochManagerInitializer is Initializable, UsingPrecompiles, UsingRegistry {
contract EpochManagerEnabler is Initializable, UsingPrecompiles, UsingRegistry {
uint256 public lastKnownEpochNumber;
address[] public lastKnownElectedAccounts;

Expand Down
4 changes: 1 addition & 3 deletions packages/protocol/contracts-0.8/common/ScoreManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,8 @@ contract ScoreManager is Initializable, Ownable {

/**
* @notice Used in place of the constructor to allow the contract to be upgradable via proxy.
* @param registryAddress The address of the registry core smart contract.
* @param newEpochDuration The duration of an epoch in seconds.
*/
function initialize(address registryAddress, uint256 newEpochDuration) external initializer {
function initialize() external initializer {
_transferOwnership(msg.sender);
}

Expand Down
9 changes: 4 additions & 5 deletions packages/protocol/contracts-0.8/common/UsingRegistry.sol
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,18 @@ import "../../contracts/common/interfaces/IAccounts.sol";
import "../../contracts/common/interfaces/IEpochManager.sol";
import "../../contracts/common/interfaces/IFreezer.sol";
import "../../contracts/common/interfaces/ICeloUnreleasedTreasure.sol";
import "../../contracts/common/interfaces/IFeeCurrencyWhitelist.sol";
import "../../contracts/common/interfaces/IFeeHandlerSeller.sol";
import "../../contracts/common/interfaces/IEpochManager.sol";
import "../../contracts/governance/interfaces/IGovernance.sol";
import "../../contracts/governance/interfaces/ILockedGold.sol";
import "../../contracts/governance/interfaces/ILockedCelo.sol";
import "../../contracts/governance/interfaces/IValidators.sol";
import "../../contracts/governance/interfaces/IElection.sol";
import "../../contracts/governance/interfaces/IEpochRewards.sol";
import "../../contracts/stability/interfaces/ISortedOracles.sol";
import "../../contracts/common/interfaces/IFeeCurrencyWhitelist.sol";
import "./interfaces/IScoreReader.sol";

import "../../contracts/governance/interfaces/IElection.sol";
import "../../contracts/common/interfaces/IFeeHandlerSeller.sol";
import "../../contracts/governance/interfaces/IEpochRewards.sol";
import "./interfaces/IScoreReader.sol";

contract UsingRegistry is Ownable {
// solhint-disable state-visibility
Expand Down
34 changes: 0 additions & 34 deletions packages/protocol/contracts-0.8/common/interfaces/ICeloToken.sol

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// SPDX-License-Identifier: LGPL-3.0-only
pragma solidity >=0.5.13 <0.9.0;

interface IEpochManagerEnablerInitializer {
function initialize(address registryAddress) external;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// SPDX-License-Identifier: LGPL-3.0-only
pragma solidity >=0.5.13 <0.9.0;

interface IEpochManagerInitializer {
function initialize(
address registryAddress,
uint256 newEpochDuration,
address _carbonOffsettingPartner,
address _epochManagerEnabler
) external;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// SPDX-License-Identifier: LGPL-3.0-only
pragma solidity >=0.5.13 <0.9.0;

interface IScoreManagerInitializer {
function initialize() external;
}
60 changes: 60 additions & 0 deletions packages/protocol/contracts-0.8/common/test/MockCeloToken.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
pragma solidity >=0.8.0 <0.9.0;
// solhint-disable no-unused-vars

/**
* @title A mock StableToken for testing. This contract can be deprecated once GoldToken gets migrated to 0.8
*/
contract MockCeloToken08 {
uint256 public totalSupply_;
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;
}

function transfer(address to, uint256 amount) external returns (bool) {
return _transfer(msg.sender, to, amount);
}

function transferFrom(address from, address to, uint256 amount) external returns (bool) {
return _transfer(from, to, amount);
}

function _transfer(address from, address to, uint256 amount) internal returns (bool) {
if (balances[from] < amount) {
return false;
}
balances[from] -= amount;
balances[to] += amount;
return true;
}

function setBalanceOf(address a, uint256 value) external {
balances[a] = value;
}

function balanceOf(address a) public view returns (uint256) {
return balances[a];
}

function totalSupply() public view returns (uint256) {
return totalSupply_;
}
function allocatedSupply() public view returns (uint256) {
return CELO_SUPPLY_CAP - L2_INITIAL_STASH_BALANCE;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
pragma solidity >=0.8.0 <0.9.0;
// solhint-disable no-unused-vars

import "../../../contracts/common/interfaces/ICeloUnreleasedTreasure.sol";
import "../UsingRegistry.sol";

/**
* @title A mock CeloUnreleasedTreasure for testing.
*/
contract MockCeloUnreleasedTreasure is ICeloUnreleasedTreasure, UsingRegistry {
function release(address to, uint256 amount) external {
require(address(this).balance >= amount, "Insufficient balance.");
require(getCeloToken().transfer(to, amount), "CELO transfer failed.");
}
}
Loading

0 comments on commit 080b430

Please sign in to comment.