Skip to content

Commit

Permalink
Merge branch 'feat/l2-epoch-system' into soloseng/return-account-inst…
Browse files Browse the repository at this point in the history
…ead-of-signer
  • Loading branch information
soloseng committed Sep 17, 2024
2 parents 30472a4 + 5dc389f commit 6b72a87
Show file tree
Hide file tree
Showing 72 changed files with 1,799 additions and 581 deletions.
13 changes: 12 additions & 1 deletion packages/protocol/contractPackages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,13 +48,24 @@ export const SOLIDITY_08_PACKAGE = {
proxiesPath: '/', // Proxies are still with 0.5 contracts
// Proxies shouldn't have to be added to a list manually
// https://github.com/celo-org/celo-monorepo/issues/10555
contracts: ['GasPriceMinimum', 'FeeCurrencyDirectory', 'CeloUnreleasedTreasure', 'Validators'],
contracts: [
'GasPriceMinimum',
'FeeCurrencyDirectory',
'CeloUnreleasedTreasure',
'Validators',
'EpochManager',
'EpochManagerEnabler',
'ScoreManager',
],
proxyContracts: [
'GasPriceMinimumProxy',
'FeeCurrencyDirectoryProxy',
'MentoFeeCurrencyAdapterV1',
'CeloUnreleasedTreasureProxy',
'ValidatorsProxy',
'EpochManagerProxy',
'EpochManagerEnablerProxy',
'ScoreManagerProxy',
],
truffleConfig: 'truffle-config0.8.js',
} satisfies ContractPackage
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
109 changes: 68 additions & 41 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 @@ -43,23 +43,26 @@ contract EpochManager is
uint256 toProcessGroups;
}

struct ProcessedGroup {
bool processed;
uint256 epochRewards;
}

// the length of an epoch in seconds
uint256 public epochDuration;

uint256 public firstKnownEpoch;
uint256 private currentEpochNumber;
address[] public elected;

// TODO this should be able to get deleted easily
// maybe even having it in a stadalone contract
mapping(address => bool) public processedGroups;
mapping(address => ProcessedGroup) public processedGroups;

EpochProcessState public epochProcessing;
mapping(uint256 => Epoch) private epochs;
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 +76,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 +96,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 +114,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 +140,7 @@ contract EpochManager is
_currentEpoch.startTimestamp = block.timestamp;

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

// TODO maybe "freezeEpochRewards" "prepareForNextEpoch"
Expand Down Expand Up @@ -168,11 +181,9 @@ contract EpochManager is
address[] calldata lessers,
address[] calldata greaters
) external nonReentrant {
// 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;
// last block should be the block before and timestamp from previous block
epochs[currentEpochNumber].lastBlock = block.number - 1;
// start new epoch
currentEpochNumber++;
Expand All @@ -181,27 +192,36 @@ contract EpochManager is

for (uint i = 0; i < elected.length; i++) {
address group = getValidators().getValidatorsGroup(elected[i]);
if (!processedGroups[group]) {
if (!processedGroups[group].processed) {
epochProcessing.toProcessGroups++;
processedGroups[group] = true;
uint256 groupScore = getScoreReader().getGroupScore(group);
// We need to precompute epoch rewards for each group since computation depends on total active votes for all groups.
uint256 epochRewards = getElection().getGroupEpochRewards(
group,
epochProcessing.totalRewardsVoter,
groupScore
);
processedGroups[group] = ProcessedGroup(true, epochRewards);
}
}

require(epochProcessing.toProcessGroups == groups.length, "number of groups does not match");

for (uint i = 0; i < groups.length; i++) {
// since we are adding values it makes sense to start from the end
for (uint ii = groups.length; ii > 0; ii--) {
uint256 i = ii - 1;
ProcessedGroup storage processedGroup = processedGroups[groups[i]];
// checks that group is actually from elected group
require(processedGroups[groups[i]], "group not processed");
// by doing this, we avoid processing a group twice
delete processedGroups[groups[i]];
// TODO what happens to uptime?
uint256 groupScore = getScoreReader().getGroupScore(groups[i]);
uint256 epochRewards = getElection().getGroupEpochRewards(
require(processedGroup.processed, "group not processed");
getElection().distributeEpochRewards(
groups[i],
epochProcessing.totalRewardsVoter,
groupScore
processedGroup.epochRewards,
lessers[i],
greaters[i]
);
getElection().distributeEpochRewards(groups[i], epochRewards, lessers[i], greaters[i]);

// by doing this, we avoid processing a group twice
delete processedGroups[groups[i]];
}
getCeloUnreleasedTreasure().release(
registry.getAddressForOrDie(GOVERNANCE_REGISTRY_ID),
Expand All @@ -212,22 +232,15 @@ contract EpochManager is
epochProcessing.totalRewardsCarbonFund
);
// run elections
elected = getElection().electNValidatorSigners(10, 20);
elected = getElection().electValidatorSigners();
// TODO check how to nullify stuct
epochProcessing.status = EpochProcessStatus.NotStarted;
}

/// 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 +249,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,7 +313,7 @@ contract EpochManager is
}

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

/**
Expand All @@ -307,15 +335,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;

getCeloUnreleasedTreasure().release(
registry.getAddressForOrDie(RESERVE_REGISTRY_ID),
CELOequivalent
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@ 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;
uint256 public lastKnownFirstBlockOfEpoch;
address[] public lastKnownElectedAccounts;

/**
Expand All @@ -32,10 +34,11 @@ contract EpochManagerInitializer is Initializable, UsingPrecompiles, UsingRegist
*/
function initEpochManager() external onlyL2 {
require(lastKnownEpochNumber != 0, "lastKnownEpochNumber not set.");
require(lastKnownFirstBlockOfEpoch != 0, "lastKnownFirstBlockOfEpoch not set.");
require(lastKnownElectedAccounts.length > 0, "lastKnownElectedAccounts not set.");
getEpochManager().initializeSystem(
lastKnownEpochNumber,
_getFirstBlockOfEpoch(lastKnownEpochNumber),
lastKnownFirstBlockOfEpoch,
lastKnownElectedAccounts
);
}
Expand All @@ -47,8 +50,8 @@ contract EpochManagerInitializer is Initializable, UsingPrecompiles, UsingRegist
lastKnownEpochNumber = getEpochNumber();

uint256 numberElectedValidators = numberValidatorsInCurrentSet();

lastKnownElectedAccounts = new address[](numberElectedValidators);
lastKnownFirstBlockOfEpoch = _getFirstBlockOfEpoch(lastKnownEpochNumber);

for (uint256 i = 0; i < numberElectedValidators; i++) {
// TODO: document how much gas this takes for 110 signers
Expand All @@ -63,6 +66,17 @@ contract EpochManagerInitializer is Initializable, UsingPrecompiles, UsingRegist
return _getFirstBlockOfEpoch(currentEpoch);
}

/**
* @notice Returns the storage, major, minor, and patch version of the contract.
* @return Storage version of the contract.
* @return Major version of the contract.
* @return Minor version of the contract.
* @return Patch version of the contract.
*/
function getVersionNumber() external pure returns (uint256, uint256, uint256, uint256) {
return (1, 1, 0, 0);
}

function _getFirstBlockOfEpoch(uint256 currentEpoch) internal view returns (uint256) {
uint256 blockToCheck = block.number - 1;
uint256 blockEpochNumber = getEpochNumberOfBlock(blockToCheck);
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
Loading

0 comments on commit 6b72a87

Please sign in to comment.