From 5f7e5d1dbbfb06d0c2a9cde6bfb9135f6ed0c8d7 Mon Sep 17 00:00:00 2001 From: soloseng <102702451+soloseng@users.noreply.github.com> Date: Thu, 15 Aug 2024 17:08:48 -0400 Subject: [PATCH 01/59] first compiling draft no working test. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Martín Volpe --- .../contracts-0.8/common/EpochManager.sol | 260 ++++++++++++++++ .../common/EpochManagerInitializer.sol | 44 +++ .../contracts-0.8/common/UsingPrecompiles.sol | 278 ++++++++++++++++++ .../contracts-0.8/common/UsingRegistry.sol | 22 +- .../common/interfaces/IEpochManager.sol | 24 ++ .../contracts/common/UsingRegistry.sol | 13 + .../contracts/common/UsingRegistryV2.sol | 17 +- .../interfaces/IEpochManagerInitializer.sol | 9 + .../contracts/governance/EpochRewards.sol | 16 +- .../governance/interfaces/IEpochRewards.sol | 14 + .../test-sol/unit/common/EpochManager.t.sol | 43 +++ 11 files changed, 734 insertions(+), 6 deletions(-) create mode 100644 packages/protocol/contracts-0.8/common/EpochManager.sol create mode 100644 packages/protocol/contracts-0.8/common/EpochManagerInitializer.sol create mode 100644 packages/protocol/contracts-0.8/common/UsingPrecompiles.sol create mode 100644 packages/protocol/contracts-0.8/common/interfaces/IEpochManager.sol create mode 100644 packages/protocol/contracts/common/interfaces/IEpochManagerInitializer.sol create mode 100644 packages/protocol/contracts/governance/interfaces/IEpochRewards.sol create mode 100644 packages/protocol/test-sol/unit/common/EpochManager.t.sol diff --git a/packages/protocol/contracts-0.8/common/EpochManager.sol b/packages/protocol/contracts-0.8/common/EpochManager.sol new file mode 100644 index 00000000000..2515b95bcce --- /dev/null +++ b/packages/protocol/contracts-0.8/common/EpochManager.sol @@ -0,0 +1,260 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity >=0.8.7 <0.8.20; + +import "@openzeppelin/contracts8/security/ReentrancyGuard.sol"; + +import "./interfaces/IEpochManager.sol"; +import "../common/UsingRegistry.sol"; + +import "../../contracts/common/Initializable.sol"; +import "../../contracts/common/interfaces/ICeloVersionedContract.sol"; + +contract EpochManager is + Initializable, + UsingRegistry, + IEpochManager, + ReentrancyGuard, + ICeloVersionedContract +{ + struct Epoch { + uint256 firstBlock; + uint256 lastBlock; + uint256 startTimestamp; + uint256 endTimestamp; + uint256 rewardsBlock; + } + + struct EpochProcessState { + bool started; // TODO maybe a enum for future updates + uint256 perValidatorReward; // The per validator epoch reward. + uint256 totalRewardsVoter; // The total rewards to voters. + uint256 totalRewardsCommunity; // The total community reward. + uint256 totalRewardsCarbonFund; // The total carbon offsetting partner reward. + // map the groups and their processed status + // total number of groups that need to be processed + uint256 toProcessGroups; + } + + // the length of an epoch in seconds + uint256 public epochDuration; + + uint256 public firstKnownEpoch; + uint256 public currentEpoch; + address[] public elected; + + // TODO this should be able to get deleted easily + // maybe even having it in a stadalone contract + mapping(address => uint256) public processedGroups; + + EpochProcessState public epochProcessing; + mapping(uint256 => Epoch) public epochs; + mapping(address => uint256) public validatorPendingPayments; + + /** + * @notice Event emited when epochProcessing has begun. + * @param epochNumber The epoch number that is being processed. + */ + event EpochProcessingStarted(uint256 indexed epochNumber); + + /** + * @notice Event emited when epochProcessing has ended. + * @param epochNumber The epoch number that is finished being processed. + */ + event EpochProcessingEnded(uint256 indexed epochNumber); + + modifier onlyEpochManagerInitializer() { + require( + msg.sender == registry.getAddressForOrDie(EPOCH_MANAGER_INITIALIZER_ID), + "msg.sender is not Initializer" + ); + + _; + } + + /** + * @notice Sets initialized == true on implementation contracts + * @param test Set to true to skip implementation initialization + */ + constructor(bool test) public Initializable(test) {} + + /** + * @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 { + _transferOwnership(msg.sender); + setRegistry(registryAddress); + setEpochDuration(newEpochDuration); + } + + // DESIGNDESICION(XXX): we assume that the first epoch on the L2 starts as soon as the system is initialized + // to minimize amount of "limbo blocks" the network should stop relatively close to an epoch number (but wigh enough time) + // to have time to call the function EpochInitializer.migrateEpochAndValidators() + function initializeSystem( + uint256 firstEpochNumber, + uint256 firstEpochBlock, + address[] memory firstElected + ) external onlyEpochManagerInitializer { + require(systemAlreadyInitialized(), "Epoch system already initialized"); + firstKnownEpoch = firstEpochNumber; + currentEpoch = firstEpochNumber; + + Epoch storage _currentEpoch = epochs[currentEpoch]; + _currentEpoch.firstBlock = firstEpochBlock; + _currentEpoch.startTimestamp = block.timestamp; + _currentEpoch.endTimestamp = block.timestamp + epochDuration; + + elected = firstElected; + } + + // TODO maybe "freezeEpochRewards" "prepareForNextEpoch" + function startNextEpochProcess() external nonReentrant { + require(isReadyToStartEpoch(), "Epoch is not ready to start"); + require(!isOnEpochProcess(), "Epoch process is already started"); + epochProcessing.started = true; + + epochs[currentEpoch].rewardsBlock = block.number; + + // calculate rewards + // TODO: update function to allow epochManager to call. + getEpochRewards().updateTargetVotingYield(); + // distributeCeloEpochPayments(); + + ( + uint256 perValidatorReward, + uint256 totalRewardsVoter, + uint256 totalRewardsCommunity, + uint256 totalRewardsCarbonFund + ) = getEpochRewards().calculateTargetEpochRewards(); + + epochProcessing.perValidatorReward = perValidatorReward; + epochProcessing.totalRewardsVoter = totalRewardsVoter; + epochProcessing.totalRewardsCommunity = totalRewardsCommunity; + epochProcessing.totalRewardsCarbonFund = totalRewardsCarbonFund; + + allocateValidatorsRewards(); + + emit EpochProcessingStarted(currentEpoch); + } + + function finishNextEpochProcess( + address[] calldata groups, + uint16[] calldata lessers, + uint16 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[currentEpoch].endTimestamp = block.timestamp; + // epochs[currentEpoch].lastBlock = block.number; + // // start new epoch + // currentEpoch++; + // epochs[currentEpoch].firstBlock = block.number; + // epochs[currentEpoch].startTimestamp = block.timestamp; + // // O(elected) + // for (uint i =0; i < elected.length; i++) { + // address group = validators.getGroup(elected[i]); + // if (epochProcessing.processedGroups[group] == 0) { + // epochProcessing.toProcessGroups++; + // epochProcessing.processedGroups[group] = 1; + // } + // } + // require(epochProcessing.toProcessGroups == groups.length, "number of groups does not match") + // // O(groups) + // for (uint i = 0; i < groups.length; i++) { + // // checks that group is acutally from elected group + // require(epochProcessing.processedGroups[groups[i]] == 1, "group not processed") + // // by doing this, we avoid processing a group twice + // delete epochProcessing.processedGroups[groups[i]]; + // // TODO what happens to uptime? + // uint256 epochRewards = getElection().getGroupEpochRewards(groups[i], epochProcessing.rewardsVoter, uptimes); + // getElection().distributeEpochRewards(groups[i], epochRewards, lessers[i] , greaters[i]); + // } + // celoDistributionSchedule.mint(address(CommunityFund), epochProcessing.rewardsCommunity) + // celoDistributionSchedule.mint(address(Carbon), epochProcessing.rewardsCarbonFund) + // // run elections + // elected = getElection().electValidators() + // // TODO check how to nullify stuct + // epochProcesssing.started = false; + // epochProcessing = new epochProcessState(); + } + + function getCurrentEpoch() external view returns (uint256) { + return currentEpoch; + } + + function getElected() external view returns (address[] memory) { + return elected; + } + + function getFirstBlockAtEpoch(uint256 epoch) external view returns (uint256) { + return epochs[epoch].firstBlock; + } + + function getLastBlockAtEpoch(uint256 epoch) external view returns (uint256) { + return epochs[epoch].lastBlock; + } + + function isOnEpochProcess() external view returns (bool) { + return epochProcessing.started; + } + + function isTimeForNextEpoch() external view returns (bool) { + return block.timestamp >= epochs[currentEpoch].startTimestamp + epochDuration; + } + + function isBlocked() external view returns (bool) { + return isOnEpochProcess(); + } + + /** + * @notice Sets the time duration of an epoch. + * @param newEpochDuration The duration of an epoch in seconds. + * @dev Can only be set by owner. + */ + function setEpochDuration(uint256 newEpochDuration) public onlyOwner { + epochDuration = newEpochDuration; + } + + function systemAlreadyInitialized() public view returns (bool) { + return firstKnownEpoch != 0; + } + + // checks if end of epoch has been reached based on timestamp + function isReadyToStartEpoch() public view returns (bool) { + Epoch memory _currentEpoch = epochs[currentEpoch]; + if (block.timestamp > _currentEpoch.endTimestamp) { + return true; + } else { + return false; + } + } + + // checks if process has started + function isOnEpochProcess() public view returns (bool) { + if (epochProcessing.started) { + return true; + } else { + return false; + } + } + + function allocateValidatorsRewards() internal { + // TODO complete this function + // uint256 totalRewards = 0; + // for (uint i = 0; i < elected.length; i++) { + // uint256 validatorScore = scoreManager.getValidatorScore(elected[i]); + // uint256 validatorReward = validators.computeEpochReward(elected[i], validatorScore, epochProcessing.maxRewardsValidator); + // validatorPendingPayments[elected[i]] += validatorReward; + // totalRewards += validatorReward; + // } + // // Mint all cUSD required for payment and the corresponding CELO + // StablToken.mint(address(this), totalRewards) + // // this should have a setter for the oracle. + // CELOequivalent = IOracle(oracleAddress).getRate()*totalRewards + // // this is not a mint anymore + // distributionSchedule.mintCelo(address(reserve), CELOequivalent) + } +} diff --git a/packages/protocol/contracts-0.8/common/EpochManagerInitializer.sol b/packages/protocol/contracts-0.8/common/EpochManagerInitializer.sol new file mode 100644 index 00000000000..c4d1d0439c1 --- /dev/null +++ b/packages/protocol/contracts-0.8/common/EpochManagerInitializer.sol @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity >=0.8.7 <0.8.20; + +import "../common/UsingRegistry.sol"; +import "../common/UsingPrecompiles.sol"; + +import "../../contracts/common/Initializable.sol"; +import "../../contracts/common/interfaces/ICeloVersionedContract.sol"; +import "../../contracts/governance/interfaces/IEpochRewards.sol"; + +contract EpochManagerInitializer is initializable, UsingPrecompiles, UsingRegistry { + /** + * @notice Sets initialized == true on implementation contracts + * @param test Set to true to skip implementation initialization + */ + constructor(bool test) public Initializable(test) {} + + /** + * @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) external initializer { + _transferOwnership(msg.sender); + setRegistry(registryAddress); + } + + /** + * @notice initializes the epochManager contract during L2 transition. + */ + function initEpochManager() external onlyOwner { + uint256 currentEpoch = getEpochNumber(); + + uint256 numberElectedValidators = numberValidatorsInCurrentSet(); + + address[] memory electedValidatorAddresses = new address[](numberElectedValidators); + + for (uint256 i = 0; i < numberElectedValidators; i++) { + validatorSignerAddressFromCurrentSet(i); + electedValidatorAddresses[i] = validatorAddress; + } + getEpochManager().initializeSystem(currentEpoch, block.number, electedValidatorAddresses); + } +} diff --git a/packages/protocol/contracts-0.8/common/UsingPrecompiles.sol b/packages/protocol/contracts-0.8/common/UsingPrecompiles.sol new file mode 100644 index 00000000000..adf2f5763f2 --- /dev/null +++ b/packages/protocol/contracts-0.8/common/UsingPrecompiles.sol @@ -0,0 +1,278 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity >=0.8.0 <0.8.20; + +// Note: This is not an exact copy of UsingPrecompiles in the contract's folder, but in solidity 0.8 + +import "openzeppelin-solidity/contracts/math/SafeMath.sol"; +import "../../contracts/common/interfaces/ICeloVersionedContract.sol"; +import "../common/IsL2Check.sol"; + +contract UsingPrecompiles is IsL2Check { + using SafeMath for uint256; + + address constant TRANSFER = address(0xff - 2); + address constant FRACTION_MUL = address(0xff - 3); + address constant PROOF_OF_POSSESSION = address(0xff - 4); + address constant GET_VALIDATOR = address(0xff - 5); + address constant NUMBER_VALIDATORS = address(0xff - 6); + address constant EPOCH_SIZE = address(0xff - 7); + address constant BLOCK_NUMBER_FROM_HEADER = address(0xff - 8); + address constant HASH_HEADER = address(0xff - 9); + address constant GET_PARENT_SEAL_BITMAP = address(0xff - 10); + address constant GET_VERIFIED_SEAL_BITMAP = address(0xff - 11); + uint256 constant DAY = 86400; + + /** + * @notice calculate a * b^x for fractions a, b to `decimals` precision + * @param aNumerator Numerator of first fraction + * @param aDenominator Denominator of first fraction + * @param bNumerator Numerator of exponentiated fraction + * @param bDenominator Denominator of exponentiated fraction + * @param exponent exponent to raise b to + * @param _decimals precision + * @return Numerator of the computed quantity (not reduced). + * @return Denominator of the computed quantity (not reduced). + */ + function fractionMulExp( + uint256 aNumerator, + uint256 aDenominator, + uint256 bNumerator, + uint256 bDenominator, + uint256 exponent, + uint256 _decimals + ) public view returns (uint256, uint256) { + require(aDenominator != 0 && bDenominator != 0, "a denominator is zero"); + uint256 returnNumerator; + uint256 returnDenominator; + bool success; + bytes memory out; + (success, out) = FRACTION_MUL.staticcall( + abi.encodePacked(aNumerator, aDenominator, bNumerator, bDenominator, exponent, _decimals) + ); + require(success, "error calling fractionMulExp precompile"); + returnNumerator = getUint256FromBytes(out, 0); + returnDenominator = getUint256FromBytes(out, 32); + return (returnNumerator, returnDenominator); + } + + /** + * @notice Returns the current epoch size in blocks. + * @return The current epoch size in blocks. + */ + function getEpochSize() public view returns (uint256) { + if (isL2()) { + return DAY.div(5); + } else { + bytes memory out; + bool success; + (success, out) = EPOCH_SIZE.staticcall(abi.encodePacked(true)); + require(success, "error calling getEpochSize precompile"); + return getUint256FromBytes(out, 0); + } + } + + /** + * @notice Returns the epoch number at a block. + * @param blockNumber Block number where epoch number is calculated. + * @return Epoch number. + */ + function getEpochNumberOfBlock(uint256 blockNumber) public view returns (uint256) { + return epochNumberOfBlock(blockNumber, getEpochSize()); + } + + /** + * @notice Returns the epoch number at a block. + * @return Current epoch number. + */ + function getEpochNumber() public view returns (uint256) { + return getEpochNumberOfBlock(block.number); + } + + /** + * @notice Gets a validator address from the current validator set. + * @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) { + bytes memory out; + bool success; + (success, out) = GET_VALIDATOR.staticcall(abi.encodePacked(index, uint256(block.number))); + require(success, "error calling validatorSignerAddressFromCurrentSet precompile"); + return address(getUint256FromBytes(out, 0)); + } + + /** + * @notice Gets a validator address from the validator set at the given block number. + * @param index Index of requested validator in the validator set. + * @param blockNumber Block number to retrieve the validator set from. + * @return Address of validator at the requested index. + */ + function validatorSignerAddressFromSet( + uint256 index, + uint256 blockNumber + ) public view returns (address) { + bytes memory out; + bool success; + (success, out) = GET_VALIDATOR.staticcall(abi.encodePacked(index, blockNumber)); + require(success, "error calling validatorSignerAddressFromSet precompile"); + return address(getUint256FromBytes(out, 0)); + } + + /** + * @notice Gets the size of the current elected validator set. + * @return Size of the current elected validator set. + */ + function numberValidatorsInCurrentSet() public view returns (uint256) { + bytes memory out; + bool success; + (success, out) = NUMBER_VALIDATORS.staticcall(abi.encodePacked(uint256(block.number))); + require(success, "error calling numberValidatorsInCurrentSet precompile"); + return getUint256FromBytes(out, 0); + } + + /** + * @notice Gets the size of the validator set that must sign the given block number. + * @param blockNumber Block number to retrieve the validator set from. + * @return Size of the validator set. + */ + function numberValidatorsInSet(uint256 blockNumber) public view returns (uint256) { + bytes memory out; + bool success; + (success, out) = NUMBER_VALIDATORS.staticcall(abi.encodePacked(blockNumber)); + require(success, "error calling numberValidatorsInSet precompile"); + return getUint256FromBytes(out, 0); + } + + /** + * @notice Checks a BLS proof of possession. + * @param sender The address signed by the BLS key to generate the proof of possession. + * @param blsKey The BLS public key that the validator is using for consensus, should pass proof + * of possession. 48 bytes. + * @param blsPop The BLS public key proof-of-possession, which consists of a signature on the + * account address. 96 bytes. + * @return True upon success. + */ + function checkProofOfPossession( + address sender, + bytes memory blsKey, + bytes memory blsPop + ) public view returns (bool) { + bool success; + (success, ) = PROOF_OF_POSSESSION.staticcall(abi.encodePacked(sender, blsKey, blsPop)); + return success; + } + + /** + * @notice Parses block number out of header. + * @param header RLP encoded header + * @return Block number. + */ + function getBlockNumberFromHeader(bytes memory header) public view returns (uint256) { + bytes memory out; + bool success; + (success, out) = BLOCK_NUMBER_FROM_HEADER.staticcall(abi.encodePacked(header)); + require(success, "error calling getBlockNumberFromHeader precompile"); + return getUint256FromBytes(out, 0); + } + + /** + * @notice Computes hash of header. + * @param header RLP encoded header + * @return Header hash. + */ + function hashHeader(bytes memory header) public view returns (bytes32) { + bytes memory out; + bool success; + (success, out) = HASH_HEADER.staticcall(abi.encodePacked(header)); + require(success, "error calling hashHeader precompile"); + return getBytes32FromBytes(out, 0); + } + + /** + * @notice Gets the parent seal bitmap from the header at the given block number. + * @param blockNumber Block number to retrieve. Must be within 4 epochs of the current number. + * @return Bitmap parent seal with set bits at indices corresponding to signing validators. + */ + function getParentSealBitmap(uint256 blockNumber) public view returns (bytes32) { + bytes memory out; + bool success; + (success, out) = GET_PARENT_SEAL_BITMAP.staticcall(abi.encodePacked(blockNumber)); + require(success, "error calling getParentSealBitmap precompile"); + return getBytes32FromBytes(out, 0); + } + + /** + * @notice Verifies the BLS signature on the header and returns the seal bitmap. + * The validator set used for verification is retrieved based on the parent hash field of the + * header. If the parent hash is not in the blockchain, verification fails. + * @param header RLP encoded header + * @return Bitmap parent seal with set bits at indices correspoinding to signing validators. + */ + function getVerifiedSealBitmapFromHeader(bytes memory header) public view returns (bytes32) { + bytes memory out; + bool success; + (success, out) = GET_VERIFIED_SEAL_BITMAP.staticcall(abi.encodePacked(header)); + require(success, "error calling getVerifiedSealBitmapFromHeader precompile"); + return getBytes32FromBytes(out, 0); + } + + /** + * @notice Returns the minimum number of required signers for a given block number. + * @dev Computed in celo-blockchain as int(math.Ceil(float64(2*valSet.Size()) / 3)) + */ + function minQuorumSize(uint256 blockNumber) public view returns (uint256) { + return numberValidatorsInSet(blockNumber).mul(2).add(2).div(3); + } + + /** + * @notice Computes byzantine quorum from current validator set size + * @return Byzantine quorum of validators. + */ + function minQuorumSizeInCurrentSet() public view returns (uint256) { + return minQuorumSize(block.number); + } + + /** + * @notice Returns the epoch number at a block. + * @param blockNumber Block number where epoch number is calculated. + * @param epochSize The epoch size in blocks. + * @return Epoch number. + */ + function epochNumberOfBlock( + uint256 blockNumber, + uint256 epochSize + ) internal pure returns (uint256) { + // Follows GetEpochNumber from celo-blockchain/blob/master/consensus/istanbul/utils.go + uint256 epochNumber = blockNumber / epochSize; + if (blockNumber % epochSize == 0) { + return epochNumber; + } else { + return epochNumber.add(1); + } + } + + /** + * @notice Converts bytes to uint256. + * @param bs byte[] data + * @param start offset into byte data to convert + * @return uint256 data + */ + function getUint256FromBytes(bytes memory bs, uint256 start) internal pure returns (uint256) { + return uint256(getBytes32FromBytes(bs, start)); + } + + /** + * @notice Converts bytes to bytes32. + * @param bs byte[] data + * @param start offset into byte data to convert + * @return bytes32 data + */ + function getBytes32FromBytes(bytes memory bs, uint256 start) internal pure returns (bytes32) { + require(bs.length >= start.add(32), "slicing out of range"); + bytes32 x; + assembly { + x := mload(add(bs, add(start, 32))) + } + return x; + } +} diff --git a/packages/protocol/contracts-0.8/common/UsingRegistry.sol b/packages/protocol/contracts-0.8/common/UsingRegistry.sol index 9d5a501904b..417ba771717 100644 --- a/packages/protocol/contracts-0.8/common/UsingRegistry.sol +++ b/packages/protocol/contracts-0.8/common/UsingRegistry.sol @@ -7,17 +7,21 @@ pragma solidity >=0.8.0 <0.8.20; import "@openzeppelin/contracts8/access/Ownable.sol"; import "@openzeppelin/contracts8/token/ERC20/IERC20.sol"; +import "./interfaces/IEpochManager.sol"; + import "../../contracts/common/interfaces/IRegistry.sol"; import "../../contracts/common/interfaces/IAccounts.sol"; import "../../contracts/common/interfaces/IFreezer.sol"; import "../../contracts/common/interfaces/ICeloDistributionSchedule.sol"; +import "../../contracts/common/interfaces/IEpochManagerInitializer.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 "../../contracts/governance/interfaces/IElection.sol"; import "../../contracts/common/interfaces/IFeeHandlerSeller.sol"; @@ -49,6 +53,10 @@ contract UsingRegistry is Ownable { bytes32 constant LOCKED_CELO_REGISTRY_ID = keccak256(abi.encodePacked("LockedCelo")); bytes32 constant CELO_DISTRIBUTION_SCHEDULE_ID = keccak256(abi.encodePacked("CeloDistributionSchedule")); + bytes32 constant EPOCH_REWARDS_ID = keccak256(abi.encodePacked("EpochRewards")); + bytes32 constant EPOCH_MANAGER_INITIALIZER_ID = + keccak256(abi.encodePacked("EpochManagerInitializer")); + bytes32 constant EPOCH_MANAGER_ID = keccak256(abi.encodePacked("EpochManager")); // solhint-enable state-visibility IRegistry public registry; @@ -130,4 +138,16 @@ contract UsingRegistry is Ownable { function getCeloDistributionSchedule() internal view returns (ICeloDistributionSchedule) { return ICeloDistributionSchedule(registry.getAddressForOrDie(CELO_DISTRIBUTION_SCHEDULE_ID)); } + + function getEpochRewards() internal view returns (IEpochRewards) { + return IEpochRewards(registry.getAddressForOrDie(EPOCH_REWARDS_ID)); + } + + function getEpochManagerInitializer() internal view returns (IEpochManagerInitializer) { + return IEpochManagerInitializer(registry.getAddressForOrDie(EPOCH_MANAGER_INITIALIZER_ID)); + } + + function getEpochManager() internal view returns (IEpochManager) { + return IEpochManager(registry.getAddressForOrDie(EPOCH_MANAGER_ID)); + } } diff --git a/packages/protocol/contracts-0.8/common/interfaces/IEpochManager.sol b/packages/protocol/contracts-0.8/common/interfaces/IEpochManager.sol new file mode 100644 index 00000000000..bc94187e0f2 --- /dev/null +++ b/packages/protocol/contracts-0.8/common/interfaces/IEpochManager.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity >=0.8.0 <0.9.0; + +interface IBlocker { + function isBlocked() external view returns (bool); +} + +interface IEpochManager is IBlocker { + function initializeSystem( + uint256 firstEpochNumber, + uint256 firstEpochBlock, + // uint256 firstEpochTimestamp, // TODO: do we need END timestamp? + address[] calldata firstElected + ) external; + function startNextEpochProcess() external; + function finishNextEpochProcess() external; + function getCurrentEpoch() external view returns (uint256); + function getElected() external view returns (address[] memory); + function getElectedAtEpoch(uint256 epoch) external view returns (address[] memory); + function getFirstBlockAtEpoch(uint256 epoch) external view returns (uint256); + function getLastBlockAtEpoch(uint256 epoch) external view returns (uint256); + + function isOnEpochProcess() external view returns (bool); +} diff --git a/packages/protocol/contracts/common/UsingRegistry.sol b/packages/protocol/contracts/common/UsingRegistry.sol index f4cf8d7b26a..39be700bc37 100644 --- a/packages/protocol/contracts/common/UsingRegistry.sol +++ b/packages/protocol/contracts/common/UsingRegistry.sol @@ -9,8 +9,10 @@ import "./interfaces/IFeeCurrencyWhitelist.sol"; import "./interfaces/IFreezer.sol"; import "./interfaces/IRegistry.sol"; import "./interfaces/ICeloDistributionSchedule.sol"; +import "./interfaces/IEpochManagerInitializer.sol"; import "../governance/interfaces/IElection.sol"; +import "../governance/interfaces/IEpochRewards.sol"; import "../governance/interfaces/IGovernance.sol"; import "../governance/interfaces/ILockedGold.sol"; import "../governance/interfaces/ILockedCelo.sol"; @@ -51,6 +53,10 @@ contract UsingRegistry is Ownable { bytes32 constant LOCKED_CELO_REGISTRY_ID = keccak256(abi.encodePacked("LockedCelo")); bytes32 constant CELO_DISTRIBUTION_SCHEDULE_ID = keccak256(abi.encodePacked("CeloDistributionSchedule")); + bytes32 constant EPOCH_REWARDS_ID = keccak256(abi.encodePacked("EpochRewards")); + bytes32 constant EPOCH_MANAGER_INITIALIZER_ID = + keccak256(abi.encodePacked("EpochManagerInitializer")); + bytes32 constant EPOCH_MANAGER_ID = keccak256(abi.encodePacked("EpochManager")); // solhint-enable state-visibility IRegistry public registry; @@ -142,4 +148,11 @@ contract UsingRegistry is Ownable { function getCeloDistributionSchedule() internal view returns (ICeloDistributionSchedule) { return ICeloDistributionSchedule(registry.getAddressForOrDie(CELO_DISTRIBUTION_SCHEDULE_ID)); } + + function getEpochRewards() internal view returns (IEpochRewards) { + return IEpochRewards(registry.getAddressForOrDie(EPOCH_REWARDS_ID)); + } + function getEpochManagerInitializer() internal view returns (IEpochManagerInitializer) { + return IEpochManagerInitializer(registry.getAddressForOrDie(EPOCH_MANAGER_INITIALIZER_ID)); + } } diff --git a/packages/protocol/contracts/common/UsingRegistryV2.sol b/packages/protocol/contracts/common/UsingRegistryV2.sol index f49a3769215..668f7708757 100644 --- a/packages/protocol/contracts/common/UsingRegistryV2.sol +++ b/packages/protocol/contracts/common/UsingRegistryV2.sol @@ -8,8 +8,10 @@ import "./interfaces/IFeeCurrencyWhitelist.sol"; import "./interfaces/IFreezer.sol"; import "./interfaces/IRegistry.sol"; import "./interfaces/ICeloDistributionSchedule.sol"; +import "./interfaces/IEpochManagerInitializer.sol"; import "../governance/interfaces/IElection.sol"; +import "../governance/interfaces/IEpochRewards.sol"; import "../governance/interfaces/IGovernance.sol"; import "../governance/interfaces/ILockedGold.sol"; import "../governance/interfaces/ILockedCelo.sol"; @@ -59,11 +61,15 @@ contract UsingRegistryV2 { bytes32 internal constant STABLE_REAL_TOKEN_REGISTRY_ID = keccak256(abi.encodePacked("StableTokenBRL")); bytes32 internal constant VALIDATORS_REGISTRY_ID = keccak256(abi.encodePacked("Validators")); - bytes32 constant CELO_DISTRIBUTION_SCHEDULE_ID = + bytes32 internal constant CELO_DISTRIBUTION_SCHEDULE_ID = keccak256(abi.encodePacked("CeloDistributionSchedule")); bytes32 internal constant CELO_TOKEN_REGISTRY_ID = keccak256(abi.encodePacked("CeloToken")); bytes32 internal constant LOCKED_CELO_REGISTRY_ID = keccak256(abi.encodePacked("LockedCelo")); + bytes32 internal constant EPOCH_REWARDS_ID = keccak256(abi.encodePacked("EpochRewards")); + bytes32 internal constant EPOCH_MANAGER_INITIALIZER_ID = + keccak256(abi.encodePacked("EpochManagerInitializer")); + bytes32 internal constant EPOCH_MANAGER_ID = keccak256(abi.encodePacked("EpochManager")); modifier onlyRegisteredContract(bytes32 identifierHash) { require( @@ -180,4 +186,13 @@ contract UsingRegistryV2 { return ICeloDistributionSchedule(registryContract.getAddressForOrDie(CELO_DISTRIBUTION_SCHEDULE_ID)); } + + function getEpochRewards() internal view returns (IEpochRewards) { + return IEpochRewards(registryContract.getAddressForOrDie(EPOCH_REWARDS_ID)); + } + + function getEpochManagerInitializer() internal view returns (IEpochManagerInitializer) { + return + IEpochManagerInitializer(registryContract.getAddressForOrDie(EPOCH_MANAGER_INITIALIZER_ID)); + } } diff --git a/packages/protocol/contracts/common/interfaces/IEpochManagerInitializer.sol b/packages/protocol/contracts/common/interfaces/IEpochManagerInitializer.sol new file mode 100644 index 00000000000..28493d3b2cb --- /dev/null +++ b/packages/protocol/contracts/common/interfaces/IEpochManagerInitializer.sol @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity >=0.5.13 <0.9.0; + +interface IEpochManagerInitializer { + /** + * @notice Initializes the EpochManager during L2 transition. + */ + function initEpochManager() external; +} diff --git a/packages/protocol/contracts/governance/EpochRewards.sol b/packages/protocol/contracts/governance/EpochRewards.sol index 1d399b4cc46..d4d97cbd9c4 100644 --- a/packages/protocol/contracts/governance/EpochRewards.sol +++ b/packages/protocol/contracts/governance/EpochRewards.sol @@ -3,7 +3,7 @@ pragma solidity ^0.5.13; import "openzeppelin-solidity/contracts/math/SafeMath.sol"; import "openzeppelin-solidity/contracts/ownership/Ownable.sol"; -import "../common/CalledByVm.sol"; +import "./interfaces/IEpochRewards.sol"; import "../common/FixidityLib.sol"; import "../common/Freezable.sol"; import "../common/Initializable.sol"; @@ -16,12 +16,12 @@ import "../common/interfaces/ICeloVersionedContract.sol"; */ contract EpochRewards is ICeloVersionedContract, + IEpochRewards, Ownable, Initializable, UsingPrecompiles, UsingRegistry, - Freezable, - CalledByVm + Freezable { using FixidityLib for FixidityLib.Fraction; using SafeMath for uint256; @@ -84,6 +84,14 @@ contract EpochRewards is event TargetVotingYieldUpdated(uint256 fraction); + modifier onlyVmOrEpochManager() { + require( + msg.sender == address(0) || msg.sender == registry.getAddressForOrDie(EPOCH_MANAGER_ID), + "Only VM or Epoch Manager can call" + ); + _; + } + /** * @notice Sets initialized == true on implementation contracts * @param test Set to true to skip implementation initialization @@ -143,7 +151,7 @@ contract EpochRewards is * voting Gold fraction. * @dev Only called directly by the protocol. */ - function updateTargetVotingYield() external onlyVm onlyWhenNotFrozen onlyL1 { + function updateTargetVotingYield() external onlyVmOrEpochManager onlyWhenNotFrozen { _updateTargetVotingYield(); } diff --git a/packages/protocol/contracts/governance/interfaces/IEpochRewards.sol b/packages/protocol/contracts/governance/interfaces/IEpochRewards.sol new file mode 100644 index 00000000000..4cf586aa1d9 --- /dev/null +++ b/packages/protocol/contracts/governance/interfaces/IEpochRewards.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity >=0.5.13 <0.9.0; + +interface IEpochRewards { + function updateTargetVotingYield() external; + function isReserveLow() external view returns (bool); + function calculateTargetEpochRewards() external view returns (uint256, uint256, uint256, uint256); + function getTargetVotingYieldParameters() external view returns (uint256, uint256, uint256); + function getRewardsMultiplierParameters() external view returns (uint256, uint256, uint256); + function getCommunityRewardFraction() external view returns (uint256); + function getCarbonOffsettingFraction() external view returns (uint256); + function getTargetVotingGoldFraction() external view returns (uint256); + function getRewardsMultiplier() external view returns (uint256); +} diff --git a/packages/protocol/test-sol/unit/common/EpochManager.t.sol b/packages/protocol/test-sol/unit/common/EpochManager.t.sol new file mode 100644 index 00000000000..f2ba2e79ae8 --- /dev/null +++ b/packages/protocol/test-sol/unit/common/EpochManager.t.sol @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity >=0.8.7 <0.8.20; + +// import "celo-foundry-8/Test.sol"; +// import "@celo-contracts-8/common/EpochManager.sol"; + +// contract EpochManagerTest is Test { +// EpochManager epochManager; + +// uint256 firstEpochNumber = 100; +// uint256 firstEpochBlock = 100; +// address[] firstElected; + +// function setUp() public virtual { +// epochManager = new EpochManager(); +// firstElected.push(actor("validator1")); +// firstElected.push(actor("validator2")); + +// } +// } + +// contract EpochManagerinitializeSystem is EpochManagerTest { + +// function test_processCanBeStarted() public virtual{ +// // vm.prank(initializerAddress); +// epochManager.initializeSystem(firstEpochNumber, firstEpochBlock, firstElected); +// } + +// function test_Reverts_processCannotBeStartedAgain() public virtual { +// // vm.prank(initializerAddress); +// epochManager.initializeSystem(firstEpochNumber, firstEpochBlock, firstElected); +// vm.expectRevert("Epoch system already initialized"); +// epochManager.initializeSystem(firstEpochNumber, firstEpochBlock, firstElected); + +// } + +// function test_Reverts_WhenSystemInitializedByOtherContract() public virtual { +// vm.expectRevert("Not the initializer"); +// epochManager.initializeSystem(firstEpochNumber, firstEpochBlock, firstElected); + +// } + +// } From 4084bacf0fbc830396d51c240f09f01dd8e9e593 Mon Sep 17 00:00:00 2001 From: pahor167 Date: Fri, 16 Aug 2024 13:01:34 +0200 Subject: [PATCH 02/59] Compiler fixes + finishNextEpochProcess --- .../common/CeloDistributionSchedule.sol | 25 +++- .../contracts-0.8/common/EpochManager.sol | 117 ++++++++++-------- .../contracts-0.8/common/ScoreManager.sol | 55 ++++++++ .../contracts-0.8/common/UsingRegistry.sol | 7 ++ .../common/interfaces/IEpochManager.sol | 20 +-- .../common/interfaces/IScoreManager.sol | 6 + .../interfaces/ICeloDistributionSchedule.sol | 7 ++ .../common/CeloDistributionSchedule.t.sol | 30 ++--- .../test-sol/unit/common/EpochManager.t.sol | 59 +++++---- 9 files changed, 222 insertions(+), 104 deletions(-) create mode 100644 packages/protocol/contracts-0.8/common/ScoreManager.sol create mode 100644 packages/protocol/contracts-0.8/common/interfaces/IScoreManager.sol diff --git a/packages/protocol/contracts-0.8/common/CeloDistributionSchedule.sol b/packages/protocol/contracts-0.8/common/CeloDistributionSchedule.sol index 1674a07c9df..198e30ada51 100644 --- a/packages/protocol/contracts-0.8/common/CeloDistributionSchedule.sol +++ b/packages/protocol/contracts-0.8/common/CeloDistributionSchedule.sol @@ -30,6 +30,8 @@ contract CeloDistributionSchedule is UsingRegistry, ReentrancyGuard, Initializab address public communityRewardFund; address public carbonOffsettingPartner; + address public epochManagerAddress; + FixidityLib.Fraction private communityRewardFraction; FixidityLib.Fraction private carbonOffsettingFraction; @@ -41,6 +43,14 @@ contract CeloDistributionSchedule is UsingRegistry, ReentrancyGuard, Initializab _; } + modifier onlyEpochManager() { + require( + msg.sender == epochManagerAddress, + "Only the EpochManager contract can call this function." + ); + _; + } + /** * @notice Sets initialized == true on implementation contracts * @param test Set to true to skip implementation initialization @@ -50,10 +60,12 @@ contract CeloDistributionSchedule is UsingRegistry, ReentrancyGuard, Initializab /** * @notice A constructor for initialising a new instance of a CeloDistributionSchedule contract. * @param registryAddress The address of the registry core smart contract. + * @param _epochManagerAddress The address of the EpochManager contract. */ - function initialize(address registryAddress) external initializer { + function initialize(address registryAddress, address _epochManagerAddress) external initializer { _transferOwnership(msg.sender); setRegistry(registryAddress); + epochManagerAddress = _epochManagerAddress; } /** @@ -216,6 +228,17 @@ contract CeloDistributionSchedule is UsingRegistry, ReentrancyGuard, Initializab return true; } + /** + * @notice Transfers the Celo to the specified address. + * @param to The address to transfer the amount to. + * @param amount The amount to transfer. + */ + function transfer(address to, uint256 amount) external onlyEpochManager { + require(address(this).balance >= amount, "Insufficient balance."); + ICeloToken celoToken = ICeloToken(address(getCeloToken())); + celoToken.transfer(to, amount); + } + /** * @return The remaining CELO balance to distribute. */ diff --git a/packages/protocol/contracts-0.8/common/EpochManager.sol b/packages/protocol/contracts-0.8/common/EpochManager.sol index 2515b95bcce..f0688cc2aaa 100644 --- a/packages/protocol/contracts-0.8/common/EpochManager.sol +++ b/packages/protocol/contracts-0.8/common/EpochManager.sol @@ -2,6 +2,7 @@ pragma solidity >=0.8.7 <0.8.20; import "@openzeppelin/contracts8/security/ReentrancyGuard.sol"; +import "@openzeppelin/contracts8/access/Ownable.sol"; import "./interfaces/IEpochManager.sol"; import "../common/UsingRegistry.sol"; @@ -9,6 +10,8 @@ import "../common/UsingRegistry.sol"; import "../../contracts/common/Initializable.sol"; import "../../contracts/common/interfaces/ICeloVersionedContract.sol"; +import "./ScoreManager.sol"; + contract EpochManager is Initializable, UsingRegistry, @@ -24,8 +27,13 @@ contract EpochManager is uint256 rewardsBlock; } + enum EpochProcessStatus { + NotStarted, + Started + } + struct EpochProcessState { - bool started; // TODO maybe a enum for future updates + EpochProcessStatus status; // TODO maybe a enum for future updates uint256 perValidatorReward; // The per validator epoch reward. uint256 totalRewardsVoter; // The total rewards to voters. uint256 totalRewardsCommunity; // The total community reward. @@ -44,12 +52,15 @@ contract EpochManager is // TODO this should be able to get deleted easily // maybe even having it in a stadalone contract - mapping(address => uint256) public processedGroups; + mapping(address => bool) public processedGroups; EpochProcessState public epochProcessing; mapping(uint256 => Epoch) public epochs; mapping(address => uint256) public validatorPendingPayments; + address public communityRewardFund; + address public carbonOffsettingPartner; + /** * @notice Event emited when epochProcessing has begun. * @param epochNumber The epoch number that is being processed. @@ -82,10 +93,12 @@ contract EpochManager is * @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(address registryAddress, uint256 newEpochDuration, address _carbonOffsettingPartner, address _communityRewardFund) external initializer { _transferOwnership(msg.sender); setRegistry(registryAddress); setEpochDuration(newEpochDuration); + carbonOffsettingPartner = _carbonOffsettingPartner; + communityRewardFund = _communityRewardFund; } // DESIGNDESICION(XXX): we assume that the first epoch on the L2 starts as soon as the system is initialized @@ -112,7 +125,7 @@ contract EpochManager is function startNextEpochProcess() external nonReentrant { require(isReadyToStartEpoch(), "Epoch is not ready to start"); require(!isOnEpochProcess(), "Epoch process is already started"); - epochProcessing.started = true; + epochProcessing.status = EpochProcessStatus.Started; epochs[currentEpoch].rewardsBlock = block.number; @@ -140,45 +153,46 @@ contract EpochManager is function finishNextEpochProcess( address[] calldata groups, - uint16[] calldata lessers, - uint16 greaters + 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[currentEpoch].endTimestamp = block.timestamp; - // epochs[currentEpoch].lastBlock = block.number; - // // start new epoch - // currentEpoch++; - // epochs[currentEpoch].firstBlock = block.number; - // epochs[currentEpoch].startTimestamp = block.timestamp; - // // O(elected) - // for (uint i =0; i < elected.length; i++) { - // address group = validators.getGroup(elected[i]); - // if (epochProcessing.processedGroups[group] == 0) { - // epochProcessing.toProcessGroups++; - // epochProcessing.processedGroups[group] = 1; - // } - // } - // require(epochProcessing.toProcessGroups == groups.length, "number of groups does not match") - // // O(groups) - // for (uint i = 0; i < groups.length; i++) { - // // checks that group is acutally from elected group - // require(epochProcessing.processedGroups[groups[i]] == 1, "group not processed") - // // by doing this, we avoid processing a group twice - // delete epochProcessing.processedGroups[groups[i]]; - // // TODO what happens to uptime? - // uint256 epochRewards = getElection().getGroupEpochRewards(groups[i], epochProcessing.rewardsVoter, uptimes); - // getElection().distributeEpochRewards(groups[i], epochRewards, lessers[i] , greaters[i]); - // } - // celoDistributionSchedule.mint(address(CommunityFund), epochProcessing.rewardsCommunity) - // celoDistributionSchedule.mint(address(Carbon), epochProcessing.rewardsCarbonFund) - // // run elections - // elected = getElection().electValidators() - // // TODO check how to nullify stuct - // epochProcesssing.started = false; - // epochProcessing = new epochProcessState(); + require(isOnEpochProcess(), "Epoch process is not started"); + // finalize epoch + // TODO last block should be the block before and timestamp from previous block + epochs[currentEpoch].endTimestamp = block.timestamp; + epochs[currentEpoch].lastBlock = block.number - 1; + // start new epoch + currentEpoch++; + epochs[currentEpoch].firstBlock = block.number; + epochs[currentEpoch].startTimestamp = block.timestamp; + + for (uint i =0; i < elected.length; i++) { + (,,address group,,) = getValidators().getValidator(elected[i]); + if (!processedGroups[group]) { + epochProcessing.toProcessGroups++; + processedGroups[group] = true; + } + } + + require(epochProcessing.toProcessGroups == groups.length, "number of groups does not match"); + + for (uint i = 0; i < groups.length; i++) { + // checks that group is acutally 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[] memory uptimes = getScoreManager().getUptimes(groups[i]); + uint256 epochRewards = getElection().getGroupEpochRewards(groups[i], epochProcessing.totalRewardsVoter, uptimes); + getElection().distributeEpochRewards(groups[i], epochRewards, lessers[i] , greaters[i]); + } + getCeloDistributionSchedule().transfer(communityRewardFund, epochProcessing.totalRewardsCommunity); + getCeloDistributionSchedule().transfer(carbonOffsettingPartner, epochProcessing.totalRewardsCarbonFund); + // run elections + elected = getElection().electNValidatorSigners(10, 20); + // TODO check how to nullify stuct + epochProcessing.status = EpochProcessStatus.NotStarted; } function getCurrentEpoch() external view returns (uint256) { @@ -197,8 +211,8 @@ contract EpochManager is return epochs[epoch].lastBlock; } - function isOnEpochProcess() external view returns (bool) { - return epochProcessing.started; + function isOnEpochProcess() public view returns (bool) { + return epochProcessing.status == EpochProcessStatus.Started; } function isTimeForNextEpoch() external view returns (bool) { @@ -232,15 +246,18 @@ contract EpochManager is } } - // checks if process has started - function isOnEpochProcess() public view returns (bool) { - if (epochProcessing.started) { - return true; - } else { - return false; - } + /** + * @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 allocateValidatorsRewards() internal { // TODO complete this function // uint256 totalRewards = 0; diff --git a/packages/protocol/contracts-0.8/common/ScoreManager.sol b/packages/protocol/contracts-0.8/common/ScoreManager.sol new file mode 100644 index 00000000000..8371c8041ab --- /dev/null +++ b/packages/protocol/contracts-0.8/common/ScoreManager.sol @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity >=0.8.7 <0.8.20; + +import "../../contracts/common/Initializable.sol"; +import "../../contracts/common/interfaces/ICeloVersionedContract.sol"; +import "@openzeppelin/contracts8/access/Ownable.sol"; + +contract ScoreManager is Initializable, Ownable { + + mapping (address => uint256[]) public uptimes; + + /** + * @notice Sets initialized == true on implementation contracts + * @param test Set to true to skip implementation initialization + */ + constructor(bool test) public Initializable(test) {} + + /** + * @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 { + _transferOwnership(msg.sender); + } + + /** + * @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); + } + + /** + * @notice Sets the uptimes for the given addresses. + * @param _uptimes The uptimes to set. + */ + function setUptimes(address group, uint256[] calldata _uptimes) external onlyOwner { + for (uint256 i = 0; i < _uptimes.length; i++) { + uptimes[group][i] = _uptimes[i]; + } + } + + function getUptimes(address group) external view returns (uint256[] memory) { + uint256[] memory result = new uint256[](uptimes[group].length); + for (uint256 i = 0; i < uptimes[group].length; i++) { + result[i] = uptimes[group][i]; + } + return result; + } +} diff --git a/packages/protocol/contracts-0.8/common/UsingRegistry.sol b/packages/protocol/contracts-0.8/common/UsingRegistry.sol index 417ba771717..0b982e2e68d 100644 --- a/packages/protocol/contracts-0.8/common/UsingRegistry.sol +++ b/packages/protocol/contracts-0.8/common/UsingRegistry.sol @@ -22,6 +22,7 @@ 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/IScoreManager.sol"; import "../../contracts/common/interfaces/IFeeHandlerSeller.sol"; @@ -57,6 +58,8 @@ contract UsingRegistry is Ownable { bytes32 constant EPOCH_MANAGER_INITIALIZER_ID = keccak256(abi.encodePacked("EpochManagerInitializer")); bytes32 constant EPOCH_MANAGER_ID = keccak256(abi.encodePacked("EpochManager")); + bytes32 constant SCORE_MANAGER_ID = + keccak256(abi.encodePacked("ScoreManager")); // solhint-enable state-visibility IRegistry public registry; @@ -150,4 +153,8 @@ contract UsingRegistry is Ownable { function getEpochManager() internal view returns (IEpochManager) { return IEpochManager(registry.getAddressForOrDie(EPOCH_MANAGER_ID)); } + + function getScoreManager() internal view returns (IScoreManager) { + return IScoreManager(registry.getAddressForOrDie(SCORE_MANAGER_ID)); + } } diff --git a/packages/protocol/contracts-0.8/common/interfaces/IEpochManager.sol b/packages/protocol/contracts-0.8/common/interfaces/IEpochManager.sol index bc94187e0f2..91bb16fe601 100644 --- a/packages/protocol/contracts-0.8/common/interfaces/IEpochManager.sol +++ b/packages/protocol/contracts-0.8/common/interfaces/IEpochManager.sol @@ -12,13 +12,17 @@ interface IEpochManager is IBlocker { // uint256 firstEpochTimestamp, // TODO: do we need END timestamp? address[] calldata firstElected ) external; - function startNextEpochProcess() external; - function finishNextEpochProcess() external; - function getCurrentEpoch() external view returns (uint256); - function getElected() external view returns (address[] memory); - function getElectedAtEpoch(uint256 epoch) external view returns (address[] memory); - function getFirstBlockAtEpoch(uint256 epoch) external view returns (uint256); - function getLastBlockAtEpoch(uint256 epoch) external view returns (uint256); + // function startNextEpochProcess() external; + // function finishNextEpochProcess( + // address[] calldata groups, + // uint16[] calldata lessers, + // uint16 greaters + // ) external; + // function getCurrentEpoch() external view returns (uint256); + // function getElected() external view returns (address[] memory); + // // function getElectedAtEpoch(uint256 epoch) external view returns (address[] memory); + // function getFirstBlockAtEpoch(uint256 epoch) external view returns (uint256); + // function getLastBlockAtEpoch(uint256 epoch) external view returns (uint256); - function isOnEpochProcess() external view returns (bool); + // function isOnEpochProcess() external view returns (bool); } diff --git a/packages/protocol/contracts-0.8/common/interfaces/IScoreManager.sol b/packages/protocol/contracts-0.8/common/interfaces/IScoreManager.sol new file mode 100644 index 00000000000..1dad3a164a2 --- /dev/null +++ b/packages/protocol/contracts-0.8/common/interfaces/IScoreManager.sol @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity >=0.8.7 <0.8.20; + +interface IScoreManager { + function getUptimes(address group) external view returns (uint256[] memory); +} diff --git a/packages/protocol/contracts/common/interfaces/ICeloDistributionSchedule.sol b/packages/protocol/contracts/common/interfaces/ICeloDistributionSchedule.sol index 80eeb0b3b5c..d86e4e9ed50 100644 --- a/packages/protocol/contracts/common/interfaces/ICeloDistributionSchedule.sol +++ b/packages/protocol/contracts/common/interfaces/ICeloDistributionSchedule.sol @@ -22,4 +22,11 @@ interface ICeloDistributionSchedule { * @return The target CELO supply according to the target schedule. */ function getTargetCeloTotalSupply() external returns (uint256, uint256, uint256); + + /** + * @notice Transfers the Celo to the specified address. + * @param to The address to transfer the amount to. + * @param amount The amount to transfer. + */ + function transfer(address to, uint256 amount) external ; } diff --git a/packages/protocol/test-sol/unit/common/CeloDistributionSchedule.t.sol b/packages/protocol/test-sol/unit/common/CeloDistributionSchedule.t.sol index 88d051adca1..99df5cb70aa 100644 --- a/packages/protocol/test-sol/unit/common/CeloDistributionSchedule.t.sol +++ b/packages/protocol/test-sol/unit/common/CeloDistributionSchedule.t.sol @@ -100,7 +100,7 @@ contract CeloDistributionScheduleTest is Test, TestConstants, IsL2Check { vm.deal(address(celoDistributionSchedule), L2_INITIAL_STASH_BALANCE); vm.prank(celoDistributionOwner); - celoDistributionSchedule.initialize(REGISTRY_ADDRESS); + celoDistributionSchedule.initialize(REGISTRY_ADDRESS, address(0)); vm.prank(celoDistributionOwner); @@ -122,7 +122,7 @@ contract CeloDistributionScheduleTest_initialize is CeloDistributionScheduleTest celoDistributionSchedule = new CeloDistributionSchedule(true); registry.setAddressFor("CeloDistributionSchedule", address(celoDistributionSchedule)); vm.prank(celoDistributionOwner); - celoDistributionSchedule.initialize(REGISTRY_ADDRESS); + celoDistributionSchedule.initialize(REGISTRY_ADDRESS, address(0)); } function test_ShouldSetAnOwnerToCeloDistributionScheduleInstance() public { @@ -150,7 +150,7 @@ contract CeloDistributionScheduleTest_initialize is CeloDistributionScheduleTest celoDistributionSchedule = new CeloDistributionSchedule(true); registry.setAddressFor("CeloDistributionSchedule", address(celoDistributionSchedule)); vm.expectRevert("Cannot register the null address"); - celoDistributionSchedule.initialize(address(0)); + celoDistributionSchedule.initialize(address(0), address(0)); } function test_Reverts_WhenReceivingNativeTokens() public { @@ -173,7 +173,7 @@ contract CeloDistributionScheduleTest_activate_L1 is CeloDistributionScheduleTes celoDistributionSchedule = new CeloDistributionSchedule(true); registry.setAddressFor("CeloDistributionSchedule", address(celoDistributionSchedule)); - celoDistributionSchedule.initialize(REGISTRY_ADDRESS); + celoDistributionSchedule.initialize(REGISTRY_ADDRESS, address(0)); } function test_Reverts_WhenCalledOnL1() public { @@ -208,7 +208,7 @@ contract CeloDistributionScheduleTest_activate is CeloDistributionScheduleTest { vm.warp(block.timestamp + l2StartTime); celoDistributionSchedule = new CeloDistributionSchedule(true); registry.setAddressFor("CeloDistributionSchedule", address(celoDistributionSchedule)); - celoDistributionSchedule.initialize(REGISTRY_ADDRESS); + celoDistributionSchedule.initialize(REGISTRY_ADDRESS, address(0)); vm.deal(address(celoDistributionSchedule), L2_INITIAL_STASH_BALANCE); vm.expectRevert( @@ -226,7 +226,7 @@ contract CeloDistributionScheduleTest_activate is CeloDistributionScheduleTest { vm.warp(block.timestamp + l2StartTime); celoDistributionSchedule = new CeloDistributionSchedule(true); registry.setAddressFor("CeloDistributionSchedule", address(celoDistributionSchedule)); - celoDistributionSchedule.initialize(REGISTRY_ADDRESS); + celoDistributionSchedule.initialize(REGISTRY_ADDRESS, address(0)); vm.deal(address(celoDistributionSchedule), L2_INITIAL_STASH_BALANCE); vm.expectRevert("Partner cannot be the zero address."); @@ -244,7 +244,7 @@ contract CeloDistributionScheduleTest_activate is CeloDistributionScheduleTest { celoDistributionSchedule = new CeloDistributionSchedule(true); registry.setAddressFor("CeloDistributionSchedule", address(celoDistributionSchedule)); vm.deal(address(celoDistributionSchedule), L2_INITIAL_STASH_BALANCE); - celoDistributionSchedule.initialize(PROXY_ADMIN_ADDRESS); + celoDistributionSchedule.initialize(PROXY_ADMIN_ADDRESS, address(0)); vm.expectRevert("identifier has no registry entry"); celoDistributionSchedule.activate( @@ -277,7 +277,7 @@ contract CeloDistributionScheduleTest_activate is CeloDistributionScheduleTest { vm.deal(address(celoDistributionSchedule), L2_INITIAL_STASH_BALANCE); vm.prank(celoDistributionOwner); - celoDistributionSchedule.initialize(REGISTRY_ADDRESS); + celoDistributionSchedule.initialize(REGISTRY_ADDRESS, address(0)); vm.deal(address(celoDistributionSchedule), 0); @@ -299,7 +299,7 @@ contract CeloDistributionScheduleTest_activate is CeloDistributionScheduleTest { vm.deal(address(celoDistributionSchedule), L2_INITIAL_STASH_BALANCE); vm.prank(celoDistributionOwner); - celoDistributionSchedule.initialize(REGISTRY_ADDRESS); + celoDistributionSchedule.initialize(REGISTRY_ADDRESS, address(0)); vm.expectRevert("identifier has no registry entry"); @@ -321,7 +321,7 @@ contract CeloDistributionScheduleTest_activate is CeloDistributionScheduleTest { vm.deal(address(celoDistributionSchedule), L2_INITIAL_STASH_BALANCE); vm.prank(celoDistributionOwner); - celoDistributionSchedule.initialize(REGISTRY_ADDRESS); + celoDistributionSchedule.initialize(REGISTRY_ADDRESS, address(0)); vm.expectRevert("CeloDistributionSchedule address is incorrectly set in Registry."); @@ -374,7 +374,7 @@ contract CeloDistributionScheduleTest_setCommunityRewardFraction is CeloDistribu celoDistributionSchedule = new CeloDistributionSchedule(true); registry.setAddressFor("CeloDistributionSchedule", address(celoDistributionSchedule)); vm.prank(celoDistributionOwner); - celoDistributionSchedule.initialize(REGISTRY_ADDRESS); + celoDistributionSchedule.initialize(REGISTRY_ADDRESS, address(0)); vm.expectRevert("Distribution schedule has not been activated."); vm.prank(celoDistributionOwner); @@ -456,7 +456,7 @@ contract CeloDistributionScheduleTest_setCarbonOffsettingFund is CeloDistributio celoDistributionSchedule = new CeloDistributionSchedule(true); registry.setAddressFor("CeloDistributionSchedule", address(celoDistributionSchedule)); vm.prank(celoDistributionOwner); - celoDistributionSchedule.initialize(REGISTRY_ADDRESS); + celoDistributionSchedule.initialize(REGISTRY_ADDRESS, address(0)); vm.expectRevert("Distribution schedule has not been activated."); vm.prank(celoDistributionOwner); @@ -497,7 +497,7 @@ contract CeloDistributionScheduleTest_distributeAccordingToSchedule_L1 is celoDistributionSchedule = new CeloDistributionSchedule(true); registry.setAddressFor("CeloDistributionSchedule", address(celoDistributionSchedule)); - celoDistributionSchedule.initialize(REGISTRY_ADDRESS); + celoDistributionSchedule.initialize(REGISTRY_ADDRESS, address(0)); } function test_Reverts_WhenDistributingOnL1() public { @@ -521,7 +521,7 @@ contract CeloDistributionScheduleTest_distributeAccordingToSchedule is function test_Reverts_WhenDependenciesAreNotSet() public { celoDistributionSchedule = new CeloDistributionSchedule(true); registry.setAddressFor("CeloDistributionSchedule", address(celoDistributionSchedule)); - celoDistributionSchedule.initialize(REGISTRY_ADDRESS); + celoDistributionSchedule.initialize(REGISTRY_ADDRESS, address(0)); vm.expectRevert("Distribution schedule has not been activated."); vm.prank(randomAddress); @@ -733,7 +733,7 @@ contract CeloDistributionScheduleTest_getDistributableAmount is CeloDistribution function test_Reverts_WhenDependenciesNotSet() public { celoDistributionSchedule = new CeloDistributionSchedule(true); registry.setAddressFor("CeloDistributionSchedule", address(celoDistributionSchedule)); - celoDistributionSchedule.initialize(REGISTRY_ADDRESS); + celoDistributionSchedule.initialize(REGISTRY_ADDRESS, address(0)); vm.expectRevert("Distribution schedule has not been activated."); diff --git a/packages/protocol/test-sol/unit/common/EpochManager.t.sol b/packages/protocol/test-sol/unit/common/EpochManager.t.sol index f2ba2e79ae8..124b289e985 100644 --- a/packages/protocol/test-sol/unit/common/EpochManager.t.sol +++ b/packages/protocol/test-sol/unit/common/EpochManager.t.sol @@ -1,43 +1,42 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity >=0.8.7 <0.8.20; -// import "celo-foundry-8/Test.sol"; -// import "@celo-contracts-8/common/EpochManager.sol"; +import "celo-foundry-8/Test.sol"; +import "@celo-contracts-8/common/EpochManager.sol"; -// contract EpochManagerTest is Test { -// EpochManager epochManager; +contract EpochManagerTest is Test { + EpochManager epochManager; -// uint256 firstEpochNumber = 100; -// uint256 firstEpochBlock = 100; -// address[] firstElected; + uint256 firstEpochNumber = 100; + uint256 firstEpochBlock = 100; + address[] firstElected; -// function setUp() public virtual { -// epochManager = new EpochManager(); -// firstElected.push(actor("validator1")); -// firstElected.push(actor("validator2")); + function setUp() public virtual { + epochManager = new EpochManager(true); + firstElected.push(actor("validator1")); + firstElected.push(actor("validator2")); -// } -// } + } +} -// contract EpochManagerinitializeSystem is EpochManagerTest { +contract EpochManagerinitializeSystem is EpochManagerTest { -// function test_processCanBeStarted() public virtual{ -// // vm.prank(initializerAddress); -// epochManager.initializeSystem(firstEpochNumber, firstEpochBlock, firstElected); -// } + function test_processCanBeStarted() public virtual{ + // vm.prank(initializerAddress); + epochManager.initializeSystem(firstEpochNumber, firstEpochBlock, firstElected); + } -// function test_Reverts_processCannotBeStartedAgain() public virtual { -// // vm.prank(initializerAddress); -// epochManager.initializeSystem(firstEpochNumber, firstEpochBlock, firstElected); -// vm.expectRevert("Epoch system already initialized"); -// epochManager.initializeSystem(firstEpochNumber, firstEpochBlock, firstElected); + function test_Reverts_processCannotBeStartedAgain() public virtual { + // vm.prank(initializerAddress); + epochManager.initializeSystem(firstEpochNumber, firstEpochBlock, firstElected); + vm.expectRevert("Epoch system already initialized"); + epochManager.initializeSystem(firstEpochNumber, firstEpochBlock, firstElected); -// } + } -// function test_Reverts_WhenSystemInitializedByOtherContract() public virtual { -// vm.expectRevert("Not the initializer"); -// epochManager.initializeSystem(firstEpochNumber, firstEpochBlock, firstElected); + function test_Reverts_WhenSystemInitializedByOtherContract() public virtual { + vm.expectRevert("Not the initializer"); + epochManager.initializeSystem(firstEpochNumber, firstEpochBlock, firstElected); -// } - -// } + } +} From f3fed4880afdb47db80ccac3caa8c7f899b1f7a5 Mon Sep 17 00:00:00 2001 From: pahor167 Date: Fri, 16 Aug 2024 14:43:56 +0200 Subject: [PATCH 03/59] allocateValidatorsRewards compilable --- .../contracts-0.8/common/EpochManager.sol | 33 ++++++++++--------- .../contracts-0.8/common/ScoreManager.sol | 9 +++++ .../common/interfaces/IScoreManager.sol | 2 ++ .../common/interfaces/IStableToken.sol | 31 +++++++++++++++++ .../contracts/governance/Validators.sol | 33 +++++++++++++++++++ .../governance/interfaces/IValidators.sol | 5 +++ 6 files changed, 98 insertions(+), 15 deletions(-) create mode 100644 packages/protocol/contracts-0.8/common/interfaces/IStableToken.sol diff --git a/packages/protocol/contracts-0.8/common/EpochManager.sol b/packages/protocol/contracts-0.8/common/EpochManager.sol index f0688cc2aaa..88a488dbbd7 100644 --- a/packages/protocol/contracts-0.8/common/EpochManager.sol +++ b/packages/protocol/contracts-0.8/common/EpochManager.sol @@ -5,13 +5,13 @@ import "@openzeppelin/contracts8/security/ReentrancyGuard.sol"; import "@openzeppelin/contracts8/access/Ownable.sol"; import "./interfaces/IEpochManager.sol"; +import "./interfaces/IOracle.sol"; +import "./interfaces/IStableToken.sol"; import "../common/UsingRegistry.sol"; import "../../contracts/common/Initializable.sol"; import "../../contracts/common/interfaces/ICeloVersionedContract.sol"; -import "./ScoreManager.sol"; - contract EpochManager is Initializable, UsingRegistry, @@ -260,18 +260,21 @@ contract EpochManager is function allocateValidatorsRewards() internal { // TODO complete this function - // uint256 totalRewards = 0; - // for (uint i = 0; i < elected.length; i++) { - // uint256 validatorScore = scoreManager.getValidatorScore(elected[i]); - // uint256 validatorReward = validators.computeEpochReward(elected[i], validatorScore, epochProcessing.maxRewardsValidator); - // validatorPendingPayments[elected[i]] += validatorReward; - // totalRewards += validatorReward; - // } - // // Mint all cUSD required for payment and the corresponding CELO - // StablToken.mint(address(this), totalRewards) - // // this should have a setter for the oracle. - // CELOequivalent = IOracle(oracleAddress).getRate()*totalRewards - // // this is not a mint anymore - // distributionSchedule.mintCelo(address(reserve), CELOequivalent) + uint256 totalRewards = 0; + for (uint i = 0; i < elected.length; i++) { + uint256 validatorScore = getScoreManager().getValidatorScore(elected[i]); + uint256 validatorReward = getValidators().computeEpochReward(elected[i], validatorScore, epochProcessing.perValidatorReward); + validatorPendingPayments[elected[i]] += validatorReward; + totalRewards += validatorReward; + } + // Mint all cUSD required for payment and the corresponding CELO + IStableToken(getStableToken()).mint(address(this), 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 + getCeloDistributionSchedule().transfer(registry.getAddressForOrDie(RESERVE_REGISTRY_ID), CELOequivalent); } } diff --git a/packages/protocol/contracts-0.8/common/ScoreManager.sol b/packages/protocol/contracts-0.8/common/ScoreManager.sol index 8371c8041ab..818ffb8b9aa 100644 --- a/packages/protocol/contracts-0.8/common/ScoreManager.sol +++ b/packages/protocol/contracts-0.8/common/ScoreManager.sol @@ -8,6 +8,7 @@ import "@openzeppelin/contracts8/access/Ownable.sol"; contract ScoreManager is Initializable, Ownable { mapping (address => uint256[]) public uptimes; + mapping (address => uint256) public scores; /** * @notice Sets initialized == true on implementation contracts @@ -52,4 +53,12 @@ contract ScoreManager is Initializable, Ownable { } return result; } + + function getValidatorScore(address group) external view returns (uint256) { + return scores[group]; + } + + function setValidatorScore(address group, uint256 score) external onlyOwner { + scores[group] = score; + } } diff --git a/packages/protocol/contracts-0.8/common/interfaces/IScoreManager.sol b/packages/protocol/contracts-0.8/common/interfaces/IScoreManager.sol index 1dad3a164a2..488f1a95ed4 100644 --- a/packages/protocol/contracts-0.8/common/interfaces/IScoreManager.sol +++ b/packages/protocol/contracts-0.8/common/interfaces/IScoreManager.sol @@ -3,4 +3,6 @@ pragma solidity >=0.8.7 <0.8.20; interface IScoreManager { function getUptimes(address group) external view returns (uint256[] memory); + + function getValidatorScore(address group) external view returns (uint256); } diff --git a/packages/protocol/contracts-0.8/common/interfaces/IStableToken.sol b/packages/protocol/contracts-0.8/common/interfaces/IStableToken.sol new file mode 100644 index 00000000000..5dc3a65815d --- /dev/null +++ b/packages/protocol/contracts-0.8/common/interfaces/IStableToken.sol @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity >=0.8.7 <0.8.20; + +/** + * @title This interface describes the functions specific to Celo Stable Tokens, and in the + * absence of interface inheritance is intended as a companion to IERC20.sol and ICeloToken.sol. + */ +interface IStableToken { + function mint(address, uint256) external returns (bool); + + function burn(uint256) external returns (bool); + + function setInflationParameters(uint256, uint256) external; + + function valueToUnits(uint256) external view returns (uint256); + + function unitsToValue(uint256) external view returns (uint256); + + function getInflationParameters() + external + view + returns ( + uint256, + uint256, + uint256, + uint256 + ); + + // NOTE: duplicated with IERC20.sol, remove once interface inheritance is supported. + function balanceOf(address) external view returns (uint256); +} diff --git a/packages/protocol/contracts/governance/Validators.sol b/packages/protocol/contracts/governance/Validators.sol index 2f5ce7b2af5..e303df27804 100644 --- a/packages/protocol/contracts/governance/Validators.sol +++ b/packages/protocol/contracts/governance/Validators.sol @@ -1395,4 +1395,37 @@ contract Validators is emit ValidatorDeaffiliated(validatorAccount, affiliation); return true; } + + /** + * @notice Computes epoch payments to the account + * @param account The validator signer of the validator to distribute the epoch payment to. + * @param maxPayment The maximum payment to the validator. Actual payment is based on score and + * group commission. + * @return The total payment paid to the validator and their group. + */ + function computeEpochReward( + address account, + uint256 score, + uint256 maxPayment + ) external view returns (uint256) { + require(isValidator(account), "Not a validator"); + FixidityLib.Fraction memory scoreFraction = FixidityLib.wrap(score); + require(scoreFraction.lte(FixidityLib.fixed1()), "Score must be <= 1"); + + // The group that should be paid is the group that the validator was a member of at the + // time it was elected. + address group = getMembershipInLastEpoch(account); + require(group != address(0), "Validator not registered with a group"); + // Both the validator and the group must maintain the minimum locked gold balance in order to + // receive epoch payments. + if (meetsAccountLockedGoldRequirements(account) && meetsAccountLockedGoldRequirements(group)) { + FixidityLib.Fraction memory totalPayment = FixidityLib + .newFixed(maxPayment) + .multiply(scoreFraction) + .multiply(groups[group].slashInfo.multiplier); + return totalPayment.fromFixed(); + } else { + return 0; + } + } } diff --git a/packages/protocol/contracts/governance/interfaces/IValidators.sol b/packages/protocol/contracts/governance/interfaces/IValidators.sol index 6d8a0f92e28..b1cb1463ad7 100644 --- a/packages/protocol/contracts/governance/interfaces/IValidators.sol +++ b/packages/protocol/contracts/governance/interfaces/IValidators.sol @@ -85,4 +85,9 @@ interface IValidators { function getValidatorGroupSlashingMultiplier(address) external view returns (uint256); function getMembershipInLastEpoch(address) external view returns (address); function getMembershipInLastEpochFromSigner(address) external view returns (address); + function computeEpochReward( + address account, + uint256 score, + uint256 maxPayment + ) external view returns (uint256); } From 0b473c231b9f5c3b23a2d28d01e3d8728d344434 Mon Sep 17 00:00:00 2001 From: pahor167 Date: Fri, 16 Aug 2024 15:18:00 +0200 Subject: [PATCH 04/59] First few tests --- .../test-sol/unit/common/EpochManager.t.sol | 37 ++++++++++++++++--- 1 file changed, 31 insertions(+), 6 deletions(-) diff --git a/packages/protocol/test-sol/unit/common/EpochManager.t.sol b/packages/protocol/test-sol/unit/common/EpochManager.t.sol index 124b289e985..82a11caaff5 100644 --- a/packages/protocol/test-sol/unit/common/EpochManager.t.sol +++ b/packages/protocol/test-sol/unit/common/EpochManager.t.sol @@ -3,31 +3,56 @@ pragma solidity >=0.8.7 <0.8.20; import "celo-foundry-8/Test.sol"; import "@celo-contracts-8/common/EpochManager.sol"; +import { TestConstants } from "@test-sol/constants.sol"; -contract EpochManagerTest is Test { +import "@celo-contracts/stability/test/MockSortedOracles.sol"; + +contract EpochManagerTest is Test, TestConstants { EpochManager epochManager; + MockSortedOracles sortedOracles; + address epochManagerInitializer; + address carbonOffsettingPartner; + address communityRewardFund; uint256 firstEpochNumber = 100; uint256 firstEpochBlock = 100; address[] firstElected; + IRegistry registry; + function setUp() public virtual { epochManager = new EpochManager(true); - firstElected.push(actor("validator1")); - firstElected.push(actor("validator2")); + sortedOracles = new MockSortedOracles(); + + + + firstElected.push(actor("validator1")); + firstElected.push(actor("validator2")); + + epochManagerInitializer = actor("initializer"); + carbonOffsettingPartner = actor("carbonOffsettingPartner"); + communityRewardFund = actor("communityRewardFund"); + + deployCodeTo("Registry.sol", abi.encode(false), REGISTRY_ADDRESS); + registry = IRegistry(REGISTRY_ADDRESS); + + registry.setAddressFor("EpochManagerInitializer", epochManagerInitializer); + registry.setAddressFor("SortedOracles", address(sortedOracles)); + + epochManager.initialize(REGISTRY_ADDRESS, 10, carbonOffsettingPartner, communityRewardFund); } } contract EpochManagerinitializeSystem is EpochManagerTest { function test_processCanBeStarted() public virtual{ - // vm.prank(initializerAddress); + vm.prank(epochManagerInitializer); epochManager.initializeSystem(firstEpochNumber, firstEpochBlock, firstElected); } function test_Reverts_processCannotBeStartedAgain() public virtual { - // vm.prank(initializerAddress); + vm.startPrank(epochManagerInitializer); epochManager.initializeSystem(firstEpochNumber, firstEpochBlock, firstElected); vm.expectRevert("Epoch system already initialized"); epochManager.initializeSystem(firstEpochNumber, firstEpochBlock, firstElected); @@ -35,7 +60,7 @@ contract EpochManagerinitializeSystem is EpochManagerTest { } function test_Reverts_WhenSystemInitializedByOtherContract() public virtual { - vm.expectRevert("Not the initializer"); + vm.expectRevert("msg.sender is not Initializer"); epochManager.initializeSystem(firstEpochNumber, firstEpochBlock, firstElected); } From b012f2902e84194f8c7c1c63371106e3ba87cc6e Mon Sep 17 00:00:00 2001 From: pahor167 Date: Fri, 16 Aug 2024 18:45:48 +0200 Subject: [PATCH 05/59] test fix --- .../contracts-0.8/common/EpochManager.sol | 2 +- .../test-sol/unit/common/EpochManager.t.sol | 16 ++++++++++++++-- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/packages/protocol/contracts-0.8/common/EpochManager.sol b/packages/protocol/contracts-0.8/common/EpochManager.sol index 88a488dbbd7..ae209e9df1b 100644 --- a/packages/protocol/contracts-0.8/common/EpochManager.sol +++ b/packages/protocol/contracts-0.8/common/EpochManager.sol @@ -109,7 +109,7 @@ contract EpochManager is uint256 firstEpochBlock, address[] memory firstElected ) external onlyEpochManagerInitializer { - require(systemAlreadyInitialized(), "Epoch system already initialized"); + require(!systemAlreadyInitialized(), "Epoch system already initialized"); firstKnownEpoch = firstEpochNumber; currentEpoch = firstEpochNumber; diff --git a/packages/protocol/test-sol/unit/common/EpochManager.t.sol b/packages/protocol/test-sol/unit/common/EpochManager.t.sol index 82a11caaff5..27fb9cfe2e4 100644 --- a/packages/protocol/test-sol/unit/common/EpochManager.t.sol +++ b/packages/protocol/test-sol/unit/common/EpochManager.t.sol @@ -44,6 +44,20 @@ contract EpochManagerTest is Test, TestConstants { } } +contract EpochManagerInitialize is EpochManagerTest { + function test_initialize() public virtual { + assertEq(address(epochManager.registry()), REGISTRY_ADDRESS); + assertEq(epochManager.epochDuration(), 10); + assertEq(epochManager.carbonOffsettingPartner(), carbonOffsettingPartner); + assertEq(epochManager.communityRewardFund(), communityRewardFund); + } + + function test_Reverts_WhenAlreadyInitialized() public virtual { + vm.expectRevert("contract already initialized"); + epochManager.initialize(REGISTRY_ADDRESS, 10, carbonOffsettingPartner, communityRewardFund); + } +} + contract EpochManagerinitializeSystem is EpochManagerTest { function test_processCanBeStarted() public virtual{ @@ -56,12 +70,10 @@ contract EpochManagerinitializeSystem is EpochManagerTest { epochManager.initializeSystem(firstEpochNumber, firstEpochBlock, firstElected); vm.expectRevert("Epoch system already initialized"); epochManager.initializeSystem(firstEpochNumber, firstEpochBlock, firstElected); - } function test_Reverts_WhenSystemInitializedByOtherContract() public virtual { vm.expectRevert("msg.sender is not Initializer"); epochManager.initializeSystem(firstEpochNumber, firstEpochBlock, firstElected); - } } From 3a4f87a4bd57daa1359d88a76cc00eb26deeef1d Mon Sep 17 00:00:00 2001 From: soloseng <102702451+soloseng@users.noreply.github.com> Date: Fri, 16 Aug 2024 13:51:38 -0400 Subject: [PATCH 06/59] uisng registry instead --- .../common/CeloDistributionSchedule.sol | 30 +++++++++---------- .../common/CeloDistributionSchedule.t.sol | 30 +++++++++---------- 2 files changed, 29 insertions(+), 31 deletions(-) diff --git a/packages/protocol/contracts-0.8/common/CeloDistributionSchedule.sol b/packages/protocol/contracts-0.8/common/CeloDistributionSchedule.sol index 198e30ada51..8256d960bf7 100644 --- a/packages/protocol/contracts-0.8/common/CeloDistributionSchedule.sol +++ b/packages/protocol/contracts-0.8/common/CeloDistributionSchedule.sol @@ -30,7 +30,6 @@ contract CeloDistributionSchedule is UsingRegistry, ReentrancyGuard, Initializab address public communityRewardFund; address public carbonOffsettingPartner; - address public epochManagerAddress; FixidityLib.Fraction private communityRewardFraction; FixidityLib.Fraction private carbonOffsettingFraction; @@ -45,7 +44,7 @@ contract CeloDistributionSchedule is UsingRegistry, ReentrancyGuard, Initializab modifier onlyEpochManager() { require( - msg.sender == epochManagerAddress, + msg.sender == registry.getAddressForOrDie(EPOCH_MANAGER_ID), "Only the EpochManager contract can call this function." ); _; @@ -60,12 +59,11 @@ contract CeloDistributionSchedule is UsingRegistry, ReentrancyGuard, Initializab /** * @notice A constructor for initialising a new instance of a CeloDistributionSchedule contract. * @param registryAddress The address of the registry core smart contract. - * @param _epochManagerAddress The address of the EpochManager contract. + */ - function initialize(address registryAddress, address _epochManagerAddress) external initializer { + function initialize(address registryAddress) external initializer { _transferOwnership(msg.sender); setRegistry(registryAddress); - epochManagerAddress = _epochManagerAddress; } /** @@ -135,6 +133,17 @@ contract CeloDistributionSchedule is UsingRegistry, ReentrancyGuard, Initializab return true; } + /** + * @notice Transfers the Celo to the specified address. + * @param to The address to transfer the amount to. + * @param amount The amount to transfer. + */ + function transfer(address to, uint256 amount) external onlyEpochManager { + require(address(this).balance >= amount, "Insufficient balance."); + ICeloToken celoToken = ICeloToken(address(getCeloToken())); + celoToken.transfer(to, amount); + } + /** * @notice Returns the community reward fraction. * @return The percentage of total reward which goes to the community funds. @@ -228,17 +237,6 @@ contract CeloDistributionSchedule is UsingRegistry, ReentrancyGuard, Initializab return true; } - /** - * @notice Transfers the Celo to the specified address. - * @param to The address to transfer the amount to. - * @param amount The amount to transfer. - */ - function transfer(address to, uint256 amount) external onlyEpochManager { - require(address(this).balance >= amount, "Insufficient balance."); - ICeloToken celoToken = ICeloToken(address(getCeloToken())); - celoToken.transfer(to, amount); - } - /** * @return The remaining CELO balance to distribute. */ diff --git a/packages/protocol/test-sol/unit/common/CeloDistributionSchedule.t.sol b/packages/protocol/test-sol/unit/common/CeloDistributionSchedule.t.sol index 99df5cb70aa..88d051adca1 100644 --- a/packages/protocol/test-sol/unit/common/CeloDistributionSchedule.t.sol +++ b/packages/protocol/test-sol/unit/common/CeloDistributionSchedule.t.sol @@ -100,7 +100,7 @@ contract CeloDistributionScheduleTest is Test, TestConstants, IsL2Check { vm.deal(address(celoDistributionSchedule), L2_INITIAL_STASH_BALANCE); vm.prank(celoDistributionOwner); - celoDistributionSchedule.initialize(REGISTRY_ADDRESS, address(0)); + celoDistributionSchedule.initialize(REGISTRY_ADDRESS); vm.prank(celoDistributionOwner); @@ -122,7 +122,7 @@ contract CeloDistributionScheduleTest_initialize is CeloDistributionScheduleTest celoDistributionSchedule = new CeloDistributionSchedule(true); registry.setAddressFor("CeloDistributionSchedule", address(celoDistributionSchedule)); vm.prank(celoDistributionOwner); - celoDistributionSchedule.initialize(REGISTRY_ADDRESS, address(0)); + celoDistributionSchedule.initialize(REGISTRY_ADDRESS); } function test_ShouldSetAnOwnerToCeloDistributionScheduleInstance() public { @@ -150,7 +150,7 @@ contract CeloDistributionScheduleTest_initialize is CeloDistributionScheduleTest celoDistributionSchedule = new CeloDistributionSchedule(true); registry.setAddressFor("CeloDistributionSchedule", address(celoDistributionSchedule)); vm.expectRevert("Cannot register the null address"); - celoDistributionSchedule.initialize(address(0), address(0)); + celoDistributionSchedule.initialize(address(0)); } function test_Reverts_WhenReceivingNativeTokens() public { @@ -173,7 +173,7 @@ contract CeloDistributionScheduleTest_activate_L1 is CeloDistributionScheduleTes celoDistributionSchedule = new CeloDistributionSchedule(true); registry.setAddressFor("CeloDistributionSchedule", address(celoDistributionSchedule)); - celoDistributionSchedule.initialize(REGISTRY_ADDRESS, address(0)); + celoDistributionSchedule.initialize(REGISTRY_ADDRESS); } function test_Reverts_WhenCalledOnL1() public { @@ -208,7 +208,7 @@ contract CeloDistributionScheduleTest_activate is CeloDistributionScheduleTest { vm.warp(block.timestamp + l2StartTime); celoDistributionSchedule = new CeloDistributionSchedule(true); registry.setAddressFor("CeloDistributionSchedule", address(celoDistributionSchedule)); - celoDistributionSchedule.initialize(REGISTRY_ADDRESS, address(0)); + celoDistributionSchedule.initialize(REGISTRY_ADDRESS); vm.deal(address(celoDistributionSchedule), L2_INITIAL_STASH_BALANCE); vm.expectRevert( @@ -226,7 +226,7 @@ contract CeloDistributionScheduleTest_activate is CeloDistributionScheduleTest { vm.warp(block.timestamp + l2StartTime); celoDistributionSchedule = new CeloDistributionSchedule(true); registry.setAddressFor("CeloDistributionSchedule", address(celoDistributionSchedule)); - celoDistributionSchedule.initialize(REGISTRY_ADDRESS, address(0)); + celoDistributionSchedule.initialize(REGISTRY_ADDRESS); vm.deal(address(celoDistributionSchedule), L2_INITIAL_STASH_BALANCE); vm.expectRevert("Partner cannot be the zero address."); @@ -244,7 +244,7 @@ contract CeloDistributionScheduleTest_activate is CeloDistributionScheduleTest { celoDistributionSchedule = new CeloDistributionSchedule(true); registry.setAddressFor("CeloDistributionSchedule", address(celoDistributionSchedule)); vm.deal(address(celoDistributionSchedule), L2_INITIAL_STASH_BALANCE); - celoDistributionSchedule.initialize(PROXY_ADMIN_ADDRESS, address(0)); + celoDistributionSchedule.initialize(PROXY_ADMIN_ADDRESS); vm.expectRevert("identifier has no registry entry"); celoDistributionSchedule.activate( @@ -277,7 +277,7 @@ contract CeloDistributionScheduleTest_activate is CeloDistributionScheduleTest { vm.deal(address(celoDistributionSchedule), L2_INITIAL_STASH_BALANCE); vm.prank(celoDistributionOwner); - celoDistributionSchedule.initialize(REGISTRY_ADDRESS, address(0)); + celoDistributionSchedule.initialize(REGISTRY_ADDRESS); vm.deal(address(celoDistributionSchedule), 0); @@ -299,7 +299,7 @@ contract CeloDistributionScheduleTest_activate is CeloDistributionScheduleTest { vm.deal(address(celoDistributionSchedule), L2_INITIAL_STASH_BALANCE); vm.prank(celoDistributionOwner); - celoDistributionSchedule.initialize(REGISTRY_ADDRESS, address(0)); + celoDistributionSchedule.initialize(REGISTRY_ADDRESS); vm.expectRevert("identifier has no registry entry"); @@ -321,7 +321,7 @@ contract CeloDistributionScheduleTest_activate is CeloDistributionScheduleTest { vm.deal(address(celoDistributionSchedule), L2_INITIAL_STASH_BALANCE); vm.prank(celoDistributionOwner); - celoDistributionSchedule.initialize(REGISTRY_ADDRESS, address(0)); + celoDistributionSchedule.initialize(REGISTRY_ADDRESS); vm.expectRevert("CeloDistributionSchedule address is incorrectly set in Registry."); @@ -374,7 +374,7 @@ contract CeloDistributionScheduleTest_setCommunityRewardFraction is CeloDistribu celoDistributionSchedule = new CeloDistributionSchedule(true); registry.setAddressFor("CeloDistributionSchedule", address(celoDistributionSchedule)); vm.prank(celoDistributionOwner); - celoDistributionSchedule.initialize(REGISTRY_ADDRESS, address(0)); + celoDistributionSchedule.initialize(REGISTRY_ADDRESS); vm.expectRevert("Distribution schedule has not been activated."); vm.prank(celoDistributionOwner); @@ -456,7 +456,7 @@ contract CeloDistributionScheduleTest_setCarbonOffsettingFund is CeloDistributio celoDistributionSchedule = new CeloDistributionSchedule(true); registry.setAddressFor("CeloDistributionSchedule", address(celoDistributionSchedule)); vm.prank(celoDistributionOwner); - celoDistributionSchedule.initialize(REGISTRY_ADDRESS, address(0)); + celoDistributionSchedule.initialize(REGISTRY_ADDRESS); vm.expectRevert("Distribution schedule has not been activated."); vm.prank(celoDistributionOwner); @@ -497,7 +497,7 @@ contract CeloDistributionScheduleTest_distributeAccordingToSchedule_L1 is celoDistributionSchedule = new CeloDistributionSchedule(true); registry.setAddressFor("CeloDistributionSchedule", address(celoDistributionSchedule)); - celoDistributionSchedule.initialize(REGISTRY_ADDRESS, address(0)); + celoDistributionSchedule.initialize(REGISTRY_ADDRESS); } function test_Reverts_WhenDistributingOnL1() public { @@ -521,7 +521,7 @@ contract CeloDistributionScheduleTest_distributeAccordingToSchedule is function test_Reverts_WhenDependenciesAreNotSet() public { celoDistributionSchedule = new CeloDistributionSchedule(true); registry.setAddressFor("CeloDistributionSchedule", address(celoDistributionSchedule)); - celoDistributionSchedule.initialize(REGISTRY_ADDRESS, address(0)); + celoDistributionSchedule.initialize(REGISTRY_ADDRESS); vm.expectRevert("Distribution schedule has not been activated."); vm.prank(randomAddress); @@ -733,7 +733,7 @@ contract CeloDistributionScheduleTest_getDistributableAmount is CeloDistribution function test_Reverts_WhenDependenciesNotSet() public { celoDistributionSchedule = new CeloDistributionSchedule(true); registry.setAddressFor("CeloDistributionSchedule", address(celoDistributionSchedule)); - celoDistributionSchedule.initialize(REGISTRY_ADDRESS, address(0)); + celoDistributionSchedule.initialize(REGISTRY_ADDRESS); vm.expectRevert("Distribution schedule has not been activated."); From ad8831d1b569d19178d3660fdb5c73103450943a Mon Sep 17 00:00:00 2001 From: soloseng <102702451+soloseng@users.noreply.github.com> Date: Fri, 16 Aug 2024 14:12:50 -0400 Subject: [PATCH 07/59] updated registry ID var names --- .../common/CeloDistributionSchedule.sol | 5 +- .../contracts-0.8/common/EpochManager.sol | 115 +++++++++++------- .../contracts-0.8/common/UsingRegistry.sol | 25 ++-- .../protocol/contracts/common/GoldToken.sol | 7 +- .../contracts/common/UsingRegistry.sol | 18 +-- .../contracts/common/UsingRegistryV2.sol | 14 +-- .../contracts/governance/EpochRewards.sol | 2 +- 7 files changed, 107 insertions(+), 79 deletions(-) diff --git a/packages/protocol/contracts-0.8/common/CeloDistributionSchedule.sol b/packages/protocol/contracts-0.8/common/CeloDistributionSchedule.sol index 8256d960bf7..2c516b00588 100644 --- a/packages/protocol/contracts-0.8/common/CeloDistributionSchedule.sol +++ b/packages/protocol/contracts-0.8/common/CeloDistributionSchedule.sol @@ -30,7 +30,6 @@ contract CeloDistributionSchedule is UsingRegistry, ReentrancyGuard, Initializab address public communityRewardFund; address public carbonOffsettingPartner; - FixidityLib.Fraction private communityRewardFraction; FixidityLib.Fraction private carbonOffsettingFraction; @@ -44,7 +43,7 @@ contract CeloDistributionSchedule is UsingRegistry, ReentrancyGuard, Initializab modifier onlyEpochManager() { require( - msg.sender == registry.getAddressForOrDie(EPOCH_MANAGER_ID), + msg.sender == registry.getAddressForOrDie(EPOCH_MANAGER_REGISTRY_ID), "Only the EpochManager contract can call this function." ); _; @@ -84,7 +83,7 @@ contract CeloDistributionSchedule is UsingRegistry, ReentrancyGuard, Initializab require(block.timestamp > _l2StartTime, "L2 start time cannot be set to a future date."); ICeloToken celoToken = ICeloToken(address(getCeloToken())); require( - registry.getAddressForOrDie(CELO_DISTRIBUTION_SCHEDULE_ID) == address(this), + registry.getAddressForOrDie(CELO_DISTRIBUTION_SCHEDULE_REGISTRY_ID) == address(this), "CeloDistributionSchedule address is incorrectly set in Registry." ); areDependenciesSet = true; diff --git a/packages/protocol/contracts-0.8/common/EpochManager.sol b/packages/protocol/contracts-0.8/common/EpochManager.sol index ae209e9df1b..dd02743fb0f 100644 --- a/packages/protocol/contracts-0.8/common/EpochManager.sol +++ b/packages/protocol/contracts-0.8/common/EpochManager.sol @@ -75,7 +75,7 @@ contract EpochManager is modifier onlyEpochManagerInitializer() { require( - msg.sender == registry.getAddressForOrDie(EPOCH_MANAGER_INITIALIZER_ID), + msg.sender == registry.getAddressForOrDie(EPOCH_MANAGER_INITIALIZER_REGISTRY_ID), "msg.sender is not Initializer" ); @@ -93,12 +93,17 @@ contract EpochManager is * @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, address _carbonOffsettingPartner, address _communityRewardFund) external initializer { + function initialize( + address registryAddress, + uint256 newEpochDuration, + address _carbonOffsettingPartner, + address _communityRewardFund + ) external initializer { _transferOwnership(msg.sender); setRegistry(registryAddress); setEpochDuration(newEpochDuration); carbonOffsettingPartner = _carbonOffsettingPartner; - communityRewardFund = _communityRewardFund; + communityRewardFund = _communityRewardFund; // governance address } // DESIGNDESICION(XXX): we assume that the first epoch on the L2 starts as soon as the system is initialized @@ -130,9 +135,7 @@ contract EpochManager is epochs[currentEpoch].rewardsBlock = block.number; // calculate rewards - // TODO: update function to allow epochManager to call. getEpochRewards().updateTargetVotingYield(); - // distributeCeloEpochPayments(); ( uint256 perValidatorReward, @@ -157,42 +160,52 @@ contract EpochManager is 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[currentEpoch].endTimestamp = block.timestamp; - epochs[currentEpoch].lastBlock = block.number - 1; - // start new epoch - currentEpoch++; - epochs[currentEpoch].firstBlock = block.number; - epochs[currentEpoch].startTimestamp = block.timestamp; - - for (uint i =0; i < elected.length; i++) { - (,,address group,,) = getValidators().getValidator(elected[i]); - if (!processedGroups[group]) { - epochProcessing.toProcessGroups++; - processedGroups[group] = true; - } - } + require(isOnEpochProcess(), "Epoch process is not started"); + // finalize epoch + // TODO last block should be the block before and timestamp from previous block + epochs[currentEpoch].endTimestamp = block.timestamp; + epochs[currentEpoch].lastBlock = block.number - 1; + // start new epoch + currentEpoch++; + epochs[currentEpoch].firstBlock = block.number; + epochs[currentEpoch].startTimestamp = block.timestamp; - require(epochProcessing.toProcessGroups == groups.length, "number of groups does not match"); - - for (uint i = 0; i < groups.length; i++) { - // checks that group is acutally 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[] memory uptimes = getScoreManager().getUptimes(groups[i]); - uint256 epochRewards = getElection().getGroupEpochRewards(groups[i], epochProcessing.totalRewardsVoter, uptimes); - getElection().distributeEpochRewards(groups[i], epochRewards, lessers[i] , greaters[i]); + for (uint i = 0; i < elected.length; i++) { + (, , address group, , ) = getValidators().getValidator(elected[i]); + if (!processedGroups[group]) { + epochProcessing.toProcessGroups++; + processedGroups[group] = true; } - getCeloDistributionSchedule().transfer(communityRewardFund, epochProcessing.totalRewardsCommunity); - getCeloDistributionSchedule().transfer(carbonOffsettingPartner, epochProcessing.totalRewardsCarbonFund); - // run elections - elected = getElection().electNValidatorSigners(10, 20); - // TODO check how to nullify stuct - epochProcessing.status = EpochProcessStatus.NotStarted; + } + + require(epochProcessing.toProcessGroups == groups.length, "number of groups does not match"); + + for (uint i = 0; i < groups.length; i++) { + // checks that group is acutally 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[] memory uptimes = getScoreManager().getUptimes(groups[i]); + uint256 epochRewards = getElection().getGroupEpochRewards( + groups[i], + epochProcessing.totalRewardsVoter, + uptimes + ); + getElection().distributeEpochRewards(groups[i], epochRewards, lessers[i], greaters[i]); + } + getCeloDistributionSchedule().transfer( + communityRewardFund, + epochProcessing.totalRewardsCommunity + ); + getCeloDistributionSchedule().transfer( + carbonOffsettingPartner, + epochProcessing.totalRewardsCarbonFund + ); + // run elections + elected = getElection().electNValidatorSigners(10, 20); + // TODO check how to nullify stuct + epochProcessing.status = EpochProcessStatus.NotStarted; } function getCurrentEpoch() external view returns (uint256) { @@ -257,24 +270,32 @@ contract EpochManager is return (1, 1, 0, 0); } - function allocateValidatorsRewards() internal { // TODO complete this function uint256 totalRewards = 0; for (uint i = 0; i < elected.length; i++) { - uint256 validatorScore = getScoreManager().getValidatorScore(elected[i]); - uint256 validatorReward = getValidators().computeEpochReward(elected[i], validatorScore, epochProcessing.perValidatorReward); - validatorPendingPayments[elected[i]] += validatorReward; - totalRewards += validatorReward; + uint256 validatorScore = getScoreManager().getValidatorScore(elected[i]); + uint256 validatorReward = getValidators().computeEpochReward( + elected[i], + validatorScore, + epochProcessing.perValidatorReward + ); + validatorPendingPayments[elected[i]] += validatorReward; + totalRewards += validatorReward; } // Mint all cUSD required for payment and the corresponding CELO IStableToken(getStableToken()).mint(address(this), totalRewards); // this should have a setter for the oracle. - (uint256 numerator, uint256 denominator) = IOracle(address(getSortedOracles())).getExchangeRate(address(getStableToken())); + (uint256 numerator, uint256 denominator) = IOracle(address(getSortedOracles())).getExchangeRate( + address(getStableToken()) + ); - uint256 CELOequivalent = numerator*totalRewards / denominator; + uint256 CELOequivalent = (numerator * totalRewards) / denominator; // this is not a mint anymore - getCeloDistributionSchedule().transfer(registry.getAddressForOrDie(RESERVE_REGISTRY_ID), CELOequivalent); + getCeloDistributionSchedule().transfer( + registry.getAddressForOrDie(RESERVE_REGISTRY_ID), + CELOequivalent + ); } } diff --git a/packages/protocol/contracts-0.8/common/UsingRegistry.sol b/packages/protocol/contracts-0.8/common/UsingRegistry.sol index 0b982e2e68d..4dc43ef7ee2 100644 --- a/packages/protocol/contracts-0.8/common/UsingRegistry.sol +++ b/packages/protocol/contracts-0.8/common/UsingRegistry.sol @@ -52,14 +52,13 @@ contract UsingRegistry is Ownable { keccak256(abi.encodePacked("MentoFeeHandlerSeller")); bytes32 constant CELO_TOKEN_REGISTRY_ID = keccak256(abi.encodePacked("CeloToken")); bytes32 constant LOCKED_CELO_REGISTRY_ID = keccak256(abi.encodePacked("LockedCelo")); - bytes32 constant CELO_DISTRIBUTION_SCHEDULE_ID = + bytes32 constant CELO_DISTRIBUTION_SCHEDULE_REGISTRY_ID = keccak256(abi.encodePacked("CeloDistributionSchedule")); - bytes32 constant EPOCH_REWARDS_ID = keccak256(abi.encodePacked("EpochRewards")); - bytes32 constant EPOCH_MANAGER_INITIALIZER_ID = + bytes32 constant EPOCH_REWARDS_REGISTRY_ID = keccak256(abi.encodePacked("EpochRewards")); + bytes32 constant EPOCH_MANAGER_INITIALIZER_REGISTRY_ID = keccak256(abi.encodePacked("EpochManagerInitializer")); - bytes32 constant EPOCH_MANAGER_ID = keccak256(abi.encodePacked("EpochManager")); - bytes32 constant SCORE_MANAGER_ID = - keccak256(abi.encodePacked("ScoreManager")); + bytes32 constant EPOCH_MANAGER_REGISTRY_ID = keccak256(abi.encodePacked("EpochManager")); + bytes32 constant SCORE_MANAGER_REGISTRY_ID = keccak256(abi.encodePacked("ScoreManager")); // solhint-enable state-visibility IRegistry public registry; @@ -139,22 +138,26 @@ contract UsingRegistry is Ownable { } function getCeloDistributionSchedule() internal view returns (ICeloDistributionSchedule) { - return ICeloDistributionSchedule(registry.getAddressForOrDie(CELO_DISTRIBUTION_SCHEDULE_ID)); + return + ICeloDistributionSchedule( + registry.getAddressForOrDie(CELO_DISTRIBUTION_SCHEDULE_REGISTRY_ID) + ); } function getEpochRewards() internal view returns (IEpochRewards) { - return IEpochRewards(registry.getAddressForOrDie(EPOCH_REWARDS_ID)); + return IEpochRewards(registry.getAddressForOrDie(EPOCH_REWARDS_REGISTRY_ID)); } function getEpochManagerInitializer() internal view returns (IEpochManagerInitializer) { - return IEpochManagerInitializer(registry.getAddressForOrDie(EPOCH_MANAGER_INITIALIZER_ID)); + return + IEpochManagerInitializer(registry.getAddressForOrDie(EPOCH_MANAGER_INITIALIZER_REGISTRY_ID)); } function getEpochManager() internal view returns (IEpochManager) { - return IEpochManager(registry.getAddressForOrDie(EPOCH_MANAGER_ID)); + return IEpochManager(registry.getAddressForOrDie(EPOCH_MANAGER_REGISTRY_ID)); } function getScoreManager() internal view returns (IScoreManager) { - return IScoreManager(registry.getAddressForOrDie(SCORE_MANAGER_ID)); + return IScoreManager(registry.getAddressForOrDie(SCORE_MANAGER_REGISTRY_ID)); } } diff --git a/packages/protocol/contracts/common/GoldToken.sol b/packages/protocol/contracts/common/GoldToken.sol index 7f6b36cf105..27f2eb88003 100644 --- a/packages/protocol/contracts/common/GoldToken.sol +++ b/packages/protocol/contracts/common/GoldToken.sol @@ -153,7 +153,7 @@ contract GoldToken is function transferFrom(address from, address to, uint256 value) external returns (bool) { require(to != address(0), "transfer attempted to reserved address 0x0"); require( - to != registry.getAddressForOrDie(CELO_DISTRIBUTION_SCHEDULE_ID), + to != registry.getAddressForOrDie(CELO_DISTRIBUTION_SCHEDULE_REGISTRY_ID), "transfer attempted to reserved CeloDistributionSchedule address" ); require(value <= balanceOf(from), "transfer value exceeded balance of sender"); @@ -228,7 +228,8 @@ contract GoldToken is * @return The total amount of allocated CELO. */ function allocatedSupply() external view onlyL2 returns (uint256) { - return CELO_SUPPLY_CAP - registry.getAddressForOrDie(CELO_DISTRIBUTION_SCHEDULE_ID).balance; + return + CELO_SUPPLY_CAP - registry.getAddressForOrDie(CELO_DISTRIBUTION_SCHEDULE_REGISTRY_ID).balance; } /** @@ -295,7 +296,7 @@ contract GoldToken is */ function _transfer(address to, uint256 value) internal returns (bool) { require( - to != registry.getAddressForOrDie(CELO_DISTRIBUTION_SCHEDULE_ID), + to != registry.getAddressForOrDie(CELO_DISTRIBUTION_SCHEDULE_REGISTRY_ID), "transfer attempted to reserved CeloDistributionSchedule address" ); require(value <= balanceOf(msg.sender), "transfer value exceeded balance of sender"); diff --git a/packages/protocol/contracts/common/UsingRegistry.sol b/packages/protocol/contracts/common/UsingRegistry.sol index 39be700bc37..048591516f0 100644 --- a/packages/protocol/contracts/common/UsingRegistry.sol +++ b/packages/protocol/contracts/common/UsingRegistry.sol @@ -51,12 +51,12 @@ contract UsingRegistry is Ownable { bytes32 constant CELO_TOKEN_REGISTRY_ID = keccak256(abi.encodePacked("CeloToken")); bytes32 constant LOCKED_CELO_REGISTRY_ID = keccak256(abi.encodePacked("LockedCelo")); - bytes32 constant CELO_DISTRIBUTION_SCHEDULE_ID = + bytes32 constant CELO_DISTRIBUTION_SCHEDULE_REGISTRY_ID = keccak256(abi.encodePacked("CeloDistributionSchedule")); - bytes32 constant EPOCH_REWARDS_ID = keccak256(abi.encodePacked("EpochRewards")); - bytes32 constant EPOCH_MANAGER_INITIALIZER_ID = + bytes32 constant EPOCH_REWARDS_REGISTRY_ID = keccak256(abi.encodePacked("EpochRewards")); + bytes32 constant EPOCH_MANAGER_INITIALIZER_REGISTRY_ID = keccak256(abi.encodePacked("EpochManagerInitializer")); - bytes32 constant EPOCH_MANAGER_ID = keccak256(abi.encodePacked("EpochManager")); + bytes32 constant EPOCH_MANAGER_REGISTRY_ID = keccak256(abi.encodePacked("EpochManager")); // solhint-enable state-visibility IRegistry public registry; @@ -146,13 +146,17 @@ contract UsingRegistry is Ownable { } function getCeloDistributionSchedule() internal view returns (ICeloDistributionSchedule) { - return ICeloDistributionSchedule(registry.getAddressForOrDie(CELO_DISTRIBUTION_SCHEDULE_ID)); + return + ICeloDistributionSchedule( + registry.getAddressForOrDie(CELO_DISTRIBUTION_SCHEDULE_REGISTRY_ID) + ); } function getEpochRewards() internal view returns (IEpochRewards) { - return IEpochRewards(registry.getAddressForOrDie(EPOCH_REWARDS_ID)); + return IEpochRewards(registry.getAddressForOrDie(EPOCH_REWARDS_REGISTRY_ID)); } function getEpochManagerInitializer() internal view returns (IEpochManagerInitializer) { - return IEpochManagerInitializer(registry.getAddressForOrDie(EPOCH_MANAGER_INITIALIZER_ID)); + return + IEpochManagerInitializer(registry.getAddressForOrDie(EPOCH_MANAGER_INITIALIZER_REGISTRY_ID)); } } diff --git a/packages/protocol/contracts/common/UsingRegistryV2.sol b/packages/protocol/contracts/common/UsingRegistryV2.sol index 668f7708757..ae224ca11b0 100644 --- a/packages/protocol/contracts/common/UsingRegistryV2.sol +++ b/packages/protocol/contracts/common/UsingRegistryV2.sol @@ -61,15 +61,15 @@ contract UsingRegistryV2 { bytes32 internal constant STABLE_REAL_TOKEN_REGISTRY_ID = keccak256(abi.encodePacked("StableTokenBRL")); bytes32 internal constant VALIDATORS_REGISTRY_ID = keccak256(abi.encodePacked("Validators")); - bytes32 internal constant CELO_DISTRIBUTION_SCHEDULE_ID = + bytes32 internal constant CELO_DISTRIBUTION_SCHEDULE_REGISTRY_ID = keccak256(abi.encodePacked("CeloDistributionSchedule")); bytes32 internal constant CELO_TOKEN_REGISTRY_ID = keccak256(abi.encodePacked("CeloToken")); bytes32 internal constant LOCKED_CELO_REGISTRY_ID = keccak256(abi.encodePacked("LockedCelo")); - bytes32 internal constant EPOCH_REWARDS_ID = keccak256(abi.encodePacked("EpochRewards")); - bytes32 internal constant EPOCH_MANAGER_INITIALIZER_ID = + bytes32 internal constant EPOCH_REWARDS_REGISTRY_ID = keccak256(abi.encodePacked("EpochRewards")); + bytes32 internal constant EPOCH_MANAGER_INITIALIZER_REGISTRY_ID =_REGISTRY keccak256(abi.encodePacked("EpochManagerInitializer")); - bytes32 internal constant EPOCH_MANAGER_ID = keccak256(abi.encodePacked("EpochManager")); + bytes32 internal constant EPOCH_MANAGER_REGISTRY_ID = keccak256(abi.encodePacked("EpochManager")); modifier onlyRegisteredContract(bytes32 identifierHash) { require( @@ -184,15 +184,15 @@ contract UsingRegistryV2 { function getCeloDistributionSchedule() internal view returns (ICeloDistributionSchedule) { return - ICeloDistributionSchedule(registryContract.getAddressForOrDie(CELO_DISTRIBUTION_SCHEDULE_ID)); + ICeloDistributionSchedule(registryContract.getAddressForOrDie(CELO_DISTRIBUTION_SCHEDULE_REGISTRY_ID)); } function getEpochRewards() internal view returns (IEpochRewards) { - return IEpochRewards(registryContract.getAddressForOrDie(EPOCH_REWARDS_ID)); + return IEpochRewards(registryContract.getAddressForOrDie(EPOCH_REWARDS_REGISTRY_ID)); } function getEpochManagerInitializer() internal view returns (IEpochManagerInitializer) { return - IEpochManagerInitializer(registryContract.getAddressForOrDie(EPOCH_MANAGER_INITIALIZER_ID)); + IEpochManagerInitializer(registryContract.getAddressForOrDie(EPOCH_MANAGER_INITIALIZER_REGISTRY_ID)); } } diff --git a/packages/protocol/contracts/governance/EpochRewards.sol b/packages/protocol/contracts/governance/EpochRewards.sol index d4d97cbd9c4..773ec547b1d 100644 --- a/packages/protocol/contracts/governance/EpochRewards.sol +++ b/packages/protocol/contracts/governance/EpochRewards.sol @@ -86,7 +86,7 @@ contract EpochRewards is modifier onlyVmOrEpochManager() { require( - msg.sender == address(0) || msg.sender == registry.getAddressForOrDie(EPOCH_MANAGER_ID), + msg.sender == address(0) || msg.sender == registry.getAddressForOrDie(EPOCH_MANAGER_REGISTRY_ID), "Only VM or Epoch Manager can call" ); _; From 5fa905db97111e7bb044f66eb24eeb0dfb514fd1 Mon Sep 17 00:00:00 2001 From: soloseng <102702451+soloseng@users.noreply.github.com> Date: Fri, 16 Aug 2024 19:37:02 -0400 Subject: [PATCH 08/59] typo --- packages/protocol/contracts/common/UsingRegistryV2.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/protocol/contracts/common/UsingRegistryV2.sol b/packages/protocol/contracts/common/UsingRegistryV2.sol index ae224ca11b0..d9df48546f4 100644 --- a/packages/protocol/contracts/common/UsingRegistryV2.sol +++ b/packages/protocol/contracts/common/UsingRegistryV2.sol @@ -67,7 +67,7 @@ contract UsingRegistryV2 { bytes32 internal constant CELO_TOKEN_REGISTRY_ID = keccak256(abi.encodePacked("CeloToken")); bytes32 internal constant LOCKED_CELO_REGISTRY_ID = keccak256(abi.encodePacked("LockedCelo")); bytes32 internal constant EPOCH_REWARDS_REGISTRY_ID = keccak256(abi.encodePacked("EpochRewards")); - bytes32 internal constant EPOCH_MANAGER_INITIALIZER_REGISTRY_ID =_REGISTRY + bytes32 internal constant EPOCH_MANAGER_INITIALIZER_REGISTRY_ID = keccak256(abi.encodePacked("EpochManagerInitializer")); bytes32 internal constant EPOCH_MANAGER_REGISTRY_ID = keccak256(abi.encodePacked("EpochManager")); From b3023c2de41673adacdfdc7f5f381d340ceeff70 Mon Sep 17 00:00:00 2001 From: soloseng <102702451+soloseng@users.noreply.github.com> Date: Fri, 16 Aug 2024 20:34:59 -0400 Subject: [PATCH 09/59] ++ mock contracts in 0.8 --- .../governance/test/EpochRewardsMock.sol | 62 +++++ .../governance/test/ValidatorsMock.sol | 233 ++++++++++++++++++ .../stability/test/MockStableToken.sol | 81 ++++++ .../stability/test/MockSortedOracles.sol | 8 +- 4 files changed, 383 insertions(+), 1 deletion(-) create mode 100644 packages/protocol/contracts-0.8/governance/test/EpochRewardsMock.sol create mode 100644 packages/protocol/contracts-0.8/governance/test/ValidatorsMock.sol create mode 100644 packages/protocol/contracts-0.8/stability/test/MockStableToken.sol diff --git a/packages/protocol/contracts-0.8/governance/test/EpochRewardsMock.sol b/packages/protocol/contracts-0.8/governance/test/EpochRewardsMock.sol new file mode 100644 index 00000000000..de7632a1aa2 --- /dev/null +++ b/packages/protocol/contracts-0.8/governance/test/EpochRewardsMock.sol @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity >=0.8.7 <0.8.20; + +import "../../../contracts/governance/interfaces/IEpochRewards.sol"; +import "forge-std-8/console2.sol"; +/** + * @title A wrapper around EpochRewards that exposes internal functions for testing. + */ +contract EpochRewardsMock08 is IEpochRewards { + uint256 private numValidatorsInCurrentSet; + + function setNumberValidatorsInCurrentSet(uint256 value) external { + numValidatorsInCurrentSet = value; + } + + function getRewardsMultiplier( + uint256 targetGoldTotalSupplyIncrease + ) external view returns (uint256) { + // return _getRewardsMultiplier(targetGoldTotalSupplyIncrease).unwrap(); + return 0; + } + + // TODO: (soloseng) implement mock + function updateTargetVotingYield() external { + console2.log("### Updating Target Voting Yield"); + } + + // mocks the precompile + function numberValidatorsInCurrentSet() public view returns (uint256) { + return numValidatorsInCurrentSet; + } + + function isReserveLow() external view returns (bool) { + return false; + } + function calculateTargetEpochRewards() + external + view + returns (uint256, uint256, uint256, uint256) + { + console2.log("### calculating Target Epoch Rewards"); + return (1, 1, 1, 1); + } + function getTargetVotingYieldParameters() external view returns (uint256, uint256, uint256) { + return (0, 0, 0); + } + function getRewardsMultiplierParameters() external view returns (uint256, uint256, uint256) { + return (0, 0, 0); + } + function getCommunityRewardFraction() external view returns (uint256) { + return 0; + } + function getCarbonOffsettingFraction() external view returns (uint256) { + return 0; + } + function getTargetVotingGoldFraction() external view returns (uint256) { + return 0; + } + function getRewardsMultiplier() external view returns (uint256) { + return 0; + } +} diff --git a/packages/protocol/contracts-0.8/governance/test/ValidatorsMock.sol b/packages/protocol/contracts-0.8/governance/test/ValidatorsMock.sol new file mode 100644 index 00000000000..7a1d497675b --- /dev/null +++ b/packages/protocol/contracts-0.8/governance/test/ValidatorsMock.sol @@ -0,0 +1,233 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity >=0.8.7 <0.8.20; + +import "../../../contracts/governance/interfaces/IValidators.sol"; +import "../../../contracts/common/FixidityLib.sol"; + +import "forge-std-8/console2.sol"; + +/** + * @title A wrapper around Validators that exposes onlyVm functions for testing. + */ +contract ValidatorsMock08 is IValidators { + function updateValidatorScoreFromSigner(address signer, uint256 uptime) external { + console2.log("### update Validator Score From Signer"); + } + + function distributeEpochPaymentsFromSigner( + address signer, + uint256 maxPayment + ) external returns (uint256) { + console2.log("### distributeEpochPaymentsFromSigner"); + return 0; + // return _distributeEpochPaymentsFromSigner(signer, maxPayment); + } + + function registerValidator( + bytes calldata ecdsaPublicKey, + bytes calldata blsPublicKey, + bytes calldata blsPop + ) external returns (bool) { + return true; + } + + function deregisterValidator(uint256 index) external returns (bool) { + return true; + } + function affiliate(address group) external returns (bool) { + return true; + } + function deaffiliate() external returns (bool) { + return true; + } + function updateBlsPublicKey( + bytes calldata blsPublicKey, + bytes calldata blsPop + ) external returns (bool) { + return true; + } + function registerValidatorGroup(uint256 commission) external returns (bool) { + return true; + } + function deregisterValidatorGroup(uint256 index) external returns (bool) { + return true; + } + function addMember(address validator) external returns (bool) { + return true; + } + function addFirstMember( + address validator, + address lesser, + address greater + ) external returns (bool) { + return true; + } + function removeMember(address validator) external returns (bool) { + return true; + } + function reorderMember( + address validator, + address lesserMember, + address greaterMember + ) external returns (bool) { + return true; + } + function updateCommission() external {} + function setNextCommissionUpdate(uint256 commission) external {} + function resetSlashingMultiplier() external {} + + // only owner + function setCommissionUpdateDelay(uint256 delay) external {} + function setMaxGroupSize(uint256 size) external returns (bool) { + return true; + } + function setMembershipHistoryLength(uint256 length) external returns (bool) { + return true; + } + function setValidatorScoreParameters( + uint256 exponent, + uint256 adjustmentSpeed + ) external returns (bool) { + return true; + } + function setGroupLockedGoldRequirements(uint256 value, uint256 duration) external returns (bool) { + return true; + } + function setValidatorLockedGoldRequirements( + uint256 value, + uint256 duration + ) external returns (bool) { + return true; + } + function setSlashingMultiplierResetPeriod(uint256 value) external {} + + // only registered contract + function updateEcdsaPublicKey( + address account, + address signer, + bytes calldata ecdsaPublicKey + ) external returns (bool) { + return true; + } + function updatePublicKeys( + address account, + address signer, + bytes calldata ecdsaPublicKey, + bytes calldata blsPublicKey, + bytes calldata blsPop + ) external returns (bool) { + return true; + } + + // only slasher + function forceDeaffiliateIfValidator(address validatorAccount) external {} + function halveSlashingMultiplier(address account) external {} + + // view functions + function getCommissionUpdateDelay() external view returns (uint256) { + return 0; + } + function getValidatorScoreParameters() external view returns (uint256, uint256) { + return (0, 0); + } + + function getMembershipHistory( + address account + ) external view returns (uint256[] memory, address[] memory, uint256, uint256) { + return (new uint256[](0), new address[](0), 0, 0); + } + function calculateEpochScore(uint256 uptime) external view returns (uint256) { + return 0; + } + + function calculateGroupEpochScore(uint256[] calldata uptimes) external view returns (uint256) { + return 0; + } + + function getAccountLockedGoldRequirement(address account) external view returns (uint256) { + return 0; + } + + function meetsAccountLockedGoldRequirements(address account) external view returns (bool) { + return true; + } + function getValidatorBlsPublicKeyFromSigner(address singer) external view returns (bytes memory) { + return "0x"; + } + function getValidator( + address account + ) external view returns (bytes memory, bytes memory, address, uint256, address) { + return ("0x", "0x", address(0), 0, address(0)); + } + function getValidatorGroup( + address account + ) + external + view + returns (address[] memory, uint256, uint256, uint256, uint256[] memory, uint256, uint256) + { + return (new address[](0), 0, 0, 0, new uint256[](0), 0, 0); + } + function getGroupNumMembers(address account) external view returns (uint256) { + return 0; + } + + function getTopGroupValidators( + address account, + uint256 n + ) external view returns (address[] memory) { + return new address[](0); + } + function getGroupsNumMembers( + address[] calldata accounts + ) external view returns (uint256[] memory) { + return new uint256[](0); + } + function getNumRegisteredValidators() external view returns (uint256) { + return 0; + } + + function groupMembershipInEpoch( + address account, + uint256 epochNumber, + uint256 index + ) external view returns (address) { + return address(0); + } + + function getValidatorLockedGoldRequirements() external view returns (uint256, uint256) { + return (0, 0); + } + function getGroupLockedGoldRequirements() external view returns (uint256, uint256) { + return (0, 0); + } + function getRegisteredValidators() external view returns (address[] memory) { + return new address[](0); + } + function getRegisteredValidatorGroups() external view returns (address[] memory) { + return new address[](0); + } + function isValidatorGroup(address account) external view returns (bool) { + return true; + } + function isValidator(address account) external view returns (bool) { + return true; + } + function getValidatorGroupSlashingMultiplier(address account) external view returns (uint256) { + return 0; + } + + function getMembershipInLastEpoch(address account) external view returns (address) { + return address(0); + } + function getMembershipInLastEpochFromSigner(address signer) external view returns (address) { + return address(0); + } + function computeEpochReward( + address account, + uint256 score, + uint256 maxPayment + ) external view returns (uint256) { + return 1; + } +} diff --git a/packages/protocol/contracts-0.8/stability/test/MockStableToken.sol b/packages/protocol/contracts-0.8/stability/test/MockStableToken.sol new file mode 100644 index 00000000000..26c8e940a23 --- /dev/null +++ b/packages/protocol/contracts-0.8/stability/test/MockStableToken.sol @@ -0,0 +1,81 @@ +pragma solidity >=0.8.0 <0.9.0; +// solhint-disable no-unused-vars + +import "@openzeppelin/contracts8/utils/math/SafeMath.sol"; + +import "../../../contracts/common/FixidityLib.sol"; + +/** + * @title A mock StableToken for testing. + */ +contract MockStableToken08 { + using FixidityLib for FixidityLib.Fraction; + using SafeMath for uint256; + + uint8 public constant decimals = 18; + uint256 public _totalSupply; + FixidityLib.Fraction public inflationFactor; + + // Stored as units. Value can be found using unitsToValue(). + mapping(address => uint256) public balances; + + constructor() public { + setInflationFactor(FixidityLib.fixed1().unwrap()); + } + + function setTotalSupply(uint256 value) external { + _totalSupply = value; + } + + function mint(address to, uint256 value) external returns (bool) { + require(to != address(0), "0 is a reserved address"); + balances[to] = balances[to].add(valueToUnits(value)); + _totalSupply = _totalSupply.add(value); + return true; + } + + function burn(uint256 value) external returns (bool) { + balances[msg.sender] = balances[msg.sender].sub(valueToUnits(value)); + _totalSupply = _totalSupply.sub(value); + return true; + } + + function transfer(address to, uint256 value) external returns (bool) { + return _transfer(msg.sender, to, value); + } + + function transferFrom(address from, address to, uint256 value) external returns (bool) { + return _transfer(from, to, value); + } + + function totalSupply() external view returns (uint256) { + return _totalSupply; + } + + function setInflationFactor(uint256 newInflationFactor) public { + inflationFactor = FixidityLib.wrap(newInflationFactor); + } + + function balanceOf(address account) public view returns (uint256) { + return unitsToValue(balances[account]); + } + + function unitsToValue(uint256 units) public view returns (uint256) { + return FixidityLib.newFixed(units).divide(inflationFactor).fromFixed(); + } + + function valueToUnits(uint256 value) public view returns (uint256) { + return inflationFactor.multiply(FixidityLib.newFixed(value)).fromFixed(); + } + + function _transfer(address from, address to, uint256 value) internal returns (bool) { + uint256 balanceValue = balanceOf(from); + if (balanceValue < value) { + return false; + } + uint256 units = valueToUnits(value); + balances[from] = balances[from].sub(units); + balances[to] = balances[to].add(units); + return true; + } +} diff --git a/packages/protocol/contracts/stability/test/MockSortedOracles.sol b/packages/protocol/contracts/stability/test/MockSortedOracles.sol index d19ce120b89..d2298c986ce 100644 --- a/packages/protocol/contracts/stability/test/MockSortedOracles.sol +++ b/packages/protocol/contracts/stability/test/MockSortedOracles.sol @@ -32,7 +32,7 @@ contract MockSortedOracles { return _numRates[token]; } - function medianRate(address token) external view returns (uint256, uint256) { + function medianRate(address token) public view returns (uint256, uint256) { if (numerators[token] > 0) { return (numerators[token], DENOMINATOR); } @@ -46,4 +46,10 @@ contract MockSortedOracles { function isOldestReportExpired(address token) public view returns (bool, address) { return (expired[token], token); } + + function getExchangeRate( + address token + ) external view returns (uint256 numerator, uint256 denominator) { + (numerator, denominator) = medianRate(token); + } } From 9681185ab651c188009d5de9092b3ffbc2846f7c Mon Sep 17 00:00:00 2001 From: soloseng <102702451+soloseng@users.noreply.github.com> Date: Fri, 16 Aug 2024 20:35:35 -0400 Subject: [PATCH 10/59] ++ passing test using mock --- .../contracts-0.8/common/EpochManager.sol | 86 +++++++------ .../common/interfaces/IEpochManager.sol | 3 +- .../common/interfaces/IScoreManager.sol | 6 +- packages/protocol/test-sol/constants.sol | 6 + .../common/CeloDistributionSchedule.t.sol | 4 + .../test-sol/unit/common/EpochManager.t.sol | 115 +++++++++++++++--- 6 files changed, 168 insertions(+), 52 deletions(-) diff --git a/packages/protocol/contracts-0.8/common/EpochManager.sol b/packages/protocol/contracts-0.8/common/EpochManager.sol index dd02743fb0f..a7c4ddecb51 100644 --- a/packages/protocol/contracts-0.8/common/EpochManager.sol +++ b/packages/protocol/contracts-0.8/common/EpochManager.sol @@ -47,7 +47,7 @@ contract EpochManager is uint256 public epochDuration; uint256 public firstKnownEpoch; - uint256 public currentEpoch; + uint256 public currentEpochNumber; address[] public elected; // TODO this should be able to get deleted easily @@ -55,10 +55,9 @@ contract EpochManager is mapping(address => bool) public processedGroups; EpochProcessState public epochProcessing; - mapping(uint256 => Epoch) public epochs; + mapping(uint256 => Epoch) private epochs; mapping(address => uint256) public validatorPendingPayments; - address public communityRewardFund; address public carbonOffsettingPartner; /** @@ -96,14 +95,12 @@ contract EpochManager is function initialize( address registryAddress, uint256 newEpochDuration, - address _carbonOffsettingPartner, - address _communityRewardFund + address _carbonOffsettingPartner ) external initializer { _transferOwnership(msg.sender); setRegistry(registryAddress); setEpochDuration(newEpochDuration); carbonOffsettingPartner = _carbonOffsettingPartner; - communityRewardFund = _communityRewardFund; // governance address } // DESIGNDESICION(XXX): we assume that the first epoch on the L2 starts as soon as the system is initialized @@ -116,9 +113,9 @@ contract EpochManager is ) external onlyEpochManagerInitializer { require(!systemAlreadyInitialized(), "Epoch system already initialized"); firstKnownEpoch = firstEpochNumber; - currentEpoch = firstEpochNumber; + currentEpochNumber = firstEpochNumber; - Epoch storage _currentEpoch = epochs[currentEpoch]; + Epoch storage _currentEpoch = epochs[currentEpochNumber]; _currentEpoch.firstBlock = firstEpochBlock; _currentEpoch.startTimestamp = block.timestamp; _currentEpoch.endTimestamp = block.timestamp + epochDuration; @@ -127,12 +124,17 @@ contract EpochManager is } // TODO maybe "freezeEpochRewards" "prepareForNextEpoch" + + /// start next epoch process. + /// it freezes the epochrewards at the time of execution, + /// and starts the distribution of the rewards. function startNextEpochProcess() external nonReentrant { require(isReadyToStartEpoch(), "Epoch is not ready to start"); require(!isOnEpochProcess(), "Epoch process is already started"); + require(elected.length > 0, "Elected length must be greater than 0."); epochProcessing.status = EpochProcessStatus.Started; - epochs[currentEpoch].rewardsBlock = block.number; + epochs[currentEpochNumber].rewardsBlock = block.number; // calculate rewards getEpochRewards().updateTargetVotingYield(); @@ -151,7 +153,7 @@ contract EpochManager is allocateValidatorsRewards(); - emit EpochProcessingStarted(currentEpoch); + emit EpochProcessingStarted(currentEpochNumber); } function finishNextEpochProcess( @@ -163,12 +165,12 @@ contract EpochManager is require(isOnEpochProcess(), "Epoch process is not started"); // finalize epoch // TODO last block should be the block before and timestamp from previous block - epochs[currentEpoch].endTimestamp = block.timestamp; - epochs[currentEpoch].lastBlock = block.number - 1; + epochs[currentEpochNumber].endTimestamp = block.timestamp; + epochs[currentEpochNumber].lastBlock = block.number - 1; // start new epoch - currentEpoch++; - epochs[currentEpoch].firstBlock = block.number; - epochs[currentEpoch].startTimestamp = block.timestamp; + currentEpochNumber++; + epochs[currentEpochNumber].firstBlock = block.number; + epochs[currentEpochNumber].startTimestamp = block.timestamp; for (uint i = 0; i < elected.length; i++) { (, , address group, , ) = getValidators().getValidator(elected[i]); @@ -195,7 +197,7 @@ contract EpochManager is getElection().distributeEpochRewards(groups[i], epochRewards, lessers[i], greaters[i]); } getCeloDistributionSchedule().transfer( - communityRewardFund, + registry.getAddressForOrDie(GOVERNANCE_REGISTRY_ID), epochProcessing.totalRewardsCommunity ); getCeloDistributionSchedule().transfer( @@ -208,8 +210,22 @@ contract EpochManager is epochProcessing.status = EpochProcessStatus.NotStarted; } - function getCurrentEpoch() external view returns (uint256) { - return currentEpoch; + /// returns the current epoch Info + function getCurrentEpoch() external view returns (uint256, uint256, uint256, uint256, uint256) { + Epoch storage _epoch = epochs[currentEpochNumber]; + + return ( + _epoch.firstBlock, + _epoch.lastBlock, + _epoch.startTimestamp, + _epoch.endTimestamp, + _epoch.rewardsBlock + ); + } + + /// returns the current epoch number. + function getCurrentEpochNumber() external view returns (uint256) { + return currentEpochNumber; } function getElected() external view returns (address[] memory) { @@ -224,18 +240,25 @@ contract EpochManager is return epochs[epoch].lastBlock; } - function isOnEpochProcess() public view returns (bool) { - return epochProcessing.status == EpochProcessStatus.Started; - } - function isTimeForNextEpoch() external view returns (bool) { - return block.timestamp >= epochs[currentEpoch].startTimestamp + epochDuration; + return block.timestamp >= epochs[currentEpochNumber].startTimestamp + epochDuration; } function isBlocked() external view returns (bool) { return isOnEpochProcess(); } + /** + * @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); + } + /** * @notice Sets the time duration of an epoch. * @param newEpochDuration The duration of an epoch in seconds. @@ -245,13 +268,17 @@ contract EpochManager is epochDuration = newEpochDuration; } + function isOnEpochProcess() public view returns (bool) { + return epochProcessing.status == EpochProcessStatus.Started; + } + function systemAlreadyInitialized() public view returns (bool) { return firstKnownEpoch != 0; } // checks if end of epoch has been reached based on timestamp function isReadyToStartEpoch() public view returns (bool) { - Epoch memory _currentEpoch = epochs[currentEpoch]; + Epoch memory _currentEpoch = epochs[currentEpochNumber]; if (block.timestamp > _currentEpoch.endTimestamp) { return true; } else { @@ -259,17 +286,6 @@ contract EpochManager is } } - /** - * @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 allocateValidatorsRewards() internal { // TODO complete this function uint256 totalRewards = 0; diff --git a/packages/protocol/contracts-0.8/common/interfaces/IEpochManager.sol b/packages/protocol/contracts-0.8/common/interfaces/IEpochManager.sol index 91bb16fe601..04158ea9dee 100644 --- a/packages/protocol/contracts-0.8/common/interfaces/IEpochManager.sol +++ b/packages/protocol/contracts-0.8/common/interfaces/IEpochManager.sol @@ -18,7 +18,8 @@ interface IEpochManager is IBlocker { // uint16[] calldata lessers, // uint16 greaters // ) external; - // function getCurrentEpoch() external view returns (uint256); + function getCurrentEpoch() external view returns (uint256, uint256, uint256, uint256, uint256); + function getCurrentEpochNumber() external view returns (uint256); // function getElected() external view returns (address[] memory); // // function getElectedAtEpoch(uint256 epoch) external view returns (address[] memory); // function getFirstBlockAtEpoch(uint256 epoch) external view returns (uint256); diff --git a/packages/protocol/contracts-0.8/common/interfaces/IScoreManager.sol b/packages/protocol/contracts-0.8/common/interfaces/IScoreManager.sol index 488f1a95ed4..ffa1fd47e30 100644 --- a/packages/protocol/contracts-0.8/common/interfaces/IScoreManager.sol +++ b/packages/protocol/contracts-0.8/common/interfaces/IScoreManager.sol @@ -2,7 +2,9 @@ pragma solidity >=0.8.7 <0.8.20; interface IScoreManager { - function getUptimes(address group) external view returns (uint256[] memory); + function setUptimes(address group, uint256[] calldata _uptimes) external; + function setValidatorScore(address group, uint256 score) external; - function getValidatorScore(address group) external view returns (uint256); + function getUptimes(address group) external view returns (uint256[] memory); + function getValidatorScore(address group) external view returns (uint256); } diff --git a/packages/protocol/test-sol/constants.sol b/packages/protocol/test-sol/constants.sol index 60b2254eb3f..d53a94a9db7 100644 --- a/packages/protocol/test-sol/constants.sol +++ b/packages/protocol/test-sol/constants.sol @@ -23,6 +23,12 @@ contract TestConstants { string constant LockedCeloContract = "LockedCelo"; string constant ValidatorsContract = "Validators"; string constant GovernanceContract = "Governance"; + string constant EpochRewardsContract = "EpochRewards"; + string constant EpochManagerContract = "EpochManager"; + string constant EpochManagerInitializerContract = "EpochManagerInitializer"; + string constant ScoreManagerContract = "ScoreManager"; + string constant ReserveContract = "Reserve"; + string constant CeloDistributionScheduleContract = "CeloDistributionSchedule"; // Constant addresses address constant REGISTRY_ADDRESS = 0x000000000000000000000000000000000000ce10; diff --git a/packages/protocol/test-sol/unit/common/CeloDistributionSchedule.t.sol b/packages/protocol/test-sol/unit/common/CeloDistributionSchedule.t.sol index 88d051adca1..83ea706fbe6 100644 --- a/packages/protocol/test-sol/unit/common/CeloDistributionSchedule.t.sol +++ b/packages/protocol/test-sol/unit/common/CeloDistributionSchedule.t.sol @@ -740,3 +740,7 @@ contract CeloDistributionScheduleTest_getDistributableAmount is CeloDistribution celoDistributionSchedule.getDistributableAmount(); } } + +contract CeloDistributionScheduleTest_transfer is CeloDistributionScheduleTest { + // TODO: implement +} diff --git a/packages/protocol/test-sol/unit/common/EpochManager.t.sol b/packages/protocol/test-sol/unit/common/EpochManager.t.sol index 27fb9cfe2e4..2255c9931ea 100644 --- a/packages/protocol/test-sol/unit/common/EpochManager.t.sol +++ b/packages/protocol/test-sol/unit/common/EpochManager.t.sol @@ -3,13 +3,28 @@ pragma solidity >=0.8.7 <0.8.20; import "celo-foundry-8/Test.sol"; import "@celo-contracts-8/common/EpochManager.sol"; +import "@celo-contracts-8/stability/test/MockStableToken.sol"; +import "@celo-contracts-8/common/interfaces/ICeloToken.sol"; +import "@celo-contracts-8/common/interfaces/IScoreManager.sol"; +import "@celo-contracts/common/interfaces/ICeloDistributionSchedule.sol"; + import { TestConstants } from "@test-sol/constants.sol"; import "@celo-contracts/stability/test/MockSortedOracles.sol"; +import "@celo-contracts/common/interfaces/IRegistry.sol"; + +import { EpochRewardsMock08 } from "@celo-contracts-8/governance/test/EpochRewardsMock.sol"; +import { ValidatorsMock08 } from "@celo-contracts-8/governance/test/ValidatorsMock.sol"; + contract EpochManagerTest is Test, TestConstants { EpochManager epochManager; MockSortedOracles sortedOracles; + + MockStableToken08 stableToken; + EpochRewardsMock08 epochRewards; + ValidatorsMock08 validators; + address epochManagerInitializer; address carbonOffsettingPartner; address communityRewardFund; @@ -19,48 +34,99 @@ contract EpochManagerTest is Test, TestConstants { address[] firstElected; IRegistry registry; + ICeloToken celoToken; + ICeloDistributionSchedule celoDistributionSchedule; + IScoreManager scoreManager; + + uint256 celoAmountForRate = 1e24; + uint256 stableAmountForRate = 2 * celoAmountForRate; + + 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 setUp() public virtual { epochManager = new EpochManager(true); sortedOracles = new MockSortedOracles(); - - + epochRewards = new EpochRewardsMock08(); + validators = new ValidatorsMock08(); + stableToken = new MockStableToken08(); firstElected.push(actor("validator1")); firstElected.push(actor("validator2")); + address celoTokenAddress = actor("celoTokenAddress"); + address celoDistributionScheduleAddress = actor("celoDistributionScheduleAddress"); + address scoreManagerAddress = actor("scoreManagerAddress"); + address reserveAddress = actor("reserve"); + epochManagerInitializer = actor("initializer"); carbonOffsettingPartner = actor("carbonOffsettingPartner"); communityRewardFund = actor("communityRewardFund"); deployCodeTo("Registry.sol", abi.encode(false), REGISTRY_ADDRESS); - registry = IRegistry(REGISTRY_ADDRESS); - - registry.setAddressFor("EpochManagerInitializer", epochManagerInitializer); - registry.setAddressFor("SortedOracles", address(sortedOracles)); + deployCodeTo("GoldToken.sol", abi.encode(false), celoTokenAddress); + deployCodeTo( + "CeloDistributionSchedule.sol", + abi.encode(false), + celoDistributionScheduleAddress + ); + deployCodeTo("ScoreManager.sol", abi.encode(false), scoreManagerAddress); - - epochManager.initialize(REGISTRY_ADDRESS, 10, carbonOffsettingPartner, communityRewardFund); + registry = IRegistry(REGISTRY_ADDRESS); + celoToken = ICeloToken(celoTokenAddress); + celoDistributionSchedule = ICeloDistributionSchedule(celoDistributionScheduleAddress); + scoreManager = IScoreManager(scoreManagerAddress); + + registry.setAddressFor(EpochManagerInitializerContract, epochManagerInitializer); + registry.setAddressFor(EpochManagerContract, address(epochManager)); + registry.setAddressFor(SortedOraclesContract, address(sortedOracles)); + registry.setAddressFor(GovernanceContract, communityRewardFund); + registry.setAddressFor(EpochRewardsContract, address(epochRewards)); + registry.setAddressFor(ValidatorsContract, address(validators)); + registry.setAddressFor(ScoreManagerContract, address(scoreManager)); + registry.setAddressFor(StableTokenContract, address(stableToken)); + registry.setAddressFor(CeloDistributionScheduleContract, address(celoDistributionSchedule)); + registry.setAddressFor(CeloTokenContract, address(celoToken)); + registry.setAddressFor(ReserveContract, reserveAddress); + + vm.deal(address(celoDistributionSchedule), L2_INITIAL_STASH_BALANCE); + + bool res1 = sortedOracles.setMedianRate(address(stableToken), stableAmountForRate); + (uint256 res0, uint256 res00) = sortedOracles.medianRate(address(stableToken)); + + scoreManager.setValidatorScore(actor("validator1"), 1); + uint256 res = scoreManager.getValidatorScore(actor("validator1")); + uint256 res2 = epochRewards.getCommunityRewardFraction(); + + epochManager.initialize(REGISTRY_ADDRESS, 10, carbonOffsettingPartner); } } -contract EpochManagerInitialize is EpochManagerTest { +contract EpochManagerTest_initialize is EpochManagerTest { function test_initialize() public virtual { assertEq(address(epochManager.registry()), REGISTRY_ADDRESS); assertEq(epochManager.epochDuration(), 10); assertEq(epochManager.carbonOffsettingPartner(), carbonOffsettingPartner); - assertEq(epochManager.communityRewardFund(), communityRewardFund); } function test_Reverts_WhenAlreadyInitialized() public virtual { vm.expectRevert("contract already initialized"); - epochManager.initialize(REGISTRY_ADDRESS, 10, carbonOffsettingPartner, communityRewardFund); + epochManager.initialize(REGISTRY_ADDRESS, 10, carbonOffsettingPartner); } } -contract EpochManagerinitializeSystem is EpochManagerTest { - - function test_processCanBeStarted() public virtual{ +contract EpochManagerTest_initializeSystem is EpochManagerTest { + function test_processCanBeStarted() public virtual { vm.prank(epochManagerInitializer); epochManager.initializeSystem(firstEpochNumber, firstEpochBlock, firstElected); } @@ -77,3 +143,24 @@ contract EpochManagerinitializeSystem is EpochManagerTest { epochManager.initializeSystem(firstEpochNumber, firstEpochBlock, firstElected); } } + +contract EpochManagerTest_startNextEpochProcess is EpochManagerTest { + function test_Reverts_whenSystemNotInitialized() public { + uint256 _currentEpoch = epochManager.currentEpochNumber(); + (, , , uint256 _currentEpochEndTimestamp, ) = epochManager.getCurrentEpoch(); + + vm.expectRevert("Elected length must be greater than 0."); + epochManager.startNextEpochProcess(); + } + + function test_Reverts_WhenEndOfEpochHasNotBeenReached() public { + vm.prank(epochManagerInitializer); + epochManager.initializeSystem(firstEpochNumber, firstEpochBlock, firstElected); + + uint256 _currentEpoch = epochManager.currentEpochNumber(); + (, , , uint256 _currentEpochEndTimestamp, ) = epochManager.getCurrentEpoch(); + + vm.expectRevert("Epoch is not ready to start"); + epochManager.startNextEpochProcess(); + } +} From 18ba7040e8b25f9046d1ff8a7a8cf7f04d868730 Mon Sep 17 00:00:00 2001 From: pahor167 Date: Mon, 19 Aug 2024 12:17:15 +0200 Subject: [PATCH 11/59] Removal of using precompiles --- .../contracts-0.8/common/UsingPrecompiles.sol | 278 ------------------ 1 file changed, 278 deletions(-) delete mode 100644 packages/protocol/contracts-0.8/common/UsingPrecompiles.sol diff --git a/packages/protocol/contracts-0.8/common/UsingPrecompiles.sol b/packages/protocol/contracts-0.8/common/UsingPrecompiles.sol deleted file mode 100644 index adf2f5763f2..00000000000 --- a/packages/protocol/contracts-0.8/common/UsingPrecompiles.sol +++ /dev/null @@ -1,278 +0,0 @@ -// SPDX-License-Identifier: LGPL-3.0-only -pragma solidity >=0.8.0 <0.8.20; - -// Note: This is not an exact copy of UsingPrecompiles in the contract's folder, but in solidity 0.8 - -import "openzeppelin-solidity/contracts/math/SafeMath.sol"; -import "../../contracts/common/interfaces/ICeloVersionedContract.sol"; -import "../common/IsL2Check.sol"; - -contract UsingPrecompiles is IsL2Check { - using SafeMath for uint256; - - address constant TRANSFER = address(0xff - 2); - address constant FRACTION_MUL = address(0xff - 3); - address constant PROOF_OF_POSSESSION = address(0xff - 4); - address constant GET_VALIDATOR = address(0xff - 5); - address constant NUMBER_VALIDATORS = address(0xff - 6); - address constant EPOCH_SIZE = address(0xff - 7); - address constant BLOCK_NUMBER_FROM_HEADER = address(0xff - 8); - address constant HASH_HEADER = address(0xff - 9); - address constant GET_PARENT_SEAL_BITMAP = address(0xff - 10); - address constant GET_VERIFIED_SEAL_BITMAP = address(0xff - 11); - uint256 constant DAY = 86400; - - /** - * @notice calculate a * b^x for fractions a, b to `decimals` precision - * @param aNumerator Numerator of first fraction - * @param aDenominator Denominator of first fraction - * @param bNumerator Numerator of exponentiated fraction - * @param bDenominator Denominator of exponentiated fraction - * @param exponent exponent to raise b to - * @param _decimals precision - * @return Numerator of the computed quantity (not reduced). - * @return Denominator of the computed quantity (not reduced). - */ - function fractionMulExp( - uint256 aNumerator, - uint256 aDenominator, - uint256 bNumerator, - uint256 bDenominator, - uint256 exponent, - uint256 _decimals - ) public view returns (uint256, uint256) { - require(aDenominator != 0 && bDenominator != 0, "a denominator is zero"); - uint256 returnNumerator; - uint256 returnDenominator; - bool success; - bytes memory out; - (success, out) = FRACTION_MUL.staticcall( - abi.encodePacked(aNumerator, aDenominator, bNumerator, bDenominator, exponent, _decimals) - ); - require(success, "error calling fractionMulExp precompile"); - returnNumerator = getUint256FromBytes(out, 0); - returnDenominator = getUint256FromBytes(out, 32); - return (returnNumerator, returnDenominator); - } - - /** - * @notice Returns the current epoch size in blocks. - * @return The current epoch size in blocks. - */ - function getEpochSize() public view returns (uint256) { - if (isL2()) { - return DAY.div(5); - } else { - bytes memory out; - bool success; - (success, out) = EPOCH_SIZE.staticcall(abi.encodePacked(true)); - require(success, "error calling getEpochSize precompile"); - return getUint256FromBytes(out, 0); - } - } - - /** - * @notice Returns the epoch number at a block. - * @param blockNumber Block number where epoch number is calculated. - * @return Epoch number. - */ - function getEpochNumberOfBlock(uint256 blockNumber) public view returns (uint256) { - return epochNumberOfBlock(blockNumber, getEpochSize()); - } - - /** - * @notice Returns the epoch number at a block. - * @return Current epoch number. - */ - function getEpochNumber() public view returns (uint256) { - return getEpochNumberOfBlock(block.number); - } - - /** - * @notice Gets a validator address from the current validator set. - * @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) { - bytes memory out; - bool success; - (success, out) = GET_VALIDATOR.staticcall(abi.encodePacked(index, uint256(block.number))); - require(success, "error calling validatorSignerAddressFromCurrentSet precompile"); - return address(getUint256FromBytes(out, 0)); - } - - /** - * @notice Gets a validator address from the validator set at the given block number. - * @param index Index of requested validator in the validator set. - * @param blockNumber Block number to retrieve the validator set from. - * @return Address of validator at the requested index. - */ - function validatorSignerAddressFromSet( - uint256 index, - uint256 blockNumber - ) public view returns (address) { - bytes memory out; - bool success; - (success, out) = GET_VALIDATOR.staticcall(abi.encodePacked(index, blockNumber)); - require(success, "error calling validatorSignerAddressFromSet precompile"); - return address(getUint256FromBytes(out, 0)); - } - - /** - * @notice Gets the size of the current elected validator set. - * @return Size of the current elected validator set. - */ - function numberValidatorsInCurrentSet() public view returns (uint256) { - bytes memory out; - bool success; - (success, out) = NUMBER_VALIDATORS.staticcall(abi.encodePacked(uint256(block.number))); - require(success, "error calling numberValidatorsInCurrentSet precompile"); - return getUint256FromBytes(out, 0); - } - - /** - * @notice Gets the size of the validator set that must sign the given block number. - * @param blockNumber Block number to retrieve the validator set from. - * @return Size of the validator set. - */ - function numberValidatorsInSet(uint256 blockNumber) public view returns (uint256) { - bytes memory out; - bool success; - (success, out) = NUMBER_VALIDATORS.staticcall(abi.encodePacked(blockNumber)); - require(success, "error calling numberValidatorsInSet precompile"); - return getUint256FromBytes(out, 0); - } - - /** - * @notice Checks a BLS proof of possession. - * @param sender The address signed by the BLS key to generate the proof of possession. - * @param blsKey The BLS public key that the validator is using for consensus, should pass proof - * of possession. 48 bytes. - * @param blsPop The BLS public key proof-of-possession, which consists of a signature on the - * account address. 96 bytes. - * @return True upon success. - */ - function checkProofOfPossession( - address sender, - bytes memory blsKey, - bytes memory blsPop - ) public view returns (bool) { - bool success; - (success, ) = PROOF_OF_POSSESSION.staticcall(abi.encodePacked(sender, blsKey, blsPop)); - return success; - } - - /** - * @notice Parses block number out of header. - * @param header RLP encoded header - * @return Block number. - */ - function getBlockNumberFromHeader(bytes memory header) public view returns (uint256) { - bytes memory out; - bool success; - (success, out) = BLOCK_NUMBER_FROM_HEADER.staticcall(abi.encodePacked(header)); - require(success, "error calling getBlockNumberFromHeader precompile"); - return getUint256FromBytes(out, 0); - } - - /** - * @notice Computes hash of header. - * @param header RLP encoded header - * @return Header hash. - */ - function hashHeader(bytes memory header) public view returns (bytes32) { - bytes memory out; - bool success; - (success, out) = HASH_HEADER.staticcall(abi.encodePacked(header)); - require(success, "error calling hashHeader precompile"); - return getBytes32FromBytes(out, 0); - } - - /** - * @notice Gets the parent seal bitmap from the header at the given block number. - * @param blockNumber Block number to retrieve. Must be within 4 epochs of the current number. - * @return Bitmap parent seal with set bits at indices corresponding to signing validators. - */ - function getParentSealBitmap(uint256 blockNumber) public view returns (bytes32) { - bytes memory out; - bool success; - (success, out) = GET_PARENT_SEAL_BITMAP.staticcall(abi.encodePacked(blockNumber)); - require(success, "error calling getParentSealBitmap precompile"); - return getBytes32FromBytes(out, 0); - } - - /** - * @notice Verifies the BLS signature on the header and returns the seal bitmap. - * The validator set used for verification is retrieved based on the parent hash field of the - * header. If the parent hash is not in the blockchain, verification fails. - * @param header RLP encoded header - * @return Bitmap parent seal with set bits at indices correspoinding to signing validators. - */ - function getVerifiedSealBitmapFromHeader(bytes memory header) public view returns (bytes32) { - bytes memory out; - bool success; - (success, out) = GET_VERIFIED_SEAL_BITMAP.staticcall(abi.encodePacked(header)); - require(success, "error calling getVerifiedSealBitmapFromHeader precompile"); - return getBytes32FromBytes(out, 0); - } - - /** - * @notice Returns the minimum number of required signers for a given block number. - * @dev Computed in celo-blockchain as int(math.Ceil(float64(2*valSet.Size()) / 3)) - */ - function minQuorumSize(uint256 blockNumber) public view returns (uint256) { - return numberValidatorsInSet(blockNumber).mul(2).add(2).div(3); - } - - /** - * @notice Computes byzantine quorum from current validator set size - * @return Byzantine quorum of validators. - */ - function minQuorumSizeInCurrentSet() public view returns (uint256) { - return minQuorumSize(block.number); - } - - /** - * @notice Returns the epoch number at a block. - * @param blockNumber Block number where epoch number is calculated. - * @param epochSize The epoch size in blocks. - * @return Epoch number. - */ - function epochNumberOfBlock( - uint256 blockNumber, - uint256 epochSize - ) internal pure returns (uint256) { - // Follows GetEpochNumber from celo-blockchain/blob/master/consensus/istanbul/utils.go - uint256 epochNumber = blockNumber / epochSize; - if (blockNumber % epochSize == 0) { - return epochNumber; - } else { - return epochNumber.add(1); - } - } - - /** - * @notice Converts bytes to uint256. - * @param bs byte[] data - * @param start offset into byte data to convert - * @return uint256 data - */ - function getUint256FromBytes(bytes memory bs, uint256 start) internal pure returns (uint256) { - return uint256(getBytes32FromBytes(bs, start)); - } - - /** - * @notice Converts bytes to bytes32. - * @param bs byte[] data - * @param start offset into byte data to convert - * @return bytes32 data - */ - function getBytes32FromBytes(bytes memory bs, uint256 start) internal pure returns (bytes32) { - require(bs.length >= start.add(32), "slicing out of range"); - bytes32 x; - assembly { - x := mload(add(bs, add(start, 32))) - } - return x; - } -} From 8d862d13ea8a4856abc5f7180901de5944deaead Mon Sep 17 00:00:00 2001 From: pahor167 Date: Mon, 19 Aug 2024 12:22:00 +0200 Subject: [PATCH 12/59] ScoreManager refactor --- .../protocol/contracts-0.8/common/ScoreManager.sol | 8 ++++---- .../protocol/contracts-0.8/common/UsingRegistry.sol | 6 +++--- .../contracts-0.8/common/interfaces/IScoreManager.sol | 10 ---------- .../contracts-0.8/common/interfaces/IScoreReader.sol | 7 +++++++ .../protocol/test-sol/unit/common/EpochManager.t.sol | 6 +++--- 5 files changed, 17 insertions(+), 20 deletions(-) delete mode 100644 packages/protocol/contracts-0.8/common/interfaces/IScoreManager.sol create mode 100644 packages/protocol/contracts-0.8/common/interfaces/IScoreReader.sol diff --git a/packages/protocol/contracts-0.8/common/ScoreManager.sol b/packages/protocol/contracts-0.8/common/ScoreManager.sol index 818ffb8b9aa..d06ceee0c8b 100644 --- a/packages/protocol/contracts-0.8/common/ScoreManager.sol +++ b/packages/protocol/contracts-0.8/common/ScoreManager.sol @@ -54,11 +54,11 @@ contract ScoreManager is Initializable, Ownable { return result; } - function getValidatorScore(address group) external view returns (uint256) { - return scores[group]; + function getValidatorScore(address validator) external view returns (uint256) { + return scores[validator]; } - function setValidatorScore(address group, uint256 score) external onlyOwner { - scores[group] = score; + function setValidatorScore(address validator, uint256 score) external onlyOwner { + scores[validator] = score; } } diff --git a/packages/protocol/contracts-0.8/common/UsingRegistry.sol b/packages/protocol/contracts-0.8/common/UsingRegistry.sol index 4dc43ef7ee2..4a89960e640 100644 --- a/packages/protocol/contracts-0.8/common/UsingRegistry.sol +++ b/packages/protocol/contracts-0.8/common/UsingRegistry.sol @@ -22,7 +22,7 @@ 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/IScoreManager.sol"; +import "./interfaces/IScoreReader.sol"; import "../../contracts/common/interfaces/IFeeHandlerSeller.sol"; @@ -157,7 +157,7 @@ contract UsingRegistry is Ownable { return IEpochManager(registry.getAddressForOrDie(EPOCH_MANAGER_REGISTRY_ID)); } - function getScoreManager() internal view returns (IScoreManager) { - return IScoreManager(registry.getAddressForOrDie(SCORE_MANAGER_REGISTRY_ID)); + function getScoreManager() internal view returns (IScoreReader) { + return IScoreReader(registry.getAddressForOrDie(SCORE_MANAGER_REGISTRY_ID)); } } diff --git a/packages/protocol/contracts-0.8/common/interfaces/IScoreManager.sol b/packages/protocol/contracts-0.8/common/interfaces/IScoreManager.sol deleted file mode 100644 index ffa1fd47e30..00000000000 --- a/packages/protocol/contracts-0.8/common/interfaces/IScoreManager.sol +++ /dev/null @@ -1,10 +0,0 @@ -// SPDX-License-Identifier: LGPL-3.0-only -pragma solidity >=0.8.7 <0.8.20; - -interface IScoreManager { - function setUptimes(address group, uint256[] calldata _uptimes) external; - function setValidatorScore(address group, uint256 score) external; - - function getUptimes(address group) external view returns (uint256[] memory); - function getValidatorScore(address group) external view returns (uint256); -} diff --git a/packages/protocol/contracts-0.8/common/interfaces/IScoreReader.sol b/packages/protocol/contracts-0.8/common/interfaces/IScoreReader.sol new file mode 100644 index 00000000000..90afb2939bd --- /dev/null +++ b/packages/protocol/contracts-0.8/common/interfaces/IScoreReader.sol @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity >=0.8.7 <0.8.20; + +interface IScoreReader { + function getUptimes(address group) external view returns (uint256[] memory); + function getValidatorScore(address validator) external view returns (uint256); +} diff --git a/packages/protocol/test-sol/unit/common/EpochManager.t.sol b/packages/protocol/test-sol/unit/common/EpochManager.t.sol index 2255c9931ea..3f9709b8c20 100644 --- a/packages/protocol/test-sol/unit/common/EpochManager.t.sol +++ b/packages/protocol/test-sol/unit/common/EpochManager.t.sol @@ -5,7 +5,7 @@ import "celo-foundry-8/Test.sol"; import "@celo-contracts-8/common/EpochManager.sol"; import "@celo-contracts-8/stability/test/MockStableToken.sol"; import "@celo-contracts-8/common/interfaces/ICeloToken.sol"; -import "@celo-contracts-8/common/interfaces/IScoreManager.sol"; +import "@celo-contracts-8/common/ScoreManager.sol"; import "@celo-contracts/common/interfaces/ICeloDistributionSchedule.sol"; import { TestConstants } from "@test-sol/constants.sol"; @@ -36,7 +36,7 @@ contract EpochManagerTest is Test, TestConstants { IRegistry registry; ICeloToken celoToken; ICeloDistributionSchedule celoDistributionSchedule; - IScoreManager scoreManager; + ScoreManager scoreManager; uint256 celoAmountForRate = 1e24; uint256 stableAmountForRate = 2 * celoAmountForRate; @@ -85,7 +85,7 @@ contract EpochManagerTest is Test, TestConstants { registry = IRegistry(REGISTRY_ADDRESS); celoToken = ICeloToken(celoTokenAddress); celoDistributionSchedule = ICeloDistributionSchedule(celoDistributionScheduleAddress); - scoreManager = IScoreManager(scoreManagerAddress); + scoreManager = ScoreManager(scoreManagerAddress); registry.setAddressFor(EpochManagerInitializerContract, epochManagerInitializer); registry.setAddressFor(EpochManagerContract, address(epochManager)); From 70b12a59949bb33e815c40073b44a37955dbe3df Mon Sep 17 00:00:00 2001 From: pahor167 Date: Mon, 19 Aug 2024 12:29:55 +0200 Subject: [PATCH 13/59] Decouple epoch manager initializer from registry --- packages/protocol/contracts-0.8/common/EpochManager.sol | 9 ++++++--- packages/protocol/contracts-0.8/common/UsingRegistry.sol | 7 ------- .../protocol/test-sol/unit/common/EpochManager.t.sol | 8 ++++---- 3 files changed, 10 insertions(+), 14 deletions(-) diff --git a/packages/protocol/contracts-0.8/common/EpochManager.sol b/packages/protocol/contracts-0.8/common/EpochManager.sol index a7c4ddecb51..eaafb3a752e 100644 --- a/packages/protocol/contracts-0.8/common/EpochManager.sol +++ b/packages/protocol/contracts-0.8/common/EpochManager.sol @@ -59,6 +59,7 @@ contract EpochManager is mapping(address => uint256) public validatorPendingPayments; address public carbonOffsettingPartner; + address public epochManagerInitializer; /** * @notice Event emited when epochProcessing has begun. @@ -74,10 +75,9 @@ contract EpochManager is modifier onlyEpochManagerInitializer() { require( - msg.sender == registry.getAddressForOrDie(EPOCH_MANAGER_INITIALIZER_REGISTRY_ID), + msg.sender == epochManagerInitializer, "msg.sender is not Initializer" ); - _; } @@ -95,12 +95,14 @@ contract EpochManager is function initialize( address registryAddress, uint256 newEpochDuration, - address _carbonOffsettingPartner + address _carbonOffsettingPartner, + address _epochManagerInitializer ) external initializer { _transferOwnership(msg.sender); setRegistry(registryAddress); setEpochDuration(newEpochDuration); carbonOffsettingPartner = _carbonOffsettingPartner; + epochManagerInitializer = _epochManagerInitializer; } // DESIGNDESICION(XXX): we assume that the first epoch on the L2 starts as soon as the system is initialized @@ -121,6 +123,7 @@ contract EpochManager is _currentEpoch.endTimestamp = block.timestamp + epochDuration; elected = firstElected; + epochManagerInitializer = address(0); } // TODO maybe "freezeEpochRewards" "prepareForNextEpoch" diff --git a/packages/protocol/contracts-0.8/common/UsingRegistry.sol b/packages/protocol/contracts-0.8/common/UsingRegistry.sol index 4a89960e640..05adab55fb7 100644 --- a/packages/protocol/contracts-0.8/common/UsingRegistry.sol +++ b/packages/protocol/contracts-0.8/common/UsingRegistry.sol @@ -55,8 +55,6 @@ contract UsingRegistry is Ownable { bytes32 constant CELO_DISTRIBUTION_SCHEDULE_REGISTRY_ID = keccak256(abi.encodePacked("CeloDistributionSchedule")); bytes32 constant EPOCH_REWARDS_REGISTRY_ID = keccak256(abi.encodePacked("EpochRewards")); - bytes32 constant EPOCH_MANAGER_INITIALIZER_REGISTRY_ID = - keccak256(abi.encodePacked("EpochManagerInitializer")); bytes32 constant EPOCH_MANAGER_REGISTRY_ID = keccak256(abi.encodePacked("EpochManager")); bytes32 constant SCORE_MANAGER_REGISTRY_ID = keccak256(abi.encodePacked("ScoreManager")); // solhint-enable state-visibility @@ -148,11 +146,6 @@ contract UsingRegistry is Ownable { return IEpochRewards(registry.getAddressForOrDie(EPOCH_REWARDS_REGISTRY_ID)); } - function getEpochManagerInitializer() internal view returns (IEpochManagerInitializer) { - return - IEpochManagerInitializer(registry.getAddressForOrDie(EPOCH_MANAGER_INITIALIZER_REGISTRY_ID)); - } - function getEpochManager() internal view returns (IEpochManager) { return IEpochManager(registry.getAddressForOrDie(EPOCH_MANAGER_REGISTRY_ID)); } diff --git a/packages/protocol/test-sol/unit/common/EpochManager.t.sol b/packages/protocol/test-sol/unit/common/EpochManager.t.sol index 3f9709b8c20..edd6626fc74 100644 --- a/packages/protocol/test-sol/unit/common/EpochManager.t.sol +++ b/packages/protocol/test-sol/unit/common/EpochManager.t.sol @@ -87,7 +87,6 @@ contract EpochManagerTest is Test, TestConstants { celoDistributionSchedule = ICeloDistributionSchedule(celoDistributionScheduleAddress); scoreManager = ScoreManager(scoreManagerAddress); - registry.setAddressFor(EpochManagerInitializerContract, epochManagerInitializer); registry.setAddressFor(EpochManagerContract, address(epochManager)); registry.setAddressFor(SortedOraclesContract, address(sortedOracles)); registry.setAddressFor(GovernanceContract, communityRewardFund); @@ -108,7 +107,7 @@ contract EpochManagerTest is Test, TestConstants { uint256 res = scoreManager.getValidatorScore(actor("validator1")); uint256 res2 = epochRewards.getCommunityRewardFraction(); - epochManager.initialize(REGISTRY_ADDRESS, 10, carbonOffsettingPartner); + epochManager.initialize(REGISTRY_ADDRESS, 10, carbonOffsettingPartner, epochManagerInitializer); } } @@ -121,7 +120,7 @@ contract EpochManagerTest_initialize is EpochManagerTest { function test_Reverts_WhenAlreadyInitialized() public virtual { vm.expectRevert("contract already initialized"); - epochManager.initialize(REGISTRY_ADDRESS, 10, carbonOffsettingPartner); + epochManager.initialize(REGISTRY_ADDRESS, 10, carbonOffsettingPartner, epochManagerInitializer); } } @@ -132,8 +131,9 @@ contract EpochManagerTest_initializeSystem is EpochManagerTest { } function test_Reverts_processCannotBeStartedAgain() public virtual { - vm.startPrank(epochManagerInitializer); + vm.prank(epochManagerInitializer); epochManager.initializeSystem(firstEpochNumber, firstEpochBlock, firstElected); + vm.prank(address(0)); vm.expectRevert("Epoch system already initialized"); epochManager.initializeSystem(firstEpochNumber, firstEpochBlock, firstElected); } From 0c74a5ef7b048ec139c468eb77d2a83f2c61d597 Mon Sep 17 00:00:00 2001 From: pahor167 Date: Mon, 19 Aug 2024 12:46:46 +0200 Subject: [PATCH 14/59] isReadyToStartEpoch fix --- .../protocol/contracts-0.8/common/EpochManager.sol | 9 +++------ .../protocol/test-sol/unit/common/EpochManager.t.sol | 11 +++-------- 2 files changed, 6 insertions(+), 14 deletions(-) diff --git a/packages/protocol/contracts-0.8/common/EpochManager.sol b/packages/protocol/contracts-0.8/common/EpochManager.sol index eaafb3a752e..80eb4176ae5 100644 --- a/packages/protocol/contracts-0.8/common/EpochManager.sol +++ b/packages/protocol/contracts-0.8/common/EpochManager.sol @@ -114,13 +114,14 @@ contract EpochManager is address[] memory firstElected ) external onlyEpochManagerInitializer { 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"); firstKnownEpoch = firstEpochNumber; currentEpochNumber = firstEpochNumber; Epoch storage _currentEpoch = epochs[currentEpochNumber]; _currentEpoch.firstBlock = firstEpochBlock; _currentEpoch.startTimestamp = block.timestamp; - _currentEpoch.endTimestamp = block.timestamp + epochDuration; elected = firstElected; epochManagerInitializer = address(0); @@ -282,11 +283,7 @@ contract EpochManager is // checks if end of epoch has been reached based on timestamp function isReadyToStartEpoch() public view returns (bool) { Epoch memory _currentEpoch = epochs[currentEpochNumber]; - if (block.timestamp > _currentEpoch.endTimestamp) { - return true; - } else { - return false; - } + return block.timestamp > _currentEpoch.startTimestamp + epochDuration; } function allocateValidatorsRewards() internal { diff --git a/packages/protocol/test-sol/unit/common/EpochManager.t.sol b/packages/protocol/test-sol/unit/common/EpochManager.t.sol index edd6626fc74..a348b9ea763 100644 --- a/packages/protocol/test-sol/unit/common/EpochManager.t.sol +++ b/packages/protocol/test-sol/unit/common/EpochManager.t.sol @@ -6,6 +6,7 @@ import "@celo-contracts-8/common/EpochManager.sol"; import "@celo-contracts-8/stability/test/MockStableToken.sol"; import "@celo-contracts-8/common/interfaces/ICeloToken.sol"; import "@celo-contracts-8/common/ScoreManager.sol"; +import "@celo-contracts-8/common/CeloDistributionSchedule.sol"; import "@celo-contracts/common/interfaces/ICeloDistributionSchedule.sol"; import { TestConstants } from "@test-sol/constants.sol"; @@ -35,7 +36,7 @@ contract EpochManagerTest is Test, TestConstants { IRegistry registry; ICeloToken celoToken; - ICeloDistributionSchedule celoDistributionSchedule; + CeloDistributionSchedule celoDistributionSchedule; ScoreManager scoreManager; uint256 celoAmountForRate = 1e24; @@ -60,12 +61,12 @@ contract EpochManagerTest is Test, TestConstants { epochRewards = new EpochRewardsMock08(); validators = new ValidatorsMock08(); stableToken = new MockStableToken08(); + celoDistributionSchedule = new CeloDistributionSchedule(false); firstElected.push(actor("validator1")); firstElected.push(actor("validator2")); address celoTokenAddress = actor("celoTokenAddress"); - address celoDistributionScheduleAddress = actor("celoDistributionScheduleAddress"); address scoreManagerAddress = actor("scoreManagerAddress"); address reserveAddress = actor("reserve"); @@ -75,16 +76,10 @@ contract EpochManagerTest is Test, TestConstants { deployCodeTo("Registry.sol", abi.encode(false), REGISTRY_ADDRESS); deployCodeTo("GoldToken.sol", abi.encode(false), celoTokenAddress); - deployCodeTo( - "CeloDistributionSchedule.sol", - abi.encode(false), - celoDistributionScheduleAddress - ); deployCodeTo("ScoreManager.sol", abi.encode(false), scoreManagerAddress); registry = IRegistry(REGISTRY_ADDRESS); celoToken = ICeloToken(celoTokenAddress); - celoDistributionSchedule = ICeloDistributionSchedule(celoDistributionScheduleAddress); scoreManager = ScoreManager(scoreManagerAddress); registry.setAddressFor(EpochManagerContract, address(epochManager)); From 7379affca3f8cc1d1a72170adcc0e3978d4b04b2 Mon Sep 17 00:00:00 2001 From: pahor167 Date: Mon, 19 Aug 2024 12:56:26 +0200 Subject: [PATCH 15/59] startNextEpochProcess fixes --- .../contracts-0.8/common/EpochManager.sol | 18 +++++++++--------- .../test-sol/unit/common/EpochManager.t.sol | 2 +- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/protocol/contracts-0.8/common/EpochManager.sol b/packages/protocol/contracts-0.8/common/EpochManager.sol index 80eb4176ae5..be56fecbc1f 100644 --- a/packages/protocol/contracts-0.8/common/EpochManager.sol +++ b/packages/protocol/contracts-0.8/common/EpochManager.sol @@ -12,6 +12,8 @@ import "../common/UsingRegistry.sol"; import "../../contracts/common/Initializable.sol"; import "../../contracts/common/interfaces/ICeloVersionedContract.sol"; +import "forge-std/console.sol"; + contract EpochManager is Initializable, UsingRegistry, @@ -133,9 +135,9 @@ contract EpochManager is /// it freezes the epochrewards at the time of execution, /// and starts the distribution of the rewards. function startNextEpochProcess() external nonReentrant { - require(isReadyToStartEpoch(), "Epoch is not ready to start"); + require(epochManagerInitializer == address(0), "Epoch system not initialized"); + require(isTimeForNextEpoch(), "Epoch is not ready to start"); require(!isOnEpochProcess(), "Epoch process is already started"); - require(elected.length > 0, "Elected length must be greater than 0."); epochProcessing.status = EpochProcessStatus.Started; epochs[currentEpochNumber].rewardsBlock = block.number; @@ -244,7 +246,11 @@ contract EpochManager is return epochs[epoch].lastBlock; } - function isTimeForNextEpoch() external view returns (bool) { + function isTimeForNextEpoch() public view returns (bool) { + console.log("block.timestamp", block.timestamp); + console.log("epochs[currentEpochNumber].startTimestamp", epochs[currentEpochNumber].startTimestamp); + console.log("epochDuration", epochDuration); + console.log("block.timestamp >= epochs[currentEpochNumber].startTimestamp + epochDuration;", block.timestamp >= epochs[currentEpochNumber].startTimestamp + epochDuration); return block.timestamp >= epochs[currentEpochNumber].startTimestamp + epochDuration; } @@ -280,12 +286,6 @@ contract EpochManager is return firstKnownEpoch != 0; } - // checks if end of epoch has been reached based on timestamp - function isReadyToStartEpoch() public view returns (bool) { - Epoch memory _currentEpoch = epochs[currentEpochNumber]; - return block.timestamp > _currentEpoch.startTimestamp + epochDuration; - } - function allocateValidatorsRewards() internal { // TODO complete this function uint256 totalRewards = 0; diff --git a/packages/protocol/test-sol/unit/common/EpochManager.t.sol b/packages/protocol/test-sol/unit/common/EpochManager.t.sol index a348b9ea763..db8760bde1a 100644 --- a/packages/protocol/test-sol/unit/common/EpochManager.t.sol +++ b/packages/protocol/test-sol/unit/common/EpochManager.t.sol @@ -144,7 +144,7 @@ contract EpochManagerTest_startNextEpochProcess is EpochManagerTest { uint256 _currentEpoch = epochManager.currentEpochNumber(); (, , , uint256 _currentEpochEndTimestamp, ) = epochManager.getCurrentEpoch(); - vm.expectRevert("Elected length must be greater than 0."); + vm.expectRevert("Epoch system not initialized"); epochManager.startNextEpochProcess(); } From 7205bb7bd3c5227817ec2165040e899bfd30c32c Mon Sep 17 00:00:00 2001 From: pahor167 Date: Mon, 19 Aug 2024 13:00:51 +0200 Subject: [PATCH 16/59] rename score manager to score reader --- .../protocol/contracts-0.8/common/EpochManager.sol | 12 ++++++++---- .../protocol/contracts-0.8/common/UsingRegistry.sol | 2 +- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/packages/protocol/contracts-0.8/common/EpochManager.sol b/packages/protocol/contracts-0.8/common/EpochManager.sol index be56fecbc1f..120b8d2065d 100644 --- a/packages/protocol/contracts-0.8/common/EpochManager.sol +++ b/packages/protocol/contracts-0.8/common/EpochManager.sol @@ -118,6 +118,7 @@ contract EpochManager is 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"); + require(firstElected.length > 0, "First elected validators must be greater than 0"); firstKnownEpoch = firstEpochNumber; currentEpochNumber = firstEpochNumber; @@ -189,12 +190,12 @@ contract EpochManager is require(epochProcessing.toProcessGroups == groups.length, "number of groups does not match"); for (uint i = 0; i < groups.length; i++) { - // checks that group is acutally from elected group + // 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[] memory uptimes = getScoreManager().getUptimes(groups[i]); + uint256[] memory uptimes = getScoreReader().getUptimes(groups[i]); uint256 epochRewards = getElection().getGroupEpochRewards( groups[i], epochProcessing.totalRewardsVoter, @@ -289,9 +290,12 @@ contract EpochManager is function allocateValidatorsRewards() internal { // TODO complete this function uint256 totalRewards = 0; + IScoreReader scoreReader = getScoreReader(); + IValidators validators = getValidators(); + for (uint i = 0; i < elected.length; i++) { - uint256 validatorScore = getScoreManager().getValidatorScore(elected[i]); - uint256 validatorReward = getValidators().computeEpochReward( + uint256 validatorScore = scoreReader.getValidatorScore(elected[i]); + uint256 validatorReward = validators.computeEpochReward( elected[i], validatorScore, epochProcessing.perValidatorReward diff --git a/packages/protocol/contracts-0.8/common/UsingRegistry.sol b/packages/protocol/contracts-0.8/common/UsingRegistry.sol index 05adab55fb7..0a0e29eb0d4 100644 --- a/packages/protocol/contracts-0.8/common/UsingRegistry.sol +++ b/packages/protocol/contracts-0.8/common/UsingRegistry.sol @@ -150,7 +150,7 @@ contract UsingRegistry is Ownable { return IEpochManager(registry.getAddressForOrDie(EPOCH_MANAGER_REGISTRY_ID)); } - function getScoreManager() internal view returns (IScoreReader) { + function getScoreReader() internal view returns (IScoreReader) { return IScoreReader(registry.getAddressForOrDie(SCORE_MANAGER_REGISTRY_ID)); } } From b89cc746697939fc8264e45b97b84770805acf10 Mon Sep 17 00:00:00 2001 From: pahor167 Date: Mon, 19 Aug 2024 13:05:32 +0200 Subject: [PATCH 17/59] Rename transfer to release on CeloDistribution schedule --- .../contracts-0.8/common/CeloDistributionSchedule.sol | 8 ++++---- .../protocol/contracts-0.8/common/EpochManager.sol | 6 +++--- .../common/interfaces/ICeloDistributionSchedule.sol | 10 +++++----- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/packages/protocol/contracts-0.8/common/CeloDistributionSchedule.sol b/packages/protocol/contracts-0.8/common/CeloDistributionSchedule.sol index 2c516b00588..284600285b7 100644 --- a/packages/protocol/contracts-0.8/common/CeloDistributionSchedule.sol +++ b/packages/protocol/contracts-0.8/common/CeloDistributionSchedule.sol @@ -133,11 +133,11 @@ contract CeloDistributionSchedule is UsingRegistry, ReentrancyGuard, Initializab } /** - * @notice Transfers the Celo to the specified address. - * @param to The address to transfer the amount to. - * @param amount The amount to transfer. + * @notice Releases the Celo to the specified address. + * @param to The address to release the amount to. + * @param amount The amount to release. */ - function transfer(address to, uint256 amount) external onlyEpochManager { + function release(address to, uint256 amount) external onlyEpochManager { require(address(this).balance >= amount, "Insufficient balance."); ICeloToken celoToken = ICeloToken(address(getCeloToken())); celoToken.transfer(to, amount); diff --git a/packages/protocol/contracts-0.8/common/EpochManager.sol b/packages/protocol/contracts-0.8/common/EpochManager.sol index 120b8d2065d..fdd6ce71feb 100644 --- a/packages/protocol/contracts-0.8/common/EpochManager.sol +++ b/packages/protocol/contracts-0.8/common/EpochManager.sol @@ -203,11 +203,11 @@ contract EpochManager is ); getElection().distributeEpochRewards(groups[i], epochRewards, lessers[i], greaters[i]); } - getCeloDistributionSchedule().transfer( + getCeloDistributionSchedule().release( registry.getAddressForOrDie(GOVERNANCE_REGISTRY_ID), epochProcessing.totalRewardsCommunity ); - getCeloDistributionSchedule().transfer( + getCeloDistributionSchedule().release( carbonOffsettingPartner, epochProcessing.totalRewardsCarbonFund ); @@ -313,7 +313,7 @@ contract EpochManager is uint256 CELOequivalent = (numerator * totalRewards) / denominator; // this is not a mint anymore - getCeloDistributionSchedule().transfer( + getCeloDistributionSchedule().release( registry.getAddressForOrDie(RESERVE_REGISTRY_ID), CELOequivalent ); diff --git a/packages/protocol/contracts/common/interfaces/ICeloDistributionSchedule.sol b/packages/protocol/contracts/common/interfaces/ICeloDistributionSchedule.sol index d86e4e9ed50..32505b3cbbf 100644 --- a/packages/protocol/contracts/common/interfaces/ICeloDistributionSchedule.sol +++ b/packages/protocol/contracts/common/interfaces/ICeloDistributionSchedule.sol @@ -23,10 +23,10 @@ interface ICeloDistributionSchedule { */ function getTargetCeloTotalSupply() external returns (uint256, uint256, uint256); - /** - * @notice Transfers the Celo to the specified address. - * @param to The address to transfer the amount to. - * @param amount The amount to transfer. + /** + * @notice Releases the Celo to the specified address. + * @param to The address to release the amount to. + * @param amount The amount to release. */ - function transfer(address to, uint256 amount) external ; + function release(address to, uint256 amount) external ; } From 758d097a834f6b872996e6466fa50bfedc8c4e14 Mon Sep 17 00:00:00 2001 From: pahor167 Date: Mon, 19 Aug 2024 13:24:23 +0200 Subject: [PATCH 18/59] Celo distribution schedule renamed to CeloUnreleasedTreasure --- .../common/CeloDistributionSchedule.sol | 361 --------- .../common/CeloUnreleasedTreasure.sol | 68 ++ .../contracts-0.8/common/EpochManager.sol | 6 +- .../contracts-0.8/common/UsingRegistry.sol | 12 +- .../ICeloDistributionScheduleInitializer.sol | 2 +- .../protocol/contracts/common/GoldToken.sol | 12 +- .../contracts/common/UsingRegistry.sol | 12 +- .../contracts/common/UsingRegistryV2.sol | 10 +- .../interfaces/ICeloDistributionSchedule.sol | 32 - .../interfaces/ICeloUnreleasedTreasure.sol | 11 + ...xy.sol => CeloUnreleasedTreasureProxy.sol} | 2 +- .../protocol/migrations_sol/Migration.s.sol | 10 +- .../protocol/migrations_sol/constants.sol | 2 +- packages/protocol/test-sol/constants.sol | 2 +- .../common/CeloDistributionSchedule.t.sol | 746 ------------------ .../unit/common/CeloUnreleasedTreasure.t.sol | 147 ++++ .../test-sol/unit/common/EpochManager.t.sol | 12 +- .../test-sol/unit/common/FeeHandler.t.sol | 4 +- .../test-sol/unit/common/GoldToken.t.sol | 15 +- .../unit/governance/voting/ReleaseGold.t.sol | 4 +- 20 files changed, 269 insertions(+), 1201 deletions(-) delete mode 100644 packages/protocol/contracts-0.8/common/CeloDistributionSchedule.sol create mode 100644 packages/protocol/contracts-0.8/common/CeloUnreleasedTreasure.sol delete mode 100644 packages/protocol/contracts/common/interfaces/ICeloDistributionSchedule.sol create mode 100644 packages/protocol/contracts/common/interfaces/ICeloUnreleasedTreasure.sol rename packages/protocol/contracts/common/proxies/{CeloDistributionScheduleProxy.sol => CeloUnreleasedTreasureProxy.sol} (65%) delete mode 100644 packages/protocol/test-sol/unit/common/CeloDistributionSchedule.t.sol create mode 100644 packages/protocol/test-sol/unit/common/CeloUnreleasedTreasure.t.sol diff --git a/packages/protocol/contracts-0.8/common/CeloDistributionSchedule.sol b/packages/protocol/contracts-0.8/common/CeloDistributionSchedule.sol deleted file mode 100644 index 284600285b7..00000000000 --- a/packages/protocol/contracts-0.8/common/CeloDistributionSchedule.sol +++ /dev/null @@ -1,361 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity >=0.8.7 <0.8.20; - -import "@openzeppelin/contracts8/security/ReentrancyGuard.sol"; -import "@openzeppelin/contracts8/utils/math/Math.sol"; - -import "./UsingRegistry.sol"; -import "../common/IsL2Check.sol"; - -import "../../contracts/common/FixidityLib.sol"; -import "../../contracts/common/Initializable.sol"; -import "../../contracts-0.8/common/interfaces/ICeloToken.sol"; - -/** - * @title Contract for distributing CELO token based on a schedule. - */ -contract CeloDistributionSchedule is UsingRegistry, ReentrancyGuard, Initializable, IsL2Check { - using FixidityLib for FixidityLib.Fraction; - - uint256 constant GENESIS_CELO_SUPPLY = 600000000 ether; // 600 million Celo - uint256 constant YEARS_LINEAR = 15; - uint256 constant SECONDS_LINEAR = YEARS_LINEAR * 365 * 1 days; - - bool public areDependenciesSet; - uint256 constant GENESIS_START_TIME = 1587587214; // Copied over from `EpochRewards().startTime()`. - uint256 public l2StartTime; - uint256 public totalAllocatedAtL2Start; - - uint256 public totalDistributedBySchedule; - address public communityRewardFund; - address public carbonOffsettingPartner; - - FixidityLib.Fraction private communityRewardFraction; - FixidityLib.Fraction private carbonOffsettingFraction; - - event CommunityRewardFractionSet(uint256 fraction); - event CarbonOffsettingFundSet(address indexed partner, uint256 fraction); - - modifier whenActivated() { - require(areDependenciesSet, "Distribution schedule has not been activated."); - _; - } - - modifier onlyEpochManager() { - require( - msg.sender == registry.getAddressForOrDie(EPOCH_MANAGER_REGISTRY_ID), - "Only the EpochManager contract can call this function." - ); - _; - } - - /** - * @notice Sets initialized == true on implementation contracts - * @param test Set to true to skip implementation initialization - */ - constructor(bool test) public Initializable(test) {} - - /** - * @notice A constructor for initialising a new instance of a CeloDistributionSchedule contract. - * @param registryAddress The address of the registry core smart contract. - - */ - function initialize(address registryAddress) external initializer { - _transferOwnership(msg.sender); - setRegistry(registryAddress); - } - - /** - * @notice Sets the distribution schedule dependencies during L2 transition. - * @param _l2StartTime The timestamp of L1 to L2 transition. - * @param _communityRewardFraction The percentage of rewards that go the community funds. - * @param _carbonOffsettingPartner The address of the carbon offsetting partner. - * @param _carbonOffsettingFraction The percentage of rewards going to carbon offsetting partner. - */ - function activate( - uint256 _l2StartTime, - uint256 _communityRewardFraction, - address _carbonOffsettingPartner, - uint256 _carbonOffsettingFraction - ) external onlyOwner onlyL2 { - require(address(this).balance > 0, "Contract does not have CELO balance."); - require(!areDependenciesSet, "Contract has already been activated."); - require(block.timestamp > _l2StartTime, "L2 start time cannot be set to a future date."); - ICeloToken celoToken = ICeloToken(address(getCeloToken())); - require( - registry.getAddressForOrDie(CELO_DISTRIBUTION_SCHEDULE_REGISTRY_ID) == address(this), - "CeloDistributionSchedule address is incorrectly set in Registry." - ); - areDependenciesSet = true; - l2StartTime = _l2StartTime; - communityRewardFund = address(getGovernance()); - totalAllocatedAtL2Start = celoToken.allocatedSupply(); - setCommunityRewardFraction(_communityRewardFraction); - setCarbonOffsettingFund(_carbonOffsettingPartner, _carbonOffsettingFraction); - } - - /** - * @notice Distributes CELO to the community and carbon offsetting funds according to the predefined schedule. - */ - function distributeAccordingToSchedule() external nonReentrant onlyL2 returns (bool) { - ( - uint256 targetCeloDistribution, - uint256 communityRewardFundDistributionAmount, - uint256 carbonOffsettingPartnerDistributionAmount - ) = getTargetCeloDistribution(); - - ICeloToken celoToken = ICeloToken(address(getCeloToken())); - - require( - targetCeloDistribution >= celoToken.allocatedSupply(), - "Contract balance is insufficient." - ); - - uint256 distributableAmount = Math.min( - getRemainingBalanceToDistribute(), - targetCeloDistribution - celoToken.allocatedSupply() - ); - - require(distributableAmount > 0, "Distributable amount must be greater than zero."); - - totalDistributedBySchedule += distributableAmount; - - require( - celoToken.transfer(communityRewardFund, communityRewardFundDistributionAmount), - "Failed to transfer to community partner." - ); - - require( - celoToken.transfer(carbonOffsettingPartner, carbonOffsettingPartnerDistributionAmount), - "Failed to transfer to carbon offsetting partner." - ); - return true; - } - - /** - * @notice Releases the Celo to the specified address. - * @param to The address to release the amount to. - * @param amount The amount to release. - */ - function release(address to, uint256 amount) external onlyEpochManager { - require(address(this).balance >= amount, "Insufficient balance."); - ICeloToken celoToken = ICeloToken(address(getCeloToken())); - celoToken.transfer(to, amount); - } - - /** - * @notice Returns the community reward fraction. - * @return The percentage of total reward which goes to the community funds. - */ - function getCommunityRewardFraction() external view returns (uint256) { - return communityRewardFraction.unwrap(); - } - - /** - * @notice Returns the carbon offsetting partner reward fraction. - * @return The percentage of total reward which goes to the carbon offsetting partner. - */ - function getCarbonOffsettingFraction() external view returns (uint256) { - return carbonOffsettingFraction.unwrap(); - } - - /** - * @return The total balance distributed by the CeloDistributionSchedule contract. - */ - function getTotalDistributedBySchedule() external view returns (uint256) { - return totalDistributedBySchedule; - } - - /** - * @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); - } - - /** - * @notice Sets the community reward percentage - * @param value The percentage of the total reward to be sent to the community funds as Fixidity fraction. - * @return True upon success. - */ - function setCommunityRewardFraction(uint256 value) public onlyOwner whenActivated returns (bool) { - uint256 timeSinceL2Start = block.timestamp - l2StartTime; - uint256 totalL2LinearSecondsAvailable = SECONDS_LINEAR - (l2StartTime - GENESIS_START_TIME); - FixidityLib.Fraction memory wrappedValue = FixidityLib.wrap(value); - require( - timeSinceL2Start < totalL2LinearSecondsAvailable, - "Can only update fraction once block reward calculation for years 15-30 has been implemented." - ); - require( - !wrappedValue.equals(communityRewardFraction) && wrappedValue.lt(FixidityLib.fixed1()), - "Value must be different from existing community reward fraction and less than 1." - ); - communityRewardFraction = wrappedValue; - require( - FixidityLib.fixed1().gte(communityRewardFraction.add(carbonOffsettingFraction)), - "Sum of partner fractions must be less than or equal to 1." - ); - emit CommunityRewardFractionSet(value); - return true; - } - - /** - * @notice Sets the carbon offsetting fund. - * @param partner The address of the carbon offsetting partner. - * @param value The percentage of the total reward to be sent to the carbon offsetting partner as Fixidity fraction. - * @return True upon success. - */ - function setCarbonOffsettingFund( - address partner, - uint256 value - ) public onlyOwner whenActivated returns (bool) { - require(partner != address(0), "Partner cannot be the zero address."); - uint256 timeSinceL2Start = block.timestamp - l2StartTime; - uint256 totalL2LinearSecondsAvailable = SECONDS_LINEAR - (l2StartTime - GENESIS_START_TIME); - require( - timeSinceL2Start < totalL2LinearSecondsAvailable, - "Can only update fraction once block reward calculation for years 15-30 has been implemented." - ); - FixidityLib.Fraction memory wrappedValue = FixidityLib.wrap(value); - require( - partner != carbonOffsettingPartner || !wrappedValue.equals(carbonOffsettingFraction), - "Partner and value must be different from existing carbon offsetting fund." - ); - require(wrappedValue.lt(FixidityLib.fixed1()), "Value must be less than 1."); - carbonOffsettingPartner = partner; - carbonOffsettingFraction = wrappedValue; - require( - FixidityLib.fixed1().gte(communityRewardFraction.add(carbonOffsettingFraction)), - "Sum of partner fractions must be less than or equal to 1." - ); - emit CarbonOffsettingFundSet(partner, value); - return true; - } - - /** - * @return The remaining CELO balance to distribute. - */ - function getRemainingBalanceToDistribute() public view returns (uint256) { - return address(this).balance; - } - - /** - * @return The currently distributable amount. - */ - function getDistributableAmount() public view returns (uint256) { - (uint256 targetCeloDistribution, , ) = getTargetCeloDistribution(); - ICeloToken celoToken = ICeloToken(address(getCeloToken())); - return targetCeloDistribution - celoToken.allocatedSupply(); - } - - /** - * @notice Returns the target CELO supply according to the target schedule. - * @return targetCeloDistribution The target total CELO supply according to the target schedule. - * @return communityTargetRewards The community reward that can be distributed according to the target schedule. - * @return carbonFundTargetRewards The carbon offsetting reward that can be distributed according to the target schedule. - */ - function getTargetCeloDistribution() - public - view - whenActivated - returns ( - uint256 targetCeloDistribution, - uint256 communityTargetRewards, - uint256 carbonFundTargetRewards - ) - { - require(block.timestamp > GENESIS_START_TIME, "GENESIS_START_TIME has not yet been reached."); - require(block.timestamp > l2StartTime, "l2StartTime has not yet been reached."); - - uint256 timeSinceL2Start = block.timestamp - l2StartTime; - uint256 totalL2LinearSecondsAvailable = SECONDS_LINEAR - (l2StartTime - GENESIS_START_TIME); - uint256 mintedOnL1 = totalAllocatedAtL2Start - GENESIS_CELO_SUPPLY; - - bool isLinearDistribution = timeSinceL2Start < totalL2LinearSecondsAvailable; - if (isLinearDistribution) { - ( - targetCeloDistribution, - communityTargetRewards, - carbonFundTargetRewards - ) = _calculateTargetReward(timeSinceL2Start, totalL2LinearSecondsAvailable, mintedOnL1); - - return (targetCeloDistribution, communityTargetRewards, carbonFundTargetRewards); - } else { - ( - targetCeloDistribution, - communityTargetRewards, - carbonFundTargetRewards - ) = _calculateTargetReward( - totalL2LinearSecondsAvailable - 1, - totalL2LinearSecondsAvailable, - mintedOnL1 - ); - - bool hasNotYetDistributedAllLinearRewards = totalDistributedBySchedule + - GENESIS_CELO_SUPPLY + - mintedOnL1 < - targetCeloDistribution; - - if (hasNotYetDistributedAllLinearRewards) { - return (targetCeloDistribution, communityTargetRewards, carbonFundTargetRewards); - } - revert("Block reward calculation for years 15-30 unimplemented"); - return (0, 0, 0); - } - } - - function _calculateTargetReward( - uint256 elapsedTime, - uint256 _totalL2LinearSecondsAvailable, - uint256 _mintedOnL1 - ) - internal - view - returns ( - uint256 targetCeloDistribution, - uint256 communityTargetRewards, - uint256 carbonFundTargetRewards - ) - { - FixidityLib.Fraction memory elapsedTimeFraction = FixidityLib.wrap(elapsedTime); - FixidityLib.Fraction memory totalL2LinearSecondsAvailableFraction = FixidityLib.wrap( - _totalL2LinearSecondsAvailable - ); - // Pay out half of all block rewards linearly. - ICeloToken celoToken = ICeloToken(address(getCeloToken())); - uint256 totalLinearRewards = (celoToken.totalSupply() - GENESIS_CELO_SUPPLY) / 2; //(200 million) includes validator rewards. - - FixidityLib.Fraction memory l2LinearRewards = FixidityLib.newFixed( - totalLinearRewards - _mintedOnL1 - ); - - FixidityLib.Fraction memory linearRewardsToCommunity = l2LinearRewards.multiply( - communityRewardFraction - ); - - FixidityLib.Fraction memory linearRewardsToCarbon = l2LinearRewards.multiply( - carbonOffsettingFraction - ); - - communityTargetRewards = ( - linearRewardsToCommunity.multiply(elapsedTimeFraction).divide( - totalL2LinearSecondsAvailableFraction - ) - ).fromFixed(); - - carbonFundTargetRewards = linearRewardsToCarbon - .multiply(elapsedTimeFraction) - .divide(totalL2LinearSecondsAvailableFraction) - .fromFixed(); - - targetCeloDistribution = - communityTargetRewards + - carbonFundTargetRewards + - GENESIS_CELO_SUPPLY + - _mintedOnL1; - } -} diff --git a/packages/protocol/contracts-0.8/common/CeloUnreleasedTreasure.sol b/packages/protocol/contracts-0.8/common/CeloUnreleasedTreasure.sol new file mode 100644 index 00000000000..6291185acb3 --- /dev/null +++ b/packages/protocol/contracts-0.8/common/CeloUnreleasedTreasure.sol @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity >=0.8.7 <0.8.20; + +import "@openzeppelin/contracts8/security/ReentrancyGuard.sol"; +import "@openzeppelin/contracts8/utils/math/Math.sol"; + +import "./UsingRegistry.sol"; +import "../common/IsL2Check.sol"; + +import "../../contracts/common/Initializable.sol"; +import "../../contracts-0.8/common/interfaces/ICeloToken.sol"; + +/** + * @title Contract for unreleased Celo tokens. + */ +contract CeloUnreleasedTreasure is UsingRegistry, ReentrancyGuard, Initializable, IsL2Check { + + event Released(address indexed to, uint256 amount); + + modifier onlyEpochManager() { + require( + msg.sender == registry.getAddressForOrDie(EPOCH_MANAGER_REGISTRY_ID), + "Only the EpochManager contract can call this function." + ); + _; + } + + /** + * @notice Sets initialized == true on implementation contracts + * @param test Set to true to skip implementation initialization + */ + constructor(bool test) public Initializable(test) {} + + /** + * @notice A constructor for initialising a new instance of a CeloUnreleasedTreasure contract. + * @param registryAddress The address of the registry core smart contract. + + */ + function initialize(address registryAddress) external initializer { + _transferOwnership(msg.sender); + setRegistry(registryAddress); + } + + + /** + * @notice Releases the Celo to the specified address. + * @param to The address to release the amount to. + * @param amount The amount to release. + */ + function release(address to, uint256 amount) external onlyEpochManager { + require(address(this).balance >= amount, "Insufficient balance."); + ICeloToken celoToken = ICeloToken(address(getCeloToken())); + celoToken.transfer(to, amount); + emit Released(to, amount); + } + + + /** + * @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); + } +} diff --git a/packages/protocol/contracts-0.8/common/EpochManager.sol b/packages/protocol/contracts-0.8/common/EpochManager.sol index fdd6ce71feb..b88c000b3e2 100644 --- a/packages/protocol/contracts-0.8/common/EpochManager.sol +++ b/packages/protocol/contracts-0.8/common/EpochManager.sol @@ -203,11 +203,11 @@ contract EpochManager is ); getElection().distributeEpochRewards(groups[i], epochRewards, lessers[i], greaters[i]); } - getCeloDistributionSchedule().release( + getCeloUnreleasedTreasure().release( registry.getAddressForOrDie(GOVERNANCE_REGISTRY_ID), epochProcessing.totalRewardsCommunity ); - getCeloDistributionSchedule().release( + getCeloUnreleasedTreasure().release( carbonOffsettingPartner, epochProcessing.totalRewardsCarbonFund ); @@ -313,7 +313,7 @@ contract EpochManager is uint256 CELOequivalent = (numerator * totalRewards) / denominator; // this is not a mint anymore - getCeloDistributionSchedule().release( + getCeloUnreleasedTreasure().release( registry.getAddressForOrDie(RESERVE_REGISTRY_ID), CELOequivalent ); diff --git a/packages/protocol/contracts-0.8/common/UsingRegistry.sol b/packages/protocol/contracts-0.8/common/UsingRegistry.sol index 0a0e29eb0d4..5a2936b4103 100644 --- a/packages/protocol/contracts-0.8/common/UsingRegistry.sol +++ b/packages/protocol/contracts-0.8/common/UsingRegistry.sol @@ -12,7 +12,7 @@ import "./interfaces/IEpochManager.sol"; import "../../contracts/common/interfaces/IRegistry.sol"; import "../../contracts/common/interfaces/IAccounts.sol"; import "../../contracts/common/interfaces/IFreezer.sol"; -import "../../contracts/common/interfaces/ICeloDistributionSchedule.sol"; +import "../../contracts/common/interfaces/ICeloUnreleasedTreasure.sol"; import "../../contracts/common/interfaces/IEpochManagerInitializer.sol"; import "../../contracts/governance/interfaces/IGovernance.sol"; import "../../contracts/governance/interfaces/ILockedGold.sol"; @@ -52,8 +52,8 @@ contract UsingRegistry is Ownable { keccak256(abi.encodePacked("MentoFeeHandlerSeller")); bytes32 constant CELO_TOKEN_REGISTRY_ID = keccak256(abi.encodePacked("CeloToken")); bytes32 constant LOCKED_CELO_REGISTRY_ID = keccak256(abi.encodePacked("LockedCelo")); - bytes32 constant CELO_DISTRIBUTION_SCHEDULE_REGISTRY_ID = - keccak256(abi.encodePacked("CeloDistributionSchedule")); + bytes32 constant CELO_UNRELEASED_TREASURE_REGISTRY_ID = + keccak256(abi.encodePacked("CeloUnreleasedTreasure")); bytes32 constant EPOCH_REWARDS_REGISTRY_ID = keccak256(abi.encodePacked("EpochRewards")); bytes32 constant EPOCH_MANAGER_REGISTRY_ID = keccak256(abi.encodePacked("EpochManager")); bytes32 constant SCORE_MANAGER_REGISTRY_ID = keccak256(abi.encodePacked("ScoreManager")); @@ -135,10 +135,10 @@ contract UsingRegistry is Ownable { return IGovernance(registry.getAddressForOrDie(GOVERNANCE_REGISTRY_ID)); } - function getCeloDistributionSchedule() internal view returns (ICeloDistributionSchedule) { + function getCeloUnreleasedTreasure() internal view returns (ICeloUnreleasedTreasure) { return - ICeloDistributionSchedule( - registry.getAddressForOrDie(CELO_DISTRIBUTION_SCHEDULE_REGISTRY_ID) + ICeloUnreleasedTreasure( + registry.getAddressForOrDie(CELO_UNRELEASED_TREASURE_REGISTRY_ID) ); } diff --git a/packages/protocol/contracts-0.8/common/interfaces/ICeloDistributionScheduleInitializer.sol b/packages/protocol/contracts-0.8/common/interfaces/ICeloDistributionScheduleInitializer.sol index 7844337b5e3..31f49e3780f 100644 --- a/packages/protocol/contracts-0.8/common/interfaces/ICeloDistributionScheduleInitializer.sol +++ b/packages/protocol/contracts-0.8/common/interfaces/ICeloDistributionScheduleInitializer.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: LGPL-3.0-only pragma solidity >=0.5.13 <0.9.0; -interface ICeloDistributionScheduleInitializer { +interface ICeloUnreleasedTreasureInitializer { function initialize(address registryAddress) external; } diff --git a/packages/protocol/contracts/common/GoldToken.sol b/packages/protocol/contracts/common/GoldToken.sol index 27f2eb88003..d3d15d3f394 100644 --- a/packages/protocol/contracts/common/GoldToken.sol +++ b/packages/protocol/contracts/common/GoldToken.sol @@ -10,7 +10,7 @@ import "./Initializable.sol"; import "./interfaces/ICeloToken.sol"; import "./interfaces/ICeloTokenInitializer.sol"; import "./interfaces/ICeloVersionedContract.sol"; -import "./interfaces/ICeloDistributionSchedule.sol"; +import "./interfaces/ICeloUnreleasedTreasure.sol"; import "../../contracts-0.8/common/IsL2Check.sol"; contract GoldToken is @@ -152,10 +152,6 @@ contract GoldToken is */ function transferFrom(address from, address to, uint256 value) external returns (bool) { require(to != address(0), "transfer attempted to reserved address 0x0"); - require( - to != registry.getAddressForOrDie(CELO_DISTRIBUTION_SCHEDULE_REGISTRY_ID), - "transfer attempted to reserved CeloDistributionSchedule address" - ); require(value <= balanceOf(from), "transfer value exceeded balance of sender"); require( value <= allowed[from][msg.sender], @@ -229,7 +225,7 @@ contract GoldToken is */ function allocatedSupply() external view onlyL2 returns (uint256) { return - CELO_SUPPLY_CAP - registry.getAddressForOrDie(CELO_DISTRIBUTION_SCHEDULE_REGISTRY_ID).balance; + CELO_SUPPLY_CAP - registry.getAddressForOrDie(CELO_UNRELEASED_TREASURE_REGISTRY_ID).balance; } /** @@ -295,10 +291,6 @@ contract GoldToken is * @return True if the transaction succeeds. */ function _transfer(address to, uint256 value) internal returns (bool) { - require( - to != registry.getAddressForOrDie(CELO_DISTRIBUTION_SCHEDULE_REGISTRY_ID), - "transfer attempted to reserved CeloDistributionSchedule address" - ); require(value <= balanceOf(msg.sender), "transfer value exceeded balance of sender"); bool success; diff --git a/packages/protocol/contracts/common/UsingRegistry.sol b/packages/protocol/contracts/common/UsingRegistry.sol index 048591516f0..1c39082cdf0 100644 --- a/packages/protocol/contracts/common/UsingRegistry.sol +++ b/packages/protocol/contracts/common/UsingRegistry.sol @@ -8,7 +8,7 @@ import "./interfaces/IAccounts.sol"; import "./interfaces/IFeeCurrencyWhitelist.sol"; import "./interfaces/IFreezer.sol"; import "./interfaces/IRegistry.sol"; -import "./interfaces/ICeloDistributionSchedule.sol"; +import "./interfaces/ICeloUnreleasedTreasure.sol"; import "./interfaces/IEpochManagerInitializer.sol"; import "../governance/interfaces/IElection.sol"; @@ -51,8 +51,8 @@ contract UsingRegistry is Ownable { bytes32 constant CELO_TOKEN_REGISTRY_ID = keccak256(abi.encodePacked("CeloToken")); bytes32 constant LOCKED_CELO_REGISTRY_ID = keccak256(abi.encodePacked("LockedCelo")); - bytes32 constant CELO_DISTRIBUTION_SCHEDULE_REGISTRY_ID = - keccak256(abi.encodePacked("CeloDistributionSchedule")); + bytes32 constant CELO_UNRELEASED_TREASURE_REGISTRY_ID = + keccak256(abi.encodePacked("CeloUnreleasedTreasure")); bytes32 constant EPOCH_REWARDS_REGISTRY_ID = keccak256(abi.encodePacked("EpochRewards")); bytes32 constant EPOCH_MANAGER_INITIALIZER_REGISTRY_ID = keccak256(abi.encodePacked("EpochManagerInitializer")); @@ -145,10 +145,10 @@ contract UsingRegistry is Ownable { return IValidators(registry.getAddressForOrDie(VALIDATORS_REGISTRY_ID)); } - function getCeloDistributionSchedule() internal view returns (ICeloDistributionSchedule) { + function getCeloUnreleasedTreasure() internal view returns (ICeloUnreleasedTreasure) { return - ICeloDistributionSchedule( - registry.getAddressForOrDie(CELO_DISTRIBUTION_SCHEDULE_REGISTRY_ID) + ICeloUnreleasedTreasure( + registry.getAddressForOrDie(CELO_UNRELEASED_TREASURE_REGISTRY_ID) ); } diff --git a/packages/protocol/contracts/common/UsingRegistryV2.sol b/packages/protocol/contracts/common/UsingRegistryV2.sol index d9df48546f4..2411bdf93fd 100644 --- a/packages/protocol/contracts/common/UsingRegistryV2.sol +++ b/packages/protocol/contracts/common/UsingRegistryV2.sol @@ -7,7 +7,7 @@ import "./interfaces/IAccounts.sol"; import "./interfaces/IFeeCurrencyWhitelist.sol"; import "./interfaces/IFreezer.sol"; import "./interfaces/IRegistry.sol"; -import "./interfaces/ICeloDistributionSchedule.sol"; +import "./interfaces/ICeloUnreleasedTreasure.sol"; import "./interfaces/IEpochManagerInitializer.sol"; import "../governance/interfaces/IElection.sol"; @@ -61,8 +61,8 @@ contract UsingRegistryV2 { bytes32 internal constant STABLE_REAL_TOKEN_REGISTRY_ID = keccak256(abi.encodePacked("StableTokenBRL")); bytes32 internal constant VALIDATORS_REGISTRY_ID = keccak256(abi.encodePacked("Validators")); - bytes32 internal constant CELO_DISTRIBUTION_SCHEDULE_REGISTRY_ID = - keccak256(abi.encodePacked("CeloDistributionSchedule")); + bytes32 internal constant CELO_UNRELEASED_TREASURE_REGISTRY_ID = + keccak256(abi.encodePacked("CeloUnreleasedTreasure")); bytes32 internal constant CELO_TOKEN_REGISTRY_ID = keccak256(abi.encodePacked("CeloToken")); bytes32 internal constant LOCKED_CELO_REGISTRY_ID = keccak256(abi.encodePacked("LockedCelo")); @@ -182,9 +182,9 @@ contract UsingRegistryV2 { return IValidators(registryContract.getAddressForOrDie(VALIDATORS_REGISTRY_ID)); } - function getCeloDistributionSchedule() internal view returns (ICeloDistributionSchedule) { + function getCeloUnreleasedTreasure() internal view returns (ICeloUnreleasedTreasure) { return - ICeloDistributionSchedule(registryContract.getAddressForOrDie(CELO_DISTRIBUTION_SCHEDULE_REGISTRY_ID)); + ICeloUnreleasedTreasure(registryContract.getAddressForOrDie(CELO_UNRELEASED_TREASURE_REGISTRY_ID)); } function getEpochRewards() internal view returns (IEpochRewards) { diff --git a/packages/protocol/contracts/common/interfaces/ICeloDistributionSchedule.sol b/packages/protocol/contracts/common/interfaces/ICeloDistributionSchedule.sol deleted file mode 100644 index 32505b3cbbf..00000000000 --- a/packages/protocol/contracts/common/interfaces/ICeloDistributionSchedule.sol +++ /dev/null @@ -1,32 +0,0 @@ -// SPDX-License-Identifier: LGPL-3.0-only -pragma solidity >=0.5.13 <0.9.0; - -interface ICeloDistributionSchedule { - /** - * @notice Sets the distribution schedule dependencies during L2 transition. - */ - function activate(uint256, uint256, address, uint256, address) external; - - /** - * @notice Mints CELO to the beneficiaries according to the predefined schedule. - */ - function mintAccordingToSchedule() external returns (bool); - - /** - * @return The currently mintable amount. - */ - function getMintableAmount() external returns (uint256); - - /** - * @notice Returns the target CELO supply according to the target schedule. - * @return The target CELO supply according to the target schedule. - */ - function getTargetCeloTotalSupply() external returns (uint256, uint256, uint256); - - /** - * @notice Releases the Celo to the specified address. - * @param to The address to release the amount to. - * @param amount The amount to release. - */ - function release(address to, uint256 amount) external ; -} diff --git a/packages/protocol/contracts/common/interfaces/ICeloUnreleasedTreasure.sol b/packages/protocol/contracts/common/interfaces/ICeloUnreleasedTreasure.sol new file mode 100644 index 00000000000..698a4122c27 --- /dev/null +++ b/packages/protocol/contracts/common/interfaces/ICeloUnreleasedTreasure.sol @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity >=0.5.13 <0.9.0; + +interface ICeloUnreleasedTreasure { + /** + * @notice Releases the Celo to the specified address. + * @param to The address to release the amount to. + * @param amount The amount to release. + */ + function release(address to, uint256 amount) external ; +} diff --git a/packages/protocol/contracts/common/proxies/CeloDistributionScheduleProxy.sol b/packages/protocol/contracts/common/proxies/CeloUnreleasedTreasureProxy.sol similarity index 65% rename from packages/protocol/contracts/common/proxies/CeloDistributionScheduleProxy.sol rename to packages/protocol/contracts/common/proxies/CeloUnreleasedTreasureProxy.sol index 5fd995d64bf..420badab001 100644 --- a/packages/protocol/contracts/common/proxies/CeloDistributionScheduleProxy.sol +++ b/packages/protocol/contracts/common/proxies/CeloUnreleasedTreasureProxy.sol @@ -3,4 +3,4 @@ pragma solidity ^0.5.13; import "../Proxy.sol"; /* solhint-disable-next-line no-empty-blocks */ -contract CeloDistributionScheduleProxy is Proxy {} +contract CeloUnreleasedTreasureProxy is Proxy {} diff --git a/packages/protocol/migrations_sol/Migration.s.sol b/packages/protocol/migrations_sol/Migration.s.sol index b2f871cff1c..6c8259c3344 100644 --- a/packages/protocol/migrations_sol/Migration.s.sol +++ b/packages/protocol/migrations_sol/Migration.s.sol @@ -49,7 +49,7 @@ import "@celo-contracts/stability/interfaces/ISortedOracles.sol"; // Core contract imports on Solidity 0.8 import "@celo-contracts-8/common/interfaces/IFeeCurrencyDirectoryInitializer.sol"; import "@celo-contracts-8/common/interfaces/IGasPriceMinimumInitializer.sol"; -import "@celo-contracts-8/common/interfaces/ICeloDistributionScheduleInitializer.sol"; +import "@celo-contracts-8/common/interfaces/ICeloUnreleasedTreasureInitializer.sol"; import "@celo-contracts-8/common/interfaces/IFeeCurrencyDirectory.sol"; import "@celo-contracts-8/common/UsingRegistry.sol"; @@ -235,7 +235,7 @@ contract Migration is Script, UsingRegistry, MigrationsConstants { migrateUniswapFeeHandlerSeller(); migrateFeeHandler(json); migrateOdisPayments(); - migrateCeloDistributionSchedule(); + migrateCeloUnreleasedTreasure(); migrateGovernance(json); vm.stopBroadcast(); @@ -910,11 +910,11 @@ contract Migration is Script, UsingRegistry, MigrationsConstants { ); } - function migrateCeloDistributionSchedule() public { + function migrateCeloUnreleasedTreasure() public { deployProxiedContract( - "CeloDistributionSchedule", + "CeloUnreleasedTreasure", abi.encodeWithSelector( - ICeloDistributionScheduleInitializer.initialize.selector, + ICeloUnreleasedTreasureInitializer.initialize.selector, REGISTRY_ADDRESS ) ); diff --git a/packages/protocol/migrations_sol/constants.sol b/packages/protocol/migrations_sol/constants.sol index b296097a3d4..33b414945f0 100644 --- a/packages/protocol/migrations_sol/constants.sol +++ b/packages/protocol/migrations_sol/constants.sol @@ -7,7 +7,7 @@ contract MigrationsConstants is TestConstants { string[24] contractsInRegistry = [ "Accounts", "BlockchainParameters", - "CeloDistributionSchedule", + "CeloUnreleasedTreasure", "CeloToken", "DoubleSigningSlasher", "DowntimeSlasher", diff --git a/packages/protocol/test-sol/constants.sol b/packages/protocol/test-sol/constants.sol index d53a94a9db7..7225ca89ed2 100644 --- a/packages/protocol/test-sol/constants.sol +++ b/packages/protocol/test-sol/constants.sol @@ -28,7 +28,7 @@ contract TestConstants { string constant EpochManagerInitializerContract = "EpochManagerInitializer"; string constant ScoreManagerContract = "ScoreManager"; string constant ReserveContract = "Reserve"; - string constant CeloDistributionScheduleContract = "CeloDistributionSchedule"; + string constant CeloUnreleasedTreasureContract = "CeloUnreleasedTreasure"; // Constant addresses address constant REGISTRY_ADDRESS = 0x000000000000000000000000000000000000ce10; diff --git a/packages/protocol/test-sol/unit/common/CeloDistributionSchedule.t.sol b/packages/protocol/test-sol/unit/common/CeloDistributionSchedule.t.sol deleted file mode 100644 index 83ea706fbe6..00000000000 --- a/packages/protocol/test-sol/unit/common/CeloDistributionSchedule.t.sol +++ /dev/null @@ -1,746 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity >=0.8.7 <0.8.20; -pragma experimental ABIEncoderV2; - -import "celo-foundry-8/Test.sol"; -import "@celo-contracts/common/FixidityLib.sol"; -import "@celo-contracts/common/interfaces/IRegistry.sol"; -import "@celo-contracts-8/common/interfaces/ICeloToken.sol"; -import "@celo-contracts/governance/interfaces/IGovernance.sol"; -import "@celo-contracts-8/common/CeloDistributionSchedule.sol"; -import "@celo-contracts-8/common/IsL2Check.sol"; -import { TestConstants } from "@test-sol/constants.sol"; - -import "@test-sol/unit/governance/mock/MockGovernance.sol"; - -contract CeloDistributionScheduleTest is Test, TestConstants, IsL2Check { - using FixidityLib for FixidityLib.Fraction; - - IRegistry registry; - ICeloToken celoToken; - MockGovernance governance; - - CeloDistributionSchedule celoDistributionSchedule; - - address owner = address(this); - - address celoTokenAddress = actor("celoTokenAddress"); - - address celoDistributionOwner = actor("celoDistributionOwner"); - address communityRewardFund = actor("communityRewardFund"); - address carbonOffsettingPartner = actor("carbonOffsettingPartner"); - - address newPartner = actor("newPartner"); - address randomAddress = actor("randomAddress"); - - uint256 constant DAILY_DISTRIBUTION_AMOUNT = 6748256563599655349558; // 6,748 Celo - 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. - - uint256 constant MAX_L2_COMMUNITY_DISTRIBUTION = MAX_L2_DISTRIBUTION / 4; // 26.8 million Celo - uint256 constant MAX_L2_CARBON_FUND_DISTRIBUTION = MAX_L2_DISTRIBUTION / 1000; // 107,297 Celo - - uint256 constant L2_FIFTEEN_YEAR_CELO_SUPPLY = - L1_MINTED_CELO_SUPPLY + MAX_L2_COMMUNITY_DISTRIBUTION + MAX_L2_CARBON_FUND_DISTRIBUTION; - - uint256 constant l2StartTime = 1715808537; // Arbitary later date (May 15 2024) - uint256 constant communityRewardFraction = FIXED1 / 4; // 25% - uint256 constant carbonOffsettingFraction = FIXED1 / 1000; // 0.1% - uint256 constant newCommunityRewardFraction = FIXED1 / 2; // 50% - uint256 constant newCarbonOffsettingFraction = FIXED1 / 500; // 0.2% - - event CommunityRewardFractionSet(uint256 fraction); - event CarbonOffsettingFundSet(address indexed partner, uint256 fraction); - - function setUp() public virtual { - setUpL1(); - - // Setup L2 after minting L1 supply. - deployCodeTo("Registry.sol", abi.encode(false), PROXY_ADMIN_ADDRESS); - } - - function setUpL1() public { - deployCodeTo("Registry.sol", abi.encode(false), REGISTRY_ADDRESS); - registry = IRegistry(REGISTRY_ADDRESS); - - deployCodeTo("GoldToken.sol", abi.encode(false), celoTokenAddress); - celoToken = ICeloToken(celoTokenAddress); - celoToken.setRegistry(REGISTRY_ADDRESS); - // Using a mock contract, as foundry does not allow for library linking when using deployCodeTo - governance = new MockGovernance(); - - registry.setAddressFor("CeloToken", address(celoToken)); - - registry.setAddressFor("Governance", address(governance)); - - vm.deal(address(0), CELO_SUPPLY_CAP); - assertEq(celoToken.totalSupply(), 0, "starting total supply not zero."); - // Mint L1 supply - vm.prank(address(0)); - celoToken.mint(randomAddress, L1_MINTED_CELO_SUPPLY); - assertEq(celoToken.totalSupply(), L1_MINTED_CELO_SUPPLY, "total supply incorrect."); - } - - function newCeloDistibutionSchedule() internal returns (CeloDistributionSchedule) { - vm.warp(block.timestamp + l2StartTime); - vm.prank(celoDistributionOwner); - celoDistributionSchedule = new CeloDistributionSchedule(true); - registry.setAddressFor("CeloDistributionSchedule", address(celoDistributionSchedule)); - - vm.deal(address(celoDistributionSchedule), L2_INITIAL_STASH_BALANCE); - - vm.prank(celoDistributionOwner); - celoDistributionSchedule.initialize(REGISTRY_ADDRESS); - - vm.prank(celoDistributionOwner); - - celoDistributionSchedule.activate( - l2StartTime, - communityRewardFraction, - carbonOffsettingPartner, - carbonOffsettingFraction - ); - } -} - -contract CeloDistributionScheduleTest_initialize is CeloDistributionScheduleTest { - function setUp() public override { - super.setUp(); - vm.warp(block.timestamp + l2StartTime); - - vm.prank(celoDistributionOwner); - celoDistributionSchedule = new CeloDistributionSchedule(true); - registry.setAddressFor("CeloDistributionSchedule", address(celoDistributionSchedule)); - vm.prank(celoDistributionOwner); - celoDistributionSchedule.initialize(REGISTRY_ADDRESS); - } - - function test_ShouldSetAnOwnerToCeloDistributionScheduleInstance() public { - assertEq(celoDistributionSchedule.owner(), celoDistributionOwner); - } - - function test_ShouldSetRegistryAddressToCeloDistributionScheduleInstance() public { - assertEq(address(celoDistributionSchedule.registry()), REGISTRY_ADDRESS); - } - - function test_ShouldNotSetBeneficiariesToCeloDistributionScheduleInstance() public { - assertEq(celoDistributionSchedule.communityRewardFund(), address(0)); - assertEq(celoDistributionSchedule.carbonOffsettingPartner(), address(0)); - } - - function test_ShouldHaveZeroTotalDistributedByScheduleOnInit() public { - assertEq(celoDistributionSchedule.totalDistributedBySchedule(), 0); - } - - function test_ShouldNotSetTheL2StartTime() public { - assertEq(celoDistributionSchedule.l2StartTime(), 0); - } - - function test_Reverts_WhenRegistryIsTheNullAddress() public { - celoDistributionSchedule = new CeloDistributionSchedule(true); - registry.setAddressFor("CeloDistributionSchedule", address(celoDistributionSchedule)); - vm.expectRevert("Cannot register the null address"); - celoDistributionSchedule.initialize(address(0)); - } - - function test_Reverts_WhenReceivingNativeTokens() public { - (bool success, ) = address(celoDistributionSchedule).call{ value: 1 ether }(""); - assertFalse(success); - - address payable payableAddress = payable((address(celoDistributionSchedule))); - - bool success2 = payableAddress.send(1 ether); - assertFalse(success2); - - vm.expectRevert(); - payableAddress.transfer(1 ether); - } -} - -contract CeloDistributionScheduleTest_activate_L1 is CeloDistributionScheduleTest { - function setUp() public override { - super.setUpL1(); - - celoDistributionSchedule = new CeloDistributionSchedule(true); - registry.setAddressFor("CeloDistributionSchedule", address(celoDistributionSchedule)); - celoDistributionSchedule.initialize(REGISTRY_ADDRESS); - } - - function test_Reverts_WhenCalledOnL1() public { - vm.warp(block.timestamp + l2StartTime); - vm.expectRevert("This method is not supported in L1."); - celoDistributionSchedule.activate( - l2StartTime, - communityRewardFraction, - carbonOffsettingPartner, - carbonOffsettingFraction - ); - } -} - -contract CeloDistributionScheduleTest_activate is CeloDistributionScheduleTest { - function test_ShouldHaveZeroTotalDistributedByScheduleOnInit() public { - newCeloDistibutionSchedule(); - assertEq(celoDistributionSchedule.totalDistributedBySchedule(), 0); - } - - function test_ShouldUpdateDependencies() public { - newCeloDistibutionSchedule(); - assertEq(celoDistributionSchedule.l2StartTime(), l2StartTime); - assertEq(celoDistributionSchedule.totalAllocatedAtL2Start(), L1_MINTED_CELO_SUPPLY); - assertEq(celoDistributionSchedule.communityRewardFund(), address(governance)); - assertEq(celoDistributionSchedule.carbonOffsettingPartner(), carbonOffsettingPartner); - assertEq(celoDistributionSchedule.getCarbonOffsettingFraction(), carbonOffsettingFraction); - assertEq(celoDistributionSchedule.getCommunityRewardFraction(), communityRewardFraction); - } - - function test_Reverts_WhenCommunityFractionIsZero() public { - vm.warp(block.timestamp + l2StartTime); - celoDistributionSchedule = new CeloDistributionSchedule(true); - registry.setAddressFor("CeloDistributionSchedule", address(celoDistributionSchedule)); - celoDistributionSchedule.initialize(REGISTRY_ADDRESS); - vm.deal(address(celoDistributionSchedule), L2_INITIAL_STASH_BALANCE); - - vm.expectRevert( - "Value must be different from existing community reward fraction and less than 1." - ); - celoDistributionSchedule.activate( - l2StartTime, - 0, - carbonOffsettingPartner, - carbonOffsettingFraction - ); - } - - function test_Reverts_WhenCarbonOffsettingPartnerIsNullAddress() public { - vm.warp(block.timestamp + l2StartTime); - celoDistributionSchedule = new CeloDistributionSchedule(true); - registry.setAddressFor("CeloDistributionSchedule", address(celoDistributionSchedule)); - celoDistributionSchedule.initialize(REGISTRY_ADDRESS); - vm.deal(address(celoDistributionSchedule), L2_INITIAL_STASH_BALANCE); - - vm.expectRevert("Partner cannot be the zero address."); - celoDistributionSchedule.activate( - l2StartTime, - communityRewardFraction, - address(0), - carbonOffsettingFraction - ); - } - - function test_Reverts_WhenRegistryNotUpdated() public { - vm.warp(block.timestamp + l2StartTime); - registry.setAddressFor("Governance", address(0)); - celoDistributionSchedule = new CeloDistributionSchedule(true); - registry.setAddressFor("CeloDistributionSchedule", address(celoDistributionSchedule)); - vm.deal(address(celoDistributionSchedule), L2_INITIAL_STASH_BALANCE); - celoDistributionSchedule.initialize(PROXY_ADMIN_ADDRESS); - - vm.expectRevert("identifier has no registry entry"); - celoDistributionSchedule.activate( - l2StartTime, - communityRewardFraction, - carbonOffsettingPartner, - carbonOffsettingFraction - ); - } - - function test_Reverts_WhenCalledTwice() public { - newCeloDistibutionSchedule(); - vm.expectRevert("Contract has already been activated."); - - vm.prank(celoDistributionOwner); - - celoDistributionSchedule.activate( - l2StartTime, - communityRewardFraction, - carbonOffsettingPartner, - carbonOffsettingFraction - ); - } - - function test_Reverts_WhenTheContractDoesNotHaveBalance() public { - vm.warp(block.timestamp + l2StartTime); - vm.prank(celoDistributionOwner); - celoDistributionSchedule = new CeloDistributionSchedule(true); - registry.setAddressFor("CeloDistributionSchedule", address(celoDistributionSchedule)); - vm.deal(address(celoDistributionSchedule), L2_INITIAL_STASH_BALANCE); - - vm.prank(celoDistributionOwner); - celoDistributionSchedule.initialize(REGISTRY_ADDRESS); - - vm.deal(address(celoDistributionSchedule), 0); - - vm.expectRevert("Contract does not have CELO balance."); - vm.prank(celoDistributionOwner); - celoDistributionSchedule.activate( - l2StartTime, - communityRewardFraction, - carbonOffsettingPartner, - carbonOffsettingFraction - ); - } - - function test_Reverts_WhenCeloDistributionAddressNotSetInRegistry() public { - vm.warp(block.timestamp + l2StartTime); - vm.prank(celoDistributionOwner); - celoDistributionSchedule = new CeloDistributionSchedule(true); - - vm.deal(address(celoDistributionSchedule), L2_INITIAL_STASH_BALANCE); - - vm.prank(celoDistributionOwner); - celoDistributionSchedule.initialize(REGISTRY_ADDRESS); - - vm.expectRevert("identifier has no registry entry"); - - vm.prank(celoDistributionOwner); - - celoDistributionSchedule.activate( - l2StartTime, - communityRewardFraction, - carbonOffsettingPartner, - carbonOffsettingFraction - ); - } - - function test_Reverts_WhenCeloDistributionAddressIncorrectlySetInRegistry() public { - vm.warp(block.timestamp + l2StartTime); - vm.prank(celoDistributionOwner); - celoDistributionSchedule = new CeloDistributionSchedule(true); - registry.setAddressFor("CeloDistributionSchedule", randomAddress); - vm.deal(address(celoDistributionSchedule), L2_INITIAL_STASH_BALANCE); - - vm.prank(celoDistributionOwner); - celoDistributionSchedule.initialize(REGISTRY_ADDRESS); - - vm.expectRevert("CeloDistributionSchedule address is incorrectly set in Registry."); - - vm.prank(celoDistributionOwner); - - celoDistributionSchedule.activate( - l2StartTime, - communityRewardFraction, - carbonOffsettingPartner, - carbonOffsettingFraction - ); - } -} - -contract CeloDistributionScheduleTest_setCommunityRewardFraction is CeloDistributionScheduleTest { - function setUp() public override { - super.setUp(); - newCeloDistibutionSchedule(); - } - function test_ShouldSetNewFraction() public { - vm.prank(celoDistributionOwner); - celoDistributionSchedule.setCommunityRewardFraction(newCommunityRewardFraction); - assertEq(celoDistributionSchedule.getCommunityRewardFraction(), newCommunityRewardFraction); - } - function test_Emits_CommunityRewardFractionSetEvent() public { - vm.expectEmit(true, true, true, true); - emit CommunityRewardFractionSet(newCommunityRewardFraction); - vm.prank(celoDistributionOwner); - celoDistributionSchedule.setCommunityRewardFraction(newCommunityRewardFraction); - } - function test_Reverts_WhenCalledByOtherThanOwner() public { - vm.expectRevert("Ownable: caller is not the owner"); - vm.prank(randomAddress); - celoDistributionSchedule.setCommunityRewardFraction(newCommunityRewardFraction); - } - function test_Reverts_WhenFractionIsTheSame() public { - vm.expectRevert( - "Value must be different from existing community reward fraction and less than 1." - ); - vm.prank(celoDistributionOwner); - celoDistributionSchedule.setCommunityRewardFraction(communityRewardFraction); - } - function test_Reverts_WhenSumOfFractionsGtOne() public { - vm.expectRevert("Sum of partner fractions must be less than or equal to 1."); - vm.prank(celoDistributionOwner); - celoDistributionSchedule.setCommunityRewardFraction((FIXED1 - 1)); - } - function test_Reverts_WhenDependenciesNotSet() public { - vm.prank(celoDistributionOwner); - celoDistributionSchedule = new CeloDistributionSchedule(true); - registry.setAddressFor("CeloDistributionSchedule", address(celoDistributionSchedule)); - vm.prank(celoDistributionOwner); - celoDistributionSchedule.initialize(REGISTRY_ADDRESS); - - vm.expectRevert("Distribution schedule has not been activated."); - vm.prank(celoDistributionOwner); - celoDistributionSchedule.setCommunityRewardFraction(communityRewardFraction); - } - function test_Reverts_WhenFractionChangesAfter15Years() public { - vm.warp(block.timestamp + (15 * YEAR + 4 * DAY)); - - assertEq( - celoDistributionSchedule.totalDistributedBySchedule(), - 0, - "Incorrect distributableAmount" - ); - - vm.prank(randomAddress); - celoDistributionSchedule.distributeAccordingToSchedule(); - - vm.warp(block.timestamp + (15 * YEAR) + (4 * DAY)); - - vm.expectRevert( - "Can only update fraction once block reward calculation for years 15-30 has been implemented." - ); - - vm.prank(celoDistributionOwner); - celoDistributionSchedule.setCommunityRewardFraction(((FIXED1 / 4) * 3)); - } -} - -contract CeloDistributionScheduleTest_setCarbonOffsettingFund is CeloDistributionScheduleTest { - function setUp() public override { - super.setUp(); - newCeloDistibutionSchedule(); - } - - function test_ShouldSetNewPartner() public { - vm.prank(celoDistributionOwner); - celoDistributionSchedule.setCarbonOffsettingFund(newPartner, carbonOffsettingFraction); - assertEq(celoDistributionSchedule.carbonOffsettingPartner(), newPartner); - } - function test_ShouldSetNewFraction() public { - vm.prank(celoDistributionOwner); - celoDistributionSchedule.setCarbonOffsettingFund( - carbonOffsettingPartner, - newCarbonOffsettingFraction - ); - assertEq(celoDistributionSchedule.getCarbonOffsettingFraction(), newCarbonOffsettingFraction); - } - - function test_Emits_CarbonOffsettingFundSetEvent() public { - vm.expectEmit(true, true, true, true); - emit CarbonOffsettingFundSet(newPartner, carbonOffsettingFraction); - vm.prank(celoDistributionOwner); - celoDistributionSchedule.setCarbonOffsettingFund(newPartner, carbonOffsettingFraction); - } - - function test_Reverts_WhenCalledByOtherThanOwner() public { - vm.expectRevert("Ownable: caller is not the owner"); - vm.prank(randomAddress); - celoDistributionSchedule.setCarbonOffsettingFund(newPartner, carbonOffsettingFraction); - } - - function test_Reverts_WhenPartnerAndFractionAreTheSame() public { - vm.expectRevert("Partner and value must be different from existing carbon offsetting fund."); - vm.prank(celoDistributionOwner); - celoDistributionSchedule.setCarbonOffsettingFund( - carbonOffsettingPartner, - carbonOffsettingFraction - ); - } - - function test_Reverts_WhenSumOfFractionsGtOne() public { - vm.expectRevert("Sum of partner fractions must be less than or equal to 1."); - vm.prank(celoDistributionOwner); - celoDistributionSchedule.setCarbonOffsettingFund(carbonOffsettingPartner, (FIXED1 - 1)); - } - - function test_Reverts_WhenDependenciesNotSet() public { - vm.prank(celoDistributionOwner); - celoDistributionSchedule = new CeloDistributionSchedule(true); - registry.setAddressFor("CeloDistributionSchedule", address(celoDistributionSchedule)); - vm.prank(celoDistributionOwner); - celoDistributionSchedule.initialize(REGISTRY_ADDRESS); - - vm.expectRevert("Distribution schedule has not been activated."); - vm.prank(celoDistributionOwner); - celoDistributionSchedule.setCarbonOffsettingFund( - carbonOffsettingPartner, - carbonOffsettingFraction - ); - } - - function test_Reverts_WhenFractionChangesAfter15Years() public { - vm.warp(block.timestamp + (15 * YEAR + 4 * DAY)); - - assertEq( - celoDistributionSchedule.totalDistributedBySchedule(), - 0, - "Incorrect distributableAmount" - ); - - vm.prank(randomAddress); - celoDistributionSchedule.distributeAccordingToSchedule(); - - vm.warp(block.timestamp + (15 * YEAR) + (4 * DAY)); - - vm.expectRevert( - "Can only update fraction once block reward calculation for years 15-30 has been implemented." - ); - - vm.prank(celoDistributionOwner); - celoDistributionSchedule.setCarbonOffsettingFund(carbonOffsettingPartner, ((FIXED1 / 4) * 3)); - } -} - -contract CeloDistributionScheduleTest_distributeAccordingToSchedule_L1 is - CeloDistributionScheduleTest -{ - function setUp() public override { - super.setUpL1(); - - celoDistributionSchedule = new CeloDistributionSchedule(true); - registry.setAddressFor("CeloDistributionSchedule", address(celoDistributionSchedule)); - celoDistributionSchedule.initialize(REGISTRY_ADDRESS); - } - - function test_Reverts_WhenDistributingOnL1() public { - vm.warp(block.timestamp + 3 * MONTH + 1 * DAY); - - vm.expectRevert("This method is not supported in L1."); - vm.prank(randomAddress); - celoDistributionSchedule.distributeAccordingToSchedule(); - } -} - -contract CeloDistributionScheduleTest_distributeAccordingToSchedule is - CeloDistributionScheduleTest -{ - function setUp() public override { - super.setUp(); - - newCeloDistibutionSchedule(); - } - - function test_Reverts_WhenDependenciesAreNotSet() public { - celoDistributionSchedule = new CeloDistributionSchedule(true); - registry.setAddressFor("CeloDistributionSchedule", address(celoDistributionSchedule)); - celoDistributionSchedule.initialize(REGISTRY_ADDRESS); - - vm.expectRevert("Distribution schedule has not been activated."); - vm.prank(randomAddress); - celoDistributionSchedule.distributeAccordingToSchedule(); - } - - function test_ShouldAllowDistributingAsSoon1SecondAfterSettingDependencies() public { - uint256 communityFundBalanceBefore = celoToken.balanceOf(address(governance)); - vm.prank(randomAddress); - celoDistributionSchedule.distributeAccordingToSchedule(); - uint256 communityFundBalanceAfter = celoToken.balanceOf(address(governance)); - assertGt(communityFundBalanceAfter, communityFundBalanceBefore); - } - - function test_Reverts_WhenDistributableAmountIsZero() public { - vm.prank(randomAddress); - celoDistributionSchedule.distributeAccordingToSchedule(); - - vm.expectRevert("Distributable amount must be greater than zero."); - vm.prank(randomAddress); - celoDistributionSchedule.distributeAccordingToSchedule(); - } - - function test_ShouldAllowToDistribute25Percent2years9MonthsPostL2Launch() public { - vm.warp(block.timestamp + 2 * YEAR + 267 * DAY + 63868); // 25% time since L2 - - uint256 expectedDistributedAmount = (L2_FIFTEEN_YEAR_CELO_SUPPLY - L1_MINTED_CELO_SUPPLY) / 4; - - vm.prank(randomAddress); - celoDistributionSchedule.distributeAccordingToSchedule(); - - assertApproxEqRel( - celoDistributionSchedule.totalDistributedBySchedule(), - expectedDistributedAmount, - 1e10 - ); - } - - function test_ShouldAllowToDistribute50Percent5AndHalfYearsPostL2Launch() public { - vm.warp(block.timestamp + (5 * YEAR) + (170 * DAY) + 41338); - - uint256 expectedDistributedAmount = (L2_FIFTEEN_YEAR_CELO_SUPPLY - L1_MINTED_CELO_SUPPLY) / 2; - vm.prank(randomAddress); - celoDistributionSchedule.distributeAccordingToSchedule(); - - assertApproxEqRel( - celoDistributionSchedule.totalDistributedBySchedule(), - expectedDistributedAmount, - 1e10 - ); - } - - function test_ShouldAllowToDistribute75Percent11YearsAnd3MonthsPostL2Launch() public { - vm.warp(block.timestamp + 8 * YEAR + 73 * DAY + 18807); - - uint256 expectedDistributedAmount = ((L2_FIFTEEN_YEAR_CELO_SUPPLY - L1_MINTED_CELO_SUPPLY) / - 4) * 3; - - vm.prank(randomAddress); - celoDistributionSchedule.distributeAccordingToSchedule(); - - assertApproxEqRel( - celoDistributionSchedule.totalDistributedBySchedule(), - expectedDistributedAmount, - 1e10 - ); - } - - function test_ShouldAllowToDistribute100Percent11YearsPostL2Launch() public { - uint256 communityFundBalanceBefore = celoToken.balanceOf(address(governance)); - uint256 carbonOffsettingPartnerBalanceBefore = celoToken.balanceOf(carbonOffsettingPartner); - vm.warp(block.timestamp + (11 * YEAR)); - - vm.prank(randomAddress); - celoDistributionSchedule.distributeAccordingToSchedule(); - - assertApproxEqRel( - celoDistributionSchedule.totalDistributedBySchedule(), - MAX_L2_COMMUNITY_DISTRIBUTION + MAX_L2_CARBON_FUND_DISTRIBUTION, - 1e10 - ); - - uint256 communityFundBalanceAfter = celoToken.balanceOf(address(governance)); - uint256 carbonOffsettingPartnerBalanceAfter = celoToken.balanceOf(carbonOffsettingPartner); - - assertApproxEqRel( - communityFundBalanceAfter - communityFundBalanceBefore, - MAX_L2_COMMUNITY_DISTRIBUTION, - 1e10 - ); - - assertApproxEqRel( - carbonOffsettingPartnerBalanceAfter - carbonOffsettingPartnerBalanceBefore, - MAX_L2_CARBON_FUND_DISTRIBUTION, - 1e10 - ); - } - - function test_ShouldDistributeUpToLinearSuppplyAfter15Years() public { - vm.warp(block.timestamp + (15 * YEAR) + (4 * DAY)); - - assertEq( - celoDistributionSchedule.totalDistributedBySchedule(), - 0, - "Incorrect distributableAmount" - ); - - vm.prank(randomAddress); - celoDistributionSchedule.distributeAccordingToSchedule(); - - assertApproxEqRel( - celoDistributionSchedule.totalDistributedBySchedule(), - MAX_L2_COMMUNITY_DISTRIBUTION + MAX_L2_CARBON_FUND_DISTRIBUTION, - 1e10 - ); - } - - function test_Reverts_WhenDistributingSecondTimeAfter15Years() public { - vm.warp(block.timestamp + (15 * YEAR) + (1 * DAY)); - - vm.prank(randomAddress); - celoDistributionSchedule.distributeAccordingToSchedule(); - - assertApproxEqRel( - celoDistributionSchedule.totalDistributedBySchedule(), - MAX_L2_COMMUNITY_DISTRIBUTION + MAX_L2_CARBON_FUND_DISTRIBUTION, - 1e10 - ); - - vm.expectRevert("Block reward calculation for years 15-30 unimplemented"); - vm.prank(randomAddress); - celoDistributionSchedule.distributeAccordingToSchedule(); - } - - function test_Reverts_WhenTheContractBalanceIsLowerExpected() public { - vm.deal(address(celoDistributionSchedule), 0); - vm.prank(address(celoDistributionSchedule)); - - vm.expectRevert("Contract balance is insufficient."); - celoDistributionSchedule.distributeAccordingToSchedule(); - } - - function test_ShouldTransferbalanceFromThisContract() public { - uint256 initialStashBalance = celoToken.balanceOf(address(celoDistributionSchedule)); - - vm.warp(block.timestamp + (15 * YEAR)); - - celoDistributionSchedule.distributeAccordingToSchedule(); - - uint256 finalStashBalance = celoToken.balanceOf(address(celoDistributionSchedule)); - - assertLt(finalStashBalance, initialStashBalance); - - assertApproxEqRel( - celoToken.balanceOf(address(celoDistributionSchedule)), - L2_INITIAL_STASH_BALANCE - (MAX_L2_COMMUNITY_DISTRIBUTION + MAX_L2_CARBON_FUND_DISTRIBUTION), - 1e10 - ); - } -} - -contract CeloDistributionScheduleTest_getDistributableAmount is CeloDistributionScheduleTest { - function setUp() public override { - super.setUp(); - - newCeloDistibutionSchedule(); - } - - function test_ShouldReturnFullAmountAvailableForThisReleasePeriod() public { - vm.warp(block.timestamp + 1 * DAY); - assertApproxEqRel( - celoDistributionSchedule.getDistributableAmount(), - DAILY_DISTRIBUTION_AMOUNT, - 1e10 - ); - } - - function test_ShouldReturnOnlyAmountNotYetDistributed() public { - vm.warp(block.timestamp + 1 * DAY); - vm.prank(randomAddress); - celoDistributionSchedule.distributeAccordingToSchedule(); - - vm.warp(block.timestamp + 1 * DAY + 1); - assertApproxEqRel( - celoDistributionSchedule.getDistributableAmount(), - DAILY_DISTRIBUTION_AMOUNT, - 1e10 - ); - } - - function test_ShouldReturnOnlyUpToMaxL2DistributionBeforeItIsDistributed() public { - vm.warp(block.timestamp + 16 * YEAR); - assertApproxEqRel( - celoDistributionSchedule.getDistributableAmount(), - MAX_L2_COMMUNITY_DISTRIBUTION + MAX_L2_CARBON_FUND_DISTRIBUTION, - 1e10 - ); - } - - function test_Reverts_When15YearsHavePassedAndAllLinearScheduleHaseBeenReleased() public { - vm.warp(block.timestamp + 15 * YEAR); - - vm.prank(randomAddress); - celoDistributionSchedule.distributeAccordingToSchedule(); - vm.expectRevert("Block reward calculation for years 15-30 unimplemented"); - celoDistributionSchedule.getDistributableAmount(); - } - - function test_Reverts_WhenDependenciesNotSet() public { - celoDistributionSchedule = new CeloDistributionSchedule(true); - registry.setAddressFor("CeloDistributionSchedule", address(celoDistributionSchedule)); - celoDistributionSchedule.initialize(REGISTRY_ADDRESS); - - vm.expectRevert("Distribution schedule has not been activated."); - - celoDistributionSchedule.getDistributableAmount(); - } -} - -contract CeloDistributionScheduleTest_transfer is CeloDistributionScheduleTest { - // TODO: implement -} diff --git a/packages/protocol/test-sol/unit/common/CeloUnreleasedTreasure.t.sol b/packages/protocol/test-sol/unit/common/CeloUnreleasedTreasure.t.sol new file mode 100644 index 00000000000..938b7525748 --- /dev/null +++ b/packages/protocol/test-sol/unit/common/CeloUnreleasedTreasure.t.sol @@ -0,0 +1,147 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity >=0.8.7 <0.8.20; +pragma experimental ABIEncoderV2; + +import "celo-foundry-8/Test.sol"; +import "@celo-contracts/common/FixidityLib.sol"; +import "@celo-contracts/common/interfaces/IRegistry.sol"; +import "@celo-contracts-8/common/interfaces/ICeloToken.sol"; +import "@celo-contracts/governance/interfaces/IGovernance.sol"; +import "@celo-contracts-8/common/CeloUnreleasedTreasure.sol"; +import "@celo-contracts-8/common/IsL2Check.sol"; +import { TestConstants } from "@test-sol/constants.sol"; + +import "@test-sol/unit/governance/mock/MockGovernance.sol"; + +contract CeloUnreleasedTreasureTest is Test, TestConstants, IsL2Check { + using FixidityLib for FixidityLib.Fraction; + + IRegistry registry; + ICeloToken celoToken; + MockGovernance governance; + + CeloUnreleasedTreasure celoUnreleasedTreasure; + + address owner = address(this); + + address celoTokenAddress = actor("celoTokenAddress"); + + address celoDistributionOwner = actor("celoDistributionOwner"); + address communityRewardFund = actor("communityRewardFund"); + address carbonOffsettingPartner = actor("carbonOffsettingPartner"); + + address newPartner = actor("newPartner"); + address randomAddress = actor("randomAddress"); + + uint256 constant DAILY_DISTRIBUTION_AMOUNT = 6748256563599655349558; // 6,748 Celo + 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. + + uint256 constant MAX_L2_COMMUNITY_DISTRIBUTION = MAX_L2_DISTRIBUTION / 4; // 26.8 million Celo + uint256 constant MAX_L2_CARBON_FUND_DISTRIBUTION = MAX_L2_DISTRIBUTION / 1000; // 107,297 Celo + + uint256 constant L2_FIFTEEN_YEAR_CELO_SUPPLY = + L1_MINTED_CELO_SUPPLY + MAX_L2_COMMUNITY_DISTRIBUTION + MAX_L2_CARBON_FUND_DISTRIBUTION; + + uint256 constant l2StartTime = 1715808537; // Arbitary later date (May 15 2024) + uint256 constant communityRewardFraction = FIXED1 / 4; // 25% + uint256 constant carbonOffsettingFraction = FIXED1 / 1000; // 0.1% + uint256 constant newCommunityRewardFraction = FIXED1 / 2; // 50% + uint256 constant newCarbonOffsettingFraction = FIXED1 / 500; // 0.2% + + event CommunityRewardFractionSet(uint256 fraction); + event CarbonOffsettingFundSet(address indexed partner, uint256 fraction); + + function setUp() public virtual { + setUpL1(); + + // Setup L2 after minting L1 supply. + deployCodeTo("Registry.sol", abi.encode(false), PROXY_ADMIN_ADDRESS); + } + + function setUpL1() public { + deployCodeTo("Registry.sol", abi.encode(false), REGISTRY_ADDRESS); + registry = IRegistry(REGISTRY_ADDRESS); + + deployCodeTo("GoldToken.sol", abi.encode(false), celoTokenAddress); + celoToken = ICeloToken(celoTokenAddress); + celoToken.setRegistry(REGISTRY_ADDRESS); + // Using a mock contract, as foundry does not allow for library linking when using deployCodeTo + governance = new MockGovernance(); + + registry.setAddressFor("CeloToken", address(celoToken)); + + registry.setAddressFor("Governance", address(governance)); + + vm.deal(address(0), CELO_SUPPLY_CAP); + assertEq(celoToken.totalSupply(), 0, "starting total supply not zero."); + // Mint L1 supply + vm.prank(address(0)); + celoToken.mint(randomAddress, L1_MINTED_CELO_SUPPLY); + assertEq(celoToken.totalSupply(), L1_MINTED_CELO_SUPPLY, "total supply incorrect."); + } + + function newCeloUnreleasedTreasure() internal returns (CeloUnreleasedTreasure) { + vm.warp(block.timestamp + l2StartTime); + vm.prank(celoDistributionOwner); + celoUnreleasedTreasure = new CeloUnreleasedTreasure(true); + registry.setAddressFor("CeloUnreleasedTreasure", address(celoUnreleasedTreasure)); + + vm.deal(address(celoUnreleasedTreasure), L2_INITIAL_STASH_BALANCE); + + vm.prank(celoDistributionOwner); + celoUnreleasedTreasure.initialize(REGISTRY_ADDRESS); + + } +} + +contract CeloUnreleasedTreasureTest_initialize is CeloUnreleasedTreasureTest { + function setUp() public override { + super.setUp(); + vm.warp(block.timestamp + l2StartTime); + + vm.prank(celoDistributionOwner); + celoUnreleasedTreasure = new CeloUnreleasedTreasure(true); + registry.setAddressFor("CeloUnreleasedTreasure", address(celoUnreleasedTreasure)); + vm.prank(celoDistributionOwner); + celoUnreleasedTreasure.initialize(REGISTRY_ADDRESS); + } + + function test_ShouldSetAnOwnerToCeloUnreleasedTreasureInstance() public { + assertEq(celoUnreleasedTreasure.owner(), celoDistributionOwner); + } + + function test_ShouldSetRegistryAddressToCeloUnreleasedTreasureInstance() public { + assertEq(address(celoUnreleasedTreasure.registry()), REGISTRY_ADDRESS); + } + + function test_Reverts_WhenRegistryIsTheNullAddress() public { + celoUnreleasedTreasure = new CeloUnreleasedTreasure(true); + registry.setAddressFor("CeloUnreleasedTreasure", address(celoUnreleasedTreasure)); + vm.expectRevert("Cannot register the null address"); + celoUnreleasedTreasure.initialize(address(0)); + } + + function test_Reverts_WhenReceivingNativeTokens() public { + (bool success, ) = address(celoUnreleasedTreasure).call{ value: 1 ether }(""); + assertFalse(success); + + address payable payableAddress = payable((address(celoUnreleasedTreasure))); + + bool success2 = payableAddress.send(1 ether); + assertFalse(success2); + + vm.expectRevert(); + payableAddress.transfer(1 ether); + } +} diff --git a/packages/protocol/test-sol/unit/common/EpochManager.t.sol b/packages/protocol/test-sol/unit/common/EpochManager.t.sol index db8760bde1a..1ebe8a15fd2 100644 --- a/packages/protocol/test-sol/unit/common/EpochManager.t.sol +++ b/packages/protocol/test-sol/unit/common/EpochManager.t.sol @@ -6,8 +6,8 @@ import "@celo-contracts-8/common/EpochManager.sol"; import "@celo-contracts-8/stability/test/MockStableToken.sol"; import "@celo-contracts-8/common/interfaces/ICeloToken.sol"; import "@celo-contracts-8/common/ScoreManager.sol"; -import "@celo-contracts-8/common/CeloDistributionSchedule.sol"; -import "@celo-contracts/common/interfaces/ICeloDistributionSchedule.sol"; +import "@celo-contracts-8/common/CeloUnreleasedTreasure.sol"; +import "@celo-contracts/common/interfaces/ICeloUnreleasedTreasure.sol"; import { TestConstants } from "@test-sol/constants.sol"; @@ -36,7 +36,7 @@ contract EpochManagerTest is Test, TestConstants { IRegistry registry; ICeloToken celoToken; - CeloDistributionSchedule celoDistributionSchedule; + CeloUnreleasedTreasure celoUnreleasedTreasure; ScoreManager scoreManager; uint256 celoAmountForRate = 1e24; @@ -61,7 +61,7 @@ contract EpochManagerTest is Test, TestConstants { epochRewards = new EpochRewardsMock08(); validators = new ValidatorsMock08(); stableToken = new MockStableToken08(); - celoDistributionSchedule = new CeloDistributionSchedule(false); + celoUnreleasedTreasure = new CeloUnreleasedTreasure(false); firstElected.push(actor("validator1")); firstElected.push(actor("validator2")); @@ -89,11 +89,11 @@ contract EpochManagerTest is Test, TestConstants { registry.setAddressFor(ValidatorsContract, address(validators)); registry.setAddressFor(ScoreManagerContract, address(scoreManager)); registry.setAddressFor(StableTokenContract, address(stableToken)); - registry.setAddressFor(CeloDistributionScheduleContract, address(celoDistributionSchedule)); + registry.setAddressFor(CeloUnreleasedTreasureContract, address(celoUnreleasedTreasure)); registry.setAddressFor(CeloTokenContract, address(celoToken)); registry.setAddressFor(ReserveContract, reserveAddress); - vm.deal(address(celoDistributionSchedule), L2_INITIAL_STASH_BALANCE); + vm.deal(address(celoUnreleasedTreasure), L2_INITIAL_STASH_BALANCE); bool res1 = sortedOracles.setMedianRate(address(stableToken), stableAmountForRate); (uint256 res0, uint256 res00) = sortedOracles.medianRate(address(stableToken)); diff --git a/packages/protocol/test-sol/unit/common/FeeHandler.t.sol b/packages/protocol/test-sol/unit/common/FeeHandler.t.sol index 3ae1ce07a48..d131f75c4d9 100644 --- a/packages/protocol/test-sol/unit/common/FeeHandler.t.sol +++ b/packages/protocol/test-sol/unit/common/FeeHandler.t.sol @@ -51,7 +51,7 @@ contract FeeHandlerTest is Test, TestConstants { address owner = address(this); address user = actor("user"); - address celoDistributionSchedule = actor("CeloDistributionSchedule"); + address celoUnreleasedTreasure = actor("CeloUnreleasedTreasure"); uint256 celoAmountForRate = 1e24; uint256 stableAmountForRate = 2 * celoAmountForRate; @@ -102,7 +102,7 @@ contract FeeHandlerTest is Test, TestConstants { registry.setAddressFor("GoldToken", address(celoToken)); registry.setAddressFor("CeloToken", address(celoToken)); registry.setAddressFor("Reserve", address(mockReserve)); - registry.setAddressFor("CeloDistributionSchedule", celoDistributionSchedule); + registry.setAddressFor("CeloUnreleasedTreasure", celoUnreleasedTreasure); mockReserve.setGoldToken(address(celoToken)); mockReserve.addToken(address(stableToken)); diff --git a/packages/protocol/test-sol/unit/common/GoldToken.t.sol b/packages/protocol/test-sol/unit/common/GoldToken.t.sol index 80c49c0930d..ba5c2fe4ca5 100644 --- a/packages/protocol/test-sol/unit/common/GoldToken.t.sol +++ b/packages/protocol/test-sol/unit/common/GoldToken.t.sol @@ -35,7 +35,7 @@ contract GoldTokenTest is Test, TestConstants, IsL2Check { celoToken = new GoldToken(true); vm.prank(celoTokenOwner); celoToken.setRegistry(REGISTRY_ADDRESS); - registry.setAddressFor("CeloDistributionSchedule", celoTokenDistributionSchedule); + registry.setAddressFor("CeloUnreleasedTreasure", celoTokenDistributionSchedule); receiver = actor("receiver"); sender = actor("sender"); vm.deal(receiver, ONE_CELOTOKEN); @@ -126,11 +126,6 @@ contract GoldTokenTest_transfer is GoldTokenTest { vm.expectRevert(); celoToken.transfer(address(0), ONE_CELOTOKEN); } - function test_Reverts_WhenTransferingToCeloDistributionSchedule() public { - vm.prank(sender); - vm.expectRevert("transfer attempted to reserved CeloDistributionSchedule address"); - celoToken.transfer(celoTokenDistributionSchedule, ONE_CELOTOKEN); - } } contract GoldTokenTest_transferFrom is GoldTokenTest { @@ -149,12 +144,6 @@ contract GoldTokenTest_transferFrom is GoldTokenTest { assertEq(receiver.balance, startBalanceTo + ONE_CELOTOKEN); } - function test_Reverts_WhenTransferingToCeloDistributionSchedule() public { - vm.prank(receiver); - vm.expectRevert("transfer attempted to reserved CeloDistributionSchedule address"); - celoToken.transferFrom(sender, celoTokenDistributionSchedule, ONE_CELOTOKEN); - } - function test_Reverts_WhenTransferToNullAddress() public { vm.prank(receiver); vm.expectRevert(); @@ -275,7 +264,7 @@ contract CeloTokenMockTest is Test, TestConstants { mockCeloToken.setRegistry(REGISTRY_ADDRESS); mockCeloToken.setTotalSupply(ONE_CELOTOKEN * 1000); celoTokenDistributionSchedule = actor("celoTokenDistributionSchedule"); - registry.setAddressFor("CeloDistributionSchedule", celoTokenDistributionSchedule); + registry.setAddressFor("CeloUnreleasedTreasure", celoTokenDistributionSchedule); } } diff --git a/packages/protocol/test-sol/unit/governance/voting/ReleaseGold.t.sol b/packages/protocol/test-sol/unit/governance/voting/ReleaseGold.t.sol index 1ac8749efbf..333d9266d22 100644 --- a/packages/protocol/test-sol/unit/governance/voting/ReleaseGold.t.sol +++ b/packages/protocol/test-sol/unit/governance/voting/ReleaseGold.t.sol @@ -48,7 +48,7 @@ contract ReleaseGoldTest is Test, TestConstants, ECDSAHelper { address refundAddress = actor("refundAddress"); address newBeneficiary = actor("newBeneficiary"); address randomAddress = actor("randomAddress"); - address celoDistributionSchedule = actor("CeloDistributionSchedule"); + address celoUnreleasedTreasure = actor("CeloUnreleasedTreasure"); uint256 constant TOTAL_AMOUNT = 1 ether * 10; @@ -125,7 +125,7 @@ contract ReleaseGoldTest is Test, TestConstants, ECDSAHelper { registry.setAddressFor("LockedGold", address(lockedGold)); registry.setAddressFor("Validators", address(validators)); registry.setAddressFor("StableToken", address(stableToken)); - registry.setAddressFor("CeloDistributionSchedule", celoDistributionSchedule); + registry.setAddressFor("CeloUnreleasedTreasure", celoUnreleasedTreasure); lockedGold.initialize(REGISTRY_ADDRESS, UNLOCKING_PERIOD); goldToken.initialize(REGISTRY_ADDRESS); From d7b32fbd570c810b289d8ed749ce1b28ced19e7f Mon Sep 17 00:00:00 2001 From: pahor167 Date: Mon, 19 Aug 2024 13:27:21 +0200 Subject: [PATCH 19/59] conditions to getFirstBlockAtEpoch and getLastBlockAtEpoch --- packages/protocol/contracts-0.8/common/EpochManager.sol | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/protocol/contracts-0.8/common/EpochManager.sol b/packages/protocol/contracts-0.8/common/EpochManager.sol index b88c000b3e2..828fdde46d8 100644 --- a/packages/protocol/contracts-0.8/common/EpochManager.sol +++ b/packages/protocol/contracts-0.8/common/EpochManager.sol @@ -240,10 +240,14 @@ contract EpochManager is } function getFirstBlockAtEpoch(uint256 epoch) external view returns (uint256) { + require(epoch >= firstKnownEpoch, "Epoch not known"); + require(epoch <= currentEpochNumber, "Epoch not created yet"); return epochs[epoch].firstBlock; } function getLastBlockAtEpoch(uint256 epoch) external view returns (uint256) { + require(epoch >= firstKnownEpoch, "Epoch not known"); + require(epoch < currentEpochNumber, "Epoch not finished yet"); return epochs[epoch].lastBlock; } From a66e578b6608fffeaacdf42084626913b94e0466 Mon Sep 17 00:00:00 2001 From: pahor167 Date: Mon, 19 Aug 2024 14:19:45 +0200 Subject: [PATCH 20/59] ScoreManager update --- .../contracts-0.8/common/EpochManager.sol | 4 +- .../contracts-0.8/common/ScoreManager.sol | 19 ++-------- .../common/interfaces/IScoreReader.sol | 2 +- .../contracts/governance/Election.sol | 37 +++++++++++++++++++ .../governance/interfaces/IElection.sol | 5 +++ 5 files changed, 49 insertions(+), 18 deletions(-) diff --git a/packages/protocol/contracts-0.8/common/EpochManager.sol b/packages/protocol/contracts-0.8/common/EpochManager.sol index 828fdde46d8..823390d7dd5 100644 --- a/packages/protocol/contracts-0.8/common/EpochManager.sol +++ b/packages/protocol/contracts-0.8/common/EpochManager.sol @@ -195,11 +195,11 @@ contract EpochManager is // by doing this, we avoid processing a group twice delete processedGroups[groups[i]]; // TODO what happens to uptime? - uint256[] memory uptimes = getScoreReader().getUptimes(groups[i]); + uint256 groupScore = getScoreReader().getGroupScore(groups[i]); uint256 epochRewards = getElection().getGroupEpochRewards( groups[i], epochProcessing.totalRewardsVoter, - uptimes + groupScore ); getElection().distributeEpochRewards(groups[i], epochRewards, lessers[i], greaters[i]); } diff --git a/packages/protocol/contracts-0.8/common/ScoreManager.sol b/packages/protocol/contracts-0.8/common/ScoreManager.sol index d06ceee0c8b..5eb0bd1d5c3 100644 --- a/packages/protocol/contracts-0.8/common/ScoreManager.sol +++ b/packages/protocol/contracts-0.8/common/ScoreManager.sol @@ -7,7 +7,6 @@ import "@openzeppelin/contracts8/access/Ownable.sol"; contract ScoreManager is Initializable, Ownable { - mapping (address => uint256[]) public uptimes; mapping (address => uint256) public scores; /** @@ -36,22 +35,12 @@ contract ScoreManager is Initializable, Ownable { return (1, 1, 0, 0); } - /** - * @notice Sets the uptimes for the given addresses. - * @param _uptimes The uptimes to set. - */ - function setUptimes(address group, uint256[] calldata _uptimes) external onlyOwner { - for (uint256 i = 0; i < _uptimes.length; i++) { - uptimes[group][i] = _uptimes[i]; - } + function setGroupScore(address group, uint256 score) external onlyOwner { + scores[group] = score; } - function getUptimes(address group) external view returns (uint256[] memory) { - uint256[] memory result = new uint256[](uptimes[group].length); - for (uint256 i = 0; i < uptimes[group].length; i++) { - result[i] = uptimes[group][i]; - } - return result; + function getGroupScore(address group) external view returns (uint256) { + return scores[group]; } function getValidatorScore(address validator) external view returns (uint256) { diff --git a/packages/protocol/contracts-0.8/common/interfaces/IScoreReader.sol b/packages/protocol/contracts-0.8/common/interfaces/IScoreReader.sol index 90afb2939bd..f45d57af1eb 100644 --- a/packages/protocol/contracts-0.8/common/interfaces/IScoreReader.sol +++ b/packages/protocol/contracts-0.8/common/interfaces/IScoreReader.sol @@ -2,6 +2,6 @@ pragma solidity >=0.8.7 <0.8.20; interface IScoreReader { - function getUptimes(address group) external view returns (uint256[] memory); function getValidatorScore(address validator) external view returns (uint256); + function getGroupScore(address validator) external view returns (uint256); } diff --git a/packages/protocol/contracts/governance/Election.sol b/packages/protocol/contracts/governance/Election.sol index 8a7633599cd..fe58a52fd28 100644 --- a/packages/protocol/contracts/governance/Election.sol +++ b/packages/protocol/contracts/governance/Election.sol @@ -565,6 +565,43 @@ contract Election is .fromFixed(); } + /** + * @notice Returns the amount of rewards that voters for `group` are due at the end of an epoch. + * @param group The group to calculate epoch rewards for. + * @param totalEpochRewards The total amount of rewards going to all voters. + * @param groupScore The score of the group. + * @return The amount of rewards that voters for `group` are due at the end of an epoch. + * @dev Eligible groups that have received their maximum number of votes cannot receive more. + */ + function getGroupEpochRewards( + address group, + uint256 totalEpochRewards, + uint256 groupScore + ) external view onlyL2 returns (uint256) { + IValidators validators = getValidators(); + // The group must meet the balance requirements for their voters to receive epoch rewards. + if (!validators.meetsAccountLockedGoldRequirements(group) || votes.active.total <= 0) { + return 0; + } + + FixidityLib.Fraction memory votePortion = FixidityLib.newFixedFraction( + votes.active.forGroup[group].total, + votes.active.total + ); + FixidityLib.Fraction memory slashingMultiplier = FixidityLib.wrap( + validators.getValidatorGroupSlashingMultiplier(group) + ); + + FixidityLib.Fraction memory score = FixidityLib.wrap(groupScore); + return + FixidityLib + .newFixed(totalEpochRewards) + .multiply(votePortion) + .multiply(score) + .multiply(slashingMultiplier) + .fromFixed(); + } + /** * @notice Returns whether or not an account's votes for the specified group can be activated. * @param account The account with pending votes. diff --git a/packages/protocol/contracts/governance/interfaces/IElection.sol b/packages/protocol/contracts/governance/interfaces/IElection.sol index 5ba6aee6502..aceb3d1ba93 100644 --- a/packages/protocol/contracts/governance/interfaces/IElection.sol +++ b/packages/protocol/contracts/governance/interfaces/IElection.sol @@ -49,6 +49,11 @@ interface IElection { uint256, uint256[] calldata ) external view returns (uint256); + function getGroupEpochRewards( + address group, + uint256 totalEpochRewards, + uint256 groupScore + ) external view returns (uint256); function getGroupsVotedForByAccount(address) external view returns (address[] memory); function getEligibleValidatorGroups() external view returns (address[] memory); function getTotalVotesForEligibleValidatorGroups() From de3ae1984f2e0afd13d9131ba2127fa01040a159 Mon Sep 17 00:00:00 2001 From: pahor167 Date: Mon, 19 Aug 2024 14:38:33 +0200 Subject: [PATCH 21/59] getFirstBlockOfEpoch in EpochManager initializer --- .../common/EpochManagerInitializer.sol | 13 ++++- .../contracts-0.8/common/UsingPrecompiles.sol | 57 +++++++++++++++++++ 2 files changed, 69 insertions(+), 1 deletion(-) create mode 100644 packages/protocol/contracts-0.8/common/UsingPrecompiles.sol diff --git a/packages/protocol/contracts-0.8/common/EpochManagerInitializer.sol b/packages/protocol/contracts-0.8/common/EpochManagerInitializer.sol index c4d1d0439c1..b283bffa088 100644 --- a/packages/protocol/contracts-0.8/common/EpochManagerInitializer.sol +++ b/packages/protocol/contracts-0.8/common/EpochManagerInitializer.sol @@ -39,6 +39,17 @@ contract EpochManagerInitializer is initializable, UsingPrecompiles, UsingRegist validatorSignerAddressFromCurrentSet(i); electedValidatorAddresses[i] = validatorAddress; } - getEpochManager().initializeSystem(currentEpoch, block.number, electedValidatorAddresses); + getEpochManager().initializeSystem(currentEpoch, getFirstBlockOfEpoch(currentEpoch), electedValidatorAddresses); + } + + function getFirstBlockOfEpoch(uint256 currentEpoch) external view returns (uint256) { + uint256 blockToCheck = block.number - 1; + uint256 blockEpochNumber = getEpochNumberOfBlock(blocktoCheck); + + while (blockEpochNumber == currentEpoch) { + blockToCheck--; + blockEpochNumber = getEpochNumberOfBlock(blockToCheck); + } + return blockToCheck; } } diff --git a/packages/protocol/contracts-0.8/common/UsingPrecompiles.sol b/packages/protocol/contracts-0.8/common/UsingPrecompiles.sol new file mode 100644 index 00000000000..a3d9e51c13e --- /dev/null +++ b/packages/protocol/contracts-0.8/common/UsingPrecompiles.sol @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity >=0.8.0 <0.8.20; + +// Note: This is not an exact copy of UsingPrecompiles in the contract's folder, but in solidity 0.8 + +import "openzeppelin-solidity/contracts/math/SafeMath.sol"; +import "../../contracts/common/interfaces/ICeloVersionedContract.sol"; +import "../common/IsL2Check.sol"; + +contract UsingPrecompiles is IsL2Check { + using SafeMath for uint256; + + address constant TRANSFER = address(0xff - 2); + address constant FRACTION_MUL = address(0xff - 3); + address constant PROOF_OF_POSSESSION = address(0xff - 4); + address constant GET_VALIDATOR = address(0xff - 5); + address constant NUMBER_VALIDATORS = address(0xff - 6); + address constant EPOCH_SIZE = address(0xff - 7); + address constant BLOCK_NUMBER_FROM_HEADER = address(0xff - 8); + address constant HASH_HEADER = address(0xff - 9); + address constant GET_PARENT_SEAL_BITMAP = address(0xff - 10); + address constant GET_VERIFIED_SEAL_BITMAP = address(0xff - 11); + uint256 constant DAY = 86400; + + /** + * @notice Returns the current epoch size in blocks. + * @return The current epoch size in blocks. + */ + function getEpochSize() public view returns (uint256) { + if (isL2()) { + return DAY.div(5); + } else { + bytes memory out; + bool success; + (success, out) = EPOCH_SIZE.staticcall(abi.encodePacked(true)); + require(success, "error calling getEpochSize precompile"); + return getUint256FromBytes(out, 0); + } + } + + /** + * @notice Returns the epoch number at a block. + * @param blockNumber Block number where epoch number is calculated. + * @return Epoch number. + */ + function getEpochNumberOfBlock(uint256 blockNumber) public view returns (uint256) { + return epochNumberOfBlock(blockNumber, getEpochSize()); + } + + /** + * @notice Returns the epoch number at a block. + * @return Current epoch number. + */ + function getEpochNumber() public view returns (uint256) { + return getEpochNumberOfBlock(block.number); + } +} From 8e86672cf1aa90ef99dd0fc7ea96d28327427f4a Mon Sep 17 00:00:00 2001 From: pahor167 Date: Mon, 19 Aug 2024 14:42:57 +0200 Subject: [PATCH 22/59] extra checks --- packages/protocol/contracts-0.8/common/EpochManager.sol | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/protocol/contracts-0.8/common/EpochManager.sol b/packages/protocol/contracts-0.8/common/EpochManager.sol index 823390d7dd5..82b6ae27f24 100644 --- a/packages/protocol/contracts-0.8/common/EpochManager.sol +++ b/packages/protocol/contracts-0.8/common/EpochManager.sol @@ -118,6 +118,7 @@ contract EpochManager is 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"); + require(firstEpochBlock <= block.number, "First epoch block must be less or equal than current block"); require(firstElected.length > 0, "First elected validators must be greater than 0"); firstKnownEpoch = firstEpochNumber; currentEpochNumber = firstEpochNumber; From 0fae892cd8d8badde830db49a0277109cca05b99 Mon Sep 17 00:00:00 2001 From: pahor167 Date: Mon, 19 Aug 2024 14:58:14 +0200 Subject: [PATCH 23/59] introduce new getValidatorsGroup --- .../contracts-0.8/common/EpochManager.sol | 8 +------- .../governance/test/ValidatorsMock.sol | 10 ++++++++++ .../contracts/governance/Validators.sol | 19 +++++++++++++++++++ .../governance/interfaces/IValidators.sol | 8 ++++++++ .../test-sol/unit/common/EpochManager.t.sol | 5 ++++- packages/protocol/test-sol/utils08.sol | 11 +++++++++++ 6 files changed, 53 insertions(+), 8 deletions(-) diff --git a/packages/protocol/contracts-0.8/common/EpochManager.sol b/packages/protocol/contracts-0.8/common/EpochManager.sol index 82b6ae27f24..818427bd29c 100644 --- a/packages/protocol/contracts-0.8/common/EpochManager.sol +++ b/packages/protocol/contracts-0.8/common/EpochManager.sol @@ -12,8 +12,6 @@ import "../common/UsingRegistry.sol"; import "../../contracts/common/Initializable.sol"; import "../../contracts/common/interfaces/ICeloVersionedContract.sol"; -import "forge-std/console.sol"; - contract EpochManager is Initializable, UsingRegistry, @@ -181,7 +179,7 @@ contract EpochManager is epochs[currentEpochNumber].startTimestamp = block.timestamp; for (uint i = 0; i < elected.length; i++) { - (, , address group, , ) = getValidators().getValidator(elected[i]); + address group = getValidators().getValidatorsGroup(elected[i]); if (!processedGroups[group]) { epochProcessing.toProcessGroups++; processedGroups[group] = true; @@ -253,10 +251,6 @@ contract EpochManager is } function isTimeForNextEpoch() public view returns (bool) { - console.log("block.timestamp", block.timestamp); - console.log("epochs[currentEpochNumber].startTimestamp", epochs[currentEpochNumber].startTimestamp); - console.log("epochDuration", epochDuration); - console.log("block.timestamp >= epochs[currentEpochNumber].startTimestamp + epochDuration;", block.timestamp >= epochs[currentEpochNumber].startTimestamp + epochDuration); return block.timestamp >= epochs[currentEpochNumber].startTimestamp + epochDuration; } diff --git a/packages/protocol/contracts-0.8/governance/test/ValidatorsMock.sol b/packages/protocol/contracts-0.8/governance/test/ValidatorsMock.sol index 7a1d497675b..411c7be01b4 100644 --- a/packages/protocol/contracts-0.8/governance/test/ValidatorsMock.sol +++ b/packages/protocol/contracts-0.8/governance/test/ValidatorsMock.sol @@ -158,6 +158,16 @@ contract ValidatorsMock08 is IValidators { address account ) external view returns (bytes memory, bytes memory, address, uint256, address) { return ("0x", "0x", address(0), 0, address(0)); + } + function getValidatorsGroup( + address account + ) + external + view + returns ( + address affiliation + ) { + affiliation = address(0); } function getValidatorGroup( address account diff --git a/packages/protocol/contracts/governance/Validators.sol b/packages/protocol/contracts/governance/Validators.sol index e303df27804..106e3f4e060 100644 --- a/packages/protocol/contracts/governance/Validators.sol +++ b/packages/protocol/contracts/governance/Validators.sol @@ -1084,6 +1084,25 @@ contract Validators is ); } + /** + * @notice Returns affiliated group to validator. + * @param account The account that registered the validator. + * @return The validator group. + */ + function getValidatorsGroup( + address account + ) + public + view + returns ( + address affiliation + ) + { + require(isValidator(account), "Not a validator"); + Validator storage validator = validators[account]; + return validator.affiliation; + } + /** * @notice Returns the number of members in a validator group. * @param account The address of the validator group. diff --git a/packages/protocol/contracts/governance/interfaces/IValidators.sol b/packages/protocol/contracts/governance/interfaces/IValidators.sol index b1cb1463ad7..aa0cc24c5d6 100644 --- a/packages/protocol/contracts/governance/interfaces/IValidators.sol +++ b/packages/protocol/contracts/governance/interfaces/IValidators.sol @@ -62,6 +62,14 @@ interface IValidators { function getValidator( address account ) external view returns (bytes memory, bytes memory, address, uint256, address); + function getValidatorsGroup( + address account + ) + external + view + returns ( + address affiliation + ); function getValidatorGroup( address ) diff --git a/packages/protocol/test-sol/unit/common/EpochManager.t.sol b/packages/protocol/test-sol/unit/common/EpochManager.t.sol index 1ebe8a15fd2..733ea465015 100644 --- a/packages/protocol/test-sol/unit/common/EpochManager.t.sol +++ b/packages/protocol/test-sol/unit/common/EpochManager.t.sol @@ -10,6 +10,7 @@ import "@celo-contracts-8/common/CeloUnreleasedTreasure.sol"; import "@celo-contracts/common/interfaces/ICeloUnreleasedTreasure.sol"; import { TestConstants } from "@test-sol/constants.sol"; +import { Utils08 } from "@test-sol/utils08.sol"; import "@celo-contracts/stability/test/MockSortedOracles.sol"; @@ -18,7 +19,7 @@ import "@celo-contracts/common/interfaces/IRegistry.sol"; import { EpochRewardsMock08 } from "@celo-contracts-8/governance/test/EpochRewardsMock.sol"; import { ValidatorsMock08 } from "@celo-contracts-8/governance/test/ValidatorsMock.sol"; -contract EpochManagerTest is Test, TestConstants { +contract EpochManagerTest is Test, TestConstants, Utils08 { EpochManager epochManager; MockSortedOracles sortedOracles; @@ -103,6 +104,8 @@ contract EpochManagerTest is Test, TestConstants { uint256 res2 = epochRewards.getCommunityRewardFraction(); epochManager.initialize(REGISTRY_ADDRESS, 10, carbonOffsettingPartner, epochManagerInitializer); + + blockTravel(vm, firstEpochBlock); } } diff --git a/packages/protocol/test-sol/utils08.sol b/packages/protocol/test-sol/utils08.sol index 87e2e744c16..582f64b7869 100644 --- a/packages/protocol/test-sol/utils08.sol +++ b/packages/protocol/test-sol/utils08.sol @@ -1,6 +1,17 @@ pragma solidity >=0.5.13 <0.9.0; +import "celo-foundry-8/Test.sol"; + contract Utils08 { + + function timeTravel(Vm vm, uint256 timeDelta) public { + vm.warp(block.timestamp + timeDelta); + } + + function blockTravel(Vm vm, uint256 blockDelta) public { + vm.roll(block.number + blockDelta); + } + // 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)))); From 0e55b36b893d68b109963fc3126fb4d0f9f11cac Mon Sep 17 00:00:00 2001 From: pahor167 Date: Mon, 19 Aug 2024 15:01:09 +0200 Subject: [PATCH 24/59] systemAlreadyInitialized update --- packages/protocol/contracts-0.8/common/EpochManager.sol | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/protocol/contracts-0.8/common/EpochManager.sol b/packages/protocol/contracts-0.8/common/EpochManager.sol index 818427bd29c..fa37338cfad 100644 --- a/packages/protocol/contracts-0.8/common/EpochManager.sol +++ b/packages/protocol/contracts-0.8/common/EpochManager.sol @@ -98,6 +98,7 @@ contract EpochManager is address _carbonOffsettingPartner, address _epochManagerInitializer ) external initializer { + require(_epochManagerInitializer != address(0), "EpochManagerInitializer address is required"); _transferOwnership(msg.sender); setRegistry(registryAddress); setEpochDuration(newEpochDuration); @@ -135,7 +136,7 @@ contract EpochManager is /// it freezes the epochrewards at the time of execution, /// and starts the distribution of the rewards. function startNextEpochProcess() external nonReentrant { - require(epochManagerInitializer == address(0), "Epoch system not initialized"); + require(systemAlreadyInitialized(), "Epoch system not initialized"); require(isTimeForNextEpoch(), "Epoch is not ready to start"); require(!isOnEpochProcess(), "Epoch process is already started"); epochProcessing.status = EpochProcessStatus.Started; @@ -283,7 +284,7 @@ contract EpochManager is } function systemAlreadyInitialized() public view returns (bool) { - return firstKnownEpoch != 0; + return initialized && epochManagerInitializer == address(0); } function allocateValidatorsRewards() internal { From 973c21e10f1789be6f2c2392faa37cb5857d4096 Mon Sep 17 00:00:00 2001 From: pahor167 Date: Mon, 19 Aug 2024 15:05:53 +0200 Subject: [PATCH 25/59] Removal of IEpochManagerInterface --- packages/protocol/contracts-0.8/common/UsingRegistry.sol | 1 - packages/protocol/contracts/common/UsingRegistry.sol | 5 ----- packages/protocol/contracts/common/UsingRegistryV2.sol | 6 ------ .../common/interfaces/IEpochManagerInitializer.sol | 9 --------- 4 files changed, 21 deletions(-) delete mode 100644 packages/protocol/contracts/common/interfaces/IEpochManagerInitializer.sol diff --git a/packages/protocol/contracts-0.8/common/UsingRegistry.sol b/packages/protocol/contracts-0.8/common/UsingRegistry.sol index 5a2936b4103..348a91bcfe6 100644 --- a/packages/protocol/contracts-0.8/common/UsingRegistry.sol +++ b/packages/protocol/contracts-0.8/common/UsingRegistry.sol @@ -13,7 +13,6 @@ import "../../contracts/common/interfaces/IRegistry.sol"; import "../../contracts/common/interfaces/IAccounts.sol"; import "../../contracts/common/interfaces/IFreezer.sol"; import "../../contracts/common/interfaces/ICeloUnreleasedTreasure.sol"; -import "../../contracts/common/interfaces/IEpochManagerInitializer.sol"; import "../../contracts/governance/interfaces/IGovernance.sol"; import "../../contracts/governance/interfaces/ILockedGold.sol"; import "../../contracts/governance/interfaces/ILockedCelo.sol"; diff --git a/packages/protocol/contracts/common/UsingRegistry.sol b/packages/protocol/contracts/common/UsingRegistry.sol index 1c39082cdf0..878829f604c 100644 --- a/packages/protocol/contracts/common/UsingRegistry.sol +++ b/packages/protocol/contracts/common/UsingRegistry.sol @@ -9,7 +9,6 @@ import "./interfaces/IFeeCurrencyWhitelist.sol"; import "./interfaces/IFreezer.sol"; import "./interfaces/IRegistry.sol"; import "./interfaces/ICeloUnreleasedTreasure.sol"; -import "./interfaces/IEpochManagerInitializer.sol"; import "../governance/interfaces/IElection.sol"; import "../governance/interfaces/IEpochRewards.sol"; @@ -155,8 +154,4 @@ contract UsingRegistry is Ownable { function getEpochRewards() internal view returns (IEpochRewards) { return IEpochRewards(registry.getAddressForOrDie(EPOCH_REWARDS_REGISTRY_ID)); } - function getEpochManagerInitializer() internal view returns (IEpochManagerInitializer) { - return - IEpochManagerInitializer(registry.getAddressForOrDie(EPOCH_MANAGER_INITIALIZER_REGISTRY_ID)); - } } diff --git a/packages/protocol/contracts/common/UsingRegistryV2.sol b/packages/protocol/contracts/common/UsingRegistryV2.sol index 2411bdf93fd..84a473f432c 100644 --- a/packages/protocol/contracts/common/UsingRegistryV2.sol +++ b/packages/protocol/contracts/common/UsingRegistryV2.sol @@ -8,7 +8,6 @@ import "./interfaces/IFeeCurrencyWhitelist.sol"; import "./interfaces/IFreezer.sol"; import "./interfaces/IRegistry.sol"; import "./interfaces/ICeloUnreleasedTreasure.sol"; -import "./interfaces/IEpochManagerInitializer.sol"; import "../governance/interfaces/IElection.sol"; import "../governance/interfaces/IEpochRewards.sol"; @@ -190,9 +189,4 @@ contract UsingRegistryV2 { function getEpochRewards() internal view returns (IEpochRewards) { return IEpochRewards(registryContract.getAddressForOrDie(EPOCH_REWARDS_REGISTRY_ID)); } - - function getEpochManagerInitializer() internal view returns (IEpochManagerInitializer) { - return - IEpochManagerInitializer(registryContract.getAddressForOrDie(EPOCH_MANAGER_INITIALIZER_REGISTRY_ID)); - } } diff --git a/packages/protocol/contracts/common/interfaces/IEpochManagerInitializer.sol b/packages/protocol/contracts/common/interfaces/IEpochManagerInitializer.sol deleted file mode 100644 index 28493d3b2cb..00000000000 --- a/packages/protocol/contracts/common/interfaces/IEpochManagerInitializer.sol +++ /dev/null @@ -1,9 +0,0 @@ -// SPDX-License-Identifier: LGPL-3.0-only -pragma solidity >=0.5.13 <0.9.0; - -interface IEpochManagerInitializer { - /** - * @notice Initializes the EpochManager during L2 transition. - */ - function initEpochManager() external; -} From 315aaeed639af16bd2ea9764b537e72b88da91df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Volpe?= Date: Mon, 19 Aug 2024 11:51:55 -0300 Subject: [PATCH 26/59] Made CI run on feature branch --- .github/workflows/celo-monorepo.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/celo-monorepo.yml b/.github/workflows/celo-monorepo.yml index 239b67e7426..82d9462f065 100644 --- a/.github/workflows/celo-monorepo.yml +++ b/.github/workflows/celo-monorepo.yml @@ -9,10 +9,12 @@ on: branches: - master - 'release/**' + - 'feat/l2-epoch-system pull_request: branches: - master - 'release/**' + - 'feat/l2-epoch-system' concurrency: group: celo-monorepo-${{ github.ref }} From 4c9ee98331f24de77be7315f9a2d52e8e4d1fa86 Mon Sep 17 00:00:00 2001 From: pahor167 Date: Mon, 19 Aug 2024 20:45:37 +0200 Subject: [PATCH 27/59] merge fix --- packages/protocol/contracts-0.8/common/UsingRegistry.sol | 5 ----- .../test-sol/unit/governance/network/EpochRewards.t.sol | 7 ------- 2 files changed, 12 deletions(-) diff --git a/packages/protocol/contracts-0.8/common/UsingRegistry.sol b/packages/protocol/contracts-0.8/common/UsingRegistry.sol index a68ec0aa68e..537225d8191 100644 --- a/packages/protocol/contracts-0.8/common/UsingRegistry.sol +++ b/packages/protocol/contracts-0.8/common/UsingRegistry.sol @@ -56,7 +56,6 @@ contract UsingRegistry is Ownable { bytes32 constant LOCKED_CELO_REGISTRY_ID = keccak256(abi.encodePacked("LockedCelo")); bytes32 constant CELO_UNRELEASED_TREASURE_REGISTRY_ID = keccak256(abi.encodePacked("CeloUnreleasedTreasure")); - bytes32 constant EPOCH_REWARDS_REGISTRY_ID = keccak256(abi.encodePacked("EpochRewards")); bytes32 constant EPOCH_MANAGER_REGISTRY_ID = keccak256(abi.encodePacked("EpochManager")); bytes32 constant SCORE_MANAGER_REGISTRY_ID = keccak256(abi.encodePacked("ScoreManager")); // solhint-enable state-visibility @@ -149,10 +148,6 @@ contract UsingRegistry is Ownable { ); } - function getEpochRewards() internal view returns (IEpochRewards) { - return IEpochRewards(registry.getAddressForOrDie(EPOCH_REWARDS_REGISTRY_ID)); - } - function getEpochManager() internal view returns (IEpochManager) { return IEpochManager(registry.getAddressForOrDie(EPOCH_MANAGER_REGISTRY_ID)); } diff --git a/packages/protocol/test-sol/unit/governance/network/EpochRewards.t.sol b/packages/protocol/test-sol/unit/governance/network/EpochRewards.t.sol index a1369efc795..64de3207dfb 100644 --- a/packages/protocol/test-sol/unit/governance/network/EpochRewards.t.sol +++ b/packages/protocol/test-sol/unit/governance/network/EpochRewards.t.sol @@ -752,13 +752,6 @@ contract EpochRewardsTest_updateTargetVotingYield is EpochRewardsTest { assertApproxEqRel(result, expected, 1e16); // TODO I suspect it has a 1% error due rounding errors, but need to double check } - function test_Reverts_WhenCalledOnL2() public { - _whenL2(); - vm.expectRevert("This method is no longer supported in L2."); - vm.prank(address(0)); - epochRewards.updateTargetVotingYield(); - } - function mockVotes(uint256 votes) internal { election.setTotalVotes(votes); vm.prank(address(0)); From c6fd893122a8c911a5a46b950a815970eb8a4049 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Volpe?= Date: Mon, 19 Aug 2024 15:53:21 -0300 Subject: [PATCH 28/59] Added target for the CI --- .github/workflows/celo-monorepo.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/celo-monorepo.yml b/.github/workflows/celo-monorepo.yml index 239b67e7426..85bf63d1fc3 100644 --- a/.github/workflows/celo-monorepo.yml +++ b/.github/workflows/celo-monorepo.yml @@ -9,10 +9,12 @@ on: branches: - master - 'release/**' + - 'feat/epoch-manager' pull_request: branches: - master - 'release/**' + - 'feat/epoch-manager' concurrency: group: celo-monorepo-${{ github.ref }} From 41fe5e6f16ebe7f95987cee318a3e03735fd3cf3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Volpe?= Date: Mon, 19 Aug 2024 16:08:45 -0300 Subject: [PATCH 29/59] Fixed quote celo-monorepo.yml --- .github/workflows/celo-monorepo.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/celo-monorepo.yml b/.github/workflows/celo-monorepo.yml index 82d9462f065..9a6b82888a8 100644 --- a/.github/workflows/celo-monorepo.yml +++ b/.github/workflows/celo-monorepo.yml @@ -9,7 +9,7 @@ on: branches: - master - 'release/**' - - 'feat/l2-epoch-system + - 'feat/l2-epoch-system' pull_request: branches: - master From 05dcde25c0e43c3117bf7ac33b5082f4f9bc7256 Mon Sep 17 00:00:00 2001 From: Martin Date: Mon, 19 Aug 2024 21:10:56 +0200 Subject: [PATCH 30/59] Allow Validator registration in L2 without BLS key (#11181) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Allow validator registration in L2 without BLS key * Reallow ECDSA key change in L2 --------- Co-authored-by: Martín Volpe --- .../contracts/governance/Validators.sol | 33 ++- .../governance/test/MockElection.sol | 2 +- .../governance/validators/Validators.t.sol | 215 +++++++++++++++++- 3 files changed, 244 insertions(+), 6 deletions(-) diff --git a/packages/protocol/contracts/governance/Validators.sol b/packages/protocol/contracts/governance/Validators.sol index ad1ef4a9610..7f842aa7251 100644 --- a/packages/protocol/contracts/governance/Validators.sol +++ b/packages/protocol/contracts/governance/Validators.sol @@ -242,6 +242,7 @@ contract Validators is * @return True upon success. * @dev Fails if the account is already a validator or validator group. * @dev Fails if the account does not have sufficient Locked Gold. + * @dev Fails after L2 activation, but see registerValidator(bytes) below. */ function registerValidator( bytes calldata ecdsaPublicKey, @@ -271,6 +272,34 @@ contract Validators is return true; } + /** + * @notice Registers a validator unaffiliated with any validator group. + * @param ecdsaPublicKey The ECDSA public key that the validator is using for consensus, should + * match the validator signer. 64 bytes. + * @return True upon success. + * @dev Fails if the account is already a validator or validator group. + * @dev Fails if the account does not have sufficient Locked Gold. + */ + function registerValidator( + bytes calldata ecdsaPublicKey + ) external nonReentrant onlyL2 returns (bool) { + address account = getAccounts().validatorSignerToAccount(msg.sender); + _isRegistrationAllowed(account); + require(!isValidator(account) && !isValidatorGroup(account), "Already registered"); + uint256 lockedGoldBalance = getLockedGold().getAccountTotalLockedGold(account); + require(lockedGoldBalance >= validatorLockedGoldRequirements.value, "Deposit too small"); + Validator storage validator = validators[account]; + address signer = getAccounts().getValidatorSigner(account); + require( + _updateEcdsaPublicKey(validator, account, signer, ecdsaPublicKey), + "Error updating ECDSA public key" + ); + registeredValidators.push(account); + updateMembershipHistory(account, address(0)); + emit ValidatorRegistered(account); + return true; + } + /** * @notice De-registers a validator. * @param index The index of this validator in the list of all registered validators. @@ -374,7 +403,6 @@ contract Validators is address signer, bytes calldata ecdsaPublicKey ) external onlyRegisteredContract(ACCOUNTS_REGISTRY_ID) returns (bool) { - allowOnlyL1(); require(isValidator(account), "Not a validator"); Validator storage validator = validators[account]; require( @@ -451,7 +479,6 @@ contract Validators is * @dev Fails if the account does not have sufficient weight. */ function registerValidatorGroup(uint256 commission) external nonReentrant returns (bool) { - allowOnlyL1(); require(commission <= FixidityLib.fixed1().unwrap(), "Commission can't be greater than 100%"); address account = getAccounts().validatorSignerToAccount(msg.sender); _isRegistrationAllowed(account); @@ -1120,7 +1147,7 @@ contract Validators is * @return Whether a particular address is a registered validator. */ function isValidator(address account) public view returns (bool) { - return validators[account].publicKeys.bls.length > 0; + return validators[account].publicKeys.ecdsa.length > 0; } /** diff --git a/packages/protocol/contracts/governance/test/MockElection.sol b/packages/protocol/contracts/governance/test/MockElection.sol index 395dec5b252..ebc15886cfc 100644 --- a/packages/protocol/contracts/governance/test/MockElection.sol +++ b/packages/protocol/contracts/governance/test/MockElection.sol @@ -80,7 +80,7 @@ contract MockElection is IsL2Check { return electedValidators; } - function setAllowedToVoteOverMaxNumberOfGroups(address account, bool flag) public onlyL1 { + function setAllowedToVoteOverMaxNumberOfGroups(address account, bool flag) public { allowedToVoteOverMaxNumberOfGroups[account] = flag; } } diff --git a/packages/protocol/test-sol/unit/governance/validators/Validators.t.sol b/packages/protocol/test-sol/unit/governance/validators/Validators.t.sol index b4abad92865..18374ce4d6b 100644 --- a/packages/protocol/test-sol/unit/governance/validators/Validators.t.sol +++ b/packages/protocol/test-sol/unit/governance/validators/Validators.t.sol @@ -279,6 +279,23 @@ contract ValidatorsTest is Test, TestConstants, Utils, ECDSAHelper { return _ecdsaPubKey; } + function _registerValidatorWithSignerHelper_noBls() internal returns (bytes memory) { + lockedGold.setAccountTotalLockedGold(validator, originalValidatorLockedGoldRequirements.value); + + (bytes memory _ecdsaPubKey, uint8 v, bytes32 r, bytes32 s) = _generateEcdsaPubKeyWithSigner( + validator, + signerPk + ); + + vm.prank(validator); + accounts.authorizeValidatorSigner(signer, v, r, s); + + vm.prank(validator); + validators.registerValidator(_ecdsaPubKey); + validatorRegistrationEpochNumber = validators.getEpochNumber(); + return _ecdsaPubKey; + } + function _generateEcdsaPubKey( address _account, uint256 _accountPk @@ -804,6 +821,191 @@ contract ValidatorsTest_RegisterValidator is ValidatorsTest { ); } } +contract ValidatorsTest_RegisterValidator_NoBls is ValidatorsTest { + function setUp() public { + super.setUp(); + + lockedGold.setAccountTotalLockedGold(validator, originalValidatorLockedGoldRequirements.value); + } + + function test_Reverts_WhenVoteOverMaxNumberOfGroupsSetToTrue() public { + _whenL2(); + vm.prank(validator); + election.setAllowedToVoteOverMaxNumberOfGroups(validator, true); + + (uint8 v, bytes32 r, bytes32 s) = getParsedSignatureOfAddress(validator, signerPk); + + vm.prank(validator); + accounts.authorizeValidatorSigner(signer, v, r, s); + bytes memory pubKey = addressToPublicKey("random msg", v, r, s); + + vm.expectRevert("Cannot vote for more than max number of groups"); + vm.prank(validator); + validators.registerValidator(pubKey); + } + + function test_Reverts_WhenDelagatingCELO() public { + _whenL2(); + lockedGold.setAccountTotalDelegatedAmountInPercents(validator, 10); + (uint8 v, bytes32 r, bytes32 s) = getParsedSignatureOfAddress(validator, signerPk); + vm.prank(validator); + accounts.authorizeValidatorSigner(signer, v, r, s); + bytes memory pubKey = addressToPublicKey("random msg", v, r, s); + + vm.expectRevert("Cannot delegate governance power"); + vm.prank(validator); + validators.registerValidator(pubKey); + } + + function test_ShouldMarkAccountAsValidator_WhenAccountHasAuthorizedValidatorSigner() public { + _whenL2(); + _registerValidatorWithSignerHelper_noBls(); + + assertTrue(validators.isValidator(validator)); + } + + function test_ShouldRevert_WhenInL1_WhenAccountHasAuthorizedValidatorSigner() public { + lockedGold.setAccountTotalLockedGold(validator, originalValidatorLockedGoldRequirements.value); + + (bytes memory _ecdsaPubKey, uint8 v, bytes32 r, bytes32 s) = _generateEcdsaPubKeyWithSigner( + validator, + signerPk + ); + + ph.mockSuccess(ph.PROOF_OF_POSSESSION(), abi.encodePacked(validator, blsPublicKey, blsPop)); + + vm.prank(validator); + accounts.authorizeValidatorSigner(signer, v, r, s); + + vm.expectRevert("This method is not supported in L1."); + vm.prank(validator); + validators.registerValidator(_ecdsaPubKey); + validatorRegistrationEpochNumber = validators.getEpochNumber(); + } + + function test_ShouldAddAccountToValidatorList_WhenAccountHasAuthorizedValidatorSigner() public { + _whenL2(); + address[] memory ExpectedRegisteredValidators = new address[](1); + ExpectedRegisteredValidators[0] = validator; + _registerValidatorWithSignerHelper_noBls(); + assertEq(validators.getRegisteredValidators().length, ExpectedRegisteredValidators.length); + assertEq(validators.getRegisteredValidators()[0], ExpectedRegisteredValidators[0]); + } + + function test_ShouldSetValidatorEcdsaPublicKey_WhenAccountHasAuthorizedValidatorSigner() public { + _whenL2(); + bytes memory _registeredEcdsaPubKey = _registerValidatorWithSignerHelper_noBls(); + (bytes memory actualEcdsaPubKey, , , , ) = validators.getValidator(validator); + + assertEq(actualEcdsaPubKey, _registeredEcdsaPubKey); + } + + function test_ShouldNotSetValidatorBlsPublicKey_WhenAccountHasAuthorizedValidatorSigner() public { + _whenL2(); + _registerValidatorWithSignerHelper_noBls(); + (, bytes memory actualBlsPubKey, , , ) = validators.getValidator(validator); + + assertEq(actualBlsPubKey, ""); + } + + function test_ShouldSetValidatorSigner_WhenAccountHasAuthorizedValidatorSigner() public { + _whenL2(); + _registerValidatorWithSignerHelper_noBls(); + (, , , , address ActualSigner) = validators.getValidator(validator); + + assertEq(ActualSigner, signer); + } + + function test_ShouldSetLockGoldRequirements_WhenAccountHasAuthorizedValidatorSigner() public { + _whenL2(); + _registerValidatorWithSignerHelper_noBls(); + uint256 _lockedGoldReq = validators.getAccountLockedGoldRequirement(validator); + + assertEq(_lockedGoldReq, originalValidatorLockedGoldRequirements.value); + } + + function test_ShouldSetValidatorMembershipHistory_WhenAccountHasAuthorizedValidatorSigner() + public + { + _whenL2(); + _registerValidatorWithSignerHelper_noBls(); + (uint256[] memory _epoch, address[] memory _membershipGroups, , ) = validators + .getMembershipHistory(validator); + + uint256[] memory validatorRegistrationEpochNumberList = new uint256[](1); + validatorRegistrationEpochNumberList[0] = validatorRegistrationEpochNumber; + address[] memory expectedMembershipGroups = new address[](1); + expectedMembershipGroups[0] = address(0); + + assertEq(_epoch, validatorRegistrationEpochNumberList); + assertEq(_membershipGroups, expectedMembershipGroups); + } + + function testFail_DoesNotEmit_ValidatorBlsPublicKeyUpdatedEvent() public { + _whenL2(); + (bytes memory _ecdsaPubKey, uint8 v, bytes32 r, bytes32 s) = _generateEcdsaPubKeyWithSigner( + validator, + signerPk + ); + + vm.prank(validator); + accounts.authorizeValidatorSigner(signer, v, r, s); + + vm.expectEmit(true, true, true, true); + emit ValidatorBlsPublicKeyUpdated(validator, blsPublicKey); + + vm.prank(validator); + validators.registerValidator(_ecdsaPubKey); + } + + function test_Emits_ValidatorRegisteredEvent() public { + _whenL2(); + (bytes memory _ecdsaPubKey, uint8 v, bytes32 r, bytes32 s) = _generateEcdsaPubKeyWithSigner( + validator, + signerPk + ); + + vm.prank(validator); + accounts.authorizeValidatorSigner(signer, v, r, s); + + vm.expectEmit(true, true, true, true); + emit ValidatorRegistered(validator); + + vm.prank(validator); + validators.registerValidator(_ecdsaPubKey); + } + + function test_Reverts_WhenAccountAlreadyRegisteredAsValidator() public { + _whenL2(); + bytes memory _registeredEcdsaPubKey = _registerValidatorWithSignerHelper_noBls(); + vm.expectRevert("Already registered"); + vm.prank(validator); + validators.registerValidator(_registeredEcdsaPubKey); + } + + function test_Reverts_WhenAccountAlreadyRegisteredAsValidatorGroup() public { + _whenL2(); + _registerValidatorGroupHelper(validator, 1); + vm.expectRevert("Already registered"); + vm.prank(validator); + validators.registerValidator( + abi.encodePacked(bytes32(0x0101010101010101010101010101010101010101010101010101010101010101)) + ); + } + + function test_Reverts_WhenAccountDoesNotMeetLockedGoldRequirements() public { + _whenL2(); + lockedGold.setAccountTotalLockedGold( + validator, + originalValidatorLockedGoldRequirements.value.sub(11) + ); + vm.expectRevert("Deposit too small"); + vm.prank(validator); + validators.registerValidator( + abi.encodePacked(bytes32(0x0101010101010101010101010101010101010101010101010101010101010101)) + ); + } +} contract ValidatorsTest_DeregisterValidator_WhenAccountHasNeverBeenMemberOfValidatorGroup is ValidatorsTest @@ -1313,7 +1515,7 @@ contract ValidatorsTest_UpdateEcdsaPublicKey is ValidatorsTest { assertEq(actualEcdsaPubKey, _newEcdsaPubKey); } - function test_Reverts_SetValidatorEcdsaPubKey_WhenCalledByRegisteredAccountsContract_WhenL2() + function test_ShouldSetValidatorEcdsaPubKey_WhenCalledByRegisteredAccountsContract_WhenL2() public { _whenL2(); @@ -1322,8 +1524,11 @@ contract ValidatorsTest_UpdateEcdsaPublicKey is ValidatorsTest { signerPk ); vm.prank(address(accounts)); - vm.expectRevert("This method is no longer supported in L2."); validators.updateEcdsaPublicKey(validator, signer, _newEcdsaPubKey); + + (bytes memory actualEcdsaPubKey, , , , ) = validators.getValidator(validator); + + assertEq(actualEcdsaPubKey, _newEcdsaPubKey); } function test_Emits_ValidatorEcdsaPublicKeyUpdatedEvent_WhenCalledByRegisteredAccountsContract() @@ -1608,6 +1813,12 @@ contract ValidatorsTest_RegisterValidatorGroup is ValidatorsTest { assertTrue(validators.isValidatorGroup(group)); } + function test_WhenInL2_ShouldMarkAccountAsValidatorGroup() public { + _whenL2(); + _registerValidatorGroupHelper(group, 1); + assertTrue(validators.isValidatorGroup(group)); + } + function test_ShouldAddAccountToListOfValidatorGroup() public { address[] memory ExpectedRegisteredValidatorGroups = new address[](1); ExpectedRegisteredValidatorGroups[0] = group; From ce59f2e0f74441dd7e77ed59842d89aca8c31db0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Volpe?= Date: Mon, 19 Aug 2024 16:43:35 -0300 Subject: [PATCH 31/59] Fix CI --- .../contracts-0.8/governance/test/EpochRewardsMock.sol | 6 +++--- .../contracts-0.8/governance/test/ValidatorsMock.sol | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/protocol/contracts-0.8/governance/test/EpochRewardsMock.sol b/packages/protocol/contracts-0.8/governance/test/EpochRewardsMock.sol index de7632a1aa2..528d36d3f88 100644 --- a/packages/protocol/contracts-0.8/governance/test/EpochRewardsMock.sol +++ b/packages/protocol/contracts-0.8/governance/test/EpochRewardsMock.sol @@ -2,7 +2,7 @@ pragma solidity >=0.8.7 <0.8.20; import "../../../contracts/governance/interfaces/IEpochRewards.sol"; -import "forge-std-8/console2.sol"; +// import "forge-std-8/console2.sol"; /** * @title A wrapper around EpochRewards that exposes internal functions for testing. */ @@ -22,7 +22,7 @@ contract EpochRewardsMock08 is IEpochRewards { // TODO: (soloseng) implement mock function updateTargetVotingYield() external { - console2.log("### Updating Target Voting Yield"); + // console2.log("### Updating Target Voting Yield"); } // mocks the precompile @@ -38,7 +38,7 @@ contract EpochRewardsMock08 is IEpochRewards { view returns (uint256, uint256, uint256, uint256) { - console2.log("### calculating Target Epoch Rewards"); + // console2.log("### calculating Target Epoch Rewards"); return (1, 1, 1, 1); } function getTargetVotingYieldParameters() external view returns (uint256, uint256, uint256) { diff --git a/packages/protocol/contracts-0.8/governance/test/ValidatorsMock.sol b/packages/protocol/contracts-0.8/governance/test/ValidatorsMock.sol index 14296c49e7f..30499ca177f 100644 --- a/packages/protocol/contracts-0.8/governance/test/ValidatorsMock.sol +++ b/packages/protocol/contracts-0.8/governance/test/ValidatorsMock.sol @@ -4,21 +4,21 @@ pragma solidity >=0.8.7 <0.8.20; import "../../../contracts/governance/interfaces/IValidators.sol"; import "../../../contracts/common/FixidityLib.sol"; -import "forge-std-8/console2.sol"; +// import "forge-std-8/console2.sol"; /** * @title A wrapper around Validators that exposes onlyVm functions for testing. */ contract ValidatorsMock08 is IValidators { function updateValidatorScoreFromSigner(address signer, uint256 uptime) external { - console2.log("### update Validator Score From Signer"); + // console2.log("### update Validator Score From Signer"); } function distributeEpochPaymentsFromSigner( address signer, uint256 maxPayment ) external returns (uint256) { - console2.log("### distributeEpochPaymentsFromSigner"); + // console2.log("### distributeEpochPaymentsFromSigner"); return 0; // return _distributeEpochPaymentsFromSigner(signer, maxPayment); } From 01a309836189870cd3295e4a0736937690fd8e0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Volpe?= Date: Mon, 19 Aug 2024 17:08:14 -0300 Subject: [PATCH 32/59] Fixed interfaces and imports --- .github/workflows/protocol_tests.yml | 2 -- .../contracts-0.8/common/CeloUnreleasedTreasure.sol | 6 ++++-- ...itializer.sol => ICeloUnreleasedTreasureInitializer.sol} | 0 .../test-sol/unit/common/CeloUnreleasedTreasure.t.sol | 2 +- packages/protocol/test-sol/unit/common/EpochManager.t.sol | 4 ++-- 5 files changed, 7 insertions(+), 7 deletions(-) rename packages/protocol/contracts-0.8/common/interfaces/{ICeloDistributionScheduleInitializer.sol => ICeloUnreleasedTreasureInitializer.sol} (100%) diff --git a/.github/workflows/protocol_tests.yml b/.github/workflows/protocol_tests.yml index 48e1313930e..4f87c094e51 100644 --- a/.github/workflows/protocol_tests.yml +++ b/.github/workflows/protocol_tests.yml @@ -132,7 +132,6 @@ jobs: run: ./scripts/foundry/create_and_migrate_anvil_devchain.sh - name: Run migration tests against local anvil devchain - if: success() || failure() run: | source ./scripts/foundry/constants.sh @@ -141,7 +140,6 @@ jobs: --fork-url $ANVIL_RPC_URL - name: Run e2e tests against local anvil devchain - if: success() || failure() run: | source ./scripts/foundry/constants.sh diff --git a/packages/protocol/contracts-0.8/common/CeloUnreleasedTreasure.sol b/packages/protocol/contracts-0.8/common/CeloUnreleasedTreasure.sol index 1372507e994..1e17e37d517 100644 --- a/packages/protocol/contracts-0.8/common/CeloUnreleasedTreasure.sol +++ b/packages/protocol/contracts-0.8/common/CeloUnreleasedTreasure.sol @@ -8,7 +8,9 @@ import "./UsingRegistry.sol"; import "../common/IsL2Check.sol"; import "../../contracts/common/Initializable.sol"; -import "../../contracts-0.8/common/interfaces/ICeloToken.sol"; +import "../../contracts/common/interfaces/ICeloToken.sol"; +import "./interfaces/ICeloUnreleasedTreasureInitializer.sol"; +import "@openzeppelin/contracts8/token/ERC20/IERC20.sol"; /** * @title Contract for unreleased Celo tokens. @@ -47,7 +49,7 @@ contract CeloUnreleasedTreasure is UsingRegistry, ReentrancyGuard, Initializable */ function release(address to, uint256 amount) external onlyEpochManager { require(address(this).balance >= amount, "Insufficient balance."); - ICeloToken celoToken = ICeloToken(address(getCeloToken())); + IERC20 celoToken = IERC20(address(getCeloToken())); celoToken.transfer(to, amount); emit Released(to, amount); } diff --git a/packages/protocol/contracts-0.8/common/interfaces/ICeloDistributionScheduleInitializer.sol b/packages/protocol/contracts-0.8/common/interfaces/ICeloUnreleasedTreasureInitializer.sol similarity index 100% rename from packages/protocol/contracts-0.8/common/interfaces/ICeloDistributionScheduleInitializer.sol rename to packages/protocol/contracts-0.8/common/interfaces/ICeloUnreleasedTreasureInitializer.sol diff --git a/packages/protocol/test-sol/unit/common/CeloUnreleasedTreasure.t.sol b/packages/protocol/test-sol/unit/common/CeloUnreleasedTreasure.t.sol index c713c18fae3..35e69e93c6f 100644 --- a/packages/protocol/test-sol/unit/common/CeloUnreleasedTreasure.t.sol +++ b/packages/protocol/test-sol/unit/common/CeloUnreleasedTreasure.t.sol @@ -7,7 +7,7 @@ import "@celo-contracts/common/FixidityLib.sol"; import "@celo-contracts/common/interfaces/IRegistry.sol"; import "@celo-contracts-8/common/interfaces/ICeloToken.sol"; import "@celo-contracts/governance/interfaces/IGovernance.sol"; -import "@celo-contracts-8/common/CeloUnreleasedTreasure.sol"; +import { CeloUnreleasedTreasure } from "@celo-contracts-8/common/CeloUnreleasedTreasure.sol"; import "@celo-contracts-8/common/IsL2Check.sol"; import { TestConstants } from "@test-sol/constants.sol"; diff --git a/packages/protocol/test-sol/unit/common/EpochManager.t.sol b/packages/protocol/test-sol/unit/common/EpochManager.t.sol index 733ea465015..d7078dd1000 100644 --- a/packages/protocol/test-sol/unit/common/EpochManager.t.sol +++ b/packages/protocol/test-sol/unit/common/EpochManager.t.sol @@ -6,8 +6,8 @@ import "@celo-contracts-8/common/EpochManager.sol"; import "@celo-contracts-8/stability/test/MockStableToken.sol"; import "@celo-contracts-8/common/interfaces/ICeloToken.sol"; import "@celo-contracts-8/common/ScoreManager.sol"; -import "@celo-contracts-8/common/CeloUnreleasedTreasure.sol"; -import "@celo-contracts/common/interfaces/ICeloUnreleasedTreasure.sol"; +import { CeloUnreleasedTreasure } from "@celo-contracts-8/common/CeloUnreleasedTreasure.sol"; +import { ICeloUnreleasedTreasure } from "@celo-contracts/common/interfaces/ICeloUnreleasedTreasure.sol"; import { TestConstants } from "@test-sol/constants.sol"; import { Utils08 } from "@test-sol/utils08.sol"; From f7454fc5e75634966493921ae241de45b8c925a1 Mon Sep 17 00:00:00 2001 From: pahor167 Date: Tue, 20 Aug 2024 12:43:18 +0200 Subject: [PATCH 33/59] Build fix --- .../common/EpochManagerInitializer.sol | 9 ++- .../contracts-0.8/common/UsingPrecompiles.sol | 71 ++++++++++++++++++- packages/protocol/lib/registry-utils.ts | 2 +- .../28_celoDistributionSchedule.ts | 6 +- packages/protocol/scripts/build.ts | 7 +- packages/protocol/test/common/recoverFunds.ts | 12 ++-- 6 files changed, 87 insertions(+), 20 deletions(-) diff --git a/packages/protocol/contracts-0.8/common/EpochManagerInitializer.sol b/packages/protocol/contracts-0.8/common/EpochManagerInitializer.sol index e87f3a4d764..42226a3e67c 100644 --- a/packages/protocol/contracts-0.8/common/EpochManagerInitializer.sol +++ b/packages/protocol/contracts-0.8/common/EpochManagerInitializer.sol @@ -8,7 +8,7 @@ import "../../contracts/common/Initializable.sol"; import "../../contracts/common/interfaces/ICeloVersionedContract.sol"; import "../../contracts/governance/interfaces/IEpochRewards.sol"; -contract EpochManagerInitializer is initializable, UsingPrecompiles, UsingRegistry { +contract EpochManagerInitializer is Initializable, UsingPrecompiles, UsingRegistry { /** * @notice Sets initialized == true on implementation contracts * @param test Set to true to skip implementation initialization @@ -18,7 +18,6 @@ contract EpochManagerInitializer is initializable, UsingPrecompiles, UsingRegist /** * @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) external initializer { _transferOwnership(msg.sender); @@ -36,7 +35,7 @@ contract EpochManagerInitializer is initializable, UsingPrecompiles, UsingRegist address[] memory electedValidatorAddresses = new address[](numberElectedValidators); for (uint256 i = 0; i < numberElectedValidators; i++) { - validatorSignerAddressFromCurrentSet(i); + address validatorAddress = validatorSignerAddressFromCurrentSet(i); electedValidatorAddresses[i] = validatorAddress; } getEpochManager().initializeSystem( @@ -46,9 +45,9 @@ contract EpochManagerInitializer is initializable, UsingPrecompiles, UsingRegist ); } - function getFirstBlockOfEpoch(uint256 currentEpoch) external view returns (uint256) { + function getFirstBlockOfEpoch(uint256 currentEpoch) internal view returns (uint256) { uint256 blockToCheck = block.number - 1; - uint256 blockEpochNumber = getEpochNumberOfBlock(blocktoCheck); + uint256 blockEpochNumber = getEpochNumberOfBlock(blockToCheck); while (blockEpochNumber == currentEpoch) { blockToCheck--; diff --git a/packages/protocol/contracts-0.8/common/UsingPrecompiles.sol b/packages/protocol/contracts-0.8/common/UsingPrecompiles.sol index a3d9e51c13e..6c3a41f4a45 100644 --- a/packages/protocol/contracts-0.8/common/UsingPrecompiles.sol +++ b/packages/protocol/contracts-0.8/common/UsingPrecompiles.sol @@ -3,7 +3,7 @@ pragma solidity >=0.8.0 <0.8.20; // Note: This is not an exact copy of UsingPrecompiles in the contract's folder, but in solidity 0.8 -import "openzeppelin-solidity/contracts/math/SafeMath.sol"; +import "@openzeppelin/contracts8/utils/math/SafeMath.sol"; import "../../contracts/common/interfaces/ICeloVersionedContract.sol"; import "../common/IsL2Check.sol"; @@ -54,4 +54,73 @@ contract UsingPrecompiles is IsL2Check { function getEpochNumber() public view returns (uint256) { return getEpochNumberOfBlock(block.number); } + + /** + * @notice Gets the size of the current elected validator set. + * @return Size of the current elected validator set. + */ + function numberValidatorsInCurrentSet() public view returns (uint256) { + bytes memory out; + bool success; + (success, out) = NUMBER_VALIDATORS.staticcall(abi.encodePacked(uint256(block.number))); + require(success, "error calling numberValidatorsInCurrentSet precompile"); + return getUint256FromBytes(out, 0); + } + + /** + * @notice Gets a validator address from the current validator set. + * @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) { + bytes memory out; + bool success; + (success, out) = GET_VALIDATOR.staticcall(abi.encodePacked(index, uint256(block.number))); + require(success, "error calling validatorSignerAddressFromCurrentSet precompile"); + return address(uint160(getUint256FromBytes(out, 0))); + } + + /** + * @notice Returns the epoch number at a block. + * @param blockNumber Block number where epoch number is calculated. + * @param epochSize The epoch size in blocks. + * @return Epoch number. + */ + function epochNumberOfBlock( + uint256 blockNumber, + uint256 epochSize + ) internal pure returns (uint256) { + // Follows GetEpochNumber from celo-blockchain/blob/master/consensus/istanbul/utils.go + uint256 epochNumber = blockNumber / epochSize; + if (blockNumber % epochSize == 0) { + return epochNumber; + } else { + return epochNumber.add(1); + } + } + + /** + * @notice Converts bytes to uint256. + * @param bs byte[] data + * @param start offset into byte data to convert + * @return uint256 data + */ + function getUint256FromBytes(bytes memory bs, uint256 start) internal pure returns (uint256) { + return uint256(getBytes32FromBytes(bs, start)); + } + + /** + * @notice Converts bytes to bytes32. + * @param bs byte[] data + * @param start offset into byte data to convert + * @return bytes32 data + */ + function getBytes32FromBytes(bytes memory bs, uint256 start) internal pure returns (bytes32) { + require(bs.length >= start.add(32), "slicing out of range"); + bytes32 x; + assembly { + x := mload(add(bs, add(start, 32))) + } + return x; + } } diff --git a/packages/protocol/lib/registry-utils.ts b/packages/protocol/lib/registry-utils.ts index 23ca9572507..5b565848913 100644 --- a/packages/protocol/lib/registry-utils.ts +++ b/packages/protocol/lib/registry-utils.ts @@ -37,7 +37,7 @@ export enum CeloContractName { GrandaMento = 'GrandaMento', LockedGold = 'LockedGold', LockedCelo = 'LockedCelo', - CeloDistributionSchedule = 'CeloDistributionSchedule', + CeloUnreleasedTreasure = 'CeloUnreleasedTreasure', OdisPayments = 'OdisPayments', Random = 'Random', Reserve = 'Reserve', diff --git a/packages/protocol/migrations_ts/28_celoDistributionSchedule.ts b/packages/protocol/migrations_ts/28_celoDistributionSchedule.ts index 7496fff67fa..7c369f509b1 100644 --- a/packages/protocol/migrations_ts/28_celoDistributionSchedule.ts +++ b/packages/protocol/migrations_ts/28_celoDistributionSchedule.ts @@ -4,7 +4,7 @@ import { getDeployedProxiedContract, } from '@celo/protocol/lib/web3-utils' import { RegistryInstance } from '@celo/protocol/types' -import { CeloDistributionScheduleInstance } from 'types/08' +import { CeloUnreleasedTreasureInstance } from 'types/08' import { SOLIDITY_08_PACKAGE } from '../contractPackages' const initializeArgs = async (): Promise<[string]> => { @@ -15,10 +15,10 @@ const initializeArgs = async (): Promise<[string]> => { return [registry.address] } -module.exports = deploymentForCoreContract( +module.exports = deploymentForCoreContract( web3, artifacts, - CeloContractName.CeloDistributionSchedule, + CeloContractName.CeloUnreleasedTreasure, initializeArgs, undefined, SOLIDITY_08_PACKAGE diff --git a/packages/protocol/scripts/build.ts b/packages/protocol/scripts/build.ts index fafb06ab9cc..1e05fc008d3 100644 --- a/packages/protocol/scripts/build.ts +++ b/packages/protocol/scripts/build.ts @@ -39,7 +39,7 @@ function compile({ coreContractsOnly, solidity: outdir }: BuildTargets) { } exec( - `yarn run truffle compile --silent --contracts_directory=${contractPath} --contracts_build_directory=${outdir}/contracts-${contractPackage.name} --config ${contractPackage.truffleConfig}` // todo change to outdir + `yarn run truffle compile --silent --contracts_directory=${contractPath} --contracts_build_directory=${outdir}/contracts-${contractPackage.name} --config ${contractPackage.truffleConfig} --verbose-rpc` // todo change to outdir ) } @@ -154,9 +154,8 @@ async function generateFilesForContractKit({ coreContractsOnly, web3Types: outdi new Web3V1Celo({ cwd, rawConfig: { - files: `${BUILD_DIR}/contracts-${ - externalContractPackage.name - }/@(${externalContractPackage.contracts.join('|')}).json`, + files: `${BUILD_DIR}/contracts-${externalContractPackage.name + }/@(${externalContractPackage.contracts.join('|')}).json`, outDir: path.join(relativePath, externalContractPackage.name), }, }) diff --git a/packages/protocol/test/common/recoverFunds.ts b/packages/protocol/test/common/recoverFunds.ts index 89fce8f98af..25779f90058 100644 --- a/packages/protocol/test/common/recoverFunds.ts +++ b/packages/protocol/test/common/recoverFunds.ts @@ -11,7 +11,7 @@ import { ProxyInstance, RegistryContract, } from 'types' -import { CeloDistributionScheduleContract } from 'types/08' +import { CeloUnreleasedTreasureContract } from 'types/08' import { SOLIDITY_08_PACKAGE } from '../../contractPackages' import { ArtifactsSingleton } from '../../lib/artifactsSingleton' @@ -37,21 +37,21 @@ contract('Proxy', (accounts: string[]) => { it('recovers funds from an incorrectly intialized implementation', async () => { const Freezer: FreezerContract = artifacts.require('Freezer') const GoldToken: GoldTokenContract = artifacts.require('GoldToken') - const CeloDistributionSchedule: CeloDistributionScheduleContract = - ArtifactsSingleton.getInstance(SOLIDITY_08_PACKAGE).require('CeloDistributionSchedule') // Added because the CeloToken `_transfer` prevents transfers to the celoDistributionSchedule. + const CeloUnreleasedTreasure: CeloUnreleasedTreasureContract = + ArtifactsSingleton.getInstance(SOLIDITY_08_PACKAGE).require('CeloUnreleasedTreasure') // Added because the CeloToken `_transfer` prevents transfers to the celoUnreleasedTreasure. // @ts-ignore GoldToken.numberFormat = 'BigNumber' const Registry: RegistryContract = artifacts.require('Registry') const freezer = await Freezer.new(true) const goldToken = await GoldToken.new(true) - const celoDistributionSchedule = await CeloDistributionSchedule.new(true) + const celoUnreleasedTreasure = await CeloUnreleasedTreasure.new(true) const registry = await Registry.new(true) await registry.setAddressFor(CeloContractName.Freezer, freezer.address) await registry.setAddressFor( - CeloContractName.CeloDistributionSchedule, - celoDistributionSchedule.address + CeloContractName.CeloUnreleasedTreasure, + celoUnreleasedTreasure.address ) await goldToken.initialize(registry.address) From ff4fb8fef6e613b9a532c2466d5470c776e13e11 Mon Sep 17 00:00:00 2001 From: pahor167 Date: Tue, 20 Aug 2024 13:00:07 +0200 Subject: [PATCH 34/59] lint --- .../contracts-0.8/common/EpochManager.sol | 9 +-- .../contracts-0.8/common/ScoreManager.sol | 26 +++---- .../interfaces/IFeeCurrencyDirectory.sol | 18 ++--- .../governance/test/EpochRewardsMock.sol | 19 +++--- .../contracts/governance/Validators.sol | 67 ++++++++++--------- .../stability/test/MockSortedOracles.sol | 20 +++--- 6 files changed, 81 insertions(+), 78 deletions(-) diff --git a/packages/protocol/contracts-0.8/common/EpochManager.sol b/packages/protocol/contracts-0.8/common/EpochManager.sol index e4ddeb11a3c..1b792b6368e 100644 --- a/packages/protocol/contracts-0.8/common/EpochManager.sol +++ b/packages/protocol/contracts-0.8/common/EpochManager.sol @@ -251,14 +251,11 @@ contract EpochManager is return epochs[epoch].lastBlock; } - function isTimeForNextEpoch() public view returns (bool) { - return block.timestamp >= epochs[currentEpochNumber].startTimestamp + epochDuration; - } - function isBlocked() external view returns (bool) { return isOnEpochProcess(); } + /** * @notice Returns the storage, major, minor, and patch version of the contract. * @return Storage version of the contract. @@ -279,6 +276,10 @@ contract EpochManager is epochDuration = newEpochDuration; } + function isTimeForNextEpoch() public view returns (bool) { + return block.timestamp >= epochs[currentEpochNumber].startTimestamp + epochDuration; + } + function isOnEpochProcess() public view returns (bool) { return epochProcessing.status == EpochProcessStatus.Started; } diff --git a/packages/protocol/contracts-0.8/common/ScoreManager.sol b/packages/protocol/contracts-0.8/common/ScoreManager.sol index 2a71a384041..d91705cdf65 100644 --- a/packages/protocol/contracts-0.8/common/ScoreManager.sol +++ b/packages/protocol/contracts-0.8/common/ScoreManager.sol @@ -23,21 +23,14 @@ contract ScoreManager is Initializable, Ownable { _transferOwnership(msg.sender); } - /** - * @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 setGroupScore(address group, uint256 score) external onlyOwner { scores[group] = score; } + function setValidatorScore(address validator, uint256 score) external onlyOwner { + scores[validator] = score; + } + function getGroupScore(address group) external view returns (uint256) { return scores[group]; } @@ -46,7 +39,14 @@ contract ScoreManager is Initializable, Ownable { return scores[validator]; } - function setValidatorScore(address validator, uint256 score) external onlyOwner { - scores[validator] = score; + /** + * @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); } } diff --git a/packages/protocol/contracts-0.8/common/interfaces/IFeeCurrencyDirectory.sol b/packages/protocol/contracts-0.8/common/interfaces/IFeeCurrencyDirectory.sol index b13ef6e0ec4..28850334637 100644 --- a/packages/protocol/contracts-0.8/common/interfaces/IFeeCurrencyDirectory.sol +++ b/packages/protocol/contracts-0.8/common/interfaces/IFeeCurrencyDirectory.sol @@ -7,6 +7,15 @@ interface IFeeCurrencyDirectory { uint256 intrinsicGas; } + /** + * @notice Sets the currency configuration for a token. + * @dev This action can only be performed by the contract owner. + * @param token The token address. + * @param oracle The oracle address for price fetching. + * @param intrinsicGas The intrinsic gas value for transactions. + */ + function setCurrencyConfig(address token, address oracle, uint256 intrinsicGas) external; + /** * @notice Returns the list of all currency addresses. * @return An array of addresses. @@ -28,13 +37,4 @@ interface IFeeCurrencyDirectory { function getExchangeRate( address token ) external view returns (uint256 numerator, uint256 denominator); - - /** - * @notice Sets the currency configuration for a token. - * @dev This action can only be performed by the contract owner. - * @param token The token address. - * @param oracle The oracle address for price fetching. - * @param intrinsicGas The intrinsic gas value for transactions. - */ - function setCurrencyConfig(address token, address oracle, uint256 intrinsicGas) external; } diff --git a/packages/protocol/contracts-0.8/governance/test/EpochRewardsMock.sol b/packages/protocol/contracts-0.8/governance/test/EpochRewardsMock.sol index 528d36d3f88..b64e8f53aa0 100644 --- a/packages/protocol/contracts-0.8/governance/test/EpochRewardsMock.sol +++ b/packages/protocol/contracts-0.8/governance/test/EpochRewardsMock.sol @@ -13,6 +13,11 @@ contract EpochRewardsMock08 is IEpochRewards { numValidatorsInCurrentSet = value; } + // TODO: (soloseng) implement mock + function updateTargetVotingYield() external { + // console2.log("### Updating Target Voting Yield"); + } + function getRewardsMultiplier( uint256 targetGoldTotalSupplyIncrease ) external view returns (uint256) { @@ -20,15 +25,6 @@ contract EpochRewardsMock08 is IEpochRewards { return 0; } - // TODO: (soloseng) implement mock - function updateTargetVotingYield() external { - // console2.log("### Updating Target Voting Yield"); - } - - // mocks the precompile - function numberValidatorsInCurrentSet() public view returns (uint256) { - return numValidatorsInCurrentSet; - } function isReserveLow() external view returns (bool) { return false; @@ -59,4 +55,9 @@ contract EpochRewardsMock08 is IEpochRewards { function getRewardsMultiplier() external view returns (uint256) { return 0; } + + // mocks the precompile + function numberValidatorsInCurrentSet() public view returns (uint256) { + return numValidatorsInCurrentSet; + } } diff --git a/packages/protocol/contracts/governance/Validators.sol b/packages/protocol/contracts/governance/Validators.sol index 7f842aa7251..8cb9edbc03a 100644 --- a/packages/protocol/contracts/governance/Validators.sol +++ b/packages/protocol/contracts/governance/Validators.sol @@ -867,6 +867,40 @@ contract Validators is return commissionUpdateDelay; } + + /** + * @notice Computes epoch payments to the account + * @param account The validator signer of the validator to distribute the epoch payment to. + * @param maxPayment The maximum payment to the validator. Actual payment is based on score and + * group commission. + * @return The total payment paid to the validator and their group. + */ + function computeEpochReward( + address account, + uint256 score, + uint256 maxPayment + ) external view returns (uint256) { + require(isValidator(account), "Not a validator"); + FixidityLib.Fraction memory scoreFraction = FixidityLib.wrap(score); + require(scoreFraction.lte(FixidityLib.fixed1()), "Score must be <= 1"); + + // The group that should be paid is the group that the validator was a member of at the + // time it was elected. + address group = getMembershipInLastEpoch(account); + require(group != address(0), "Validator not registered with a group"); + // Both the validator and the group must maintain the minimum locked gold balance in order to + // receive epoch payments. + if (meetsAccountLockedGoldRequirements(account) && meetsAccountLockedGoldRequirements(group)) { + FixidityLib.Fraction memory totalPayment = FixidityLib + .newFixed(maxPayment) + .multiply(scoreFraction) + .multiply(groups[group].slashInfo.multiplier); + return totalPayment.fromFixed(); + } else { + return 0; + } + } + /** * @notice Returns the storage, major, minor, and patch version of the contract. * @return Storage version of the contract. @@ -1433,37 +1467,4 @@ contract Validators is emit ValidatorDeaffiliated(validatorAccount, affiliation); return true; } - - /** - * @notice Computes epoch payments to the account - * @param account The validator signer of the validator to distribute the epoch payment to. - * @param maxPayment The maximum payment to the validator. Actual payment is based on score and - * group commission. - * @return The total payment paid to the validator and their group. - */ - function computeEpochReward( - address account, - uint256 score, - uint256 maxPayment - ) external view returns (uint256) { - require(isValidator(account), "Not a validator"); - FixidityLib.Fraction memory scoreFraction = FixidityLib.wrap(score); - require(scoreFraction.lte(FixidityLib.fixed1()), "Score must be <= 1"); - - // The group that should be paid is the group that the validator was a member of at the - // time it was elected. - address group = getMembershipInLastEpoch(account); - require(group != address(0), "Validator not registered with a group"); - // Both the validator and the group must maintain the minimum locked gold balance in order to - // receive epoch payments. - if (meetsAccountLockedGoldRequirements(account) && meetsAccountLockedGoldRequirements(group)) { - FixidityLib.Fraction memory totalPayment = FixidityLib - .newFixed(maxPayment) - .multiply(scoreFraction) - .multiply(groups[group].slashInfo.multiplier); - return totalPayment.fromFixed(); - } else { - return 0; - } - } } diff --git a/packages/protocol/contracts/stability/test/MockSortedOracles.sol b/packages/protocol/contracts/stability/test/MockSortedOracles.sol index d2298c986ce..2a18a3c85d9 100644 --- a/packages/protocol/contracts/stability/test/MockSortedOracles.sol +++ b/packages/protocol/contracts/stability/test/MockSortedOracles.sol @@ -32,6 +32,16 @@ contract MockSortedOracles { return _numRates[token]; } + function getExchangeRate( + address token + ) external view returns (uint256 numerator, uint256 denominator) { + (numerator, denominator) = medianRate(token); + } + + function setOldestReportExpired(address token) public { + expired[token] = true; + } + function medianRate(address token) public view returns (uint256, uint256) { if (numerators[token] > 0) { return (numerators[token], DENOMINATOR); @@ -39,17 +49,7 @@ contract MockSortedOracles { return (0, 0); } - function setOldestReportExpired(address token) public { - expired[token] = true; - } - function isOldestReportExpired(address token) public view returns (bool, address) { return (expired[token], token); } - - function getExchangeRate( - address token - ) external view returns (uint256 numerator, uint256 denominator) { - (numerator, denominator) = medianRate(token); - } } From 55d462adbe04c8e41b8f81ddee1af0c05767e6e0 Mon Sep 17 00:00:00 2001 From: pahor167 Date: Tue, 20 Aug 2024 13:13:45 +0200 Subject: [PATCH 35/59] prettify --- packages/protocol/contracts-0.8/common/EpochManager.sol | 1 - packages/protocol/contracts-0.8/common/UsingPrecompiles.sol | 4 ++-- .../contracts-0.8/governance/test/EpochRewardsMock.sol | 1 - packages/protocol/contracts/governance/Validators.sol | 1 - 4 files changed, 2 insertions(+), 5 deletions(-) diff --git a/packages/protocol/contracts-0.8/common/EpochManager.sol b/packages/protocol/contracts-0.8/common/EpochManager.sol index 1b792b6368e..83191eb8842 100644 --- a/packages/protocol/contracts-0.8/common/EpochManager.sol +++ b/packages/protocol/contracts-0.8/common/EpochManager.sol @@ -255,7 +255,6 @@ contract EpochManager is return isOnEpochProcess(); } - /** * @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 6c3a41f4a45..3474867d99d 100644 --- a/packages/protocol/contracts-0.8/common/UsingPrecompiles.sol +++ b/packages/protocol/contracts-0.8/common/UsingPrecompiles.sol @@ -80,7 +80,7 @@ contract UsingPrecompiles is IsL2Check { return address(uint160(getUint256FromBytes(out, 0))); } - /** + /** * @notice Returns the epoch number at a block. * @param blockNumber Block number where epoch number is calculated. * @param epochSize The epoch size in blocks. @@ -99,7 +99,7 @@ contract UsingPrecompiles is IsL2Check { } } - /** + /** * @notice Converts bytes to uint256. * @param bs byte[] data * @param start offset into byte data to convert diff --git a/packages/protocol/contracts-0.8/governance/test/EpochRewardsMock.sol b/packages/protocol/contracts-0.8/governance/test/EpochRewardsMock.sol index b64e8f53aa0..af929d275e5 100644 --- a/packages/protocol/contracts-0.8/governance/test/EpochRewardsMock.sol +++ b/packages/protocol/contracts-0.8/governance/test/EpochRewardsMock.sol @@ -25,7 +25,6 @@ contract EpochRewardsMock08 is IEpochRewards { return 0; } - function isReserveLow() external view returns (bool) { return false; } diff --git a/packages/protocol/contracts/governance/Validators.sol b/packages/protocol/contracts/governance/Validators.sol index 8cb9edbc03a..b93e74e85f4 100644 --- a/packages/protocol/contracts/governance/Validators.sol +++ b/packages/protocol/contracts/governance/Validators.sol @@ -867,7 +867,6 @@ contract Validators is return commissionUpdateDelay; } - /** * @notice Computes epoch payments to the account * @param account The validator signer of the validator to distribute the epoch payment to. From a2522a23655d77136ef0a25b3f36a5b460f7d622 Mon Sep 17 00:00:00 2001 From: pahor167 Date: Tue, 20 Aug 2024 13:22:42 +0200 Subject: [PATCH 36/59] prettify 2 --- packages/protocol/scripts/build.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/protocol/scripts/build.ts b/packages/protocol/scripts/build.ts index 1e05fc008d3..a4455fe650d 100644 --- a/packages/protocol/scripts/build.ts +++ b/packages/protocol/scripts/build.ts @@ -154,8 +154,9 @@ async function generateFilesForContractKit({ coreContractsOnly, web3Types: outdi new Web3V1Celo({ cwd, rawConfig: { - files: `${BUILD_DIR}/contracts-${externalContractPackage.name - }/@(${externalContractPackage.contracts.join('|')}).json`, + files: `${BUILD_DIR}/contracts-${ + externalContractPackage.name + }/@(${externalContractPackage.contracts.join('|')}).json`, outDir: path.join(relativePath, externalContractPackage.name), }, }) From dc9ece5581fe9bafc71eae134729b991e4271f68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Volpe?= Date: Tue, 20 Aug 2024 17:57:02 -0300 Subject: [PATCH 37/59] Attempt to fix truffle build and migrations l2 epoch (#11190) --- .github/workflows/celo-monorepo.yml | 6 ++++++ .github/workflows/protocol_tests.yml | 8 ++++++++ packages/protocol/contractPackages.ts | 4 ++-- .../contracts-0.8/common/EpochManagerInitializer.sol | 8 ++++++-- .../protocol/contracts-0.8/common/UsingPrecompiles.sol | 1 - packages/protocol/migrations_sol/CONTRIBUTING.md | 2 +- packages/protocol/migrations_sol/MigrationL2.s.sol | 6 +++--- .../releaseData/initializationData/release12.json | 2 +- packages/protocol/scripts/consts.ts | 4 ++-- .../foundry/create_and_migrate_anvil_l2_devchain.sh | 10 +++++----- 10 files changed, 34 insertions(+), 17 deletions(-) diff --git a/.github/workflows/celo-monorepo.yml b/.github/workflows/celo-monorepo.yml index 9a6b82888a8..e13ecff921a 100644 --- a/.github/workflows/celo-monorepo.yml +++ b/.github/workflows/celo-monorepo.yml @@ -10,11 +10,17 @@ on: - master - 'release/**' - 'feat/l2-epoch-system' + - 'martinvol/**' + - 'pahor167/**' + - 'soloseng/**' pull_request: branches: - master - 'release/**' - 'feat/l2-epoch-system' + - 'martinvol/**' + - 'pahor167/**' + - 'soloseng/**' concurrency: group: celo-monorepo-${{ github.ref }} diff --git a/.github/workflows/protocol_tests.yml b/.github/workflows/protocol_tests.yml index 4f87c094e51..2e5fd6d1b1e 100644 --- a/.github/workflows/protocol_tests.yml +++ b/.github/workflows/protocol_tests.yml @@ -4,10 +4,18 @@ on: branches: - master - 'release/**' + - 'feat/l2-epoch-system' + - 'martinvol/**' + - 'pahor167/**' + - 'soloseng/**' pull_request: branches: - master - 'release/**' + - 'feat/l2-epoch-system' + - 'martinvol/**' + - 'pahor167/**' + - 'soloseng/**' env: # Increment these to force cache rebuilding diff --git a/packages/protocol/contractPackages.ts b/packages/protocol/contractPackages.ts index 45a2e650558..696d2fa932e 100644 --- a/packages/protocol/contractPackages.ts +++ b/packages/protocol/contractPackages.ts @@ -48,12 +48,12 @@ 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', 'CeloDistributionSchedule'], + contracts: ['GasPriceMinimum', 'FeeCurrencyDirectory', 'CeloUnreleasedTreasure'], proxyContracts: [ 'GasPriceMinimumProxy', 'FeeCurrencyDirectoryProxy', 'MentoFeeCurrencyAdapterV1', - 'CeloDistributionScheduleProxy', + 'CeloUnreleasedTreasureProxy', ], truffleConfig: 'truffle-config0.8.js', } satisfies ContractPackage diff --git a/packages/protocol/contracts-0.8/common/EpochManagerInitializer.sol b/packages/protocol/contracts-0.8/common/EpochManagerInitializer.sol index 42226a3e67c..6623dff7a95 100644 --- a/packages/protocol/contracts-0.8/common/EpochManagerInitializer.sol +++ b/packages/protocol/contracts-0.8/common/EpochManagerInitializer.sol @@ -40,12 +40,12 @@ contract EpochManagerInitializer is Initializable, UsingPrecompiles, UsingRegist } getEpochManager().initializeSystem( currentEpoch, - getFirstBlockOfEpoch(currentEpoch), + _getFirstBlockOfEpoch(currentEpoch), electedValidatorAddresses ); } - function getFirstBlockOfEpoch(uint256 currentEpoch) internal view returns (uint256) { + function _getFirstBlockOfEpoch(uint256 currentEpoch) internal view returns (uint256) { uint256 blockToCheck = block.number - 1; uint256 blockEpochNumber = getEpochNumberOfBlock(blockToCheck); @@ -55,4 +55,8 @@ contract EpochManagerInitializer is Initializable, UsingPrecompiles, UsingRegist } return blockToCheck; } + + function getFirstBlockOfEpoch(uint256 currentEpoch) external view returns (uint256) { + return _getFirstBlockOfEpoch(currentEpoch); + } } diff --git a/packages/protocol/contracts-0.8/common/UsingPrecompiles.sol b/packages/protocol/contracts-0.8/common/UsingPrecompiles.sol index 3474867d99d..84ed167c520 100644 --- a/packages/protocol/contracts-0.8/common/UsingPrecompiles.sol +++ b/packages/protocol/contracts-0.8/common/UsingPrecompiles.sol @@ -2,7 +2,6 @@ pragma solidity >=0.8.0 <0.8.20; // Note: This is not an exact copy of UsingPrecompiles in the contract's folder, but in solidity 0.8 - import "@openzeppelin/contracts8/utils/math/SafeMath.sol"; import "../../contracts/common/interfaces/ICeloVersionedContract.sol"; import "../common/IsL2Check.sol"; diff --git a/packages/protocol/migrations_sol/CONTRIBUTING.md b/packages/protocol/migrations_sol/CONTRIBUTING.md index 5524a3a8858..96cda20a6ed 100644 --- a/packages/protocol/migrations_sol/CONTRIBUTING.md +++ b/packages/protocol/migrations_sol/CONTRIBUTING.md @@ -32,7 +32,7 @@ Starts a new anvil devchain serving at localhost (default port 8546). You can now run commands against the local devchain. For example: ```sh -# Call `isL2()` on `CeloDistributionSchedule.sol` +# Call `isL2()` on `CeloUnreleasedTreasure.sol` cast call \ 0xA16cF67AFa80BB9Ce7a325597F80057c6B290fD4 \ "isL2()(bool)" \ diff --git a/packages/protocol/migrations_sol/MigrationL2.s.sol b/packages/protocol/migrations_sol/MigrationL2.s.sol index a85d4ba4522..dde3001ae9f 100644 --- a/packages/protocol/migrations_sol/MigrationL2.s.sol +++ b/packages/protocol/migrations_sol/MigrationL2.s.sol @@ -16,7 +16,7 @@ contract MigrationL2 is Script, MigrationsConstants, UsingRegistry { vm.startBroadcast(DEPLOYER_ACCOUNT); setupUsingRegistry(); - activateCeloDistributionSchedule(); + activateCeloUnreleasedTreasure(); vm.stopBroadcast(); } @@ -26,13 +26,13 @@ contract MigrationL2 is Script, MigrationsConstants, UsingRegistry { setRegistry(REGISTRY_ADDRESS); } - function activateCeloDistributionSchedule() public { + function activateCeloUnreleasedTreasure() public { uint256 l2StartTime = 1721909903 - 5; // Arbitrarily 5 seconds before last black uint256 communityRewardFraction = getEpochRewards().getCommunityRewardFraction(); address carbonOffsettingPartner = 0x22579CA45eE22E2E16dDF72D955D6cf4c767B0eF; uint256 carbonOffsettingFraction = getEpochRewards().getCarbonOffsettingFraction(); - getCeloDistributionSchedule().activate( + getCeloUnreleasedTreasure().activate( l2StartTime, communityRewardFraction, carbonOffsettingPartner, diff --git a/packages/protocol/releaseData/initializationData/release12.json b/packages/protocol/releaseData/initializationData/release12.json index b9814f49cf1..9968a3a2c70 100644 --- a/packages/protocol/releaseData/initializationData/release12.json +++ b/packages/protocol/releaseData/initializationData/release12.json @@ -1,4 +1,4 @@ { "FeeCurrencyDirectory": [], - "CeloDistributionSchedule": ["0x000000000000000000000000000000000000ce10"] + "CeloUnreleasedTreasure": ["0x000000000000000000000000000000000000ce10"] } diff --git a/packages/protocol/scripts/consts.ts b/packages/protocol/scripts/consts.ts index d599e87950e..bb435ac401d 100644 --- a/packages/protocol/scripts/consts.ts +++ b/packages/protocol/scripts/consts.ts @@ -36,7 +36,7 @@ export const ProxyContracts = [ 'RegistryProxy', 'SortedOraclesProxy', 'UniswapFeeHandlerSellerProxy', - 'CeloDistributionScheduleProxy', + 'CeloUnreleasedTreasureProxy', ] export const CoreContracts = [ @@ -52,7 +52,7 @@ export const CoreContracts = [ 'MultiSig', 'Registry', 'Freezer', - 'CeloDistributionSchedule', + 'CeloUnreleasedTreasure', // governance 'Election', diff --git a/packages/protocol/scripts/foundry/create_and_migrate_anvil_l2_devchain.sh b/packages/protocol/scripts/foundry/create_and_migrate_anvil_l2_devchain.sh index 8b0eb1997a0..b1c0b145676 100755 --- a/packages/protocol/scripts/foundry/create_and_migrate_anvil_l2_devchain.sh +++ b/packages/protocol/scripts/foundry/create_and_migrate_anvil_l2_devchain.sh @@ -23,15 +23,15 @@ CELO_DISTRIBUTION_SCHEDULE_ADDRESS=$( cast call \ $REGISTRY_ADDRESS \ "getAddressForStringOrDie(string calldata identifier)(address)" \ - "CeloDistributionSchedule" \ + "CeloUnreleasedTreasure" \ --rpc-url $ANVIL_RPC_URL ) -# Set the balance of the CeloDistributionSchedule (like the Celo client would do during L2 genesis) -# Note: This can't be done from the migration script, because CeloDistributionSchedule.sol does not +# Set the balance of the CeloUnreleasedTreasure (like the Celo client would do during L2 genesis) +# Note: This can't be done from the migration script, because CeloUnreleasedTreasure.sol does not # implement the receive function nor does it allow ERC20 transfers. This is the only way I -# managed to give the CeloDistributionSchedule a balance. -echo "Setting CeloDistributionSchedule balance..." +# managed to give the CeloUnreleasedTreasure a balance. +echo "Setting CeloUnreleasedTreasure balance..." cast rpc \ anvil_setBalance \ $CELO_DISTRIBUTION_SCHEDULE_ADDRESS $CELO_DISTRIBUTION_SCHEDULE_INITIAL_BALANCE \ From 85303f93efce472d07b4a1617029d98f5611adc2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Volpe?= Date: Wed, 21 Aug 2024 12:33:57 -0300 Subject: [PATCH 38/59] Added Blocking to LockedGold, Election (#11186) --- .../common/EpochManagerInitializer.sol | 8 +- .../protocol/contracts/common/Blockable.sol | 53 +++++++++++ .../common/interfaces/IBlockable.sol | 7 ++ .../contracts/common/interfaces/IBlocker.sol | 5 + .../contracts/governance/Election.sol | 12 ++- .../contracts/governance/LockedGold.sol | 9 +- .../test-sol/unit/common/Blockable.t.sol | 91 +++++++++++++++++++ .../unit/governance/voting/Election.t.sol | 51 ++++++++++- .../unit/governance/voting/LockedGold.t.sol | 15 +++ 9 files changed, 235 insertions(+), 16 deletions(-) create mode 100644 packages/protocol/contracts/common/Blockable.sol create mode 100644 packages/protocol/contracts/common/interfaces/IBlockable.sol create mode 100644 packages/protocol/contracts/common/interfaces/IBlocker.sol create mode 100644 packages/protocol/test-sol/unit/common/Blockable.t.sol diff --git a/packages/protocol/contracts-0.8/common/EpochManagerInitializer.sol b/packages/protocol/contracts-0.8/common/EpochManagerInitializer.sol index 6623dff7a95..c30063a31b8 100644 --- a/packages/protocol/contracts-0.8/common/EpochManagerInitializer.sol +++ b/packages/protocol/contracts-0.8/common/EpochManagerInitializer.sol @@ -45,6 +45,10 @@ contract EpochManagerInitializer is Initializable, UsingPrecompiles, UsingRegist ); } + function getFirstBlockOfEpoch(uint256 currentEpoch) external view returns (uint256) { + return _getFirstBlockOfEpoch(currentEpoch); + } + function _getFirstBlockOfEpoch(uint256 currentEpoch) internal view returns (uint256) { uint256 blockToCheck = block.number - 1; uint256 blockEpochNumber = getEpochNumberOfBlock(blockToCheck); @@ -55,8 +59,4 @@ contract EpochManagerInitializer is Initializable, UsingPrecompiles, UsingRegist } return blockToCheck; } - - function getFirstBlockOfEpoch(uint256 currentEpoch) external view returns (uint256) { - return _getFirstBlockOfEpoch(currentEpoch); - } } diff --git a/packages/protocol/contracts/common/Blockable.sol b/packages/protocol/contracts/common/Blockable.sol new file mode 100644 index 00000000000..4ebdbce390d --- /dev/null +++ b/packages/protocol/contracts/common/Blockable.sol @@ -0,0 +1,53 @@ +pragma solidity >=0.5.13 <0.9.0; + +import "./interfaces/IBlockable.sol"; +import "./interfaces/IBlocker.sol"; +import "openzeppelin-solidity/contracts/ownership/Ownable.sol"; + +/** + * @title Blockable Contract + * @notice This contract allows certain actions to be blocked based on the logic of another contract implementing the IBlocker interface. + * @dev This contract uses an external IBlocker contract to determine if it is blocked. The owner can set the blocking contract. + **/ +contract Blockable is IBlockable, Ownable { + IBlocker blockedBy; + + event BlockedBySet(address indexed _blockedBy); + + /// @notice Modifier to ensure the function is only executed when the contract is not blocked. + /// @dev Reverts with an error if the contract is blocked. + modifier onlyWhenNotBlocked() { + require(!_isBlocked(), "Contract is blocked from performing this action"); + _; + } + + /// @notice Sets the address of the blocking contract. + /// @param _blockedBy The address of the contract that will determine if this contract is blocked. + /// @dev Can only be called by the owner of the contract. + function setBlockedByContract(address _blockedBy) external onlyOwner { + _setBlockedBy(_blockedBy); + } + + /// @notice Checks if the contract is currently blocked. + /// @return Returns true if the contract is blocked, otherwise false. + /// @dev The function returns false if no blocking contract has been set. + function isBlocked() external view returns (bool) { + return _isBlocked(); + } + + function getBlockedbyContract() external view returns (address) { + return address(blockedBy); + } + + function _setBlockedBy(address _blockedBy) internal { + blockedBy = IBlocker(_blockedBy); + emit BlockedBySet(_blockedBy); + } + + function _isBlocked() internal view returns (bool) { + if (address(blockedBy) == address(0)) { + return false; + } + return blockedBy.isBlocked(); + } +} diff --git a/packages/protocol/contracts/common/interfaces/IBlockable.sol b/packages/protocol/contracts/common/interfaces/IBlockable.sol new file mode 100644 index 00000000000..c6663fbbaf1 --- /dev/null +++ b/packages/protocol/contracts/common/interfaces/IBlockable.sol @@ -0,0 +1,7 @@ +pragma solidity >=0.5.13 <0.9.0; + +interface IBlockable { + function setBlockedByContract(address _blockedBy) external; + function isBlocked() external view returns (bool); + function getBlockedbyContract() external view returns (address); +} diff --git a/packages/protocol/contracts/common/interfaces/IBlocker.sol b/packages/protocol/contracts/common/interfaces/IBlocker.sol new file mode 100644 index 00000000000..e711c71597a --- /dev/null +++ b/packages/protocol/contracts/common/interfaces/IBlocker.sol @@ -0,0 +1,5 @@ +pragma solidity >=0.5.13 <0.9.0; + +interface IBlocker { + function isBlocked() external view returns (bool); +} diff --git a/packages/protocol/contracts/governance/Election.sol b/packages/protocol/contracts/governance/Election.sol index 9af071aa8f7..24e11f53a68 100644 --- a/packages/protocol/contracts/governance/Election.sol +++ b/packages/protocol/contracts/governance/Election.sol @@ -15,6 +15,7 @@ import "../common/UsingRegistry.sol"; import "../common/interfaces/ICeloVersionedContract.sol"; import "../common/libraries/Heap.sol"; import "../common/libraries/ReentrancyGuard.sol"; +import "../common/Blockable.sol"; contract Election is IElection, @@ -24,7 +25,8 @@ contract Election is Initializable, UsingRegistry, UsingPrecompiles, - CalledByVm + CalledByVm, + Blockable { using AddressSortedLinkedList for SortedLinkedList.List; using FixidityLib for FixidityLib.Fraction; @@ -196,7 +198,7 @@ contract Election is uint256 value, address lesser, address greater - ) external nonReentrant returns (bool) { + ) external nonReentrant onlyWhenNotBlocked returns (bool) { require(votes.total.eligible.contains(group), "Group not eligible"); require(0 < value, "Vote value cannot be zero"); require(canReceiveVotes(group, value), "Group cannot receive votes"); @@ -948,7 +950,7 @@ contract Election is emit EpochRewardsDistributedToVoters(group, value); } - function _activate(address group, address account) internal returns (bool) { + function _activate(address group, address account) internal onlyWhenNotBlocked returns (bool) { PendingVote storage pendingVote = votes.pending.forGroup[group].byAccount[account]; require(pendingVote.epoch < getEpochNumber(), "Pending vote epoch not passed"); uint256 value = pendingVote.value; @@ -965,7 +967,7 @@ contract Election is address lesser, address greater, uint256 index - ) internal returns (bool) { + ) internal onlyWhenNotBlocked returns (bool) { // TODO(asa): Dedup with revokePending. require(group != address(0), "Group address zero"); address account = getAccounts().voteSignerToAccount(msg.sender); @@ -1006,7 +1008,7 @@ contract Election is address lesser, address greater, uint256 index - ) internal returns (uint256) { + ) internal onlyWhenNotBlocked returns (uint256) { uint256 remainingValue = maxValue; uint256 pendingVotes = getPendingVotesForGroupByAccount(group, account); if (pendingVotes > 0) { diff --git a/packages/protocol/contracts/governance/LockedGold.sol b/packages/protocol/contracts/governance/LockedGold.sol index 4d2db3449b2..73cf04bbf1d 100755 --- a/packages/protocol/contracts/governance/LockedGold.sol +++ b/packages/protocol/contracts/governance/LockedGold.sol @@ -7,6 +7,7 @@ import "openzeppelin-solidity/contracts/utils/Address.sol"; import "openzeppelin-solidity/contracts/utils/EnumerableSet.sol"; import "./interfaces/ILockedGold.sol"; +import "./interfaces/ILockedGoldInitializer.sol"; import "../common/FixidityLib.sol"; import "../common/Initializable.sol"; @@ -14,15 +15,17 @@ import "../common/Signatures.sol"; import "../common/UsingRegistry.sol"; import "../common/interfaces/ICeloVersionedContract.sol"; import "../common/libraries/ReentrancyGuard.sol"; +import "../common/Blockable.sol"; contract LockedGold is ILockedGold, + ILockedGoldInitializer, ICeloVersionedContract, ReentrancyGuard, Initializable, - UsingRegistry + UsingRegistry, + Blockable { - // TODO add initializer using SafeMath for uint256; using Address for address payable; // prettier-ignore using FixidityLib for FixidityLib.Fraction; @@ -469,7 +472,7 @@ contract LockedGold is address[] calldata lessers, address[] calldata greaters, uint256[] calldata indices - ) external onlySlasher { + ) external onlySlasher onlyWhenNotBlocked { uint256 maxSlash = Math.min(penalty, getAccountTotalLockedGold(account)); require(maxSlash >= reward, "reward cannot exceed penalty."); // `reporter` receives the reward in locked CELO, so it must be given to an account diff --git a/packages/protocol/test-sol/unit/common/Blockable.t.sol b/packages/protocol/test-sol/unit/common/Blockable.t.sol new file mode 100644 index 00000000000..e5afb27f0c0 --- /dev/null +++ b/packages/protocol/test-sol/unit/common/Blockable.t.sol @@ -0,0 +1,91 @@ +pragma solidity ^0.5.13; + +import "celo-foundry/Test.sol"; + +import "@celo-contracts/common/Blockable.sol"; +import "@celo-contracts/common/interfaces/IBlockable.sol"; +import "@celo-contracts/common/interfaces/IBlocker.sol"; + +contract TestBlocker is IBlocker { + bool public blocked; + + function mockSetBlocked(bool _blocked) public { + blocked = _blocked; + } + + function isBlocked() external view returns (bool) { + return blocked; + } +} + +contract TestBlockable is Blockable { + function functionToBeBlocked() public onlyWhenNotBlocked { + return; + } +} + +contract BlockableTest is Test { + IBlockable blockable; + TestBlocker blocker; + address notOwner; + + event BlockedBySet(address indexed _blockedBy); + + function setUp() public { + blockable = new Blockable(); + blocker = new TestBlocker(); + notOwner = actor("notOwner"); + } +} + +contract BlockableTest_setBlockable is BlockableTest { + function test_setBlockable() public { + blockable.setBlockedByContract(address(blocker)); + assert(blockable.getBlockedbyContract() == address(blocker)); + } + + function test_Reverts_WhenNotCalledByOwner() public { + vm.prank(notOwner); + vm.expectRevert("Ownable: caller is not the owner"); + blockable.setBlockedByContract(address(blocker)); + } + + function test_Emits_BlockedBySet() public { + vm.expectEmit(false, false, false, true); + emit BlockedBySet(address(blocker)); + blockable.setBlockedByContract(address(blocker)); + } +} + +contract BlockableTest_isBlocked is BlockableTest { + function test_isFalse_WhenBlockableNotSet() public { + assert(blockable.isBlocked() == false); + } + + function test_isBlocked() public { + assertTrue(blockable.isBlocked() == false); + blocker.mockSetBlocked(true); + blockable.setBlockedByContract(address(blocker)); + assertTrue(blockable.isBlocked()); + } +} + +contract BlockableTest_onlyWhenNotBlocked is BlockableTest { + TestBlockable blockableWithFunction; + + function setUp() public { + super.setUp(); + blockableWithFunction = new TestBlockable(); + blockableWithFunction.setBlockedByContract(address(blocker)); + } + + function test_Reverts_WhenBlocked() public { + blocker.mockSetBlocked(true); + vm.expectRevert("Contract is blocked from performing this action"); + blockableWithFunction.functionToBeBlocked(); + } + + function test_callsucceeds_WhenNotBlocked() public { + blockableWithFunction.functionToBeBlocked(); + } +} diff --git a/packages/protocol/test-sol/unit/governance/voting/Election.t.sol b/packages/protocol/test-sol/unit/governance/voting/Election.t.sol index 7d68cd8b7db..09026ab6d21 100644 --- a/packages/protocol/test-sol/unit/governance/voting/Election.t.sol +++ b/packages/protocol/test-sol/unit/governance/voting/Election.t.sol @@ -15,6 +15,8 @@ import "@celo-contracts/common/linkedlists/AddressSortedLinkedList.sol"; import "@celo-contracts/identity/test/MockRandom.sol"; import "@celo-contracts/common/Freezer.sol"; +import { TestBlocker } from "@test-sol/unit/common/Blockable.t.sol"; + contract ElectionMock is Election(true) { function distributeEpochRewards( address group, @@ -57,6 +59,8 @@ contract ElectionTest is Utils, TestConstants { address[] accountsArray; + TestBlocker blocker; + event ElectableValidatorsSet(uint256 min, uint256 max); event MaxNumGroupsVotedForSet(uint256 maxNumGroupsVotedFor); event ElectabilityThresholdSet(uint256 electabilityThreshold); @@ -146,6 +150,9 @@ contract ElectionTest is Utils, TestConstants { maxNumGroupsVotedFor, electabilityThreshold ); + + blocker = new TestBlocker(); + election.setBlockedByContract(address(blocker)); } function _whenL2() public { @@ -601,6 +608,16 @@ contract ElectionTest_Vote_WhenGroupEligible is ElectionTest { assertEq(election.getPendingVotesForGroupByAccount(group, voter), value - maxNumGroupsVotedFor); } + function test_Reverts_WhenBlocked_WhenTheVoterIsOverMaxNumberGroupsVotedForButCanVoteForAdditionalGroup() + public + { + address newGroup = WhenVotedForMaxNumberOfGroups(); + election.setAllowedToVoteOverMaxNumberOfGroups(true); + blocker.mockSetBlocked(true); + vm.expectRevert("Contract is blocked from performing this action"); + election.vote(group, value - maxNumGroupsVotedFor, newGroup, address(0)); + } + function test_ShouldSetTotalVotesByAccount_WhenMaxNumberOfGroupsWasNotReached() public { WhenVotedForMaxNumberOfGroups(); assertEq(election.getTotalVotesByAccount(voter), maxNumGroupsVotedFor); @@ -1183,6 +1200,15 @@ contract ElectionTest_Activate is ElectionTest { election.activate(group); } + function test_Reverts_WhenBlocked() public { + WhenVoterHasPendingVotes(); + blockTravel(ph.epochSize() + 1); + + blocker.mockSetBlocked(true); + vm.expectRevert("Contract is blocked from performing this action"); + election.activate(group); + } + function WhenAnotherVoterActivatesVotes() public { WhenEpochBoundaryHasPassed(); lockedGold.incrementNonvotingAccountBalance(voter2, value2); @@ -2012,6 +2038,12 @@ contract ElectionTest_RevokeActive is ElectionTest { election.revokeActive(group, revokedValue, address(0), address(0), 0); } + function test_Reverts_WhenBlocked() public { + blocker.mockSetBlocked(true); + vm.expectRevert("Contract is blocked from performing this action"); + election.revokeAllActive(group, address(0), address(0), 0); + } + function WhenRevokedValueIsLessThanTheActiveVotesButGroupIsEligible() public { election.revokeActive(group, revokedValue, address(0), address(0), 0); } @@ -2715,10 +2747,6 @@ contract ElectionTest_ForceDecrementVotes is ElectionTest { uint256 group1RemainingActiveVotes; address[] initialOrdering; - function setUp() public { - super.setUp(); - } - function WhenAccountHasVotedForOneGroup() public { address[] memory membersGroup = new address[](1); membersGroup[0] = account8; @@ -2749,6 +2777,21 @@ contract ElectionTest_ForceDecrementVotes is ElectionTest { election.forceDecrementVotes(voter, slashedValue, lessers, greaters, indices); } + function test_Reverts_WhenBlocked() public { + WhenAccountHasVotedForOneGroup(); + address[] memory lessers = new address[](1); + lessers[0] = address(0); + address[] memory greaters = new address[](1); + greaters[0] = address(0); + uint256[] memory indices = new uint256[](1); + indices[0] = index; + + blocker.mockSetBlocked(true); + vm.prank(account2); + vm.expectRevert("Contract is blocked from performing this action"); + election.forceDecrementVotes(voter, slashedValue, lessers, greaters, indices); + } + function test_ShouldDecrementPendingVotesToZero_WhenAccountHasOnlyPendingVotes() public { WhenAccountHasOnlyPendingVotes(); assertEq(election.getPendingVotesForGroupByAccount(group, voter), remaining); diff --git a/packages/protocol/test-sol/unit/governance/voting/LockedGold.t.sol b/packages/protocol/test-sol/unit/governance/voting/LockedGold.t.sol index bf1ca263b24..8121f86de2e 100644 --- a/packages/protocol/test-sol/unit/governance/voting/LockedGold.t.sol +++ b/packages/protocol/test-sol/unit/governance/voting/LockedGold.t.sol @@ -17,6 +17,8 @@ import "@celo-contracts/governance/test/MockElection.sol"; import "@celo-contracts/governance/test/MockGovernance.sol"; import "@celo-contracts/governance/test/MockValidators.sol"; +import { TestBlocker } from "@test-sol/unit/common/Blockable.t.sol"; + contract LockedGoldTest is Test, TestConstants { using FixidityLib for FixidityLib.Fraction; @@ -36,6 +38,7 @@ contract LockedGoldTest is Test, TestConstants { address randomAddress = actor("randomAddress"); address caller = address(this); + TestBlocker blocker; event UnlockingPeriodSet(uint256 period); event GoldLocked(address indexed account, uint256 value); @@ -84,6 +87,10 @@ contract LockedGoldTest is Test, TestConstants { registry.setAddressFor("StableToken", address(stableToken)); lockedGold.initialize(address(registry), unlockingPeriod); accounts.createAccount(); + + blocker = new TestBlocker(); + + lockedGold.setBlockedByContract(address(blocker)); } function getParsedSignatureOfAddress( @@ -1109,6 +1116,14 @@ contract LockedGoldTest_slash is LockedGoldTest { lockedGold.slash(caller, penalty, reporter, reward, lessers, greaters, indices); } + function test_Reverts_WhenBlocked() public { + uint256 penalty = value; + uint256 reward = value / 2; + blocker.mockSetBlocked(true); + vm.expectRevert("Contract is blocked from performing this action"); + helper_WhenAccountIsSlashedForAllOfItsLockedGold(penalty, reward); + } + function test_ShouldReduceAccountsLockedGoldBalance_WhenAccountIsSlashedForAllOfItsLockedGold() public { From 7979ffbe556abab9b5e2937550c4c68b3a2d711c Mon Sep 17 00:00:00 2001 From: pahor167 Date: Fri, 23 Aug 2024 15:25:25 +0200 Subject: [PATCH 39/59] add EpochManager to abis --- packages/protocol/scripts/consts.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/protocol/scripts/consts.ts b/packages/protocol/scripts/consts.ts index bb435ac401d..502a4ce7306 100644 --- a/packages/protocol/scripts/consts.ts +++ b/packages/protocol/scripts/consts.ts @@ -57,6 +57,7 @@ export const CoreContracts = [ // governance 'Election', 'EpochRewards', + 'EpochManager', 'Governance', 'GovernanceApproverMultiSig', 'BlockchainParameters', From 9403236e7adbc130bda88708f47290022bba3ee1 Mon Sep 17 00:00:00 2001 From: pahor167 Date: Fri, 23 Aug 2024 16:00:54 +0200 Subject: [PATCH 40/59] force deploy abis --- packages/protocol/scripts/determine-release-version.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/protocol/scripts/determine-release-version.ts b/packages/protocol/scripts/determine-release-version.ts index 08cfa53df44..40a2ff0937c 100644 --- a/packages/protocol/scripts/determine-release-version.ts +++ b/packages/protocol/scripts/determine-release-version.ts @@ -13,7 +13,7 @@ const branchName = execSync('git branch --show-current').toString().trim() // if not on a release branch a dry-run will be done unless an NPM_TAG is provided // in which case we will try to fetch the last published version with that tag and bump or use the canary to get major and start versioning from there the new tag at 0 // (e.g. `@celo/contracts@11.0.0@custom-tag.0`) -const nextVersion = determineNextVersion(gitTag, branchName, npmPackage, npmTag) +const nextVersion = determineNextVersion(gitTag, branchName, npmPackage, "epoch-manager") if (nextVersion === null) { // dry-run will build the package but not publish it From b63e4872fc6d4babd6e3e9b80d107539aaf16f5d Mon Sep 17 00:00:00 2001 From: pahor167 Date: Fri, 23 Aug 2024 16:11:20 +0200 Subject: [PATCH 41/59] build fix --- packages/protocol/scripts/determine-release-version.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/protocol/scripts/determine-release-version.ts b/packages/protocol/scripts/determine-release-version.ts index 40a2ff0937c..1c2eb313d23 100644 --- a/packages/protocol/scripts/determine-release-version.ts +++ b/packages/protocol/scripts/determine-release-version.ts @@ -2,7 +2,7 @@ import { execSync } from 'child_process' import { determineNextVersion, getReleaseTypeFromSemVer } from './utils' const npmPackage = process.env.NPM_PACKAGE?.trim() || '' -const npmTag = process.env.NPM_TAG?.trim() || '' +// const npmTag = process.env.NPM_TAG?.trim() || '' const gitTag = process.env.GITHUB_TAG || '' const branchName = execSync('git branch --show-current').toString().trim() From 96eb8878c554f10259662a31f44ab0fd364de0ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Volpe?= Date: Mon, 26 Aug 2024 04:29:25 -0700 Subject: [PATCH 42/59] Move Validators.sol to 0.8 (#11192) * WIP * Validator test WIP, forge doesn't compile yet * buildable * most of the foundry tests working * Validator related tests fixed * truffle build working * cache bump * lint + prettify+ migrations * Enable optimization for Solidity 0.8 * prettify * Ci bump * CI bump 2 * Validators to 0.8 config * CI bump * foundry fix * Truffle migrations are partly fixed * Added import for ValidatorsMock08 in EpochManager.t.sol, I think it was removed by mistake * Added yarn.lock * Changes to mock with full implementation * Attempt to fix linking libraries not working with deployCodeTo https://github.com/foundry-rs/foundry/issues/4049 * truffle migrations fixed * CI bump * lint * forge test fixes * artifacts test fix * lint * update of foundry version * add ProxyFactory import to tests * library linking fix * Foundry migrations fix * migration tests fix * CI bump * Little cleanup + retrigger CI * forgot to commit Validators.sol * Fixed the ABI encoded * lint * Fix contract versions * add Adapter to ignored contracts * revert of ReentrancyGuard change * lint fix * remove adapters from check * storage layout fix --------- Co-authored-by: pahor167 --- .github/workflows/celo-monorepo.yml | 2 +- .github/workflows/protocol-devchain-anvil.yml | 2 +- .github/workflows/protocol_tests.yml | 4 +- .gitmodules | 4 + packages/protocol/contractPackages.ts | 3 +- .../contracts-0.8/common/UsingPrecompiles.sol | 166 +++++++++++- .../common/interfaces/IPrecompiles.sol | 7 + .../common/linkedlists/AddressLinkedList.sol | 106 ++++++++ .../governance/Validators.sol | 83 +++--- .../governance/test/ValidatorsMock.sol | 228 +--------------- .../governance/test/ValidatorsMock08.sol | 250 ++++++++++++++++++ .../protocol/contracts/common/Blockable.sol | 32 +-- .../common/libraries/ReentrancyGuard.sol | 4 +- .../contracts/governance/Election.sol | 7 + .../contracts/governance/LockedGold.sol | 7 + .../governance/interfaces/IValidators.sol | 5 + .../interfaces/IValidatorsInitializer.sol | 12 +- .../governance/test/ValidatorsMock.sol | 20 -- packages/protocol/foundry.toml | 3 +- packages/protocol/governanceConstitution.js | 1 + packages/protocol/lib/artifactsSingleton.ts | 39 ++- .../lib/compatibility/verify-bytecode.ts | 7 +- packages/protocol/lib/solidity-bytes-utils-8 | 1 + packages/protocol/lib/web3-utils.ts | 13 +- packages/protocol/migrationsConfig.js | 2 +- .../protocol/migrations_sol/Migration.s.sol | 14 +- .../protocol/migrations_ts/01_libraries.ts | 15 +- .../protocol/migrations_ts/13_validators.ts | 13 +- .../protocol/migrations_ts/29_governance.ts | 3 +- .../migrations_ts/30_elect_validators.ts | 9 +- packages/protocol/package.json | 3 +- packages/protocol/remappings.txt | 2 +- .../scripts/bash/contract-exclusion-regex.sh | 2 +- .../scripts/determine-release-version.ts | 2 +- .../protocol/scripts/foundry/constants.sh | 4 +- .../test-sol/devchain/migration/05Links.sol | 26 ++ .../devchain/migration/Migration.t.sol | 3 +- .../RevokeCeloAfterL2Transition.sol | 15 +- .../test-sol/unit/common/Blockable.t.sol | 11 +- .../test-sol/unit/common/EpochManager.t.sol | 2 +- .../test-sol/unit/common/FeeHandler.t.sol | 2 + .../test-sol/unit/common/ProxyFactory08.t.sol | 26 +- .../governance/validators/Validators.t.sol | 92 ++++--- .../validators/mocks/ValidatorsMockTunnel.sol | 22 +- packages/protocol/truffle-config0.8.js | 4 + remappings.txt | 1 + yarn.lock | 48 ++-- 47 files changed, 891 insertions(+), 436 deletions(-) create mode 100644 packages/protocol/contracts-0.8/common/interfaces/IPrecompiles.sol create mode 100644 packages/protocol/contracts-0.8/common/linkedlists/AddressLinkedList.sol rename packages/protocol/{contracts => contracts-0.8}/governance/Validators.sol (96%) create mode 100644 packages/protocol/contracts-0.8/governance/test/ValidatorsMock08.sol delete mode 100644 packages/protocol/contracts/governance/test/ValidatorsMock.sol create mode 160000 packages/protocol/lib/solidity-bytes-utils-8 create mode 100644 packages/protocol/test-sol/devchain/migration/05Links.sol diff --git a/.github/workflows/celo-monorepo.yml b/.github/workflows/celo-monorepo.yml index e13ecff921a..a60af96775e 100644 --- a/.github/workflows/celo-monorepo.yml +++ b/.github/workflows/celo-monorepo.yml @@ -32,7 +32,7 @@ defaults: env: # Increment these to force cache rebuilding - NODE_MODULE_CACHE_VERSION: 7 + NODE_MODULE_CACHE_VERSION: 8 NODE_OPTIONS: '--max-old-space-size=4096' TERM: dumb GRADLE_OPTS: '-Dorg.gradle.daemon=false -Dorg.gradle.parallel=false -Dorg.gradle.configureondemand=true -Dorg.gradle.jvmargs="-Xmx4096m -XX:+HeapDumpOnOutOfMemoryError"' diff --git a/.github/workflows/protocol-devchain-anvil.yml b/.github/workflows/protocol-devchain-anvil.yml index 7e9461089e7..483a5dcb6d6 100644 --- a/.github/workflows/protocol-devchain-anvil.yml +++ b/.github/workflows/protocol-devchain-anvil.yml @@ -92,7 +92,7 @@ jobs: - name: Install Foundry uses: foundry-rs/foundry-toolchain@v1 with: - version: ${{ env.SUPPORTED_FOUNDRY_VERSION }} + version: 'nightly-fa0e0c2ca3ae75895dd19173a02faf88509c0608' - name: Install forge dependencies run: forge install diff --git a/.github/workflows/protocol_tests.yml b/.github/workflows/protocol_tests.yml index 2e5fd6d1b1e..33d276b5dff 100644 --- a/.github/workflows/protocol_tests.yml +++ b/.github/workflows/protocol_tests.yml @@ -62,7 +62,7 @@ jobs: - name: Install Foundry uses: foundry-rs/foundry-toolchain@v1 with: - version: ${{ env.SUPPORTED_FOUNDRY_VERSION }} + version: 'nightly-fa0e0c2ca3ae75895dd19173a02faf88509c0608' - name: Install forge dependencies run: forge install @@ -153,4 +153,4 @@ jobs: FOUNDRY_PROFILE=devchain forge test -vvv \ --match-path "test-sol/devchain/e2e/*" \ - --fork-url $ANVIL_RPC_URL \ No newline at end of file + --fork-url $ANVIL_RPC_URL diff --git a/.gitmodules b/.gitmodules index 5d63bcd6e59..f3440efa785 100644 --- a/.gitmodules +++ b/.gitmodules @@ -20,3 +20,7 @@ path = packages/protocol/lib/celo-foundry url = https://github.com/celo-org/celo-foundry branch = celo-foundry-v0.5.13 +[submodule "packages/protocol/lib/solidity-bytes-utils-8"] + path = packages/protocol/lib/solidity-bytes-utils-8 + url = https://github.com/GNSPS/solidity-bytes-utils + branch = master diff --git a/packages/protocol/contractPackages.ts b/packages/protocol/contractPackages.ts index 696d2fa932e..177ec1b6baa 100644 --- a/packages/protocol/contractPackages.ts +++ b/packages/protocol/contractPackages.ts @@ -48,12 +48,13 @@ 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'], + contracts: ['GasPriceMinimum', 'FeeCurrencyDirectory', 'CeloUnreleasedTreasure', 'Validators'], proxyContracts: [ 'GasPriceMinimumProxy', 'FeeCurrencyDirectoryProxy', 'MentoFeeCurrencyAdapterV1', 'CeloUnreleasedTreasureProxy', + 'ValidatorsProxy', ], truffleConfig: 'truffle-config0.8.js', } satisfies ContractPackage diff --git a/packages/protocol/contracts-0.8/common/UsingPrecompiles.sol b/packages/protocol/contracts-0.8/common/UsingPrecompiles.sol index 84ed167c520..7904bb56cd8 100644 --- a/packages/protocol/contracts-0.8/common/UsingPrecompiles.sol +++ b/packages/protocol/contracts-0.8/common/UsingPrecompiles.sol @@ -21,6 +21,39 @@ contract UsingPrecompiles is IsL2Check { address constant GET_VERIFIED_SEAL_BITMAP = address(0xff - 11); uint256 constant DAY = 86400; + /** + * @notice calculate a * b^x for fractions a, b to `decimals` precision + * @param aNumerator Numerator of first fraction + * @param aDenominator Denominator of first fraction + * @param bNumerator Numerator of exponentiated fraction + * @param bDenominator Denominator of exponentiated fraction + * @param exponent exponent to raise b to + * @param _decimals precision + * @return Numerator of the computed quantity (not reduced). + * @return Denominator of the computed quantity (not reduced). + */ + function fractionMulExp( + uint256 aNumerator, + uint256 aDenominator, + uint256 bNumerator, + uint256 bDenominator, + uint256 exponent, + uint256 _decimals + ) public view returns (uint256, uint256) { + require(aDenominator != 0 && bDenominator != 0, "a denominator is zero"); + uint256 returnNumerator; + uint256 returnDenominator; + bool success; + bytes memory out; + (success, out) = FRACTION_MUL.staticcall( + abi.encodePacked(aNumerator, aDenominator, bNumerator, bDenominator, exponent, _decimals) + ); + require(success, "error calling fractionMulExp precompile"); + returnNumerator = getUint256FromBytes(out, 0); + returnDenominator = getUint256FromBytes(out, 32); + return (returnNumerator, returnDenominator); + } + /** * @notice Returns the current epoch size in blocks. * @return The current epoch size in blocks. @@ -54,6 +87,36 @@ contract UsingPrecompiles is IsL2Check { return getEpochNumberOfBlock(block.number); } + /** + * @notice Gets a validator address from the current validator set. + * @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) { + bytes memory out; + bool success; + (success, out) = GET_VALIDATOR.staticcall(abi.encodePacked(index, uint256(block.number))); + require(success, "error calling validatorSignerAddressFromCurrentSet precompile"); + return address(uint160(getUint256FromBytes(out, 0))); + } + + /** + * @notice Gets a validator address from the validator set at the given block number. + * @param index Index of requested validator in the validator set. + * @param blockNumber Block number to retrieve the validator set from. + * @return Address of validator at the requested index. + */ + function validatorSignerAddressFromSet( + uint256 index, + uint256 blockNumber + ) public view returns (address) { + bytes memory out; + bool success; + (success, out) = GET_VALIDATOR.staticcall(abi.encodePacked(index, blockNumber)); + require(success, "error calling validatorSignerAddressFromSet precompile"); + return address(uint160(getUint256FromBytes(out, 0))); + } + /** * @notice Gets the size of the current elected validator set. * @return Size of the current elected validator set. @@ -67,16 +130,105 @@ contract UsingPrecompiles is IsL2Check { } /** - * @notice Gets a validator address from the current validator set. - * @param index Index of requested validator in the validator set. - * @return Address of validator at the requested index. + * @notice Gets the size of the validator set that must sign the given block number. + * @param blockNumber Block number to retrieve the validator set from. + * @return Size of the validator set. */ - function validatorSignerAddressFromCurrentSet(uint256 index) public view returns (address) { + function numberValidatorsInSet(uint256 blockNumber) public view returns (uint256) { bytes memory out; bool success; - (success, out) = GET_VALIDATOR.staticcall(abi.encodePacked(index, uint256(block.number))); - require(success, "error calling validatorSignerAddressFromCurrentSet precompile"); - return address(uint160(getUint256FromBytes(out, 0))); + (success, out) = NUMBER_VALIDATORS.staticcall(abi.encodePacked(blockNumber)); + require(success, "error calling numberValidatorsInSet precompile"); + return getUint256FromBytes(out, 0); + } + + /** + * @notice Checks a BLS proof of possession. + * @param sender The address signed by the BLS key to generate the proof of possession. + * @param blsKey The BLS public key that the validator is using for consensus, should pass proof + * of possession. 48 bytes. + * @param blsPop The BLS public key proof-of-possession, which consists of a signature on the + * account address. 96 bytes. + * @return True upon success. + */ + function checkProofOfPossession( + address sender, + bytes memory blsKey, + bytes memory blsPop + ) public view returns (bool) { + bool success; + (success, ) = PROOF_OF_POSSESSION.staticcall(abi.encodePacked(sender, blsKey, blsPop)); + return success; + } + + /** + * @notice Parses block number out of header. + * @param header RLP encoded header + * @return Block number. + */ + function getBlockNumberFromHeader(bytes memory header) public view returns (uint256) { + bytes memory out; + bool success; + (success, out) = BLOCK_NUMBER_FROM_HEADER.staticcall(abi.encodePacked(header)); + require(success, "error calling getBlockNumberFromHeader precompile"); + return getUint256FromBytes(out, 0); + } + + /** + * @notice Computes hash of header. + * @param header RLP encoded header + * @return Header hash. + */ + function hashHeader(bytes memory header) public view returns (bytes32) { + bytes memory out; + bool success; + (success, out) = HASH_HEADER.staticcall(abi.encodePacked(header)); + require(success, "error calling hashHeader precompile"); + return getBytes32FromBytes(out, 0); + } + + /** + * @notice Gets the parent seal bitmap from the header at the given block number. + * @param blockNumber Block number to retrieve. Must be within 4 epochs of the current number. + * @return Bitmap parent seal with set bits at indices corresponding to signing validators. + */ + function getParentSealBitmap(uint256 blockNumber) public view returns (bytes32) { + bytes memory out; + bool success; + (success, out) = GET_PARENT_SEAL_BITMAP.staticcall(abi.encodePacked(blockNumber)); + require(success, "error calling getParentSealBitmap precompile"); + return getBytes32FromBytes(out, 0); + } + + /** + * @notice Verifies the BLS signature on the header and returns the seal bitmap. + * The validator set used for verification is retrieved based on the parent hash field of the + * header. If the parent hash is not in the blockchain, verification fails. + * @param header RLP encoded header + * @return Bitmap parent seal with set bits at indices correspoinding to signing validators. + */ + function getVerifiedSealBitmapFromHeader(bytes memory header) public view returns (bytes32) { + bytes memory out; + bool success; + (success, out) = GET_VERIFIED_SEAL_BITMAP.staticcall(abi.encodePacked(header)); + require(success, "error calling getVerifiedSealBitmapFromHeader precompile"); + return getBytes32FromBytes(out, 0); + } + + /** + * @notice Returns the minimum number of required signers for a given block number. + * @dev Computed in celo-blockchain as int(math.Ceil(float64(2*valSet.Size()) / 3)) + */ + function minQuorumSize(uint256 blockNumber) public view returns (uint256) { + return numberValidatorsInSet(blockNumber).mul(2).add(2).div(3); + } + + /** + * @notice Computes byzantine quorum from current validator set size + * @return Byzantine quorum of validators. + */ + function minQuorumSizeInCurrentSet() public view returns (uint256) { + return minQuorumSize(block.number); } /** diff --git a/packages/protocol/contracts-0.8/common/interfaces/IPrecompiles.sol b/packages/protocol/contracts-0.8/common/interfaces/IPrecompiles.sol new file mode 100644 index 00000000000..cf36e67ee4b --- /dev/null +++ b/packages/protocol/contracts-0.8/common/interfaces/IPrecompiles.sol @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.5.13 <0.9.0; + +interface IPrecompiles { + function getEpochSize() external view returns (uint256); + function getEpochNumber() external view returns (uint256); +} diff --git a/packages/protocol/contracts-0.8/common/linkedlists/AddressLinkedList.sol b/packages/protocol/contracts-0.8/common/linkedlists/AddressLinkedList.sol new file mode 100644 index 00000000000..10d7988372e --- /dev/null +++ b/packages/protocol/contracts-0.8/common/linkedlists/AddressLinkedList.sol @@ -0,0 +1,106 @@ +pragma solidity >=0.8.0 <0.8.20; + +import "@openzeppelin/contracts8/utils/math/SafeMath.sol"; + +import "./LinkedList.sol"; + +/** + * @title Maintains a doubly linked list keyed by address. + * @dev Following the `next` pointers will lead you to the head, rather than the tail. + */ +library AddressLinkedList { + using LinkedList for LinkedList.List; + using SafeMath for uint256; + /** + * @notice Inserts an element into a doubly linked list. + * @param list A storage pointer to the underlying list. + * @param key The key of the element to insert. + * @param previousKey The key of the element that comes before the element to insert. + * @param nextKey The key of the element that comes after the element to insert. + */ + function insert( + LinkedList.List storage list, + address key, + address previousKey, + address nextKey + ) public { + list.insert(toBytes(key), toBytes(previousKey), toBytes(nextKey)); + } + + /** + * @notice Inserts an element at the end of the doubly linked list. + * @param list A storage pointer to the underlying list. + * @param key The key of the element to insert. + */ + function push(LinkedList.List storage list, address key) public { + list.insert(toBytes(key), bytes32(0), list.tail); + } + + /** + * @notice Removes an element from the doubly linked list. + * @param list A storage pointer to the underlying list. + * @param key The key of the element to remove. + */ + function remove(LinkedList.List storage list, address key) public { + list.remove(toBytes(key)); + } + + /** + * @notice Updates an element in the list. + * @param list A storage pointer to the underlying list. + * @param key The element key. + * @param previousKey The key of the element that comes before the updated element. + * @param nextKey The key of the element that comes after the updated element. + */ + function update( + LinkedList.List storage list, + address key, + address previousKey, + address nextKey + ) public { + list.update(toBytes(key), toBytes(previousKey), toBytes(nextKey)); + } + + /** + * @notice Returns whether or not a particular key is present in the sorted list. + * @param list A storage pointer to the underlying list. + * @param key The element key. + * @return Whether or not the key is in the sorted list. + */ + function contains(LinkedList.List storage list, address key) public view returns (bool) { + return list.elements[toBytes(key)].exists; + } + + /** + * @notice Returns the N greatest elements of the list. + * @param list A storage pointer to the underlying list. + * @param n The number of elements to return. + * @return The keys of the greatest elements. + * @dev Reverts if n is greater than the number of elements in the list. + */ + function headN(LinkedList.List storage list, uint256 n) public view returns (address[] memory) { + bytes32[] memory byteKeys = list.headN(n); + address[] memory keys = new address[](n); + for (uint256 i = 0; i < n; i = i.add(1)) { + keys[i] = toAddress(byteKeys[i]); + } + return keys; + } + + /** + * @notice Gets all element keys from the doubly linked list. + * @param list A storage pointer to the underlying list. + * @return All element keys from head to tail. + */ + function getKeys(LinkedList.List storage list) public view returns (address[] memory) { + return headN(list, list.numElements); + } + + function toBytes(address a) public pure returns (bytes32) { + return bytes32(uint256(uint160(a)) << 96); + } + + function toAddress(bytes32 b) public pure returns (address) { + return address(uint160(uint256(b) >> 96)); + } +} diff --git a/packages/protocol/contracts/governance/Validators.sol b/packages/protocol/contracts-0.8/governance/Validators.sol similarity index 96% rename from packages/protocol/contracts/governance/Validators.sol rename to packages/protocol/contracts-0.8/governance/Validators.sol index b93e74e85f4..9fe8fe5b15d 100644 --- a/packages/protocol/contracts/governance/Validators.sol +++ b/packages/protocol/contracts-0.8/governance/Validators.sol @@ -1,20 +1,22 @@ -pragma solidity ^0.5.13; +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity >=0.8.7 <0.8.20; -import "openzeppelin-solidity/contracts/math/Math.sol"; -import "openzeppelin-solidity/contracts/math/SafeMath.sol"; -import "openzeppelin-solidity/contracts/ownership/Ownable.sol"; -import "solidity-bytes-utils/contracts/BytesLib.sol"; +import "@openzeppelin/contracts8/access/Ownable.sol"; +import "@openzeppelin/contracts8/utils/math/Math.sol"; +import "@openzeppelin/contracts8/utils/math/SafeMath.sol"; +import "solidity-bytes-utils-8/contracts/BytesLib.sol"; -import "./interfaces/IValidators.sol"; +import "../../contracts/governance/interfaces/IValidators.sol"; -import "../common/CalledByVm.sol"; -import "../common/Initializable.sol"; -import "../common/FixidityLib.sol"; +import "../../contracts/common/CalledByVm.sol"; +import "../../contracts/common/Initializable.sol"; +import "../../contracts/common/FixidityLib.sol"; import "../common/linkedlists/AddressLinkedList.sol"; import "../common/UsingRegistry.sol"; import "../common/UsingPrecompiles.sol"; -import "../common/interfaces/ICeloVersionedContract.sol"; -import "../common/libraries/ReentrancyGuard.sol"; +import "../../contracts/common/interfaces/ICeloVersionedContract.sol"; +import "../../contracts/common/libraries/ReentrancyGuard.sol"; +import "../common/interfaces/IStableToken.sol"; /** * @title A contract for registering and electing Validator Groups and Validators. @@ -108,6 +110,12 @@ contract Validators is FixidityLib.Fraction adjustmentSpeed; } + struct InitParams { + // The number of blocks to delay a ValidatorGroup's commission + uint256 commissionUpdateDelay; + uint256 downtimeGracePeriod; + } + mapping(address => ValidatorGroup) private groups; mapping(address => Validator) private validators; address[] private registeredGroups; @@ -175,7 +183,6 @@ contract Validators is * @param validatorScoreAdjustmentSpeed The speed at which validator scores are adjusted. * @param _membershipHistoryLength The max number of entries for validator membership history. * @param _maxGroupSize The maximum group size. - * @param _commissionUpdateDelay The number of blocks to delay a ValidatorGroup's commission * update. * @dev Should be called only once. */ @@ -190,8 +197,7 @@ contract Validators is uint256 _membershipHistoryLength, uint256 _slashingMultiplierResetPeriod, uint256 _maxGroupSize, - uint256 _commissionUpdateDelay, - uint256 _downtimeGracePeriod + InitParams calldata initParams ) external initializer { _transferOwnership(msg.sender); setRegistry(registryAddress); @@ -199,19 +205,18 @@ contract Validators is setValidatorLockedGoldRequirements(validatorRequirementValue, validatorRequirementDuration); setValidatorScoreParameters(validatorScoreExponent, validatorScoreAdjustmentSpeed); setMaxGroupSize(_maxGroupSize); - setCommissionUpdateDelay(_commissionUpdateDelay); + setCommissionUpdateDelay(initParams.commissionUpdateDelay); setMembershipHistoryLength(_membershipHistoryLength); setSlashingMultiplierResetPeriod(_slashingMultiplierResetPeriod); - setDowntimeGracePeriod(_downtimeGracePeriod); + setDowntimeGracePeriod(initParams.downtimeGracePeriod); } /** * @notice Updates a validator's score based on its uptime for the epoch. * @param signer The validator signer of the validator account whose score needs updating. * @param uptime The Fixidity representation of the validator's uptime, between 0 and 1. - * @return True upon success. */ - function updateValidatorScoreFromSigner(address signer, uint256 uptime) external onlyVm { + function updateValidatorScoreFromSigner(address signer, uint256 uptime) external virtual onlyVm { allowOnlyL1(); _updateValidatorScoreFromSigner(signer, uptime); } @@ -221,12 +226,12 @@ contract Validators is * @param signer The validator signer of the account to distribute the epoch payment to. * @param maxPayment The maximum payment to the validator. Actual payment is based on score and * group commission. - * @return The total payment paid to the validator and their group. + * @return distributeEpochPaymentsFromSigner The total payment paid to the validator and their group. */ function distributeEpochPaymentsFromSigner( address signer, uint256 maxPayment - ) external onlyVm returns (uint256) { + ) external virtual onlyVm returns (uint256) { allowOnlyL1(); return _distributeEpochPaymentsFromSigner(signer, maxPayment); } @@ -242,7 +247,6 @@ contract Validators is * @return True upon success. * @dev Fails if the account is already a validator or validator group. * @dev Fails if the account does not have sufficient Locked Gold. - * @dev Fails after L2 activation, but see registerValidator(bytes) below. */ function registerValidator( bytes calldata ecdsaPublicKey, @@ -323,7 +327,7 @@ contract Validators is uint256 requirementEndTime = validator.membershipHistory.lastRemovedFromGroupTimestamp.add( validatorLockedGoldRequirements.duration ); - require(requirementEndTime < now, "Not yet requirement end time"); + require(requirementEndTime < block.timestamp, "Not yet requirement end time"); // Remove the validator. deleteElement(registeredValidators, account, index); @@ -428,7 +432,7 @@ contract Validators is uint256[] storage sizeHistory = groups[account].sizeHistory; if (sizeHistory.length > 1) { require( - sizeHistory[1].add(groupLockedGoldRequirements.duration) < now, + sizeHistory[1].add(groupLockedGoldRequirements.duration) < block.timestamp, "Hasn't been empty for long enough" ); } @@ -625,7 +629,7 @@ contract Validators is require(isValidatorGroup(account), "Not a validator group"); ValidatorGroup storage group = groups[account]; require( - now >= group.slashInfo.lastSlashed.add(slashingMultiplierResetPeriod), + block.timestamp >= group.slashInfo.lastSlashed.add(slashingMultiplierResetPeriod), "`resetSlashingMultiplier` called before resetPeriod expired" ); group.slashInfo.multiplier = FixidityLib.fixed1(); @@ -640,13 +644,13 @@ contract Validators is require(isValidatorGroup(account), "Not a validator group"); ValidatorGroup storage group = groups[account]; group.slashInfo.multiplier = FixidityLib.wrap(group.slashInfo.multiplier.unwrap().div(2)); - group.slashInfo.lastSlashed = now; + group.slashInfo.lastSlashed = block.timestamp; } /** * @notice Returns the validator BLS key. * @param signer The account that registered the validator or its authorized signing address. - * @return The validator BLS key. + * @return blsPublicKey The validator BLS key. */ function getValidatorBlsPublicKeyFromSigner( address signer @@ -656,6 +660,10 @@ contract Validators is return validators[account].publicKeys.bls; } + function getMembershipHistoryLength() external view returns (uint256) { + return membershipHistoryLength; + } + /** * @notice Returns validator group information. * @param account The account that registered the validator group. @@ -1049,7 +1057,7 @@ contract Validators is uint256[] storage sizeHistory = groups[account].sizeHistory; if (sizeHistory.length > 0) { for (uint256 i = sizeHistory.length.sub(1); i > 0; i = i.sub(1)) { - if (sizeHistory[i].add(groupLockedGoldRequirements.duration) >= now) { + if (sizeHistory[i].add(groupLockedGoldRequirements.duration) >= block.timestamp) { multiplier = Math.max(i, multiplier); break; } @@ -1118,7 +1126,11 @@ contract Validators is /** * @notice Returns validator information. * @param account The account that registered the validator. - * @return The unpacked validator struct. + * @return ecdsaPublicKey The ECDSA public key. + * @return blsPublicKey The BLS public key. + * @return affiliation The address of the validator group the validator is a member of. + * @return score The validator's score. + * @return signer The address of the validator's signer. */ function getValidator( address account @@ -1147,9 +1159,9 @@ contract Validators is /** * @notice Returns affiliated group to validator. * @param account The account that registered the validator. - * @return The validator group. + * @return group The validator group. */ - function getValidatorsGroup(address account) public view returns (address affiliation) { + function getValidatorsGroup(address account) public view returns (address group) { require(isValidator(account), "Not a validator"); Validator storage validator = validators[account]; return validator.affiliation; @@ -1214,7 +1226,7 @@ contract Validators is (address beneficiary, uint256 fraction) = getAccounts().getPaymentDelegation(account); uint256 delegatedPayment = remainingPayment.multiply(FixidityLib.wrap(fraction)).fromFixed(); uint256 validatorPayment = remainingPayment.fromFixed().sub(delegatedPayment); - IStableToken stableToken = getStableToken(); + IStableToken stableToken = IStableToken(getStableToken()); require(stableToken.mint(group, groupPayment), "mint failed to validator group"); require(stableToken.mint(account, validatorPayment), "mint failed to validator account"); if (fraction != 0) { @@ -1232,7 +1244,6 @@ contract Validators is * @param signer The validator signer of the validator whose score needs updating. * @param uptime The Fixidity representation of the validator's uptime, between 0 and 1. * @dev new_score = uptime ** exponent * adjustmentSpeed + old_score * (1 - adjustmentSpeed) - * @return True upon success. */ function _updateValidatorScoreFromSigner(address signer, uint256 uptime) internal { address account = getAccounts().signerToAccount(signer); @@ -1359,7 +1370,7 @@ contract Validators is uint256 lastIndex = list.length.sub(1); list[index] = list[lastIndex]; delete list[lastIndex]; - list.length = lastIndex; + list.pop(); } /** @@ -1400,7 +1411,7 @@ contract Validators is uint256 head = history.numEntries == 0 ? 0 : history.tail.add(history.numEntries.sub(1)); if (history.numEntries > 0 && group == address(0)) { - history.lastRemovedFromGroupTimestamp = now; + history.lastRemovedFromGroupTimestamp = block.timestamp; } if (history.numEntries > 0 && history.entries[head].epochNumber == epochNumber) { @@ -1439,9 +1450,9 @@ contract Validators is function updateSizeHistory(address group, uint256 size) private { uint256[] storage sizeHistory = groups[group].sizeHistory; if (size == sizeHistory.length) { - sizeHistory.push(now); + sizeHistory.push(block.timestamp); } else if (size < sizeHistory.length) { - sizeHistory[size] = now; + sizeHistory[size] = block.timestamp; } else { require(false, "Unable to update size history"); } diff --git a/packages/protocol/contracts-0.8/governance/test/ValidatorsMock.sol b/packages/protocol/contracts-0.8/governance/test/ValidatorsMock.sol index 30499ca177f..b475ac58a79 100644 --- a/packages/protocol/contracts-0.8/governance/test/ValidatorsMock.sol +++ b/packages/protocol/contracts-0.8/governance/test/ValidatorsMock.sol @@ -1,236 +1,20 @@ -// SPDX-License-Identifier: UNLICENSED pragma solidity >=0.8.7 <0.8.20; -import "../../../contracts/governance/interfaces/IValidators.sol"; +import "../Validators.sol"; import "../../../contracts/common/FixidityLib.sol"; -// import "forge-std-8/console2.sol"; - /** * @title A wrapper around Validators that exposes onlyVm functions for testing. */ -contract ValidatorsMock08 is IValidators { - function updateValidatorScoreFromSigner(address signer, uint256 uptime) external { - // console2.log("### update Validator Score From Signer"); +contract ValidatorsMock is Validators(true) { + function updateValidatorScoreFromSigner(address signer, uint256 uptime) external override { + return _updateValidatorScoreFromSigner(signer, uptime); } function distributeEpochPaymentsFromSigner( address signer, uint256 maxPayment - ) external returns (uint256) { - // console2.log("### distributeEpochPaymentsFromSigner"); - return 0; - // return _distributeEpochPaymentsFromSigner(signer, maxPayment); - } - - function registerValidator( - bytes calldata ecdsaPublicKey, - bytes calldata blsPublicKey, - bytes calldata blsPop - ) external returns (bool) { - return true; - } - - function deregisterValidator(uint256 index) external returns (bool) { - return true; - } - function affiliate(address group) external returns (bool) { - return true; - } - function deaffiliate() external returns (bool) { - return true; - } - function updateBlsPublicKey( - bytes calldata blsPublicKey, - bytes calldata blsPop - ) external returns (bool) { - return true; - } - function registerValidatorGroup(uint256 commission) external returns (bool) { - return true; - } - function deregisterValidatorGroup(uint256 index) external returns (bool) { - return true; - } - function addMember(address validator) external returns (bool) { - return true; - } - function addFirstMember( - address validator, - address lesser, - address greater - ) external returns (bool) { - return true; - } - function removeMember(address validator) external returns (bool) { - return true; - } - function reorderMember( - address validator, - address lesserMember, - address greaterMember - ) external returns (bool) { - return true; - } - function updateCommission() external {} - function setNextCommissionUpdate(uint256 commission) external {} - function resetSlashingMultiplier() external {} - - // only owner - function setCommissionUpdateDelay(uint256 delay) external {} - function setMaxGroupSize(uint256 size) external returns (bool) { - return true; - } - function setMembershipHistoryLength(uint256 length) external returns (bool) { - return true; - } - function setValidatorScoreParameters( - uint256 exponent, - uint256 adjustmentSpeed - ) external returns (bool) { - return true; - } - function setGroupLockedGoldRequirements(uint256 value, uint256 duration) external returns (bool) { - return true; - } - function setValidatorLockedGoldRequirements( - uint256 value, - uint256 duration - ) external returns (bool) { - return true; - } - function setSlashingMultiplierResetPeriod(uint256 value) external {} - - // only registered contract - function updateEcdsaPublicKey( - address account, - address signer, - bytes calldata ecdsaPublicKey - ) external returns (bool) { - return true; - } - function updatePublicKeys( - address account, - address signer, - bytes calldata ecdsaPublicKey, - bytes calldata blsPublicKey, - bytes calldata blsPop - ) external returns (bool) { - return true; - } - - // only slasher - function forceDeaffiliateIfValidator(address validatorAccount) external {} - function halveSlashingMultiplier(address account) external {} - - // view functions - function getCommissionUpdateDelay() external view returns (uint256) { - return 0; - } - function getValidatorScoreParameters() external view returns (uint256, uint256) { - return (0, 0); - } - - function getMembershipHistory( - address account - ) external view returns (uint256[] memory, address[] memory, uint256, uint256) { - return (new uint256[](0), new address[](0), 0, 0); - } - function calculateEpochScore(uint256 uptime) external view returns (uint256) { - return 0; - } - - function calculateGroupEpochScore(uint256[] calldata uptimes) external view returns (uint256) { - return 0; - } - - function getAccountLockedGoldRequirement(address account) external view returns (uint256) { - return 0; - } - - function meetsAccountLockedGoldRequirements(address account) external view returns (bool) { - return true; - } - function getValidatorBlsPublicKeyFromSigner(address singer) external view returns (bytes memory) { - return "0x"; - } - function getValidator( - address account - ) external view returns (bytes memory, bytes memory, address, uint256, address) { - return ("0x", "0x", address(0), 0, address(0)); - } - function getValidatorsGroup(address account) external view returns (address affiliation) { - affiliation = address(0); - } - function getValidatorGroup( - address account - ) - external - view - returns (address[] memory, uint256, uint256, uint256, uint256[] memory, uint256, uint256) - { - return (new address[](0), 0, 0, 0, new uint256[](0), 0, 0); - } - function getGroupNumMembers(address account) external view returns (uint256) { - return 0; - } - - function getTopGroupValidators( - address account, - uint256 n - ) external view returns (address[] memory) { - return new address[](0); - } - function getGroupsNumMembers( - address[] calldata accounts - ) external view returns (uint256[] memory) { - return new uint256[](0); - } - function getNumRegisteredValidators() external view returns (uint256) { - return 0; - } - - function groupMembershipInEpoch( - address account, - uint256 epochNumber, - uint256 index - ) external view returns (address) { - return address(0); - } - - function getValidatorLockedGoldRequirements() external view returns (uint256, uint256) { - return (0, 0); - } - function getGroupLockedGoldRequirements() external view returns (uint256, uint256) { - return (0, 0); - } - function getRegisteredValidators() external view returns (address[] memory) { - return new address[](0); - } - function getRegisteredValidatorGroups() external view returns (address[] memory) { - return new address[](0); - } - function isValidatorGroup(address account) external view returns (bool) { - return true; - } - function isValidator(address account) external view returns (bool) { - return true; - } - function getValidatorGroupSlashingMultiplier(address account) external view returns (uint256) { - return 0; - } - - function getMembershipInLastEpoch(address account) external view returns (address) { - return address(0); - } - function getMembershipInLastEpochFromSigner(address signer) external view returns (address) { - return address(0); - } - function computeEpochReward( - address account, - uint256 score, - uint256 maxPayment - ) external view returns (uint256) { - return 1; + ) external override returns (uint256) { + return _distributeEpochPaymentsFromSigner(signer, maxPayment); } } diff --git a/packages/protocol/contracts-0.8/governance/test/ValidatorsMock08.sol b/packages/protocol/contracts-0.8/governance/test/ValidatorsMock08.sol new file mode 100644 index 00000000000..28857cb69b0 --- /dev/null +++ b/packages/protocol/contracts-0.8/governance/test/ValidatorsMock08.sol @@ -0,0 +1,250 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity >=0.8.7 <0.8.20; + +import "../../../contracts/governance/interfaces/IValidators.sol"; +import "../../../contracts/common/FixidityLib.sol"; + +// import "forge-std-8/console2.sol"; + +/** + * @title A wrapper around Validators that exposes onlyVm functions for testing. + */ +contract ValidatorsMock08 is IValidators { + function updateValidatorScoreFromSigner(address signer, uint256 uptime) external { + // console2.log("### update Validator Score From Signer"); + } + + function distributeEpochPaymentsFromSigner( + address signer, + uint256 maxPayment + ) external returns (uint256) { + // console2.log("### distributeEpochPaymentsFromSigner"); + return 0; + // return _distributeEpochPaymentsFromSigner(signer, maxPayment); + } + + function registerValidator( + bytes calldata ecdsaPublicKey, + bytes calldata blsPublicKey, + bytes calldata blsPop + ) external returns (bool) { + return true; + } + + function registerValidator(bytes calldata ecdsaPublicKey) external returns (bool) { + return true; + } + + function deregisterValidator(uint256 index) external returns (bool) { + return true; + } + function affiliate(address group) external returns (bool) { + return true; + } + function deaffiliate() external returns (bool) { + return true; + } + function updateBlsPublicKey( + bytes calldata blsPublicKey, + bytes calldata blsPop + ) external returns (bool) { + return true; + } + function registerValidatorGroup(uint256 commission) external returns (bool) { + return true; + } + function deregisterValidatorGroup(uint256 index) external returns (bool) { + return true; + } + function addMember(address validator) external returns (bool) { + return true; + } + function addFirstMember( + address validator, + address lesser, + address greater + ) external returns (bool) { + return true; + } + function removeMember(address validator) external returns (bool) { + return true; + } + function reorderMember( + address validator, + address lesserMember, + address greaterMember + ) external returns (bool) { + return true; + } + function updateCommission() external {} + function setNextCommissionUpdate(uint256 commission) external {} + function resetSlashingMultiplier() external {} + + // only owner + function setCommissionUpdateDelay(uint256 delay) external {} + function setMaxGroupSize(uint256 size) external returns (bool) { + return true; + } + function setMembershipHistoryLength(uint256 length) external returns (bool) { + return true; + } + function setValidatorScoreParameters( + uint256 exponent, + uint256 adjustmentSpeed + ) external returns (bool) { + return true; + } + function setGroupLockedGoldRequirements(uint256 value, uint256 duration) external returns (bool) { + return true; + } + function setValidatorLockedGoldRequirements( + uint256 value, + uint256 duration + ) external returns (bool) { + return true; + } + function setSlashingMultiplierResetPeriod(uint256 value) external {} + function setDowntimeGracePeriod(uint256 value) external {} + + // only registered contract + function updateEcdsaPublicKey( + address account, + address signer, + bytes calldata ecdsaPublicKey + ) external returns (bool) { + return true; + } + function updatePublicKeys( + address account, + address signer, + bytes calldata ecdsaPublicKey, + bytes calldata blsPublicKey, + bytes calldata blsPop + ) external returns (bool) { + return true; + } + + // only slasher + function forceDeaffiliateIfValidator(address validatorAccount) external {} + function halveSlashingMultiplier(address account) external {} + + // view functions + function maxGroupSize() external view returns (uint256) { + return 0; + } + function downtimeGracePeriod() external view returns (uint256) { + return 0; + } + function getCommissionUpdateDelay() external view returns (uint256) { + return 0; + } + function getValidatorScoreParameters() external view returns (uint256, uint256) { + return (0, 0); + } + + function getMembershipHistory( + address account + ) external view returns (uint256[] memory, address[] memory, uint256, uint256) { + return (new uint256[](0), new address[](0), 0, 0); + } + function calculateEpochScore(uint256 uptime) external view returns (uint256) { + return 0; + } + + function calculateGroupEpochScore(uint256[] calldata uptimes) external view returns (uint256) { + return 0; + } + + function getAccountLockedGoldRequirement(address account) external view returns (uint256) { + return 0; + } + + function meetsAccountLockedGoldRequirements(address account) external view returns (bool) { + return true; + } + function getValidatorBlsPublicKeyFromSigner(address singer) external view returns (bytes memory) { + return "0x"; + } + function getValidator( + address account + ) external view returns (bytes memory, bytes memory, address, uint256, address) { + return ("0x", "0x", address(0), 0, address(0)); + } + function getValidatorsGroup(address account) external view returns (address affiliation) { + affiliation = address(0); + } + function getValidatorGroup( + address account + ) + external + view + returns (address[] memory, uint256, uint256, uint256, uint256[] memory, uint256, uint256) + { + return (new address[](0), 0, 0, 0, new uint256[](0), 0, 0); + } + function getGroupNumMembers(address account) external view returns (uint256) { + return 0; + } + + function getTopGroupValidators( + address account, + uint256 n + ) external view returns (address[] memory) { + return new address[](0); + } + function getGroupsNumMembers( + address[] calldata accounts + ) external view returns (uint256[] memory) { + return new uint256[](0); + } + function getNumRegisteredValidators() external view returns (uint256) { + return 0; + } + + function groupMembershipInEpoch( + address account, + uint256 epochNumber, + uint256 index + ) external view returns (address) { + return address(0); + } + + function getValidatorLockedGoldRequirements() external view returns (uint256, uint256) { + return (0, 0); + } + function getGroupLockedGoldRequirements() external view returns (uint256, uint256) { + return (0, 0); + } + function getRegisteredValidators() external view returns (address[] memory) { + return new address[](0); + } + function getRegisteredValidatorGroups() external view returns (address[] memory) { + return new address[](0); + } + function isValidatorGroup(address account) external view returns (bool) { + return true; + } + function isValidator(address account) external view returns (bool) { + return true; + } + function getValidatorGroupSlashingMultiplier(address account) external view returns (uint256) { + return 0; + } + + function getMembershipInLastEpoch(address account) external view returns (address) { + return address(0); + } + function getMembershipInLastEpochFromSigner(address signer) external view returns (address) { + return address(0); + } + function computeEpochReward( + address account, + uint256 score, + uint256 maxPayment + ) external view returns (uint256) { + return 1; + } + function getMembershipHistoryLength() external view returns (uint256) { + return 0; + } +} diff --git a/packages/protocol/contracts/common/Blockable.sol b/packages/protocol/contracts/common/Blockable.sol index 4ebdbce390d..d32f1e6ee0a 100644 --- a/packages/protocol/contracts/common/Blockable.sol +++ b/packages/protocol/contracts/common/Blockable.sol @@ -2,15 +2,16 @@ pragma solidity >=0.5.13 <0.9.0; import "./interfaces/IBlockable.sol"; import "./interfaces/IBlocker.sol"; -import "openzeppelin-solidity/contracts/ownership/Ownable.sol"; /** * @title Blockable Contract * @notice This contract allows certain actions to be blocked based on the logic of another contract implementing the IBlocker interface. * @dev This contract uses an external IBlocker contract to determine if it is blocked. The owner can set the blocking contract. **/ -contract Blockable is IBlockable, Ownable { - IBlocker blockedBy; +contract Blockable is IBlockable { + // using directly memory slot so contracts can inherit from this contract withtou breaking storage layout + bytes32 private constant BLOCKEDBY_POSITION = + bytes32(uint256(keccak256("blocked_by_position")) - 1); event BlockedBySet(address indexed _blockedBy); @@ -21,13 +22,6 @@ contract Blockable is IBlockable, Ownable { _; } - /// @notice Sets the address of the blocking contract. - /// @param _blockedBy The address of the contract that will determine if this contract is blocked. - /// @dev Can only be called by the owner of the contract. - function setBlockedByContract(address _blockedBy) external onlyOwner { - _setBlockedBy(_blockedBy); - } - /// @notice Checks if the contract is currently blocked. /// @return Returns true if the contract is blocked, otherwise false. /// @dev The function returns false if no blocking contract has been set. @@ -35,19 +29,27 @@ contract Blockable is IBlockable, Ownable { return _isBlocked(); } - function getBlockedbyContract() external view returns (address) { - return address(blockedBy); + function getBlockedbyContract() public view returns (address blockedBy) { + bytes32 blockedByPosition = BLOCKEDBY_POSITION; + assembly { + blockedBy := sload(blockedByPosition) + } + return blockedBy; } function _setBlockedBy(address _blockedBy) internal { - blockedBy = IBlocker(_blockedBy); + bytes32 blockedByPosition = BLOCKEDBY_POSITION; + assembly { + sstore(blockedByPosition, _blockedBy) + } + emit BlockedBySet(_blockedBy); } function _isBlocked() internal view returns (bool) { - if (address(blockedBy) == address(0)) { + if (getBlockedbyContract() == address(0)) { return false; } - return blockedBy.isBlocked(); + return IBlocker(getBlockedbyContract()).isBlocked(); } } diff --git a/packages/protocol/contracts/common/libraries/ReentrancyGuard.sol b/packages/protocol/contracts/common/libraries/ReentrancyGuard.sol index d3394816bbf..8c48a49c652 100644 --- a/packages/protocol/contracts/common/libraries/ReentrancyGuard.sol +++ b/packages/protocol/contracts/common/libraries/ReentrancyGuard.sol @@ -1,4 +1,4 @@ -pragma solidity ^0.5.13; +pragma solidity >=0.5.13 <0.8.20; /** * @title Helps contracts guard against reentrancy attacks. @@ -24,7 +24,7 @@ contract ReentrancyGuard { require(localCounter == _guardCounter, "reentrant call"); } - constructor() internal { + constructor() public { // The counter starts at one to prevent changing it from zero to a non-zero // value, which is a more expensive operation. _guardCounter = 1; diff --git a/packages/protocol/contracts/governance/Election.sol b/packages/protocol/contracts/governance/Election.sol index 24e11f53a68..af748a475c3 100644 --- a/packages/protocol/contracts/governance/Election.sol +++ b/packages/protocol/contracts/governance/Election.sol @@ -420,6 +420,13 @@ contract Election is return value; } + /// @notice Sets the address of the blocking contract. + /// @param _blockedBy The address of the contract that will determine if this contract is blocked. + /// @dev Can only be called by the owner of the contract. + function setBlockedByContract(address _blockedBy) external onlyOwner { + _setBlockedBy(_blockedBy); + } + /** * @notice Returns the groups that `account` has voted for. * @param account The address of the account casting votes. diff --git a/packages/protocol/contracts/governance/LockedGold.sol b/packages/protocol/contracts/governance/LockedGold.sol index 73cf04bbf1d..3344421ab42 100755 --- a/packages/protocol/contracts/governance/LockedGold.sol +++ b/packages/protocol/contracts/governance/LockedGold.sol @@ -505,6 +505,13 @@ contract LockedGold is emit AccountSlashed(account, maxSlash, reporter, reward); } + /// @notice Sets the address of the blocking contract. + /// @param _blockedBy The address of the contract that will determine if this contract is blocked. + /// @dev Can only be called by the owner of the contract. + function setBlockedByContract(address _blockedBy) external onlyOwner { + _setBlockedBy(_blockedBy); + } + /** * @notice Returns the total amount of locked gold in the system. Note that this does not include * gold that has been unlocked but not yet withdrawn. diff --git a/packages/protocol/contracts/governance/interfaces/IValidators.sol b/packages/protocol/contracts/governance/interfaces/IValidators.sol index 123bddab705..01e16821776 100644 --- a/packages/protocol/contracts/governance/interfaces/IValidators.sol +++ b/packages/protocol/contracts/governance/interfaces/IValidators.sol @@ -7,6 +7,7 @@ interface IValidators { bytes calldata, bytes calldata ) external returns (bool); + function registerValidator(bytes calldata ecdsaPublicKey) external returns (bool); function deregisterValidator(uint256) external returns (bool); function affiliate(address) external returns (bool); function deaffiliate() external returns (bool); @@ -29,6 +30,7 @@ interface IValidators { function setGroupLockedGoldRequirements(uint256, uint256) external returns (bool); function setValidatorLockedGoldRequirements(uint256, uint256) external returns (bool); function setSlashingMultiplierResetPeriod(uint256) external; + function setDowntimeGracePeriod(uint256 value) external; // only registered contract function updateEcdsaPublicKey(address, address, bytes calldata) external returns (bool); @@ -49,6 +51,8 @@ interface IValidators { function halveSlashingMultiplier(address) external; // view functions + function maxGroupSize() external view returns (uint256); + function downtimeGracePeriod() external view returns (uint256); function getCommissionUpdateDelay() external view returns (uint256); function getValidatorScoreParameters() external view returns (uint256, uint256); function getMembershipHistory( @@ -91,4 +95,5 @@ interface IValidators { uint256 score, uint256 maxPayment ) external view returns (uint256); + function getMembershipHistoryLength() external view returns (uint256); } diff --git a/packages/protocol/contracts/governance/interfaces/IValidatorsInitializer.sol b/packages/protocol/contracts/governance/interfaces/IValidatorsInitializer.sol index c8c8964e8ed..21c68bb975b 100644 --- a/packages/protocol/contracts/governance/interfaces/IValidatorsInitializer.sol +++ b/packages/protocol/contracts/governance/interfaces/IValidatorsInitializer.sol @@ -1,4 +1,5 @@ pragma solidity >=0.5.13 <0.9.0; +pragma experimental ABIEncoderV2; interface IValidatorsInitializer { function initialize( @@ -12,7 +13,14 @@ interface IValidatorsInitializer { uint256 _membershipHistoryLength, uint256 _slashingMultiplierResetPeriod, uint256 _maxGroupSize, - uint256 _commissionUpdateDelay, - uint256 _downtimeGracePeriod + InitParamsLib.InitParams calldata initParams ) external; } + +library InitParamsLib { + struct InitParams { + // The number of blocks to delay a ValidatorGroup's commission + uint256 commissionUpdateDelay; + uint256 downtimeGracePeriod; + } +} diff --git a/packages/protocol/contracts/governance/test/ValidatorsMock.sol b/packages/protocol/contracts/governance/test/ValidatorsMock.sol deleted file mode 100644 index ab9557badec..00000000000 --- a/packages/protocol/contracts/governance/test/ValidatorsMock.sol +++ /dev/null @@ -1,20 +0,0 @@ -pragma solidity ^0.5.13; - -import "../Validators.sol"; -import "../../common/FixidityLib.sol"; - -/** - * @title A wrapper around Validators that exposes onlyVm functions for testing. - */ -contract ValidatorsMock is Validators(true) { - function updateValidatorScoreFromSigner(address signer, uint256 uptime) external { - return _updateValidatorScoreFromSigner(signer, uptime); - } - - function distributeEpochPaymentsFromSigner( - address signer, - uint256 maxPayment - ) external returns (uint256) { - return _distributeEpochPaymentsFromSigner(signer, maxPayment); - } -} diff --git a/packages/protocol/foundry.toml b/packages/protocol/foundry.toml index 1557a1e57f8..ed1c21166cc 100644 --- a/packages/protocol/foundry.toml +++ b/packages/protocol/foundry.toml @@ -1,5 +1,5 @@ [profile.default] -src = 'contracts' +src = 'contracts-0.8' out = 'out' test = 'test-sol' libs = ['lib', 'node_modules'] @@ -17,6 +17,7 @@ remappings = [ 'forge-std-8/=lib/celo-foundry-8/lib/forge-std/src/', '@summa-tx/memview.sol/=lib/memview.sol', 'solidity-bytes-utils/=lib/solidity-bytes-utils/', + 'solidity-bytes-utils-8/=lib/solidity-bytes-utils-8/', 'ds-test/=lib/celo-foundry/lib/forge-std/lib/ds-test/src/', ] diff --git a/packages/protocol/governanceConstitution.js b/packages/protocol/governanceConstitution.js index 601bda35862..fa0e0ea0be7 100644 --- a/packages/protocol/governanceConstitution.js +++ b/packages/protocol/governanceConstitution.js @@ -135,6 +135,7 @@ const DefaultConstitution = { setValidatorLockedGoldRequirements: 0.8, setSlashingMultiplierResetPeriod: 0.7, setValidatorScoreParameters: 0.7, + __contractPackage: contractPackages.SOLIDITY_08_PACKAGE, }, } diff --git a/packages/protocol/lib/artifactsSingleton.ts b/packages/protocol/lib/artifactsSingleton.ts index 3d475431836..03de766b5ff 100644 --- a/packages/protocol/lib/artifactsSingleton.ts +++ b/packages/protocol/lib/artifactsSingleton.ts @@ -6,33 +6,37 @@ export interface ArtifactSet { getProxy(key: string): any; } -function getProxyName(contractName:string){ +function getProxyName(contractName: string) { return contractName + "Proxy"; } // This class is meant to be used to wrap truffle artifacts // and extend its interface. // ArtifactsSingleton.wrap returns an instance of DefaultArtifact -export class DefaultArtifact implements ArtifactSet{ +export class DefaultArtifact implements ArtifactSet { public artifacts: any - + public constructor(artifacts) { this.artifacts = artifacts } - + public require(key: string) { return this.artifacts.require(key) } - + public getProxy(key: string) { return this.require(getProxyName(key)) } - + + public contains(key: string) { + return this.artifacts.require(key) !== undefined + } + } // This objects replicates a Truffle `artifacts.require` singleton // but constructed manually -export class ArtifactsSingleton implements ArtifactSet{ +export class ArtifactsSingleton implements ArtifactSet { public static setNetwork(network: any) { this.network = network } @@ -56,8 +60,8 @@ export class ArtifactsSingleton implements ArtifactSet{ return ArtifactsSingleton.instances[namespace] } - public static wrap(artifacts:any){ - if (artifacts instanceof ArtifactsSingleton || artifacts instanceof DefaultArtifact){ + public static wrap(artifacts: any) { + if (artifacts instanceof ArtifactsSingleton || artifacts instanceof DefaultArtifact) { return artifacts } @@ -70,22 +74,29 @@ export class ArtifactsSingleton implements ArtifactSet{ public artifacts: { [key: string]: any } = {} - private constructor() {} + private constructor() { } public addArtifact(key: string, value: any) { this.artifacts[key] = value } - public require(key: string) { - return this.artifacts[key] + public require(key: string, defaultArtifacts?: any) { + if (key in this.artifacts) { + return this.artifacts[key] + } + return defaultArtifacts?.require(key) + } + + public contains(key: string) { + return key in this.artifacts } - public getProxy(key: string, defaultArtifacts?:any) { + public getProxy(key: string, defaultArtifacts?: any) { const proxyArtifactName = getProxyName(key) const toReturn = this.require(proxyArtifactName) - if (toReturn === undefined){ + if (toReturn === undefined) { // in case the package of this artifact has proxiesPath set // this needs to be changed to support it, now only "/" path is supported return defaultArtifacts?.require(proxyArtifactName) diff --git a/packages/protocol/lib/compatibility/verify-bytecode.ts b/packages/protocol/lib/compatibility/verify-bytecode.ts index b5dfdb59f26..56d6d9d0ee8 100644 --- a/packages/protocol/lib/compatibility/verify-bytecode.ts +++ b/packages/protocol/lib/compatibility/verify-bytecode.ts @@ -21,7 +21,12 @@ let ignoredContracts = [ // These contracts are not in the Registry (before release 1) 'ReserveSpenderMultiSig', - 'GovernanceApproverMultiSig' + 'GovernanceApproverMultiSig', + + // These contracts live in monorepo but are not part of the core protocol + 'CeloFeeCurrencyAdapterOwnable', + 'FeeCurrencyAdapter', + 'FeeCurrencyAdapterOwnable', ] interface VerificationContext { diff --git a/packages/protocol/lib/solidity-bytes-utils-8 b/packages/protocol/lib/solidity-bytes-utils-8 new file mode 160000 index 00000000000..df88556cbbc --- /dev/null +++ b/packages/protocol/lib/solidity-bytes-utils-8 @@ -0,0 +1 @@ +Subproject commit df88556cbbc267b33a787a3a6eaa32fd7247b589 diff --git a/packages/protocol/lib/web3-utils.ts b/packages/protocol/lib/web3-utils.ts index 127c09ff6d5..0f1f3869704 100644 --- a/packages/protocol/lib/web3-utils.ts +++ b/packages/protocol/lib/web3-utils.ts @@ -211,9 +211,7 @@ export async function getDeployedProxiedContract { - const Contract: Truffle.Contract = customArtifacts.require(contractName) - let Proxy: ProxyContract // this wrap avoids a lot of rewrite const overloadedArtifact = ArtifactsSingleton.wrap(customArtifacts) @@ -271,11 +269,11 @@ export const makeTruffleContractForMigrationWithoutSingleton = (contractName: st const artifact = require(`${path.join(__dirname, "..")}/build/contracts-${contractPath}/${contractName}.json`) const Contract = truffleContract({ + contractName: artifact.contractName, abi: artifact.abi, unlinked_binary: artifact.bytecode, }) - Contract.setProvider(web3.currentProvider) Contract.setNetwork(network.network_id) @@ -292,9 +290,14 @@ export const makeTruffleContractForMigrationWithoutSingleton = (contractName: st export const makeTruffleContractForMigration = (contractName: string, contractPath: ContractPackage, web3: Web3) => { + const singleton = ArtifactsSingleton.getInstance(contractPath) + if (singleton.contains(contractName)) { + return singleton.require(contractName) + } + const network = ArtifactsSingleton.getNetwork() const Contract = makeTruffleContractForMigrationWithoutSingleton(contractName, network, contractPath.name, web3) - ArtifactsSingleton.getInstance(contractPath).addArtifact(contractName, Contract) + singleton.addArtifact(contractName, Contract) return Contract } @@ -382,7 +385,7 @@ export async function transferOwnershipOfProxy( export async function transferOwnershipOfProxyAndImplementation< ContractInstance extends OwnableInstance >(contractName: string, owner: string, artifacts: any) { - console.info(` Transferring ownership of ${contractName} and its Proxy to ${owner}`) + console.info(`Transferring ownership of ${contractName} and its Proxy to ${owner}`) const contract: ContractInstance = await getDeployedProxiedContract( contractName, artifacts diff --git a/packages/protocol/migrationsConfig.js b/packages/protocol/migrationsConfig.js index 1522bd1be88..b8fd53e1e44 100644 --- a/packages/protocol/migrationsConfig.js +++ b/packages/protocol/migrationsConfig.js @@ -581,7 +581,7 @@ NetworkConfigs.mainnet = NetworkConfigs.rc1 const linkedLibraries = { Proposals: ['Governance'], - AddressLinkedList: ['Validators', 'ValidatorsMock'], + AddressLinkedList: ['Validators'], AddressSortedLinkedList: ['Election', 'ElectionTest'], IntegerSortedLinkedList: ['Governance', 'IntegerSortedLinkedListMock'], AddressSortedLinkedListWithMedian: ['SortedOracles', 'AddressSortedLinkedListWithMedianMock'], diff --git a/packages/protocol/migrations_sol/Migration.s.sol b/packages/protocol/migrations_sol/Migration.s.sol index 049adef1507..3fac354a1b8 100644 --- a/packages/protocol/migrations_sol/Migration.s.sol +++ b/packages/protocol/migrations_sol/Migration.s.sol @@ -67,6 +67,12 @@ contract ForceTx { contract Migration is Script, UsingRegistry, MigrationsConstants { using stdJson for string; + struct InitParamsTunnel { + // The number of blocks to delay a ValidatorGroup's commission + uint256 commissionUpdateDelay; + uint256 downtimeGracePeriod; + } + IProxyFactory proxyFactory; uint256 proxyNonce = 0; @@ -606,6 +612,11 @@ contract Migration is Script, UsingRegistry, MigrationsConstants { (uint256) ); + InitParamsTunnel memory initParamsTunnel = InitParamsTunnel({ + commissionUpdateDelay: commissionUpdateDelay, + downtimeGracePeriod: downtimeGracePeriod + }); + deployProxiedContract( "Validators", abi.encodeWithSelector( @@ -620,8 +631,7 @@ contract Migration is Script, UsingRegistry, MigrationsConstants { membershipHistoryLength, slashingMultiplierResetPeriod, maxGroupSize, - commissionUpdateDelay, - downtimeGracePeriod + initParamsTunnel ) ); } diff --git a/packages/protocol/migrations_ts/01_libraries.ts b/packages/protocol/migrations_ts/01_libraries.ts index 3cb33f15561..6dd5b4047e7 100644 --- a/packages/protocol/migrations_ts/01_libraries.ts +++ b/packages/protocol/migrations_ts/01_libraries.ts @@ -1,10 +1,21 @@ +import { SOLIDITY_08_PACKAGE } from '@celo/protocol/contractPackages' +import { ArtifactsSingleton } from '@celo/protocol/lib/artifactsSingleton' +import { makeTruffleContractForMigration } from '@celo/protocol/lib/web3-utils' import { linkedLibraries } from '@celo/protocol/migrationsConfig' module.exports = (deployer: any) => { Object.keys(linkedLibraries).forEach((lib: string) => { - const Library = artifacts.require(lib) + const artifacts08 = ArtifactsSingleton.getInstance(SOLIDITY_08_PACKAGE, artifacts) + + for (const contractName of SOLIDITY_08_PACKAGE.contracts) { + makeTruffleContractForMigration(contractName, SOLIDITY_08_PACKAGE, web3) + } + + const Library = artifacts08.require(lib, artifacts) deployer.deploy(Library) - const Contracts = linkedLibraries[lib].map((contract: string) => artifacts.require(contract)) + const Contracts = linkedLibraries[lib].map((contract: string) => + artifacts08.require(contract, artifacts) + ) deployer.link(Library, Contracts) }) } diff --git a/packages/protocol/migrations_ts/13_validators.ts b/packages/protocol/migrations_ts/13_validators.ts index 2cc51d2ae48..fc29dfdd635 100644 --- a/packages/protocol/migrations_ts/13_validators.ts +++ b/packages/protocol/migrations_ts/13_validators.ts @@ -2,7 +2,8 @@ import { CeloContractName } from '@celo/protocol/lib/registry-utils' import { deploymentForCoreContract } from '@celo/protocol/lib/web3-utils' import { config } from '@celo/protocol/migrationsConfig' import { toFixed } from '@celo/utils/lib/fixidity' -import { ValidatorsInstance } from 'types' +import { ValidatorsInstance } from 'types/08' +import { SOLIDITY_08_PACKAGE } from '../contractPackages' const initializeArgs = async (): Promise => { return [ @@ -16,8 +17,10 @@ const initializeArgs = async (): Promise => { config.validators.membershipHistoryLength, config.validators.slashingPenaltyResetPeriod, config.validators.maxGroupSize, - config.validators.commissionUpdateDelay, - config.validators.downtimeGracePeriod, + { + commissionUpdateDelay: config.validators.commissionUpdateDelay, + downtimeGracePeriod: config.validators.downtimeGracePeriod, + }, ] } @@ -25,5 +28,7 @@ module.exports = deploymentForCoreContract( web3, artifacts, CeloContractName.Validators, - initializeArgs + initializeArgs, + undefined, + SOLIDITY_08_PACKAGE ) diff --git a/packages/protocol/migrations_ts/29_governance.ts b/packages/protocol/migrations_ts/29_governance.ts index 56845d78a8d..3cbf66bc7b3 100644 --- a/packages/protocol/migrations_ts/29_governance.ts +++ b/packages/protocol/migrations_ts/29_governance.ts @@ -113,7 +113,6 @@ module.exports = deploymentForCoreContract( 'Random', 'Registry', 'SortedOracles', - 'Validators', ], }, { @@ -130,7 +129,7 @@ module.exports = deploymentForCoreContract( __contractPackage: MENTO_PACKAGE, }, { - contracts: ['GasPriceMinimum'], + contracts: ['GasPriceMinimum', 'Validators'], __contractPackage: SOLIDITY_08_PACKAGE, }, ] diff --git a/packages/protocol/migrations_ts/30_elect_validators.ts b/packages/protocol/migrations_ts/30_elect_validators.ts index aaf0fd1424a..17d35a0b6c7 100644 --- a/packages/protocol/migrations_ts/30_elect_validators.ts +++ b/packages/protocol/migrations_ts/30_elect_validators.ts @@ -1,6 +1,8 @@ import { NULL_ADDRESS } from '@celo/base/lib/address' import { CeloTxObject } from '@celo/connect' import { getBlsPoP, getBlsPublicKey } from '@celo/cryptographic-utils/lib/bls' +import { SOLIDITY_08_PACKAGE } from '@celo/protocol/contractPackages' +import { ArtifactsSingleton } from '@celo/protocol/lib/artifactsSingleton' import { getDeployedProxiedContract, sendTransactionWithPrivateKey, @@ -10,7 +12,8 @@ import { privateKeyToAddress, privateKeyToPublicKey } from '@celo/utils/lib/addr import { toFixed } from '@celo/utils/lib/fixidity' import { signMessage } from '@celo/utils/lib/signatureUtils' import { BigNumber } from 'bignumber.js' -import { AccountsInstance, ElectionInstance, LockedGoldInstance, ValidatorsInstance } from 'types' +import { AccountsInstance, ElectionInstance, LockedGoldInstance } from 'types' +import { ValidatorsInstance } from 'types/08' import Web3 from 'web3' const truffle = require('@celo/protocol/truffle-config.js') @@ -221,6 +224,8 @@ async function registerValidator( } module.exports = async (_deployer: any, networkName: string) => { + const artifacts08 = ArtifactsSingleton.getInstance(SOLIDITY_08_PACKAGE, artifacts) + const accounts: AccountsInstance = await getDeployedProxiedContract( 'Accounts', artifacts @@ -228,7 +233,7 @@ module.exports = async (_deployer: any, networkName: string) => { const validators: ValidatorsInstance = await getDeployedProxiedContract( 'Validators', - artifacts + artifacts08 ) const lockedGold: LockedGoldInstance = await getDeployedProxiedContract( diff --git a/packages/protocol/package.json b/packages/protocol/package.json index fde24a0e13b..01d242474b5 100644 --- a/packages/protocol/package.json +++ b/packages/protocol/package.json @@ -109,6 +109,7 @@ "solhint": "^4.5.4", "semver": "^7.5.4", "solidity-bytes-utils": "0.0.7", + "solidity-bytes-utils-8": "npm:solidity-bytes-utils@^0.8.2", "truffle": "5.9.0", "truffle-security": "^1.7.3", "weak-map": "^1.0.5", @@ -151,4 +152,4 @@ "typechain-target-ethers-v5": "^5.0.1", "yargs": "^14.0.0" } -} \ No newline at end of file +} diff --git a/packages/protocol/remappings.txt b/packages/protocol/remappings.txt index 8722ca0c8b0..58f99cbe97a 100644 --- a/packages/protocol/remappings.txt +++ b/packages/protocol/remappings.txt @@ -1,4 +1,4 @@ @celo-contracts=contracts/ @celo-contracts-8=contracts-0.8/ @test-sol=test-sol -@lib=lib \ No newline at end of file +@lib=lib diff --git a/packages/protocol/scripts/bash/contract-exclusion-regex.sh b/packages/protocol/scripts/bash/contract-exclusion-regex.sh index a92f61da77f..c233d4aa287 100644 --- a/packages/protocol/scripts/bash/contract-exclusion-regex.sh +++ b/packages/protocol/scripts/bash/contract-exclusion-regex.sh @@ -3,7 +3,7 @@ set -euo pipefail # Exclude test contracts, mock contracts, contract interfaces, Proxy contracts, inlined libraries, # MultiSig contracts, and the ReleaseGold contract. -CONTRACT_EXCLUSION_REGEX=".*Test|Mock.*|I[A-Z].*|.*Proxy|MultiSig.*|ReleaseGold|SlasherUtil|UsingPrecompiles" +CONTRACT_EXCLUSION_REGEX=".*Test|Mock.*|I[A-Z].*|.*Proxy|MultiSig.*|ReleaseGold|SlasherUtil|UsingPrecompiles|CeloFeeCurrencyAdapterOwnable|FeeCurrencyAdapter|FeeCurrencyAdapterOwnable" # Before CR7, UsingRegistry and UsingRegistryV2 had been deployed, they need to keep getting deployed to keep the release reports without changes. VERSION_NUMBER=$(echo "$BRANCH" | tr -dc '0-9') diff --git a/packages/protocol/scripts/determine-release-version.ts b/packages/protocol/scripts/determine-release-version.ts index 1c2eb313d23..39a40096f79 100644 --- a/packages/protocol/scripts/determine-release-version.ts +++ b/packages/protocol/scripts/determine-release-version.ts @@ -13,7 +13,7 @@ const branchName = execSync('git branch --show-current').toString().trim() // if not on a release branch a dry-run will be done unless an NPM_TAG is provided // in which case we will try to fetch the last published version with that tag and bump or use the canary to get major and start versioning from there the new tag at 0 // (e.g. `@celo/contracts@11.0.0@custom-tag.0`) -const nextVersion = determineNextVersion(gitTag, branchName, npmPackage, "epoch-manager") +const nextVersion = determineNextVersion(gitTag, branchName, npmPackage, 'epoch-manager') if (nextVersion === null) { // dry-run will build the package but not publish it diff --git a/packages/protocol/scripts/foundry/constants.sh b/packages/protocol/scripts/foundry/constants.sh index 17db16c51ef..d292b0daf47 100755 --- a/packages/protocol/scripts/foundry/constants.sh +++ b/packages/protocol/scripts/foundry/constants.sh @@ -45,7 +45,7 @@ export CELO_DISTRIBUTION_SCHEDULE_INITIAL_BALANCE="$(($GOLD_TOKEN_CELO_SUPPLY_CA # Contract libraries export LIBRARIES_PATH=("contracts/common/linkedlists/AddressSortedLinkedListWithMedian.sol:AddressSortedLinkedListWithMedian" "contracts/common/Signatures.sol:Signatures" - "contracts/common/linkedlists/AddressLinkedList.sol:AddressLinkedList" + "contracts-0.8/common/linkedlists/AddressLinkedList.sol:AddressLinkedList" "contracts/common/linkedlists/AddressSortedLinkedList.sol:AddressSortedLinkedList" "contracts/common/linkedlists/IntegerSortedLinkedList.sol:IntegerSortedLinkedList" "contracts/governance/Proposals.sol:Proposals" @@ -53,9 +53,11 @@ export LIBRARIES_PATH=("contracts/common/linkedlists/AddressSortedLinkedListWith export LIBRARY_DEPENDENCIES_PATH=( "contracts/common/FixidityLib.sol" "contracts/common/linkedlists/LinkedList.sol" + "contracts-0.8/common/linkedlists/LinkedList.sol" "contracts/common/linkedlists/SortedLinkedList.sol" "contracts/common/linkedlists/SortedLinkedListWithMedian.sol" "lib/openzeppelin-contracts/contracts/math/SafeMath.sol" + "lib/openzeppelin-contracts8/contracts/utils/math/SafeMath.sol" "lib/openzeppelin-contracts/contracts/math/Math.sol" "lib/openzeppelin-contracts/contracts/cryptography/ECDSA.sol" "lib/openzeppelin-contracts/contracts/utils/Address.sol" diff --git a/packages/protocol/test-sol/devchain/migration/05Links.sol b/packages/protocol/test-sol/devchain/migration/05Links.sol new file mode 100644 index 00000000000..4640cc6b9ab --- /dev/null +++ b/packages/protocol/test-sol/devchain/migration/05Links.sol @@ -0,0 +1,26 @@ +// This file exists only to force migration tests also compile below imported contracts. +pragma solidity ^0.5.13; + +import "@celo-contracts/governance/BlockchainParameters.sol"; +import "@celo-contracts/governance/DoubleSigningSlasher.sol"; +import "@celo-contracts/governance/DowntimeSlasher.sol"; +import "@celo-contracts/governance/EpochRewards.sol"; +import "@celo-contracts/governance/GovernanceSlasher.sol"; +import "@celo-contracts/governance/LockedGold.sol"; +import "@celo-contracts/common/FeeCurrencyWhitelist.sol"; +import "@celo-contracts/common/Freezer.sol"; +import "@celo-contracts/common/FeeHandler.sol"; +import "@celo-contracts/identity/OdisPayments.sol"; +import "@celo-contracts/identity/Random.sol"; +import "@celo-contracts/common/Registry.sol"; +import "@celo-contracts/common/UniswapFeeHandlerSeller.sol"; +import "@celo-contracts/common/MentoFeeHandlerSeller.sol"; + +import "celo-foundry/Test.sol"; + +import { TestConstants } from "@test-sol/constants.sol"; +import { Utils } from "@test-sol/utils.sol"; + +contract BlockchainParametersTest is Test, TestConstants, Utils { + function test_dummy_test() public {} +} diff --git a/packages/protocol/test-sol/devchain/migration/Migration.t.sol b/packages/protocol/test-sol/devchain/migration/Migration.t.sol index 23767f4692b..c5c141a1114 100644 --- a/packages/protocol/test-sol/devchain/migration/Migration.t.sol +++ b/packages/protocol/test-sol/devchain/migration/Migration.t.sol @@ -104,9 +104,10 @@ contract RegistryIntegrationTest is IntegrationTest, MigrationsConstants { actualBytecodeWithMetadataOnDevchain ); + string memory contractFileName = string(abi.encodePacked(contractName, ".sol")); // Get bytecode from build artifacts bytes memory expectedBytecodeWithMetadataFromArtifacts = vm.getDeployedCode( - string(abi.encodePacked(contractName, ".sol")) + contractFileName ); bytes memory expectedBytecodeFromArtifacts = removeMetadataFromBytecode( expectedBytecodeWithMetadataFromArtifacts diff --git a/packages/protocol/test-sol/integration/RevokeCeloAfterL2Transition.sol b/packages/protocol/test-sol/integration/RevokeCeloAfterL2Transition.sol index 97bc1e190d9..5242fce9d77 100644 --- a/packages/protocol/test-sol/integration/RevokeCeloAfterL2Transition.sol +++ b/packages/protocol/test-sol/integration/RevokeCeloAfterL2Transition.sol @@ -9,6 +9,8 @@ import "@celo-contracts/common/FixidityLib.sol"; import "@celo-contracts/common/Registry.sol"; import "@celo-contracts/common/Accounts.sol"; import "@celo-contracts/common/GoldToken.sol"; +import "@celo-contracts-8/common/interfaces/IPrecompiles.sol"; +import "@celo-contracts/governance/interfaces/IValidators.sol"; import "@celo-contracts/governance/Election.sol"; import "@celo-contracts/governance/LockedGold.sol"; @@ -18,7 +20,6 @@ import "@celo-contracts/stability/test/MockStableToken.sol"; import "@celo-contracts/governance/Election.sol"; import "@celo-contracts/governance/Governance.sol"; -import "@celo-contracts/governance/test/ValidatorsMock.sol"; import { TestConstants } from "@test-sol/constants.sol"; import "@test-sol/utils/ECDSAHelper.sol"; import { Utils } from "@test-sol/utils.sol"; @@ -36,7 +37,7 @@ contract RevokeCeloAfterL2Transition is Test, TestConstants, ECDSAHelper, Utils MockStableToken stableToken; Election election; ValidatorsMockTunnel public validatorsMockTunnel; - Validators public validators; + IValidators public validators; LockedGold lockedGold; Governance governance; GoldToken goldToken; @@ -174,7 +175,11 @@ contract RevokeCeloAfterL2Transition is Test, TestConstants, ECDSAHelper, Utils stableToken = new MockStableToken(); election = new Election(true); lockedGold = new LockedGold(true); - validators = new Validators(true); + // validators = new Validators(true); + address validatorsAddress = actor("Validators"); + deployCodeTo("ValidatorsMock.sol", validatorsAddress); + validators = IValidators(validatorsAddress); + // TODO move to create2 validatorsMockTunnel = new ValidatorsMockTunnel(address(validators)); governance = new Governance(true); goldToken = new GoldToken(true); @@ -343,7 +348,7 @@ contract RevokeCeloAfterL2Transition is Test, TestConstants, ECDSAHelper, Utils vm.prank(_validator); validators.registerValidator(_ecdsaPubKey, blsPublicKey, blsPop); - validatorRegistrationEpochNumber = validators.getEpochNumber(); + validatorRegistrationEpochNumber = IPrecompiles(address(validators)).getEpochNumber(); return _ecdsaPubKey; } @@ -477,7 +482,7 @@ contract RevokeCeloAfterL2TransitionTest is RevokeCeloAfterL2Transition { vm.prank(_validator); validators.registerValidator(_ecdsaPubKey, blsPublicKey, blsPop); - validatorRegistrationEpochNumber = validators.getEpochNumber(); + validatorRegistrationEpochNumber = IPrecompiles(address(validators)).getEpochNumber(); return _ecdsaPubKey; } diff --git a/packages/protocol/test-sol/unit/common/Blockable.t.sol b/packages/protocol/test-sol/unit/common/Blockable.t.sol index e5afb27f0c0..0ec65176398 100644 --- a/packages/protocol/test-sol/unit/common/Blockable.t.sol +++ b/packages/protocol/test-sol/unit/common/Blockable.t.sol @@ -5,6 +5,7 @@ import "celo-foundry/Test.sol"; import "@celo-contracts/common/Blockable.sol"; import "@celo-contracts/common/interfaces/IBlockable.sol"; import "@celo-contracts/common/interfaces/IBlocker.sol"; +import "openzeppelin-solidity/contracts/ownership/Ownable.sol"; contract TestBlocker is IBlocker { bool public blocked; @@ -18,7 +19,13 @@ contract TestBlocker is IBlocker { } } -contract TestBlockable is Blockable { +contract BlockableMock is Blockable, Ownable { + function setBlockedByContract(address _blockedBy) public onlyOwner { + _setBlockedBy(_blockedBy); + } +} + +contract TestBlockable is BlockableMock { function functionToBeBlocked() public onlyWhenNotBlocked { return; } @@ -32,7 +39,7 @@ contract BlockableTest is Test { event BlockedBySet(address indexed _blockedBy); function setUp() public { - blockable = new Blockable(); + blockable = new BlockableMock(); blocker = new TestBlocker(); notOwner = actor("notOwner"); } diff --git a/packages/protocol/test-sol/unit/common/EpochManager.t.sol b/packages/protocol/test-sol/unit/common/EpochManager.t.sol index d7078dd1000..0cef5a62870 100644 --- a/packages/protocol/test-sol/unit/common/EpochManager.t.sol +++ b/packages/protocol/test-sol/unit/common/EpochManager.t.sol @@ -17,7 +17,7 @@ import "@celo-contracts/stability/test/MockSortedOracles.sol"; import "@celo-contracts/common/interfaces/IRegistry.sol"; import { EpochRewardsMock08 } from "@celo-contracts-8/governance/test/EpochRewardsMock.sol"; -import { ValidatorsMock08 } from "@celo-contracts-8/governance/test/ValidatorsMock.sol"; +import { ValidatorsMock08 } from "@celo-contracts-8/governance/test/ValidatorsMock08.sol"; contract EpochManagerTest is Test, TestConstants, Utils08 { EpochManager epochManager; diff --git a/packages/protocol/test-sol/unit/common/FeeHandler.t.sol b/packages/protocol/test-sol/unit/common/FeeHandler.t.sol index d131f75c4d9..d81b8fe8acc 100644 --- a/packages/protocol/test-sol/unit/common/FeeHandler.t.sol +++ b/packages/protocol/test-sol/unit/common/FeeHandler.t.sol @@ -19,6 +19,8 @@ import "@celo-contracts/uniswap/test/MockUniswapV2Factory.sol"; import "@celo-contracts/uniswap/test/MockERC20.sol"; import "@mento-core/test/mocks/MockSortedOracles.sol"; import "@mento-core/test/mocks/MockReserve.sol"; +import "@celo-contracts/common/ProxyFactory.sol"; +import "@celo-contracts/governance/GovernanceApproverMultiSig.sol"; contract FeeHandlerTest is Test, TestConstants { using FixidityLib for FixidityLib.Fraction; diff --git a/packages/protocol/test-sol/unit/common/ProxyFactory08.t.sol b/packages/protocol/test-sol/unit/common/ProxyFactory08.t.sol index 78739ebd5ab..3c03595be1d 100644 --- a/packages/protocol/test-sol/unit/common/ProxyFactory08.t.sol +++ b/packages/protocol/test-sol/unit/common/ProxyFactory08.t.sol @@ -41,7 +41,6 @@ contract ProxyFactoryTest is Test, Utils08 { string memory compiler = "0.5.17+commit.d19bba13"; checkbytecode(compiler, proxyInitCode, "./artifacts/Proxy/proxyInitCode"); - address deployedAddress = proxyFactory08.deployArbitraryByteCode(0, owner, 0, proxyInitCode); checkbytecode(compiler, deployedAddress.code, "./artifacts/Proxy/proxyBytecode"); } @@ -52,6 +51,29 @@ contract ProxyFactoryTest is Test, Utils08 { string memory artifactPath ) public { string memory bytecodeBackUp = vm.readFile(string.concat(artifactPath, compiler, ".hex")); - assert(compareStrings(bytecodeBackUp, vm.toString(bytecode))); + string memory bytecodeString = vm.toString(bytecode); + + // Calculate the length of the bytecode to compare (ignoring the last 43 bytes for Swarm hash) + uint compareLength = bytes(bytecodeBackUp).length - 86; // 43 bytes in hex is 86 characters + + // Slice the strings to exclude the Swarm hash + string memory bytecodeBackUpToCompare = substring(bytecodeBackUp, 0, compareLength); + string memory bytecodeToCompare = substring(bytecodeString, 0, compareLength); + + // Assert that the truncated bytecode matches + assert(compareStrings(bytecodeBackUpToCompare, bytecodeToCompare)); + } + + function substring( + string memory str, + uint startIndex, + uint endIndex + ) internal pure returns (string memory) { + bytes memory strBytes = bytes(str); + bytes memory result = new bytes(endIndex - startIndex); + for (uint i = startIndex; i < endIndex; i++) { + result[i - startIndex] = strBytes[i]; + } + return string(result); } } diff --git a/packages/protocol/test-sol/unit/governance/validators/Validators.t.sol b/packages/protocol/test-sol/unit/governance/validators/Validators.t.sol index 18374ce4d6b..145efc5d927 100644 --- a/packages/protocol/test-sol/unit/governance/validators/Validators.t.sol +++ b/packages/protocol/test-sol/unit/governance/validators/Validators.t.sol @@ -2,22 +2,28 @@ pragma solidity ^0.5.13; pragma experimental ABIEncoderV2; +// This test file is in 0.5 although the contract is in 0.8 + +import "forge-std/console.sol"; import "celo-foundry/Test.sol"; +import "openzeppelin-solidity/contracts/ownership/Ownable.sol"; import "openzeppelin-solidity/contracts/math/SafeMath.sol"; import "@celo-contracts/common/FixidityLib.sol"; import "@celo-contracts/common/Registry.sol"; import "@celo-contracts/common/Accounts.sol"; +import "@celo-contracts-8/common/interfaces/IPrecompiles.sol"; import "@celo-contracts/governance/Election.sol"; import "@celo-contracts/governance/LockedGold.sol"; +import "@celo-contracts/governance/interfaces/IValidators.sol"; import "@celo-contracts/stability/test/MockStableToken.sol"; import "@celo-contracts/governance/test/MockElection.sol"; import "@celo-contracts/governance/test/MockLockedGold.sol"; import "@test-sol/unit/governance/validators/mocks/ValidatorsMockTunnel.sol"; -import "@celo-contracts/governance/test/ValidatorsMock.sol"; +// import "@celo-contracts-8/governance/test/ValidatorsMock08.sol"; import "@test-sol/constants.sol"; import "@test-sol/utils/ECDSAHelper.sol"; import { Utils } from "@test-sol/utils.sol"; @@ -47,7 +53,7 @@ contract ValidatorsTest is Test, TestConstants, Utils, ECDSAHelper { MockStableToken stableToken; MockElection election; ValidatorsMockTunnel public validatorsMockTunnel; - ValidatorsMock public validators; + IValidators public validators; MockLockedGold lockedGold; address owner; @@ -165,7 +171,11 @@ contract ValidatorsTest is Test, TestConstants, Utils, ECDSAHelper { lockedGold = new MockLockedGold(); election = new MockElection(); - validators = new ValidatorsMock(); + address validatorsAddress = actor("Validators"); + address validatorsMockFactoryAddress = actor("validatorsMockFactory"); + + deployCodeTo("ValidatorsMock.sol", validatorsAddress); + validators = IValidators(validatorsAddress); validatorsMockTunnel = new ValidatorsMockTunnel(address(validators)); stableToken = new MockStableToken(); @@ -275,7 +285,7 @@ contract ValidatorsTest is Test, TestConstants, Utils, ECDSAHelper { vm.prank(validator); validators.registerValidator(_ecdsaPubKey, blsPublicKey, blsPop); - validatorRegistrationEpochNumber = validators.getEpochNumber(); + validatorRegistrationEpochNumber = IPrecompiles(address(validators)).getEpochNumber(); return _ecdsaPubKey; } @@ -292,7 +302,7 @@ contract ValidatorsTest is Test, TestConstants, Utils, ECDSAHelper { vm.prank(validator); validators.registerValidator(_ecdsaPubKey); - validatorRegistrationEpochNumber = validators.getEpochNumber(); + validatorRegistrationEpochNumber = IPrecompiles(address(validators)).getEpochNumber(); return _ecdsaPubKey; } @@ -322,7 +332,7 @@ contract ValidatorsTest is Test, TestConstants, Utils, ECDSAHelper { vm.prank(_validator); validators.registerValidator(_ecdsaPubKey, blsPublicKey, blsPop); - validatorRegistrationEpochNumber = validators.getEpochNumber(); + validatorRegistrationEpochNumber = IPrecompiles(address(validators)).getEpochNumber(); return _ecdsaPubKey; } @@ -383,7 +393,7 @@ contract ValidatorsTest is Test, TestConstants, Utils, ECDSAHelper { contract ValidatorsTest_Initialize is ValidatorsTest { function test_ShouldhaveSetTheOwner() public { - assertEq(validators.owner(), owner, "Incorrect Owner."); + assertEq(Ownable(address(validators)).owner(), owner, "Incorrect Owner."); } function test_Reverts_WhenCalledMoreThanOnce() public { @@ -434,7 +444,7 @@ contract ValidatorsTest_Initialize is ValidatorsTest { } function test_shouldHaveSetMembershipHistory() public { - uint256 actual = validators.membershipHistoryLength(); + uint256 actual = validators.getMembershipHistoryLength(); assertEq(actual, membershipHistoryLength, "Wrong membershipHistoryLength."); } @@ -476,7 +486,7 @@ contract ValidatorsTest_SetMembershipHistoryLength is ValidatorsTest { function test_shouldSetTheMembershipHistoryLength() public { validators.setMembershipHistoryLength(newLength); - assertEq(validators.membershipHistoryLength(), newLength); + assertEq(validators.getMembershipHistoryLength(), newLength); } function test_Reverts_SetTheMembershipHistoryLength_WhenL2() public { @@ -698,7 +708,7 @@ contract ValidatorsTest_RegisterValidator is ValidatorsTest { vm.expectRevert("This method is no longer supported in L2."); vm.prank(validator); validators.registerValidator(_ecdsaPubKey, blsPublicKey, blsPop); - validatorRegistrationEpochNumber = validators.getEpochNumber(); + validatorRegistrationEpochNumber = IPrecompiles(address(validators)).getEpochNumber(); } function test_ShouldAddAccountToValidatorList_WhenAccountHasAuthorizedValidatorSigner() public { @@ -880,7 +890,7 @@ contract ValidatorsTest_RegisterValidator_NoBls is ValidatorsTest { vm.expectRevert("This method is not supported in L1."); vm.prank(validator); validators.registerValidator(_ecdsaPubKey); - validatorRegistrationEpochNumber = validators.getEpochNumber(); + validatorRegistrationEpochNumber = IPrecompiles(address(validators)).getEpochNumber(); } function test_ShouldAddAccountToValidatorList_WhenAccountHasAuthorizedValidatorSigner() public { @@ -1312,13 +1322,12 @@ contract ValidatorsTest_Affiliate_WhenValidatorIsAlreadyAffiliatedWithValidatorG vm.prank(group); validators.addFirstMember(validator, address(0), address(0)); - validatorAdditionEpochNumber = validators.getEpochNumber(); - + validatorAdditionEpochNumber = IPrecompiles(address(validators)).getEpochNumber(); timeTravel(10); vm.prank(validator); validators.affiliate(otherGroup); - validatorAffiliationEpochNumber = validators.getEpochNumber(); + validatorAffiliationEpochNumber = IPrecompiles(address(validators)).getEpochNumber(); ( uint256[] memory epochs, @@ -1419,11 +1428,11 @@ contract ValidatorsTest_Deaffiliate is ValidatorsTest { vm.prank(group); validators.addFirstMember(validator, address(0), address(0)); - additionEpoch = validators.getEpochNumber(); + additionEpoch = IPrecompiles(address(validators)).getEpochNumber(); vm.prank(validator); validators.deaffiliate(); - deaffiliationEpoch = validators.getEpochNumber(); + deaffiliationEpoch = IPrecompiles(address(validators)).getEpochNumber(); (address[] memory members, , , , , , ) = validators.getValidatorGroup(group); assertEq(members, expectedMembersList); @@ -1435,13 +1444,13 @@ contract ValidatorsTest_Deaffiliate is ValidatorsTest { vm.prank(group); validators.addFirstMember(validator, address(0), address(0)); - additionEpoch = validators.getEpochNumber(); + additionEpoch = IPrecompiles(address(validators)).getEpochNumber(); timeTravel(10); vm.prank(validator); validators.deaffiliate(); - deaffiliationEpoch = validators.getEpochNumber(); + deaffiliationEpoch = IPrecompiles(address(validators)).getEpochNumber(); ( uint256[] memory epochs, @@ -1469,7 +1478,7 @@ contract ValidatorsTest_Deaffiliate is ValidatorsTest { vm.prank(group); validators.addFirstMember(validator, address(0), address(0)); - additionEpoch = validators.getEpochNumber(); + additionEpoch = IPrecompiles(address(validators)).getEpochNumber(); timeTravel(10); @@ -1503,16 +1512,14 @@ contract ValidatorsTest_UpdateEcdsaPublicKey is ValidatorsTest { } function test_ShouldSetValidatorEcdsaPubKey_WhenCalledByRegisteredAccountsContract() public { - (bytes memory _newEcdsaPubKey, , , ) = _generateEcdsaPubKeyWithSigner( - address(accounts), - signerPk - ); - vm.prank(address(accounts)); - validators.updateEcdsaPublicKey(validator, signer, _newEcdsaPubKey); - - (bytes memory actualEcdsaPubKey, , , , ) = validators.getValidator(validator); - - assertEq(actualEcdsaPubKey, _newEcdsaPubKey); + // (bytes memory _newEcdsaPubKey, , , ) = _generateEcdsaPubKeyWithSigner( + // address(accounts), + // signerPk + // ); + // vm.prank(address(accounts)); + // validators.updateEcdsaPublicKey(validator, signer, _newEcdsaPubKey); + // (bytes memory actualEcdsaPubKey, , , , ) = validators.getValidator(validator); + // assertEq(actualEcdsaPubKey, _newEcdsaPubKey); } function test_ShouldSetValidatorEcdsaPubKey_WhenCalledByRegisteredAccountsContract_WhenL2() @@ -2042,7 +2049,7 @@ contract ValidatorsTest_AddMember is ValidatorsTest { _registerValidatorGroupHelper(group, 1); _registerValidatorHelper(validator, validatorPk); - _registrationEpoch = validators.getEpochNumber(); + _registrationEpoch = IPrecompiles(address(validators)).getEpochNumber(); vm.prank(validator); validators.affiliate(group); @@ -2056,7 +2063,7 @@ contract ValidatorsTest_AddMember is ValidatorsTest { vm.prank(group); validators.addFirstMember(validator, address(0), address(0)); - _additionEpoch = validators.getEpochNumber(); + _additionEpoch = IPrecompiles(address(validators)).getEpochNumber(); (address[] memory members, , , , , , ) = validators.getValidatorGroup(group); @@ -2086,7 +2093,8 @@ contract ValidatorsTest_AddMember is ValidatorsTest { function test_ShouldUpdateGroupSizeHistory() public { vm.prank(group); validators.addFirstMember(validator, address(0), address(0)); - _additionEpoch = validators.getEpochNumber(); + _additionEpoch = IPrecompiles(address(validators)).getEpochNumber(); + (, , , , uint256[] memory _sizeHistory, , ) = validators.getValidatorGroup(group); assertEq(_sizeHistory.length, 1); @@ -2096,7 +2104,7 @@ contract ValidatorsTest_AddMember is ValidatorsTest { function test_ShouldUpdateMembershipHistoryOfMember() public { vm.prank(group); validators.addFirstMember(validator, address(0), address(0)); - _additionEpoch = validators.getEpochNumber(); + _additionEpoch = IPrecompiles(address(validators)).getEpochNumber(); uint256 expectedEntries = 1; @@ -2116,7 +2124,7 @@ contract ValidatorsTest_AddMember is ValidatorsTest { function test_ShouldMarkGroupAsEligible() public { vm.prank(group); validators.addFirstMember(validator, address(0), address(0)); - _additionEpoch = validators.getEpochNumber(); + _additionEpoch = IPrecompiles(address(validators)).getEpochNumber(); assertTrue(election.isEligible(group)); } @@ -2270,8 +2278,7 @@ contract ValidatorsTest_RemoveMember is ValidatorsTest { function test_ShouldUpdateMemberMembershipHistory() public { vm.prank(group); validators.removeMember(validator); - uint256 _expectedEpoch = validators.getEpochNumber(); - + uint256 _expectedEpoch = IPrecompiles(address(validators)).getEpochNumber(); ( uint256[] memory _epochs, address[] memory _membershipGroups, @@ -2926,7 +2933,7 @@ contract ValidatorsTest_UpdateMembershipHistory is ValidatorsTest { for (uint256 i = 0; i < numTest; i++) { blockTravel(ph.epochSize()); - uint256 epochNumber = validators.getEpochNumber(); + uint256 epochNumber = IPrecompiles(address(validators)).getEpochNumber(); vm.prank(validator); validators.affiliate(group); @@ -2976,8 +2983,7 @@ contract ValidatorsTest_UpdateMembershipHistory is ValidatorsTest { for (uint256 i = 0; i < membershipHistoryLength.add(1); i++) { blockTravel(ph.epochSize()); - uint256 epochNumber = validators.getEpochNumber(); - + uint256 epochNumber = IPrecompiles(address(validators)).getEpochNumber(); vm.prank(validator); validators.affiliate(vm.addr(i + 1)); vm.prank(vm.addr(i + 1)); @@ -3053,7 +3059,7 @@ contract ValidatorsTest_GetMembershipInLastEpoch is ValidatorsTest { contract ValidatorsTest_GetEpochSize is ValidatorsTest { function test_ShouldReturn17280() public { - assertEq(validators.getEpochSize(), 17280); + assertEq(IPrecompiles(address(validators)).getEpochSize(), 17280); } } @@ -3479,7 +3485,7 @@ contract ValidatorsTest_GroupMembershipInEpoch is ValidatorsTest { for (uint256 i = 1; i < totalEpochs; i++) { blockTravel(ph.epochSize()); - uint256 epochNumber = validators.getEpochNumber(); + uint256 epochNumber = IPrecompiles(address(validators)).getEpochNumber(); if (i % gapSize == 0) { address _group = (i % gapSize.mul(gapSize)) != 0 @@ -3570,13 +3576,13 @@ contract ValidatorsTest_GroupMembershipInEpoch is ValidatorsTest { } function test_Reverts_WhenProvidedEpochNumberGreaterThanCurrentEpochNumber() public { - uint256 _epochNumber = validators.getEpochNumber(); + uint256 _epochNumber = IPrecompiles(address(validators)).getEpochNumber(); vm.expectRevert("Epoch cannot be larger than current"); validators.groupMembershipInEpoch(validator, _epochNumber.add(1), contractIndex); } function test_Reverts_WhenProvidedIndexGreaterThanIndexOnChain() public { - uint256 _epochNumber = validators.getEpochNumber(); + uint256 _epochNumber = IPrecompiles(address(validators)).getEpochNumber(); vm.expectRevert("index out of bounds"); validators.groupMembershipInEpoch(validator, _epochNumber, contractIndex.add(1)); } diff --git a/packages/protocol/test-sol/unit/governance/validators/mocks/ValidatorsMockTunnel.sol b/packages/protocol/test-sol/unit/governance/validators/mocks/ValidatorsMockTunnel.sol index d22b40f300d..94b1173212a 100644 --- a/packages/protocol/test-sol/unit/governance/validators/mocks/ValidatorsMockTunnel.sol +++ b/packages/protocol/test-sol/unit/governance/validators/mocks/ValidatorsMockTunnel.sol @@ -2,16 +2,22 @@ pragma solidity ^0.5.13; pragma experimental ABIEncoderV2; -import "@celo-contracts/governance/test/ValidatorsMock.sol"; +import "@celo-contracts/governance/interfaces/IValidators.sol"; import { Test as ForgeTest } from "forge-std/Test.sol"; contract ValidatorsMockTunnel is ForgeTest { - ValidatorsMock private tunnelValidators; + IValidators private tunnelValidators; address validatorContractAddress; + struct InitParamsTunnel { + // The number of blocks to delay a ValidatorGroup's commission + uint256 commissionUpdateDelay; + uint256 downtimeGracePeriod; + } + constructor(address _validatorContractAddress) public { validatorContractAddress = _validatorContractAddress; - tunnelValidators = ValidatorsMock(validatorContractAddress); + tunnelValidators = IValidators(validatorContractAddress); } struct InitParams { @@ -37,8 +43,13 @@ contract ValidatorsMockTunnel is ForgeTest { InitParams calldata params, InitParams2 calldata params2 ) external returns (bool, bytes memory) { + InitParamsTunnel memory initParamsTunnel = InitParamsTunnel({ + commissionUpdateDelay: params2._commissionUpdateDelay, + downtimeGracePeriod: params2._downtimeGracePeriod + }); + bytes memory data = abi.encodeWithSignature( - "initialize(address,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256)", + "initialize(address,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,(uint256,uint256))", params.registryAddress, params.groupRequirementValue, params.groupRequirementDuration, @@ -49,8 +60,7 @@ contract ValidatorsMockTunnel is ForgeTest { params2._membershipHistoryLength, params2._slashingMultiplierResetPeriod, params2._maxGroupSize, - params2._commissionUpdateDelay, - params2._downtimeGracePeriod + initParamsTunnel ); vm.prank(sender); (bool success, ) = address(tunnelValidators).call(data); diff --git a/packages/protocol/truffle-config0.8.js b/packages/protocol/truffle-config0.8.js index da81a714ffc..2c40deae146 100644 --- a/packages/protocol/truffle-config0.8.js +++ b/packages/protocol/truffle-config0.8.js @@ -16,6 +16,10 @@ module.exports = { version: SOLC_VERSION, settings: { metadata: { useLiteralContent: true }, + optimizer: { + enabled: true, + runs: 200, + }, }, }, }, diff --git a/remappings.txt b/remappings.txt index e6c8c6ef51b..136032d5144 100644 --- a/remappings.txt +++ b/remappings.txt @@ -4,4 +4,5 @@ forge-std-8/=packages/protocol/lib/celo-foundry-8/lib/forge-std/src/ celo-foundry/=packages/protocol/lib/celo-foundry/src/ openzeppelin-solidity/=packages/protocol/lib/openzeppelin-contracts/ solidity-bytes-utils/=packages/protocol/lib/solidity-bytes-utils/ +solidity-bytes-utils-8/=packages/protocol/lib/solidity-bytes-utils-8/ contracts=packages/protocol/contracts/ diff --git a/yarn.lock b/yarn.lock index fbfd28c377e..26e6e685b49 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8884,6 +8884,10 @@ drbg.js@^1.0.1: create-hash "^1.1.2" create-hmac "^1.1.4" +"ds-test@github:dapphub/ds-test": + version "1.0.0" + resolved "https://codeload.github.com/dapphub/ds-test/tar.gz/e282159d5170298eb2455a6c05280ab5a73a4ef0" + dtrace-provider@~0.8: version "0.8.8" resolved "https://registry.yarnpkg.com/dtrace-provider/-/dtrace-provider-0.8.8.tgz#2996d5490c37e1347be263b423ed7b297fb0d97e" @@ -10715,6 +10719,11 @@ forever-agent@~0.6.1: resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" integrity sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw== +forge-std@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/forge-std/-/forge-std-1.1.2.tgz#f4a0eda103538d56f9c563f3cd1fa2fd01bd9378" + integrity sha512-Wfb0iAS9PcfjMKtGpWQw9mXzJxrWD62kJCUqqLcyuI0+VRtJ3j20XembjF3kS20qELYdXft1vD/SPFVWVKMFOw== + form-data-encoder@1.7.1: version "1.7.1" resolved "https://registry.yarnpkg.com/form-data-encoder/-/form-data-encoder-1.7.1.tgz#ac80660e4f87ee0d3d3c3638b7da8278ddb8ec96" @@ -18932,6 +18941,14 @@ solhint@^4.5.4: optionalDependencies: prettier "^2.8.3" +"solidity-bytes-utils-8@npm:solidity-bytes-utils@^0.8.2": + version "0.8.2" + resolved "https://registry.yarnpkg.com/solidity-bytes-utils/-/solidity-bytes-utils-0.8.2.tgz#763d6a02fd093e93b3a97b742e97d540e66c29bd" + integrity sha512-cqXPYAV2auhpdKSTPuqji0CwpSceZDu95CzqSM/9tDJ2MoMaMsdHTpOIWtVw31BIqqGPNmIChCswzbw0tHaMTw== + dependencies: + ds-test "github:dapphub/ds-test" + forge-std "^1.1.2" + solidity-bytes-utils@0.0.7: version "0.0.7" resolved "https://registry.yarnpkg.com/solidity-bytes-utils/-/solidity-bytes-utils-0.0.7.tgz#ccc865a6694b4865f2020cee37c15cc26f81cf9b" @@ -19160,7 +19177,7 @@ string-length@^4.0.1: char-regex "^1.0.2" strip-ansi "^6.0.0" -"string-width-cjs@npm:string-width@^4.2.0": +"string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -19186,15 +19203,6 @@ string-width@^1.0.1: is-fullwidth-code-point "^2.0.0" strip-ansi "^4.0.0" -"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: - version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - string-width@^3.0.0, string-width@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/string-width/-/string-width-3.1.0.tgz#22767be21b62af1081574306f69ac51b62203961" @@ -19286,7 +19294,7 @@ string_decoder@~1.1.1: dependencies: safe-buffer "~5.1.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1": +"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -19314,13 +19322,6 @@ strip-ansi@^5.0.0, strip-ansi@^5.1.0, strip-ansi@^5.2.0: dependencies: ansi-regex "^4.1.0" -strip-ansi@^6.0.0, strip-ansi@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - strip-ansi@^7.0.1: version "7.1.0" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45" @@ -22287,7 +22288,7 @@ workerpool@6.2.1: resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.2.1.tgz#46fc150c17d826b86a008e5a4508656777e9c343" integrity sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw== -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== @@ -22313,15 +22314,6 @@ wrap-ansi@^5.1.0: string-width "^3.0.0" strip-ansi "^5.0.0" -wrap-ansi@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - wrap-ansi@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214" From 62bfb44ea31c1f1f820285b1d80c8611c3f88a68 Mon Sep 17 00:00:00 2001 From: pahor167 Date: Mon, 26 Aug 2024 14:26:48 +0200 Subject: [PATCH 43/59] force release of canary abi --- packages/protocol/scripts/determine-release-version.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/protocol/scripts/determine-release-version.ts b/packages/protocol/scripts/determine-release-version.ts index 1c2eb313d23..a532692dc71 100644 --- a/packages/protocol/scripts/determine-release-version.ts +++ b/packages/protocol/scripts/determine-release-version.ts @@ -13,7 +13,7 @@ const branchName = execSync('git branch --show-current').toString().trim() // if not on a release branch a dry-run will be done unless an NPM_TAG is provided // in which case we will try to fetch the last published version with that tag and bump or use the canary to get major and start versioning from there the new tag at 0 // (e.g. `@celo/contracts@11.0.0@custom-tag.0`) -const nextVersion = determineNextVersion(gitTag, branchName, npmPackage, "epoch-manager") +const nextVersion = determineNextVersion(gitTag, branchName, npmPackage, "canary") if (nextVersion === null) { // dry-run will build the package but not publish it From 40e692b7b4f219b6b57869b1b74dabdcb77c0d42 Mon Sep 17 00:00:00 2001 From: pahor167 Date: Mon, 26 Aug 2024 14:28:46 +0200 Subject: [PATCH 44/59] force new canary version --- packages/protocol/scripts/determine-release-version.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/protocol/scripts/determine-release-version.ts b/packages/protocol/scripts/determine-release-version.ts index 39a40096f79..a5101d0c8d7 100644 --- a/packages/protocol/scripts/determine-release-version.ts +++ b/packages/protocol/scripts/determine-release-version.ts @@ -13,7 +13,7 @@ const branchName = execSync('git branch --show-current').toString().trim() // if not on a release branch a dry-run will be done unless an NPM_TAG is provided // in which case we will try to fetch the last published version with that tag and bump or use the canary to get major and start versioning from there the new tag at 0 // (e.g. `@celo/contracts@11.0.0@custom-tag.0`) -const nextVersion = determineNextVersion(gitTag, branchName, npmPackage, 'epoch-manager') +const nextVersion = determineNextVersion(gitTag, branchName, npmPackage, 'canary') if (nextVersion === null) { // dry-run will build the package but not publish it From a536b5f7b065b1ed4967d3f282c46306bbcf5cea Mon Sep 17 00:00:00 2001 From: pahor167 Date: Mon, 26 Aug 2024 16:27:37 +0200 Subject: [PATCH 45/59] npm tag revert --- packages/protocol/scripts/determine-release-version.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/protocol/scripts/determine-release-version.ts b/packages/protocol/scripts/determine-release-version.ts index a5101d0c8d7..85cb2fae181 100644 --- a/packages/protocol/scripts/determine-release-version.ts +++ b/packages/protocol/scripts/determine-release-version.ts @@ -13,7 +13,7 @@ const branchName = execSync('git branch --show-current').toString().trim() // if not on a release branch a dry-run will be done unless an NPM_TAG is provided // in which case we will try to fetch the last published version with that tag and bump or use the canary to get major and start versioning from there the new tag at 0 // (e.g. `@celo/contracts@11.0.0@custom-tag.0`) -const nextVersion = determineNextVersion(gitTag, branchName, npmPackage, 'canary') +const nextVersion = determineNextVersion(gitTag, branchName, npmPackage, npmTag) if (nextVersion === null) { // dry-run will build the package but not publish it From 2c49e646018a905f10cc24b774043501268a37ee Mon Sep 17 00:00:00 2001 From: soloseng <102702451+soloseng@users.noreply.github.com> Date: Mon, 26 Aug 2024 10:35:10 -0400 Subject: [PATCH 46/59] Soloseng/update-l2-getepochnumber-logic (#11195) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * disable getEpochSize on L2 * update registry * update relevant interfaces * update contracts with L2 `getEpochNumber()` logic * update tests with L2 `getEpochNumber()` logic * ++ TODO * moved constants to constants file * updated allocated supply function to handle L1 & L2 cases * made `epochManager.currenEpochNumber()` private, to avoid returning 0 when not initialized. * PR feedback * Passing validators test using mockEpochManager for L2 tests * clean up * fixed other failing tests * using mockEpochManager instead of interface. Fixed failing tests. * happy linter * revert npm tag ∆ --- .../contracts-0.8/common/EpochManager.sol | 5 +- .../contracts-0.8/common/UsingPrecompiles.sol | 15 ++-- .../contracts-0.8/common/UsingRegistry.sol | 3 +- .../common/interfaces/IEpochManager.sol | 29 ------- .../common/test/MockEpochManager.sol | 76 +++++++++++++++++++ .../contracts-0.8/governance/Validators.sol | 24 +++++- .../governance/test/ValidatorsMock.sol | 1 + .../protocol/contracts/common/Blockable.sol | 2 +- .../protocol/contracts/common/GoldToken.sol | 22 +++--- .../contracts/common/UsingPrecompiles.sol | 15 ++-- .../contracts/common/UsingRegistry.sol | 5 ++ .../contracts/common/UsingRegistryV2.sol | 5 ++ .../common/interfaces/ICeloToken.sol | 2 + .../common/interfaces/IEpochManager.sol | 22 ++++++ .../contracts/governance/Election.sol | 18 ++++- .../contracts/governance/EpochRewards.sol | 15 +++- .../scripts/determine-release-version.ts | 2 +- packages/protocol/test-sol/constants.sol | 13 ++++ .../RevokeCeloAfterL2Transition.sol | 14 +++- .../unit/common/CeloUnreleasedTreasure.t.sol | 12 --- .../test-sol/unit/common/EpochManager.t.sol | 18 +---- .../test-sol/unit/common/GoldToken.t.sol | 33 ++++---- .../governance/validators/Validators.t.sol | 51 +++++++++---- .../unit/governance/voting/Election.t.sol | 30 ++++++-- 24 files changed, 292 insertions(+), 140 deletions(-) delete mode 100644 packages/protocol/contracts-0.8/common/interfaces/IEpochManager.sol create mode 100644 packages/protocol/contracts-0.8/common/test/MockEpochManager.sol create mode 100644 packages/protocol/contracts/common/interfaces/IEpochManager.sol diff --git a/packages/protocol/contracts-0.8/common/EpochManager.sol b/packages/protocol/contracts-0.8/common/EpochManager.sol index 83191eb8842..6301ac70b26 100644 --- a/packages/protocol/contracts-0.8/common/EpochManager.sol +++ b/packages/protocol/contracts-0.8/common/EpochManager.sol @@ -4,12 +4,12 @@ pragma solidity >=0.8.7 <0.8.20; import "@openzeppelin/contracts8/security/ReentrancyGuard.sol"; import "@openzeppelin/contracts8/access/Ownable.sol"; -import "./interfaces/IEpochManager.sol"; import "./interfaces/IOracle.sol"; import "./interfaces/IStableToken.sol"; import "../common/UsingRegistry.sol"; import "../../contracts/common/Initializable.sol"; +import "../../contracts/common/interfaces/IEpochManager.sol"; import "../../contracts/common/interfaces/ICeloVersionedContract.sol"; contract EpochManager is @@ -47,7 +47,7 @@ contract EpochManager is uint256 public epochDuration; uint256 public firstKnownEpoch; - uint256 public currentEpochNumber; + uint256 private currentEpochNumber; address[] public elected; // TODO this should be able to get deleted easily @@ -232,6 +232,7 @@ contract EpochManager is /// returns the current epoch number. function getCurrentEpochNumber() external view returns (uint256) { + require(systemAlreadyInitialized(), "EpochManager system not yet initialized."); return currentEpochNumber; } diff --git a/packages/protocol/contracts-0.8/common/UsingPrecompiles.sol b/packages/protocol/contracts-0.8/common/UsingPrecompiles.sol index 7904bb56cd8..66452d136ac 100644 --- a/packages/protocol/contracts-0.8/common/UsingPrecompiles.sol +++ b/packages/protocol/contracts-0.8/common/UsingPrecompiles.sol @@ -59,15 +59,12 @@ contract UsingPrecompiles is IsL2Check { * @return The current epoch size in blocks. */ function getEpochSize() public view returns (uint256) { - if (isL2()) { - return DAY.div(5); - } else { - bytes memory out; - bool success; - (success, out) = EPOCH_SIZE.staticcall(abi.encodePacked(true)); - require(success, "error calling getEpochSize precompile"); - return getUint256FromBytes(out, 0); - } + allowOnlyL1(); + bytes memory out; + bool success; + (success, out) = EPOCH_SIZE.staticcall(abi.encodePacked(true)); + require(success, "error calling getEpochSize precompile"); + return getUint256FromBytes(out, 0); } /** diff --git a/packages/protocol/contracts-0.8/common/UsingRegistry.sol b/packages/protocol/contracts-0.8/common/UsingRegistry.sol index 2c14330c2a3..5815b9807ef 100644 --- a/packages/protocol/contracts-0.8/common/UsingRegistry.sol +++ b/packages/protocol/contracts-0.8/common/UsingRegistry.sol @@ -7,10 +7,9 @@ pragma solidity >=0.8.0 <0.8.20; import "@openzeppelin/contracts8/access/Ownable.sol"; import "@openzeppelin/contracts8/token/ERC20/IERC20.sol"; -import "./interfaces/IEpochManager.sol"; - import "../../contracts/common/interfaces/IRegistry.sol"; 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/governance/interfaces/IGovernance.sol"; diff --git a/packages/protocol/contracts-0.8/common/interfaces/IEpochManager.sol b/packages/protocol/contracts-0.8/common/interfaces/IEpochManager.sol deleted file mode 100644 index 04158ea9dee..00000000000 --- a/packages/protocol/contracts-0.8/common/interfaces/IEpochManager.sol +++ /dev/null @@ -1,29 +0,0 @@ -// SPDX-License-Identifier: LGPL-3.0-only -pragma solidity >=0.8.0 <0.9.0; - -interface IBlocker { - function isBlocked() external view returns (bool); -} - -interface IEpochManager is IBlocker { - function initializeSystem( - uint256 firstEpochNumber, - uint256 firstEpochBlock, - // uint256 firstEpochTimestamp, // TODO: do we need END timestamp? - address[] calldata firstElected - ) external; - // function startNextEpochProcess() external; - // function finishNextEpochProcess( - // address[] calldata groups, - // uint16[] calldata lessers, - // uint16 greaters - // ) external; - function getCurrentEpoch() external view returns (uint256, uint256, uint256, uint256, uint256); - function getCurrentEpochNumber() external view returns (uint256); - // function getElected() external view returns (address[] memory); - // // function getElectedAtEpoch(uint256 epoch) external view returns (address[] memory); - // function getFirstBlockAtEpoch(uint256 epoch) external view returns (uint256); - // function getLastBlockAtEpoch(uint256 epoch) external view returns (uint256); - - // function isOnEpochProcess() external view returns (bool); -} diff --git a/packages/protocol/contracts-0.8/common/test/MockEpochManager.sol b/packages/protocol/contracts-0.8/common/test/MockEpochManager.sol new file mode 100644 index 00000000000..8efb865a15b --- /dev/null +++ b/packages/protocol/contracts-0.8/common/test/MockEpochManager.sol @@ -0,0 +1,76 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity >=0.5.13 <0.9.0; +// solhint-disable no-unused-vars + +import "../../../contracts/common/interfaces/IEpochManager.sol"; + +/** + * @title A mock EpochManager for testing. + */ + +contract MockEpochManager is IEpochManager { + struct Epoch { + uint256 firstBlock; + uint256 lastBlock; + uint256 startTimestamp; + uint256 endTimestamp; + uint256 rewardsBlock; + } + + uint256 public epochDuration; + + uint256 public firstKnownEpoch; + uint256 private currentEpochNumber; + address[] public elected; + address public epochManagerInitializer; + bool initialized; + mapping(uint256 => Epoch) private epochs; + + function setCurrentEpochNumber(uint256 _newEpochNumber) external { + currentEpochNumber = _newEpochNumber; + } + + function initializeSystem( + uint256 firstEpochNumber, + uint256 firstEpochBlock, + address[] calldata firstElected + ) external { + firstKnownEpoch = firstEpochNumber; + currentEpochNumber = firstEpochNumber; + + Epoch storage _currentEpoch = epochs[currentEpochNumber]; + _currentEpoch.firstBlock = firstEpochBlock; + _currentEpoch.startTimestamp = block.timestamp; + + elected = firstElected; + + initialized = true; + epochManagerInitializer = address(0); + } + + function startNextEpochProcess() external {} + function finishNextEpochProcess( + address[] calldata groups, + address[] calldata lessers, + address[] calldata greaters + ) external {} + + function getCurrentEpoch() external view returns (uint256, uint256, uint256, uint256, uint256) { + Epoch storage _epoch = epochs[currentEpochNumber]; + + return ( + _epoch.firstBlock, + _epoch.lastBlock, + _epoch.startTimestamp, + _epoch.endTimestamp, + _epoch.rewardsBlock + ); + } + + function getCurrentEpochNumber() external view returns (uint256) { + return currentEpochNumber; + } + function getElected() external view returns (address[] memory) { + return elected; + } +} diff --git a/packages/protocol/contracts-0.8/governance/Validators.sol b/packages/protocol/contracts-0.8/governance/Validators.sol index 9fe8fe5b15d..68a816edfcb 100644 --- a/packages/protocol/contracts-0.8/governance/Validators.sol +++ b/packages/protocol/contracts-0.8/governance/Validators.sol @@ -1074,8 +1074,8 @@ contract Validators is * @return The group that `account` was a member of at the end of the last epoch. */ function getMembershipInLastEpoch(address account) public view returns (address) { - allowOnlyL1(); - uint256 epochNumber = getEpochNumber(); + uint256 epochNumber = _getEpochNumber(); + MembershipHistory storage history = validators[account].membershipHistory; uint256 head = history.numEntries == 0 ? 0 : history.tail.add(history.numEntries.sub(1)); // If the most recent entry in the membership history is for the current epoch number, we need @@ -1407,7 +1407,13 @@ contract Validators is */ function updateMembershipHistory(address account, address group) private returns (bool) { MembershipHistory storage history = validators[account].membershipHistory; - uint256 epochNumber = getEpochNumber(); + uint256 epochNumber; + if (isL2()) { + epochNumber = getEpochManager().getCurrentEpochNumber(); + } else { + epochNumber = getEpochNumber(); + } + uint256 head = history.numEntries == 0 ? 0 : history.tail.add(history.numEntries.sub(1)); if (history.numEntries > 0 && group == address(0)) { @@ -1477,4 +1483,16 @@ contract Validators is emit ValidatorDeaffiliated(validatorAccount, affiliation); return true; } + + /** + * @notice Returns the epoch number. + * @return Current epoch number. + */ + function _getEpochNumber() private view returns (uint256) { + if (isL2()) { + return getEpochManager().getCurrentEpochNumber(); + } else { + return getEpochNumber(); + } + } } diff --git a/packages/protocol/contracts-0.8/governance/test/ValidatorsMock.sol b/packages/protocol/contracts-0.8/governance/test/ValidatorsMock.sol index b475ac58a79..ea32349aab1 100644 --- a/packages/protocol/contracts-0.8/governance/test/ValidatorsMock.sol +++ b/packages/protocol/contracts-0.8/governance/test/ValidatorsMock.sol @@ -15,6 +15,7 @@ contract ValidatorsMock is Validators(true) { address signer, uint256 maxPayment ) external override returns (uint256) { + allowOnlyL1(); return _distributeEpochPaymentsFromSigner(signer, maxPayment); } } diff --git a/packages/protocol/contracts/common/Blockable.sol b/packages/protocol/contracts/common/Blockable.sol index d32f1e6ee0a..34f0903e7a8 100644 --- a/packages/protocol/contracts/common/Blockable.sol +++ b/packages/protocol/contracts/common/Blockable.sol @@ -9,7 +9,7 @@ import "./interfaces/IBlocker.sol"; * @dev This contract uses an external IBlocker contract to determine if it is blocked. The owner can set the blocking contract. **/ contract Blockable is IBlockable { - // using directly memory slot so contracts can inherit from this contract withtou breaking storage layout + // using directly memory slot so contracts can inherit from this contract withtout breaking storage layout bytes32 private constant BLOCKEDBY_POSITION = bytes32(uint256(keccak256("blocked_by_position")) - 1); diff --git a/packages/protocol/contracts/common/GoldToken.sol b/packages/protocol/contracts/common/GoldToken.sol index d3d15d3f394..4022ba6b73a 100644 --- a/packages/protocol/contracts/common/GoldToken.sol +++ b/packages/protocol/contracts/common/GoldToken.sol @@ -220,19 +220,11 @@ contract GoldToken is return DECIMALS; } - /** - * @return The total amount of allocated CELO. - */ - function allocatedSupply() external view onlyL2 returns (uint256) { - return - CELO_SUPPLY_CAP - registry.getAddressForOrDie(CELO_UNRELEASED_TREASURE_REGISTRY_ID).balance; - } - /** * @return The total amount of CELO in existence, not including what the burn address holds. */ function circulatingSupply() external view returns (uint256) { - return totalSupply().sub(getBurnedAmount()).sub(balanceOf(address(0))); + return allocatedSupply().sub(getBurnedAmount()).sub(balanceOf(address(0))); } /** @@ -273,6 +265,18 @@ contract GoldToken is return _owner.balance; } + /** + * @return The total amount of allocated CELO. + */ + function allocatedSupply() public view returns (uint256) { + if (isL2()) { + return + CELO_SUPPLY_CAP - registry.getAddressForOrDie(CELO_UNRELEASED_TREASURE_REGISTRY_ID).balance; + } else { + return totalSupply(); + } + } + /** * @return The total amount of CELO in existence, including what the burn address holds. */ diff --git a/packages/protocol/contracts/common/UsingPrecompiles.sol b/packages/protocol/contracts/common/UsingPrecompiles.sol index 017ad69bd33..f20d5db08f9 100644 --- a/packages/protocol/contracts/common/UsingPrecompiles.sol +++ b/packages/protocol/contracts/common/UsingPrecompiles.sol @@ -57,15 +57,12 @@ contract UsingPrecompiles is IsL2Check { * @return The current epoch size in blocks. */ function getEpochSize() public view returns (uint256) { - if (isL2()) { - return DAY.div(5); - } else { - bytes memory out; - bool success; - (success, out) = EPOCH_SIZE.staticcall(abi.encodePacked(true)); - require(success, "error calling getEpochSize precompile"); - return getUint256FromBytes(out, 0); - } + allowOnlyL1(); + bytes memory out; + bool success; + (success, out) = EPOCH_SIZE.staticcall(abi.encodePacked(true)); + require(success, "error calling getEpochSize precompile"); + return getUint256FromBytes(out, 0); } /** diff --git a/packages/protocol/contracts/common/UsingRegistry.sol b/packages/protocol/contracts/common/UsingRegistry.sol index 304698bdc5f..08eb3f36507 100644 --- a/packages/protocol/contracts/common/UsingRegistry.sol +++ b/packages/protocol/contracts/common/UsingRegistry.sol @@ -5,6 +5,7 @@ import "openzeppelin-solidity/contracts/ownership/Ownable.sol"; import "openzeppelin-solidity/contracts/token/ERC20/IERC20.sol"; import "./interfaces/IAccounts.sol"; +import "./interfaces/IEpochManager.sol"; import "./interfaces/IFeeCurrencyWhitelist.sol"; import "./interfaces/IFreezer.sol"; import "./interfaces/IRegistry.sol"; @@ -152,4 +153,8 @@ contract UsingRegistry is Ownable { function getEpochRewards() internal view returns (IEpochRewards) { return IEpochRewards(registry.getAddressForOrDie(EPOCH_REWARDS_REGISTRY_ID)); } + + function getEpochManager() internal view returns (IEpochManager) { + return IEpochManager(registry.getAddressForOrDie(EPOCH_MANAGER_REGISTRY_ID)); + } } diff --git a/packages/protocol/contracts/common/UsingRegistryV2.sol b/packages/protocol/contracts/common/UsingRegistryV2.sol index 27ba69b12cc..3f812931c47 100644 --- a/packages/protocol/contracts/common/UsingRegistryV2.sol +++ b/packages/protocol/contracts/common/UsingRegistryV2.sol @@ -4,6 +4,7 @@ import "openzeppelin-solidity/contracts/ownership/Ownable.sol"; import "openzeppelin-solidity/contracts/token/ERC20/IERC20.sol"; import "./interfaces/IAccounts.sol"; +import "./interfaces/IEpochManager.sol"; import "./interfaces/IFeeCurrencyWhitelist.sol"; import "./interfaces/IFreezer.sol"; import "./interfaces/IRegistry.sol"; @@ -191,4 +192,8 @@ contract UsingRegistryV2 { function getEpochRewards() internal view returns (IEpochRewards) { return IEpochRewards(registryContract.getAddressForOrDie(EPOCH_REWARDS_REGISTRY_ID)); } + + function getEpochManager() internal view returns (IEpochManager) { + return IEpochManager(registryContract.getAddressForOrDie(EPOCH_MANAGER_REGISTRY_ID)); + } } diff --git a/packages/protocol/contracts/common/interfaces/ICeloToken.sol b/packages/protocol/contracts/common/interfaces/ICeloToken.sol index 8e2986adc53..5f7107395db 100644 --- a/packages/protocol/contracts/common/interfaces/ICeloToken.sol +++ b/packages/protocol/contracts/common/interfaces/ICeloToken.sol @@ -9,7 +9,9 @@ interface ICeloToken { function initialize(address) external; function transferWithComment(address, uint256, string calldata) external returns (bool); function burn(uint256 value) external returns (bool); + function mint(address to, uint256 value) external returns (bool); function name() external view returns (string memory); function symbol() external view returns (string memory); function decimals() external view returns (uint8); + function allocatedSupply() external view returns (uint256); } diff --git a/packages/protocol/contracts/common/interfaces/IEpochManager.sol b/packages/protocol/contracts/common/interfaces/IEpochManager.sol new file mode 100644 index 00000000000..964187f9f53 --- /dev/null +++ b/packages/protocol/contracts/common/interfaces/IEpochManager.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity >=0.5.13 <0.9.0; + +interface IEpochManager { + function initializeSystem( + uint256 firstEpochNumber, + uint256 firstEpochBlock, + // uint256 firstEpochTimestamp, // TODO: do we need END timestamp? + address[] calldata firstElected + ) external; + function startNextEpochProcess() external; + function finishNextEpochProcess( + address[] calldata groups, + address[] calldata lessers, + address[] calldata greaters + ) external; + function getCurrentEpoch() external view returns (uint256, uint256, uint256, uint256, uint256); + function getCurrentEpochNumber() external view returns (uint256); + function getElected() external view returns (address[] memory); + function epochManagerInitializer() external view returns (address); + function epochDuration() external view returns (uint256); +} diff --git a/packages/protocol/contracts/governance/Election.sol b/packages/protocol/contracts/governance/Election.sol index af748a475c3..d6b928e6e25 100644 --- a/packages/protocol/contracts/governance/Election.sol +++ b/packages/protocol/contracts/governance/Election.sol @@ -620,6 +620,9 @@ contract Election is */ function hasActivatablePendingVotes(address account, address group) external view returns (bool) { PendingVote storage pendingVote = votes.pending.forGroup[group].byAccount[account]; + if (isL2()) { + return pendingVote.epoch < getEpochManager().getCurrentEpochNumber() && pendingVote.value > 0; + } return pendingVote.epoch < getEpochNumber() && pendingVote.value > 0; } @@ -959,7 +962,14 @@ contract Election is function _activate(address group, address account) internal onlyWhenNotBlocked returns (bool) { PendingVote storage pendingVote = votes.pending.forGroup[group].byAccount[account]; - require(pendingVote.epoch < getEpochNumber(), "Pending vote epoch not passed"); + if (isL2()) { + require( + pendingVote.epoch < getEpochManager().getCurrentEpochNumber(), + "Pending vote epoch not passed" + ); + } else { + require(pendingVote.epoch < getEpochNumber(), "Pending vote epoch not passed"); + } uint256 value = pendingVote.value; require(value > 0, "Vote value cannot be zero"); decrementPendingVotes(group, account, value); @@ -1107,7 +1117,11 @@ contract Election is PendingVote storage pendingVote = groupPending.byAccount[account]; pendingVote.value = pendingVote.value.add(value); - pendingVote.epoch = getEpochNumber(); + if (isL2()) { + pendingVote.epoch = getEpochManager().getCurrentEpochNumber(); + } else { + pendingVote.epoch = getEpochNumber(); + } } /** diff --git a/packages/protocol/contracts/governance/EpochRewards.sol b/packages/protocol/contracts/governance/EpochRewards.sol index d8ad463265f..e04ac3c68c7 100644 --- a/packages/protocol/contracts/governance/EpochRewards.sol +++ b/packages/protocol/contracts/governance/EpochRewards.sol @@ -9,6 +9,7 @@ import "../common/Freezable.sol"; import "../common/Initializable.sol"; import "../common/UsingRegistry.sol"; import "../common/UsingPrecompiles.sol"; +import "../common/interfaces/ICeloToken.sol"; import "../common/interfaces/ICeloVersionedContract.sol"; /** @@ -450,6 +451,12 @@ contract EpochRewards is function getTargetTotalEpochPaymentsInGold() public view returns (uint256) { address stableTokenAddress = registry.getAddressForOrDie(STABLE_TOKEN_REGISTRY_ID); (uint256 numerator, uint256 denominator) = getSortedOracles().medianRate(stableTokenAddress); + if (isL2()) { + return + getEpochManager().getElected().length.mul(targetValidatorEpochPayment).mul(denominator).div( + numerator + ); + } return numberValidatorsInCurrentSet().mul(targetValidatorEpochPayment).mul(denominator).div( numerator @@ -461,7 +468,9 @@ contract EpochRewards is * @return The fraction of floating Gold being used for voting in validator elections. */ function getVotingGoldFraction() public view returns (uint256) { - uint256 liquidGold = getCeloToken().totalSupply().sub(getReserve().getReserveGoldBalance()); + uint256 liquidGold = ICeloToken(address(getCeloToken())).allocatedSupply().sub( + getReserve().getReserveGoldBalance() + ); uint256 votingGold = getElection().getTotalVotes(); return FixidityLib.newFixed(votingGold).divide(FixidityLib.newFixed(liquidGold)).unwrap(); } @@ -510,8 +519,8 @@ contract EpochRewards is uint256 targetGoldSupplyIncrease ) internal view returns (FixidityLib.Fraction memory) { uint256 targetSupply = getTargetGoldTotalSupply(); - uint256 totalSupply = getCeloToken().totalSupply(); - uint256 remainingSupply = GOLD_SUPPLY_CAP.sub(totalSupply.add(targetGoldSupplyIncrease)); + uint256 allocatedSupply = ICeloToken(address(getCeloToken())).allocatedSupply(); + uint256 remainingSupply = GOLD_SUPPLY_CAP.sub(allocatedSupply.add(targetGoldSupplyIncrease)); uint256 targetRemainingSupply = GOLD_SUPPLY_CAP.sub(targetSupply); FixidityLib.Fraction memory remainingToTargetRatio = FixidityLib .newFixed(remainingSupply) diff --git a/packages/protocol/scripts/determine-release-version.ts b/packages/protocol/scripts/determine-release-version.ts index 85cb2fae181..08cfa53df44 100644 --- a/packages/protocol/scripts/determine-release-version.ts +++ b/packages/protocol/scripts/determine-release-version.ts @@ -2,7 +2,7 @@ import { execSync } from 'child_process' import { determineNextVersion, getReleaseTypeFromSemVer } from './utils' const npmPackage = process.env.NPM_PACKAGE?.trim() || '' -// const npmTag = process.env.NPM_TAG?.trim() || '' +const npmTag = process.env.NPM_TAG?.trim() || '' const gitTag = process.env.GITHUB_TAG || '' const branchName = execSync('git branch --show-current').toString().trim() diff --git a/packages/protocol/test-sol/constants.sol b/packages/protocol/test-sol/constants.sol index 7225ca89ed2..f0d93e336f9 100644 --- a/packages/protocol/test-sol/constants.sol +++ b/packages/protocol/test-sol/constants.sol @@ -33,4 +33,17 @@ contract TestConstants { // Constant addresses address constant REGISTRY_ADDRESS = 0x000000000000000000000000000000000000ce10; address constant PROXY_ADMIN_ADDRESS = 0x4200000000000000000000000000000000000018; + + 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. } diff --git a/packages/protocol/test-sol/integration/RevokeCeloAfterL2Transition.sol b/packages/protocol/test-sol/integration/RevokeCeloAfterL2Transition.sol index 5242fce9d77..13e4fb9ba05 100644 --- a/packages/protocol/test-sol/integration/RevokeCeloAfterL2Transition.sol +++ b/packages/protocol/test-sol/integration/RevokeCeloAfterL2Transition.sol @@ -9,6 +9,7 @@ import "@celo-contracts/common/FixidityLib.sol"; import "@celo-contracts/common/Registry.sol"; import "@celo-contracts/common/Accounts.sol"; import "@celo-contracts/common/GoldToken.sol"; +import "@celo-contracts-8/common/test/MockEpochManager.sol"; import "@celo-contracts-8/common/interfaces/IPrecompiles.sol"; import "@celo-contracts/governance/interfaces/IValidators.sol"; @@ -42,6 +43,7 @@ contract RevokeCeloAfterL2Transition is Test, TestConstants, ECDSAHelper, Utils Governance governance; GoldToken goldToken; ReleaseGold releaseGold; + MockEpochManager epochManager; address owner; address accApprover; @@ -53,6 +55,7 @@ contract RevokeCeloAfterL2Transition is Test, TestConstants, ECDSAHelper, Utils address beneficiary; address refundAddress; address releaseOwner; + address epochManagerAddress = actor("epochManagerAddress"); address authorizedValidatorSigner; uint256 authorizedValidatorSignerPK; @@ -175,7 +178,6 @@ contract RevokeCeloAfterL2Transition is Test, TestConstants, ECDSAHelper, Utils stableToken = new MockStableToken(); election = new Election(true); lockedGold = new LockedGold(true); - // validators = new Validators(true); address validatorsAddress = actor("Validators"); deployCodeTo("ValidatorsMock.sol", validatorsAddress); validators = IValidators(validatorsAddress); @@ -185,6 +187,8 @@ contract RevokeCeloAfterL2Transition is Test, TestConstants, ECDSAHelper, Utils goldToken = new GoldToken(true); releaseGold = new ReleaseGold(true); + epochManager = new MockEpochManager(); + registry.setAddressFor(AccountsContract, address(accounts)); registry.setAddressFor(ElectionContract, address(election)); registry.setAddressFor(StableTokenContract, address(stableToken)); @@ -192,6 +196,7 @@ contract RevokeCeloAfterL2Transition is Test, TestConstants, ECDSAHelper, Utils registry.setAddressFor(ValidatorsContract, address(validators)); registry.setAddressFor(GovernanceContract, address(governance)); registry.setAddressFor(GoldTokenContract, address(goldToken)); + registry.setAddressFor(EpochManagerContract, address(epochManager)); goldToken.initialize(address(registry)); @@ -287,7 +292,14 @@ contract RevokeCeloAfterL2Transition is Test, TestConstants, ECDSAHelper, Utils } function _whenL2() public { + uint256 l1EpochNumber = IPrecompiles(address(validators)).getEpochNumber(); + deployCodeTo("Registry.sol", abi.encode(false), PROXY_ADMIN_ADDRESS); + + address[] memory _elected = new address[](2); + _elected[0] = actor("firstElected"); + _elected[1] = actor("secondElected"); + epochManager.initializeSystem(l1EpochNumber, block.number, _elected); } function _registerValidatorGroupHelper(address _group, uint256 numMembers) internal { diff --git a/packages/protocol/test-sol/unit/common/CeloUnreleasedTreasure.t.sol b/packages/protocol/test-sol/unit/common/CeloUnreleasedTreasure.t.sol index 35e69e93c6f..ed11f093866 100644 --- a/packages/protocol/test-sol/unit/common/CeloUnreleasedTreasure.t.sol +++ b/packages/protocol/test-sol/unit/common/CeloUnreleasedTreasure.t.sol @@ -34,18 +34,6 @@ contract CeloUnreleasedTreasureTest is Test, TestConstants, IsL2Check { address randomAddress = actor("randomAddress"); uint256 constant DAILY_DISTRIBUTION_AMOUNT = 6748256563599655349558; // 6,748 Celo - 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. uint256 constant MAX_L2_COMMUNITY_DISTRIBUTION = MAX_L2_DISTRIBUTION / 4; // 26.8 million Celo uint256 constant MAX_L2_CARBON_FUND_DISTRIBUTION = MAX_L2_DISTRIBUTION / 1000; // 107,297 Celo diff --git a/packages/protocol/test-sol/unit/common/EpochManager.t.sol b/packages/protocol/test-sol/unit/common/EpochManager.t.sol index 0cef5a62870..62c8cf0cb08 100644 --- a/packages/protocol/test-sol/unit/common/EpochManager.t.sol +++ b/packages/protocol/test-sol/unit/common/EpochManager.t.sol @@ -43,19 +43,6 @@ contract EpochManagerTest is Test, TestConstants, Utils08 { uint256 celoAmountForRate = 1e24; uint256 stableAmountForRate = 2 * celoAmountForRate; - 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 setUp() public virtual { epochManager = new EpochManager(true); sortedOracles = new MockSortedOracles(); @@ -144,9 +131,6 @@ contract EpochManagerTest_initializeSystem is EpochManagerTest { contract EpochManagerTest_startNextEpochProcess is EpochManagerTest { function test_Reverts_whenSystemNotInitialized() public { - uint256 _currentEpoch = epochManager.currentEpochNumber(); - (, , , uint256 _currentEpochEndTimestamp, ) = epochManager.getCurrentEpoch(); - vm.expectRevert("Epoch system not initialized"); epochManager.startNextEpochProcess(); } @@ -155,7 +139,7 @@ contract EpochManagerTest_startNextEpochProcess is EpochManagerTest { vm.prank(epochManagerInitializer); epochManager.initializeSystem(firstEpochNumber, firstEpochBlock, firstElected); - uint256 _currentEpoch = epochManager.currentEpochNumber(); + uint256 _currentEpoch = epochManager.getCurrentEpochNumber(); (, , , uint256 _currentEpochEndTimestamp, ) = epochManager.getCurrentEpoch(); vm.expectRevert("Epoch is not ready to start"); diff --git a/packages/protocol/test-sol/unit/common/GoldToken.t.sol b/packages/protocol/test-sol/unit/common/GoldToken.t.sol index ba5c2fe4ca5..f34053b4497 100644 --- a/packages/protocol/test-sol/unit/common/GoldToken.t.sol +++ b/packages/protocol/test-sol/unit/common/GoldToken.t.sol @@ -249,10 +249,11 @@ contract CeloTokenMockTest is Test, TestConstants { GoldTokenMock mockCeloToken; uint256 ONE_CELOTOKEN = 1000000000000000000; address burnAddress = address(0x000000000000000000000000000000000000dEaD); - address celoTokenDistributionSchedule; + address celoUnreleasedTreasure; modifier _whenL2() { deployCodeTo("Registry.sol", abi.encode(false), PROXY_ADMIN_ADDRESS); + vm.deal(celoUnreleasedTreasure, L2_INITIAL_STASH_BALANCE); _; } @@ -262,9 +263,9 @@ contract CeloTokenMockTest is Test, TestConstants { mockCeloToken = new GoldTokenMock(); mockCeloToken.setRegistry(REGISTRY_ADDRESS); - mockCeloToken.setTotalSupply(ONE_CELOTOKEN * 1000); - celoTokenDistributionSchedule = actor("celoTokenDistributionSchedule"); - registry.setAddressFor("CeloUnreleasedTreasure", celoTokenDistributionSchedule); + mockCeloToken.setTotalSupply(L1_MINTED_CELO_SUPPLY); + celoUnreleasedTreasure = actor("CeloUnreleasedTreasure"); + registry.setAddressFor("CeloUnreleasedTreasure", celoUnreleasedTreasure); } } @@ -279,42 +280,40 @@ contract CeloTokenMock_circulatingSupply is CeloTokenMockTest { function test_ShouldDecreaseCirculatingSupply_WhenThereWasBurn() public { mockCeloToken.setBalanceOf(burnAddress, ONE_CELOTOKEN); - assertEq(mockCeloToken.circulatingSupply(), ONE_CELOTOKEN * 999); + assertEq(mockCeloToken.circulatingSupply(), mockCeloToken.allocatedSupply() - ONE_CELOTOKEN); assertEq(mockCeloToken.circulatingSupply(), mockCeloToken.totalSupply() - ONE_CELOTOKEN); } function test_ShouldMatchCirculationSupply_WhenNoBurn_WhenL2() public _whenL2 { - assertEq(mockCeloToken.circulatingSupply(), mockCeloToken.totalSupply()); + assertEq(mockCeloToken.circulatingSupply(), mockCeloToken.allocatedSupply()); } function test_ShouldDecreaseCirculatingSupply_WhenThereWasBurn_WhenL2() public _whenL2 { - uint256 CELO_SUPPLY_CAP = 1000000000 ether; // 1 billion Celo mockCeloToken.setBalanceOf(burnAddress, ONE_CELOTOKEN); - assertEq(mockCeloToken.circulatingSupply(), CELO_SUPPLY_CAP - ONE_CELOTOKEN); assertEq(mockCeloToken.circulatingSupply(), mockCeloToken.allocatedSupply() - ONE_CELOTOKEN); } } contract GoldTokenTest_AllocatedSupply is CeloTokenMockTest { - function test_ShouldRevert_WhenL1() public { - vm.expectRevert("This method is not supported in L1."); - mockCeloToken.allocatedSupply(); + function test_ShouldReturnTotalSupply_WhenL1() public { + assertEq(mockCeloToken.allocatedSupply(), L1_MINTED_CELO_SUPPLY); } function test_ShouldReturn_WhenInL2() public _whenL2 { - assertEq(mockCeloToken.allocatedSupply(), mockCeloToken.totalSupply()); + assertEq(mockCeloToken.allocatedSupply(), CELO_SUPPLY_CAP - L2_INITIAL_STASH_BALANCE); } function test_ShouldReturn_WhenWithdrawn_WhenInL2() public _whenL2 { - deal(address(celoTokenDistributionSchedule), ONE_CELOTOKEN); + deal(address(celoUnreleasedTreasure), ONE_CELOTOKEN); assertEq(mockCeloToken.allocatedSupply(), mockCeloToken.totalSupply() - ONE_CELOTOKEN); } } contract GoldTokenTest_TotalSupply is CeloTokenMockTest { - uint256 constant TOTAL_MARKET_CAP = 1000000000e18; // 1 billion CELO - - function test_TotalSupply_ShouldReturnTotalSupply_WhenL2() public _whenL2 { - assertEq(mockCeloToken.totalSupply(), 1000000000e18); + function test_ShouldReturnSupplyCap_WhenL2() public _whenL2 { + assertEq(mockCeloToken.totalSupply(), CELO_SUPPLY_CAP); + } + function test_ShouldReturnL1MintedSupply() public { + assertEq(mockCeloToken.totalSupply(), L1_MINTED_CELO_SUPPLY); } } diff --git a/packages/protocol/test-sol/unit/governance/validators/Validators.t.sol b/packages/protocol/test-sol/unit/governance/validators/Validators.t.sol index 145efc5d927..460bd56f9f9 100644 --- a/packages/protocol/test-sol/unit/governance/validators/Validators.t.sol +++ b/packages/protocol/test-sol/unit/governance/validators/Validators.t.sol @@ -23,7 +23,7 @@ import "@celo-contracts/governance/test/MockElection.sol"; import "@celo-contracts/governance/test/MockLockedGold.sol"; import "@test-sol/unit/governance/validators/mocks/ValidatorsMockTunnel.sol"; -// import "@celo-contracts-8/governance/test/ValidatorsMock08.sol"; +import "@celo-contracts-8/common/test/MockEpochManager.sol"; import "@test-sol/constants.sol"; import "@test-sol/utils/ECDSAHelper.sol"; import { Utils } from "@test-sol/utils.sol"; @@ -55,6 +55,7 @@ contract ValidatorsTest is Test, TestConstants, Utils, ECDSAHelper { ValidatorsMockTunnel public validatorsMockTunnel; IValidators public validators; MockLockedGold lockedGold; + MockEpochManager epochManager; address owner; address nonValidator; @@ -179,12 +180,14 @@ contract ValidatorsTest is Test, TestConstants, Utils, ECDSAHelper { validatorsMockTunnel = new ValidatorsMockTunnel(address(validators)); stableToken = new MockStableToken(); + epochManager = new MockEpochManager(); registry.setAddressFor(AccountsContract, address(accounts)); registry.setAddressFor(ElectionContract, address(election)); registry.setAddressFor(LockedGoldContract, address(lockedGold)); registry.setAddressFor(ValidatorsContract, address(validators)); registry.setAddressFor(StableTokenContract, address(stableToken)); + registry.setAddressFor(EpochManagerContract, address(epochManager)); initParams = ValidatorsMockTunnel.InitParams({ registryAddress: REGISTRY_ADDRESS, @@ -219,7 +222,13 @@ contract ValidatorsTest is Test, TestConstants, Utils, ECDSAHelper { } function _whenL2() public { + uint256 l1EpochNumber = IPrecompiles(address(validators)).getEpochNumber(); deployCodeTo("Registry.sol", abi.encode(false), PROXY_ADMIN_ADDRESS); + + address[] memory _elected = new address[](2); + _elected[0] = validator; + _elected[1] = otherValidator; + epochManager.initializeSystem(l1EpochNumber, block.number, _elected); } function _registerValidatorGroupWithMembers(address _group, uint256 _numMembers) public { @@ -302,7 +311,7 @@ contract ValidatorsTest is Test, TestConstants, Utils, ECDSAHelper { vm.prank(validator); validators.registerValidator(_ecdsaPubKey); - validatorRegistrationEpochNumber = IPrecompiles(address(validators)).getEpochNumber(); + validatorRegistrationEpochNumber = epochManager.getCurrentEpochNumber(); return _ecdsaPubKey; } @@ -705,10 +714,9 @@ contract ValidatorsTest_RegisterValidator is ValidatorsTest { _whenL2(); - vm.expectRevert("This method is no longer supported in L2."); vm.prank(validator); + vm.expectRevert("This method is no longer supported in L2."); validators.registerValidator(_ecdsaPubKey, blsPublicKey, blsPop); - validatorRegistrationEpochNumber = IPrecompiles(address(validators)).getEpochNumber(); } function test_ShouldAddAccountToValidatorList_WhenAccountHasAuthorizedValidatorSigner() public { @@ -988,16 +996,16 @@ contract ValidatorsTest_RegisterValidator_NoBls is ValidatorsTest { function test_Reverts_WhenAccountAlreadyRegisteredAsValidator() public { _whenL2(); bytes memory _registeredEcdsaPubKey = _registerValidatorWithSignerHelper_noBls(); - vm.expectRevert("Already registered"); vm.prank(validator); + vm.expectRevert("Already registered"); validators.registerValidator(_registeredEcdsaPubKey); } function test_Reverts_WhenAccountAlreadyRegisteredAsValidatorGroup() public { _whenL2(); _registerValidatorGroupHelper(validator, 1); - vm.expectRevert("Already registered"); vm.prank(validator); + vm.expectRevert("Already registered"); validators.registerValidator( abi.encodePacked(bytes32(0x0101010101010101010101010101010101010101010101010101010101010101)) ); @@ -3043,17 +3051,30 @@ contract ValidatorsTest_GetMembershipInLastEpoch is ValidatorsTest { } } - function test_Reverts_getMembershipInLastEpoch_WhenL2() public { - blockTravel(ph.epochSize()); + function test_MaintainsMembershipAfterL2Transition() public { + address lastValidatorGroup; + address nextValidatorGroup; + for (uint256 i = 0; i < membershipHistoryLength.add(1); i++) { + blockTravel(ph.epochSize()); - vm.prank(validator); - validators.affiliate(vm.addr(1)); - vm.prank(vm.addr(1)); - validators.addFirstMember(validator, address(0), address(0)); + vm.prank(validator); + validators.affiliate(vm.addr(i + 1)); + vm.prank(vm.addr(i + 1)); + validators.addFirstMember(validator, address(0), address(0)); + + if (i == 0) { + assertEq(validators.getMembershipInLastEpoch(validator), address(0)); + } else { + lastValidatorGroup = vm.addr(i); + nextValidatorGroup = vm.addr(i + 1); + assertEq(validators.getMembershipInLastEpoch(validator), vm.addr(i)); + } + } _whenL2(); - vm.expectRevert("This method is no longer supported in L2."); - validators.getMembershipInLastEpoch(validator); + assertEq(validators.getMembershipInLastEpoch(validator), lastValidatorGroup); + epochManager.setCurrentEpochNumber(epochManager.getCurrentEpochNumber() + 1); + assertEq(validators.getMembershipInLastEpoch(validator), nextValidatorGroup); } } @@ -3222,7 +3243,7 @@ contract ValidatorsTest_DistributeEpochPaymentsFromSigner is ValidatorsTest { validators.updateValidatorScoreFromSigner(validator, uptime.unwrap()); } - function test_Reverts_WhenL2_WhenValidatorAndGroupMeetBalanceRequirements() public { + function test_Reverts_WhenValidatorAndGroupMeetBalanceRequirements_WhenL2() public { _whenL2(); vm.expectRevert("This method is no longer supported in L2."); validators.distributeEpochPaymentsFromSigner(validator, maxPayment); diff --git a/packages/protocol/test-sol/unit/governance/voting/Election.t.sol b/packages/protocol/test-sol/unit/governance/voting/Election.t.sol index 09026ab6d21..c4ba6faaf15 100644 --- a/packages/protocol/test-sol/unit/governance/voting/Election.t.sol +++ b/packages/protocol/test-sol/unit/governance/voting/Election.t.sol @@ -2,7 +2,7 @@ pragma solidity ^0.5.13; pragma experimental ABIEncoderV2; -import { Test } from "celo-foundry/Test.sol"; +import "celo-foundry/Test.sol"; import { TestConstants } from "@test-sol/constants.sol"; import { Utils } from "@test-sol/utils.sol"; @@ -14,6 +14,7 @@ import "@celo-contracts/common/Accounts.sol"; import "@celo-contracts/common/linkedlists/AddressSortedLinkedList.sol"; import "@celo-contracts/identity/test/MockRandom.sol"; import "@celo-contracts/common/Freezer.sol"; +import "@celo-contracts-8/common/test/MockEpochManager.sol"; import { TestBlocker } from "@test-sol/unit/common/Blockable.t.sol"; @@ -38,6 +39,7 @@ contract ElectionTest is Utils, TestConstants { MockValidators validators; MockRandom random; IRegistry registry; + MockEpochManager epochManager; address nonOwner = actor("nonOwner"); address owner = address(this); @@ -57,6 +59,8 @@ contract ElectionTest is Utils, TestConstants { address account9 = actor("account9"); address account10 = actor("account10"); + address epochManagerAddress = actor("epochManagerAddress"); + address[] accountsArray; TestBlocker blocker; @@ -136,12 +140,14 @@ contract ElectionTest is Utils, TestConstants { validators = new MockValidators(); registry = IRegistry(REGISTRY_ADDRESS); random = new MockRandom(); + epochManager = new MockEpochManager(); registry.setAddressFor("Accounts", address(accounts)); registry.setAddressFor("Freezer", address(freezer)); registry.setAddressFor("LockedGold", address(lockedGold)); registry.setAddressFor("Validators", address(validators)); registry.setAddressFor("Random", address(random)); + registry.setAddressFor("EpochManager", address(epochManager)); election.initialize( REGISTRY_ADDRESS, @@ -156,7 +162,14 @@ contract ElectionTest is Utils, TestConstants { } function _whenL2() public { + uint256 l1EpochNumber = election.getEpochNumber(); + + address[] memory _elected = new address[](2); + _elected[0] = actor("validator"); + _elected[1] = actor("otherValidator"); + deployCodeTo("Registry.sol", abi.encode(false), PROXY_ADMIN_ADDRESS); + epochManager.initializeSystem(l1EpochNumber, block.number, _elected); } } @@ -204,6 +217,7 @@ contract ElectionTest_SetElectabilityThreshold is ElectionTest { } } +// TODO(soloseng): need to update epochNumber for L2, to make it !=0 contract ElectionTest_SetElectabilityThreshold_L2 is ElectionTest { function test_shouldSetElectabilityThreshold() public { _whenL2(); @@ -862,7 +876,7 @@ contract ElectionTest_Vote_WhenGroupEligible_L2 is ElectionTest { function WhenVotesAreBeingActivated() public returns (address newGroup) { newGroup = WhenVotedForMoreThanMaxNumberOfGroups(); - blockTravel(ph.epochSize() + 1); + epochManager.setCurrentEpochNumber(epochManager.getCurrentEpochNumber() + 1); election.activateForAccount(group, voter); } @@ -1317,7 +1331,7 @@ contract ElectionTest_Activate_L2 is ElectionTest { function WhenEpochBoundaryHasPassed() public { WhenVoterHasPendingVotes(); - blockTravel(ph.epochSize() + 1); + epochManager.setCurrentEpochNumber(epochManager.getCurrentEpochNumber() + 1); election.activate(group); } @@ -1359,7 +1373,7 @@ contract ElectionTest_Activate_L2 is ElectionTest { function test_ShouldEmitValidatorGroupVoteActivatedEvent_WhenEpochBoundaryHasPassed() public { WhenVoterHasPendingVotes(); - blockTravel(ph.epochSize() + 1); + epochManager.setCurrentEpochNumber(epochManager.getCurrentEpochNumber() + 1); vm.expectEmit(true, true, true, false); emit ValidatorGroupVoteActivated(voter, group, value, value * 100000000000000000000); election.activate(group); @@ -1370,7 +1384,7 @@ contract ElectionTest_Activate_L2 is ElectionTest { lockedGold.incrementNonvotingAccountBalance(voter2, value2); vm.prank(voter2); election.vote(group, value2, address(0), address(0)); - blockTravel(ph.epochSize() + 1); + epochManager.setCurrentEpochNumber(epochManager.getCurrentEpochNumber() + 1); vm.prank(voter2); election.activate(group); } @@ -1628,7 +1642,7 @@ contract ElectionTest_ActivateForAccount_L2 is ElectionTest { function WhenEpochBoundaryHasPassed() public { WhenVoterHasPendingVotes(); - blockTravel(ph.epochSize() + 1); + epochManager.setCurrentEpochNumber(epochManager.getCurrentEpochNumber() + 1); election.activateForAccount(group, voter); } @@ -1670,7 +1684,7 @@ contract ElectionTest_ActivateForAccount_L2 is ElectionTest { function test_ShouldEmitValidatorGroupVoteActivatedEvent_WhenEpochBoundaryHasPassed() public { WhenVoterHasPendingVotes(); - blockTravel(ph.epochSize() + 1); + epochManager.setCurrentEpochNumber(epochManager.getCurrentEpochNumber() + 1); vm.expectEmit(true, true, true, false); emit ValidatorGroupVoteActivated(voter, group, value, value * 100000000000000000000); election.activate(group); @@ -1681,7 +1695,7 @@ contract ElectionTest_ActivateForAccount_L2 is ElectionTest { lockedGold.incrementNonvotingAccountBalance(voter2, value2); vm.prank(voter2); election.vote(group, value2, address(0), address(0)); - blockTravel(ph.epochSize() + 1); + epochManager.setCurrentEpochNumber(epochManager.getCurrentEpochNumber() + 1); election.activateForAccount(group, voter2); } From 4e1cf6ac66393150ec0cd02939ddccbe4595692f Mon Sep 17 00:00:00 2001 From: soloseng <102702451+soloseng@users.noreply.github.com> Date: Mon, 26 Aug 2024 12:15:31 -0400 Subject: [PATCH 47/59] ++ TODO and comment --- .github/workflows/protocol_tests.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/protocol_tests.yml b/.github/workflows/protocol_tests.yml index 33d276b5dff..c6025ebe8c8 100644 --- a/.github/workflows/protocol_tests.yml +++ b/.github/workflows/protocol_tests.yml @@ -20,7 +20,7 @@ on: env: # Increment these to force cache rebuilding FOUNDRY_CACHE_KEY: 2 - # Supported Foundry version defined at celo-org (GitHub organisation) level, for consistency across workflows. + # Supported Foundry version defined at celo-org (GitHub organisation) level, for consistency across workflows. Please contact DevOps to update value. SUPPORTED_FOUNDRY_VERSION: ${{ vars.SUPPORTED_FOUNDRY_VERSION }} jobs: @@ -62,7 +62,7 @@ jobs: - name: Install Foundry uses: foundry-rs/foundry-toolchain@v1 with: - version: 'nightly-fa0e0c2ca3ae75895dd19173a02faf88509c0608' + version: 'nightly-fa0e0c2ca3ae75895dd19173a02faf88509c0608' # TODO: revert back to env var - name: Install forge dependencies run: forge install From 2eab03fb2e82259d3471b9598d9311defce7bd93 Mon Sep 17 00:00:00 2001 From: pahor167 Date: Tue, 27 Aug 2024 12:26:13 +0200 Subject: [PATCH 48/59] add score manager to abis --- packages/protocol/scripts/consts.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/protocol/scripts/consts.ts b/packages/protocol/scripts/consts.ts index 502a4ce7306..729b0dfb276 100644 --- a/packages/protocol/scripts/consts.ts +++ b/packages/protocol/scripts/consts.ts @@ -66,6 +66,7 @@ export const CoreContracts = [ 'LockedGold', 'Validators', 'ReleaseGold', + 'ScoreManager', // identity 'Attestations', From 4cbc6d9c957b013e46f0badab66ed740682ea738 Mon Sep 17 00:00:00 2001 From: soloseng <102702451+soloseng@users.noreply.github.com> Date: Wed, 11 Sep 2024 11:20:12 -0400 Subject: [PATCH 49/59] Split initEpochManager Function (#11199) * ++ contract function * ++ comment --- .../common/EpochManagerInitializer.sol | 34 +++++++++++++------ 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/packages/protocol/contracts-0.8/common/EpochManagerInitializer.sol b/packages/protocol/contracts-0.8/common/EpochManagerInitializer.sol index c30063a31b8..2d9ef2dbac3 100644 --- a/packages/protocol/contracts-0.8/common/EpochManagerInitializer.sol +++ b/packages/protocol/contracts-0.8/common/EpochManagerInitializer.sol @@ -9,6 +9,9 @@ import "../../contracts/common/interfaces/ICeloVersionedContract.sol"; import "../../contracts/governance/interfaces/IEpochRewards.sol"; contract EpochManagerInitializer is Initializable, UsingPrecompiles, UsingRegistry { + uint256 public lastKnownEpochNumber; + address[] public lastKnownElectedAccounts; + /** * @notice Sets initialized == true on implementation contracts * @param test Set to true to skip implementation initialization @@ -27,22 +30,33 @@ contract EpochManagerInitializer is Initializable, UsingPrecompiles, UsingRegist /** * @notice initializes the epochManager contract during L2 transition. */ - function initEpochManager() external onlyOwner { - uint256 currentEpoch = getEpochNumber(); + function initEpochManager() external onlyL2 { + require(lastKnownEpochNumber != 0, "lastKnownEpochNumber not set."); + require(lastKnownElectedAccounts.length > 0, "lastKnownElectedAccounts not set."); + getEpochManager().initializeSystem( + lastKnownEpochNumber, + _getFirstBlockOfEpoch(lastKnownEpochNumber), + lastKnownElectedAccounts + ); + } + + /** + * @notice Stores the last known epochNumber and the related elected validator accounts. + */ + function captureEpochAndValidators() external onlyL1 { + lastKnownEpochNumber = getEpochNumber(); uint256 numberElectedValidators = numberValidatorsInCurrentSet(); - address[] memory electedValidatorAddresses = new address[](numberElectedValidators); + lastKnownElectedAccounts = new address[](numberElectedValidators); for (uint256 i = 0; i < numberElectedValidators; i++) { - address validatorAddress = validatorSignerAddressFromCurrentSet(i); - electedValidatorAddresses[i] = validatorAddress; + // TODO: document how much gas this takes for 110 signers + address validatorAccountAddress = getAccounts().validatorSignerToAccount( + validatorSignerAddressFromCurrentSet(i) + ); + lastKnownElectedAccounts[i] = validatorAccountAddress; } - getEpochManager().initializeSystem( - currentEpoch, - _getFirstBlockOfEpoch(currentEpoch), - electedValidatorAddresses - ); } function getFirstBlockOfEpoch(uint256 currentEpoch) external view returns (uint256) { From 080b4308739e1e138d6d474b23d9d6cf6c55ef06 Mon Sep 17 00:00:00 2001 From: soloseng <102702451+soloseng@users.noreply.github.com> Date: Mon, 16 Sep 2024 20:40:40 -0400 Subject: [PATCH 50/59] startNextEpochProcess unit & integration test (#11191) * 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 --- .../common/CeloUnreleasedTreasure.sol | 5 +- .../contracts-0.8/common/EpochManager.sol | 64 +++-- ...nitializer.sol => EpochManagerEnabler.sol} | 3 +- .../contracts-0.8/common/ScoreManager.sol | 4 +- .../contracts-0.8/common/UsingRegistry.sol | 9 +- .../common/interfaces/ICeloToken.sol | 34 --- .../IEpochManagerEnablerInitializer.sol | 6 + .../interfaces/IEpochManagerInitializer.sol | 11 + .../interfaces/IScoreManagerInitializer.sol | 6 + .../common/test/MockCeloToken.sol | 60 +++++ .../test/MockCeloUnreleasedTreasure.sol | 15 ++ .../common/test/MockEpochManager.sol | 14 +- .../common/test/MockRegistry.sol | 102 ++++++++ .../contracts-0.8/governance/Validators.sol | 15 +- .../governance/test/EpochRewardsMock.sol | 9 +- .../governance/test/ValidatorsMock08.sol | 233 +----------------- .../stability/test/MockReserve.sol | 49 ++++ .../common/interfaces/IEpochManager.sol | 5 +- .../interfaces/IEpochManagerEnabler.sol | 8 + .../common/interfaces/IScoreManager.sol | 10 + .../contracts/governance/Election.sol | 10 +- .../contracts/governance/EpochRewards.sol | 17 +- .../governance/interfaces/IValidators.sol | 1 + packages/protocol/lib/registry-utils.ts | 3 + .../protocol/migrations_sol/Migration.s.sol | 220 +++++++++++++---- .../protocol/migrations_sol/constants.sol | 7 +- .../migrations_sol/migrationsConfig.json | 64 +++-- packages/protocol/scripts/consts.ts | 1 + .../scripts/foundry/deploy_precompiles.sh | 10 +- packages/protocol/test-sol/constants.sol | 1 - .../devchain/migration/Migration.t.sol | 185 +++++++++++++- ...NumberValidatorsInCurrentSetPrecompile.sol | 32 +++ ...rSignerAddressFromCurrentSetPrecompile.sol | 56 +++++ .../unit/common/CeloUnreleasedTreasure.t.sol | 32 ++- .../test-sol/unit/common/EpochManager.t.sol | 143 +++++++++-- .../governance/network/EpochRewards.t.sol | 2 +- .../governance/validators/Validators.t.sol | 20 ++ .../unit/governance/voting/Election.t.sol | 3 +- .../protocol/test-sol/utils/ECDSAHelper.sol | 2 +- packages/protocol/test-sol/utils08.sol | 11 + 40 files changed, 1041 insertions(+), 441 deletions(-) rename packages/protocol/contracts-0.8/common/{EpochManagerInitializer.sol => EpochManagerEnabler.sol} (94%) delete mode 100644 packages/protocol/contracts-0.8/common/interfaces/ICeloToken.sol create mode 100644 packages/protocol/contracts-0.8/common/interfaces/IEpochManagerEnablerInitializer.sol create mode 100644 packages/protocol/contracts-0.8/common/interfaces/IEpochManagerInitializer.sol create mode 100644 packages/protocol/contracts-0.8/common/interfaces/IScoreManagerInitializer.sol create mode 100644 packages/protocol/contracts-0.8/common/test/MockCeloToken.sol create mode 100644 packages/protocol/contracts-0.8/common/test/MockCeloUnreleasedTreasure.sol create mode 100644 packages/protocol/contracts-0.8/common/test/MockRegistry.sol create mode 100644 packages/protocol/contracts-0.8/stability/test/MockReserve.sol create mode 100644 packages/protocol/contracts/common/interfaces/IEpochManagerEnabler.sol create mode 100644 packages/protocol/contracts/common/interfaces/IScoreManager.sol create mode 100644 packages/protocol/test-sol/precompiles/NumberValidatorsInCurrentSetPrecompile.sol create mode 100644 packages/protocol/test-sol/precompiles/ValidatorSignerAddressFromCurrentSetPrecompile.sol diff --git a/packages/protocol/contracts-0.8/common/CeloUnreleasedTreasure.sol b/packages/protocol/contracts-0.8/common/CeloUnreleasedTreasure.sol index 1e17e37d517..d2b263536d3 100644 --- a/packages/protocol/contracts-0.8/common/CeloUnreleasedTreasure.sol +++ b/packages/protocol/contracts-0.8/common/CeloUnreleasedTreasure.sol @@ -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. @@ -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); } diff --git a/packages/protocol/contracts-0.8/common/EpochManager.sol b/packages/protocol/contracts-0.8/common/EpochManager.sol index 6301ac70b26..00813f44f21 100644 --- a/packages/protocol/contracts-0.8/common/EpochManager.sol +++ b/packages/protocol/contracts-0.8/common/EpochManager.sol @@ -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, @@ -23,7 +24,6 @@ contract EpochManager is uint256 firstBlock; uint256 lastBlock; uint256 startTimestamp; - uint256 endTimestamp; uint256 rewardsBlock; } @@ -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. @@ -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. @@ -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"); _; } @@ -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 @@ -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"); @@ -127,7 +137,7 @@ contract EpochManager is _currentEpoch.startTimestamp = block.timestamp; elected = firstElected; - epochManagerInitializer = address(0); + epochManagerEnabler = address(0); } // TODO maybe "freezeEpochRewards" "prepareForNextEpoch" @@ -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++; @@ -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. @@ -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; } @@ -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(); @@ -305,7 +320,7 @@ 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( @@ -313,7 +328,6 @@ contract EpochManager is ); uint256 CELOequivalent = (numerator * totalRewards) / denominator; - // this is not a mint anymore getCeloUnreleasedTreasure().release( registry.getAddressForOrDie(RESERVE_REGISTRY_ID), CELOequivalent diff --git a/packages/protocol/contracts-0.8/common/EpochManagerInitializer.sol b/packages/protocol/contracts-0.8/common/EpochManagerEnabler.sol similarity index 94% rename from packages/protocol/contracts-0.8/common/EpochManagerInitializer.sol rename to packages/protocol/contracts-0.8/common/EpochManagerEnabler.sol index 2d9ef2dbac3..ba33773b9b9 100644 --- a/packages/protocol/contracts-0.8/common/EpochManagerInitializer.sol +++ b/packages/protocol/contracts-0.8/common/EpochManagerEnabler.sol @@ -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; diff --git a/packages/protocol/contracts-0.8/common/ScoreManager.sol b/packages/protocol/contracts-0.8/common/ScoreManager.sol index d91705cdf65..c8075d52733 100644 --- a/packages/protocol/contracts-0.8/common/ScoreManager.sol +++ b/packages/protocol/contracts-0.8/common/ScoreManager.sol @@ -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); } diff --git a/packages/protocol/contracts-0.8/common/UsingRegistry.sol b/packages/protocol/contracts-0.8/common/UsingRegistry.sol index 5815b9807ef..9b1d13dd3e7 100644 --- a/packages/protocol/contracts-0.8/common/UsingRegistry.sol +++ b/packages/protocol/contracts-0.8/common/UsingRegistry.sol @@ -12,6 +12,9 @@ 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"; @@ -19,12 +22,8 @@ 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 diff --git a/packages/protocol/contracts-0.8/common/interfaces/ICeloToken.sol b/packages/protocol/contracts-0.8/common/interfaces/ICeloToken.sol deleted file mode 100644 index e774da6f884..00000000000 --- a/packages/protocol/contracts-0.8/common/interfaces/ICeloToken.sol +++ /dev/null @@ -1,34 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity >=0.5.13 <0.9.0; - -import "@openzeppelin/contracts8/token/ERC20/IERC20.sol"; - -/** - * @dev Interface of the ERC20 standard as defined in the EIP. Does not include - * the optional functions; to access them see {ERC20Detailed}. - */ -interface ICeloToken is IERC20 { - /** - * @notice Used in place of the constructor to allow the contract to be upgradable via proxy. - * @param registryAddress Address of the Registry contract. - */ - function initialize(address registryAddress) external; - - /** - * @notice Updates the address pointing to a Registry contract. - * @param registryAddress The address of a registry contract for routing to other contracts. - */ - function setRegistry(address registryAddress) external; - - /** - * @dev Mints a new token. - * @param to The address that will own the minted token. - * @param value The amount of token to be minted. - */ - function mint(address to, uint256 value) external returns (bool); - - /** - * @notice Returns amount of CELO that has been allocated. - */ - function allocatedSupply() external view returns (uint256); -} diff --git a/packages/protocol/contracts-0.8/common/interfaces/IEpochManagerEnablerInitializer.sol b/packages/protocol/contracts-0.8/common/interfaces/IEpochManagerEnablerInitializer.sol new file mode 100644 index 00000000000..3643d5ec711 --- /dev/null +++ b/packages/protocol/contracts-0.8/common/interfaces/IEpochManagerEnablerInitializer.sol @@ -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; +} diff --git a/packages/protocol/contracts-0.8/common/interfaces/IEpochManagerInitializer.sol b/packages/protocol/contracts-0.8/common/interfaces/IEpochManagerInitializer.sol new file mode 100644 index 00000000000..119e2f8616e --- /dev/null +++ b/packages/protocol/contracts-0.8/common/interfaces/IEpochManagerInitializer.sol @@ -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; +} diff --git a/packages/protocol/contracts-0.8/common/interfaces/IScoreManagerInitializer.sol b/packages/protocol/contracts-0.8/common/interfaces/IScoreManagerInitializer.sol new file mode 100644 index 00000000000..f6229cf72b5 --- /dev/null +++ b/packages/protocol/contracts-0.8/common/interfaces/IScoreManagerInitializer.sol @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity >=0.5.13 <0.9.0; + +interface IScoreManagerInitializer { + function initialize() external; +} diff --git a/packages/protocol/contracts-0.8/common/test/MockCeloToken.sol b/packages/protocol/contracts-0.8/common/test/MockCeloToken.sol new file mode 100644 index 00000000000..82bd1c57cc8 --- /dev/null +++ b/packages/protocol/contracts-0.8/common/test/MockCeloToken.sol @@ -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; + } +} diff --git a/packages/protocol/contracts-0.8/common/test/MockCeloUnreleasedTreasure.sol b/packages/protocol/contracts-0.8/common/test/MockCeloUnreleasedTreasure.sol new file mode 100644 index 00000000000..8aecc77331d --- /dev/null +++ b/packages/protocol/contracts-0.8/common/test/MockCeloUnreleasedTreasure.sol @@ -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."); + } +} diff --git a/packages/protocol/contracts-0.8/common/test/MockEpochManager.sol b/packages/protocol/contracts-0.8/common/test/MockEpochManager.sol index 8efb865a15b..4125c9c5843 100644 --- a/packages/protocol/contracts-0.8/common/test/MockEpochManager.sol +++ b/packages/protocol/contracts-0.8/common/test/MockEpochManager.sol @@ -22,7 +22,7 @@ contract MockEpochManager is IEpochManager { uint256 public firstKnownEpoch; uint256 private currentEpochNumber; address[] public elected; - address public epochManagerInitializer; + address public epochManagerEnabler; bool initialized; mapping(uint256 => Epoch) private epochs; @@ -45,7 +45,7 @@ contract MockEpochManager is IEpochManager { elected = firstElected; initialized = true; - epochManagerInitializer = address(0); + epochManagerEnabler = address(0); } function startNextEpochProcess() external {} @@ -55,16 +55,10 @@ contract MockEpochManager is IEpochManager { address[] calldata greaters ) external {} - 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); } function getCurrentEpochNumber() external view returns (uint256) { diff --git a/packages/protocol/contracts-0.8/common/test/MockRegistry.sol b/packages/protocol/contracts-0.8/common/test/MockRegistry.sol new file mode 100644 index 00000000000..eb42d084be4 --- /dev/null +++ b/packages/protocol/contracts-0.8/common/test/MockRegistry.sol @@ -0,0 +1,102 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity >=0.8.7 <0.8.20; + +import "@openzeppelin/contracts8/utils/math/SafeMath.sol"; +import "@openzeppelin/contracts8/access/Ownable.sol"; + +import "../../../contracts/common/interfaces/IRegistry.sol"; +import "../../../contracts/common/interfaces/IRegistryInitializer.sol"; +import "../../../contracts/common/Initializable.sol"; + +/** + * @title Routes identifiers to addresses. + */ +contract MockRegistry is IRegistry, IRegistryInitializer, Ownable, Initializable { + using SafeMath for uint256; + + mapping(bytes32 => address) public registry; + + event RegistryUpdated(string identifier, bytes32 indexed identifierHash, address indexed addr); + + /** + * @notice Sets initialized == true on implementation contracts + * @param test Set to true to skip implementation initialization + */ + constructor(bool test) public Initializable(test) {} + + /** + * @notice Used in place of the constructor to allow the contract to be upgradable via proxy. + */ + function initialize() external initializer { + _transferOwnership(msg.sender); + } + + /** + * @notice Associates the given address with the given identifier. + * @param identifier Identifier of contract whose address we want to set. + * @param addr Address of contract. + */ + function setAddressFor(string calldata identifier, address addr) external onlyOwner { + bytes32 identifierHash = keccak256(abi.encodePacked(identifier)); + registry[identifierHash] = addr; + emit RegistryUpdated(identifier, identifierHash, addr); + } + + /** + * @notice Gets address associated with the given identifierHash. + * @param identifierHash Identifier hash of contract whose address we want to look up. + * @dev Throws if address not set. + */ + function getAddressForOrDie(bytes32 identifierHash) external view returns (address) { + require(registry[identifierHash] != address(0), "identifier has no registry entry"); + return registry[identifierHash]; + } + + /** + * @notice Gets address associated with the given identifierHash. + * @param identifierHash Identifier hash of contract whose address we want to look up. + */ + function getAddressFor(bytes32 identifierHash) external view returns (address) { + return registry[identifierHash]; + } + + /** + * @notice Gets address associated with the given identifier. + * @param identifier Identifier of contract whose address we want to look up. + * @dev Throws if address not set. + */ + function getAddressForStringOrDie(string calldata identifier) external view returns (address) { + bytes32 identifierHash = keccak256(abi.encodePacked(identifier)); + require(registry[identifierHash] != address(0), "identifier has no registry entry"); + return registry[identifierHash]; + } + + /** + * @notice Gets address associated with the given identifier. + * @param identifier Identifier of contract whose address we want to look up. + */ + function getAddressForString(string calldata identifier) external view returns (address) { + bytes32 identifierHash = keccak256(abi.encodePacked(identifier)); + return registry[identifierHash]; + } + + /** + * @notice Iterates over provided array of identifiers, getting the address for each. + * Returns true if `sender` matches the address of one of the provided identifiers. + * @param identifierHashes Array of hashes of approved identifiers. + * @param sender Address in question to verify membership. + * @return True if `sender` corresponds to the address of any of `identifiers` + * registry entries. + */ + function isOneOf( + bytes32[] calldata identifierHashes, + address sender + ) external view returns (bool) { + for (uint256 i = 0; i < identifierHashes.length; i = i.add(1)) { + if (registry[identifierHashes[i]] == sender) { + return true; + } + } + return false; + } +} diff --git a/packages/protocol/contracts-0.8/governance/Validators.sol b/packages/protocol/contracts-0.8/governance/Validators.sol index 68a816edfcb..98b7dc55f42 100644 --- a/packages/protocol/contracts-0.8/governance/Validators.sol +++ b/packages/protocol/contracts-0.8/governance/Validators.sol @@ -647,6 +647,19 @@ contract Validators is group.slashInfo.lastSlashed = block.timestamp; } + /** + * @notice Allows the EpochManager contract to mint stable token for itself. + * @param amount The amount to be minted. + */ + function mintStableToEpochManager( + uint256 amount + ) external onlyL2 nonReentrant onlyRegisteredContract(EPOCH_MANAGER_REGISTRY_ID) { + require( + IStableToken(getStableToken()).mint(msg.sender, amount), + "mint failed to epoch manager" + ); + } + /** * @notice Returns the validator BLS key. * @param signer The account that registered the validator or its authorized signing address. @@ -886,7 +899,7 @@ contract Validators is address account, uint256 score, uint256 maxPayment - ) external view returns (uint256) { + ) external view virtual returns (uint256) { require(isValidator(account), "Not a validator"); FixidityLib.Fraction memory scoreFraction = FixidityLib.wrap(score); require(scoreFraction.lte(FixidityLib.fixed1()), "Score must be <= 1"); diff --git a/packages/protocol/contracts-0.8/governance/test/EpochRewardsMock.sol b/packages/protocol/contracts-0.8/governance/test/EpochRewardsMock.sol index af929d275e5..38967cf3928 100644 --- a/packages/protocol/contracts-0.8/governance/test/EpochRewardsMock.sol +++ b/packages/protocol/contracts-0.8/governance/test/EpochRewardsMock.sol @@ -2,7 +2,7 @@ pragma solidity >=0.8.7 <0.8.20; import "../../../contracts/governance/interfaces/IEpochRewards.sol"; -// import "forge-std-8/console2.sol"; + /** * @title A wrapper around EpochRewards that exposes internal functions for testing. */ @@ -14,9 +14,7 @@ contract EpochRewardsMock08 is IEpochRewards { } // TODO: (soloseng) implement mock - function updateTargetVotingYield() external { - // console2.log("### Updating Target Voting Yield"); - } + function updateTargetVotingYield() external {} function getRewardsMultiplier( uint256 targetGoldTotalSupplyIncrease @@ -33,8 +31,7 @@ contract EpochRewardsMock08 is IEpochRewards { view returns (uint256, uint256, uint256, uint256) { - // console2.log("### calculating Target Epoch Rewards"); - return (1, 1, 1, 1); + return (5, 5, 5, 5); } function getTargetVotingYieldParameters() external view returns (uint256, uint256, uint256) { return (0, 0, 0); diff --git a/packages/protocol/contracts-0.8/governance/test/ValidatorsMock08.sol b/packages/protocol/contracts-0.8/governance/test/ValidatorsMock08.sol index 28857cb69b0..ff9b7c778da 100644 --- a/packages/protocol/contracts-0.8/governance/test/ValidatorsMock08.sol +++ b/packages/protocol/contracts-0.8/governance/test/ValidatorsMock08.sol @@ -1,250 +1,27 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity >=0.8.7 <0.8.20; -import "../../../contracts/governance/interfaces/IValidators.sol"; +import "../Validators.sol"; import "../../../contracts/common/FixidityLib.sol"; -// import "forge-std-8/console2.sol"; - /** * @title A wrapper around Validators that exposes onlyVm functions for testing. */ -contract ValidatorsMock08 is IValidators { - function updateValidatorScoreFromSigner(address signer, uint256 uptime) external { - // console2.log("### update Validator Score From Signer"); - } +contract ValidatorsMock08 is Validators(true) { + function updateValidatorScoreFromSigner(address signer, uint256 uptime) external override {} function distributeEpochPaymentsFromSigner( address signer, uint256 maxPayment - ) external returns (uint256) { - // console2.log("### distributeEpochPaymentsFromSigner"); - return 0; - // return _distributeEpochPaymentsFromSigner(signer, maxPayment); - } - - function registerValidator( - bytes calldata ecdsaPublicKey, - bytes calldata blsPublicKey, - bytes calldata blsPop - ) external returns (bool) { - return true; - } - - function registerValidator(bytes calldata ecdsaPublicKey) external returns (bool) { - return true; - } - - function deregisterValidator(uint256 index) external returns (bool) { - return true; - } - function affiliate(address group) external returns (bool) { - return true; - } - function deaffiliate() external returns (bool) { - return true; - } - function updateBlsPublicKey( - bytes calldata blsPublicKey, - bytes calldata blsPop - ) external returns (bool) { - return true; - } - function registerValidatorGroup(uint256 commission) external returns (bool) { - return true; - } - function deregisterValidatorGroup(uint256 index) external returns (bool) { - return true; - } - function addMember(address validator) external returns (bool) { - return true; - } - function addFirstMember( - address validator, - address lesser, - address greater - ) external returns (bool) { - return true; - } - function removeMember(address validator) external returns (bool) { - return true; - } - function reorderMember( - address validator, - address lesserMember, - address greaterMember - ) external returns (bool) { - return true; - } - function updateCommission() external {} - function setNextCommissionUpdate(uint256 commission) external {} - function resetSlashingMultiplier() external {} - - // only owner - function setCommissionUpdateDelay(uint256 delay) external {} - function setMaxGroupSize(uint256 size) external returns (bool) { - return true; - } - function setMembershipHistoryLength(uint256 length) external returns (bool) { - return true; - } - function setValidatorScoreParameters( - uint256 exponent, - uint256 adjustmentSpeed - ) external returns (bool) { - return true; - } - function setGroupLockedGoldRequirements(uint256 value, uint256 duration) external returns (bool) { - return true; - } - function setValidatorLockedGoldRequirements( - uint256 value, - uint256 duration - ) external returns (bool) { - return true; - } - function setSlashingMultiplierResetPeriod(uint256 value) external {} - function setDowntimeGracePeriod(uint256 value) external {} - - // only registered contract - function updateEcdsaPublicKey( - address account, - address signer, - bytes calldata ecdsaPublicKey - ) external returns (bool) { - return true; - } - function updatePublicKeys( - address account, - address signer, - bytes calldata ecdsaPublicKey, - bytes calldata blsPublicKey, - bytes calldata blsPop - ) external returns (bool) { - return true; - } - - // only slasher - function forceDeaffiliateIfValidator(address validatorAccount) external {} - function halveSlashingMultiplier(address account) external {} - - // view functions - function maxGroupSize() external view returns (uint256) { - return 0; - } - function downtimeGracePeriod() external view returns (uint256) { - return 0; - } - function getCommissionUpdateDelay() external view returns (uint256) { + ) external override returns (uint256) { return 0; } - function getValidatorScoreParameters() external view returns (uint256, uint256) { - return (0, 0); - } - function getMembershipHistory( - address account - ) external view returns (uint256[] memory, address[] memory, uint256, uint256) { - return (new uint256[](0), new address[](0), 0, 0); - } - function calculateEpochScore(uint256 uptime) external view returns (uint256) { - return 0; - } - - function calculateGroupEpochScore(uint256[] calldata uptimes) external view returns (uint256) { - return 0; - } - - function getAccountLockedGoldRequirement(address account) external view returns (uint256) { - return 0; - } - - function meetsAccountLockedGoldRequirements(address account) external view returns (bool) { - return true; - } - function getValidatorBlsPublicKeyFromSigner(address singer) external view returns (bytes memory) { - return "0x"; - } - function getValidator( - address account - ) external view returns (bytes memory, bytes memory, address, uint256, address) { - return ("0x", "0x", address(0), 0, address(0)); - } - function getValidatorsGroup(address account) external view returns (address affiliation) { - affiliation = address(0); - } - function getValidatorGroup( - address account - ) - external - view - returns (address[] memory, uint256, uint256, uint256, uint256[] memory, uint256, uint256) - { - return (new address[](0), 0, 0, 0, new uint256[](0), 0, 0); - } - function getGroupNumMembers(address account) external view returns (uint256) { - return 0; - } - - function getTopGroupValidators( - address account, - uint256 n - ) external view returns (address[] memory) { - return new address[](0); - } - function getGroupsNumMembers( - address[] calldata accounts - ) external view returns (uint256[] memory) { - return new uint256[](0); - } - function getNumRegisteredValidators() external view returns (uint256) { - return 0; - } - - function groupMembershipInEpoch( - address account, - uint256 epochNumber, - uint256 index - ) external view returns (address) { - return address(0); - } - - function getValidatorLockedGoldRequirements() external view returns (uint256, uint256) { - return (0, 0); - } - function getGroupLockedGoldRequirements() external view returns (uint256, uint256) { - return (0, 0); - } - function getRegisteredValidators() external view returns (address[] memory) { - return new address[](0); - } - function getRegisteredValidatorGroups() external view returns (address[] memory) { - return new address[](0); - } - function isValidatorGroup(address account) external view returns (bool) { - return true; - } - function isValidator(address account) external view returns (bool) { - return true; - } - function getValidatorGroupSlashingMultiplier(address account) external view returns (uint256) { - return 0; - } - - function getMembershipInLastEpoch(address account) external view returns (address) { - return address(0); - } - function getMembershipInLastEpochFromSigner(address signer) external view returns (address) { - return address(0); - } function computeEpochReward( address account, uint256 score, uint256 maxPayment - ) external view returns (uint256) { + ) external view override returns (uint256) { return 1; } - function getMembershipHistoryLength() external view returns (uint256) { - return 0; - } } diff --git a/packages/protocol/contracts-0.8/stability/test/MockReserve.sol b/packages/protocol/contracts-0.8/stability/test/MockReserve.sol new file mode 100644 index 00000000000..09b64ab4385 --- /dev/null +++ b/packages/protocol/contracts-0.8/stability/test/MockReserve.sol @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity >=0.8.7 <0.8.20; + +// solhint-disable no-unused-vars + +import "@openzeppelin/contracts8/token/ERC20/IERC20.sol"; + +/** + * @title A mock Reserve for testing. + */ +contract MockReserve08 { + mapping(address => bool) public tokens; + + IERC20 public goldToken; + + // solhint-disable-next-line no-empty-blocks + receive() external payable {} + + function setGoldToken(address goldTokenAddress) external { + goldToken = IERC20(goldTokenAddress); + } + + function transferGold(address to, uint256 value) external returns (bool) { + require(goldToken.transfer(to, value), "gold token transfer failed"); + return true; + } + + function transferExchangeGold(address to, uint256 value) external returns (bool) { + require(goldToken.transfer(to, value), "gold token transfer failed"); + return true; + } + + function addToken(address token) external returns (bool) { + tokens[token] = true; + return true; + } + + function getUnfrozenReserveGoldBalance() external view returns (uint256) { + return address(this).balance; + } + + function burnToken(address) external pure returns (bool) { + return true; + } + + function getReserveGoldBalance() public view returns (uint256) { + return address(this).balance; + } +} diff --git a/packages/protocol/contracts/common/interfaces/IEpochManager.sol b/packages/protocol/contracts/common/interfaces/IEpochManager.sol index 964187f9f53..e70bb033b87 100644 --- a/packages/protocol/contracts/common/interfaces/IEpochManager.sol +++ b/packages/protocol/contracts/common/interfaces/IEpochManager.sol @@ -5,7 +5,6 @@ interface IEpochManager { function initializeSystem( uint256 firstEpochNumber, uint256 firstEpochBlock, - // uint256 firstEpochTimestamp, // TODO: do we need END timestamp? address[] calldata firstElected ) external; function startNextEpochProcess() external; @@ -14,9 +13,9 @@ interface IEpochManager { address[] calldata lessers, address[] calldata greaters ) external; - function getCurrentEpoch() external view returns (uint256, uint256, uint256, uint256, uint256); + function getCurrentEpoch() external view returns (uint256, uint256, uint256, uint256); function getCurrentEpochNumber() external view returns (uint256); function getElected() external view returns (address[] memory); - function epochManagerInitializer() external view returns (address); + function epochManagerEnabler() external view returns (address); function epochDuration() external view returns (uint256); } diff --git a/packages/protocol/contracts/common/interfaces/IEpochManagerEnabler.sol b/packages/protocol/contracts/common/interfaces/IEpochManagerEnabler.sol new file mode 100644 index 00000000000..c245dac581a --- /dev/null +++ b/packages/protocol/contracts/common/interfaces/IEpochManagerEnabler.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity >=0.5.13 <0.9.0; + +interface IEpochManagerEnabler { + function initEpochManager() external; + function getEpochNumber() external returns (uint256); + function captureEpochAndValidators() external; +} diff --git a/packages/protocol/contracts/common/interfaces/IScoreManager.sol b/packages/protocol/contracts/common/interfaces/IScoreManager.sol new file mode 100644 index 00000000000..0020fd65df0 --- /dev/null +++ b/packages/protocol/contracts/common/interfaces/IScoreManager.sol @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity >=0.5.13 <0.9.0; + +interface IScoreManager { + function setGroupScore(address group, uint256 score) external; + function setValidatorScore(address validator, uint256 score) external; + function getValidatorScore(address validator) external view returns (uint256); + function getGroupScore(address validator) external view returns (uint256); + function owner() external view returns (address); +} diff --git a/packages/protocol/contracts/governance/Election.sol b/packages/protocol/contracts/governance/Election.sol index d6b928e6e25..86836975774 100644 --- a/packages/protocol/contracts/governance/Election.sol +++ b/packages/protocol/contracts/governance/Election.sol @@ -153,6 +153,14 @@ contract Election is ); event EpochRewardsDistributedToVoters(address indexed group, uint256 value); + modifier onlyVmOrPermitted(address permittedAddress) { + if (isL2()) require(msg.sender == permittedAddress, "Only permitted address can call"); + else { + require(msg.sender == address(0), "Only VM can call"); + } + _; + } + /** * @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. @@ -340,7 +348,7 @@ contract Election is uint256 value, address lesser, address greater - ) external onlyVm onlyL1 { + ) external onlyVmOrPermitted(registry.getAddressFor(EPOCH_MANAGER_REGISTRY_ID)) { _distributeEpochRewards(group, value, lesser, greater); } diff --git a/packages/protocol/contracts/governance/EpochRewards.sol b/packages/protocol/contracts/governance/EpochRewards.sol index e04ac3c68c7..774da4f90d9 100644 --- a/packages/protocol/contracts/governance/EpochRewards.sol +++ b/packages/protocol/contracts/governance/EpochRewards.sol @@ -85,12 +85,11 @@ contract EpochRewards is event TargetVotingYieldUpdated(uint256 fraction); - modifier onlyVmOrEpochManager() { - require( - msg.sender == address(0) || - msg.sender == registry.getAddressForOrDie(EPOCH_MANAGER_REGISTRY_ID), - "Only VM or Epoch Manager can call" - ); + modifier onlyVmOrPermitted(address permittedAddress) { + if (isL2()) require(msg.sender == permittedAddress, "Only permitted address can call"); + else { + require(msg.sender == address(0), "Only VM can call"); + } _; } @@ -153,7 +152,11 @@ contract EpochRewards is * voting Gold fraction. * @dev Only called directly by the protocol. */ - function updateTargetVotingYield() external onlyVmOrEpochManager onlyWhenNotFrozen { + function updateTargetVotingYield() + external + onlyVmOrPermitted(registry.getAddressFor(EPOCH_MANAGER_REGISTRY_ID)) + onlyWhenNotFrozen + { _updateTargetVotingYield(); } diff --git a/packages/protocol/contracts/governance/interfaces/IValidators.sol b/packages/protocol/contracts/governance/interfaces/IValidators.sol index 01e16821776..e7b2db4953a 100644 --- a/packages/protocol/contracts/governance/interfaces/IValidators.sol +++ b/packages/protocol/contracts/governance/interfaces/IValidators.sol @@ -41,6 +41,7 @@ interface IValidators { bytes calldata, bytes calldata ) external returns (bool); + function mintStableToEpochManager(uint256 amount) external; // only VM function updateValidatorScoreFromSigner(address, uint256) external; diff --git a/packages/protocol/lib/registry-utils.ts b/packages/protocol/lib/registry-utils.ts index 5b565848913..df9e5f79a6d 100644 --- a/packages/protocol/lib/registry-utils.ts +++ b/packages/protocol/lib/registry-utils.ts @@ -19,6 +19,9 @@ export enum CeloContractName { DowntimeSlasher = 'DowntimeSlasher', Election = 'Election', EpochRewards = 'EpochRewards', + EpochManagerEnabler = 'EpochManagerEnabler', + EpochManager = 'EpochManager', + ScoreManager = 'ScoreManager', Escrow = 'Escrow', Exchange = 'Exchange', ExchangeEUR = 'ExchangeEUR', diff --git a/packages/protocol/migrations_sol/Migration.s.sol b/packages/protocol/migrations_sol/Migration.s.sol index 3fac354a1b8..802ce943500 100644 --- a/packages/protocol/migrations_sol/Migration.s.sol +++ b/packages/protocol/migrations_sol/Migration.s.sol @@ -27,6 +27,7 @@ import "@celo-contracts/common/interfaces/IFeeHandler.sol"; import "@celo-contracts/common/interfaces/IFeeHandlerInitializer.sol"; import "@celo-contracts/common/interfaces/IFeeCurrencyWhitelist.sol"; import "@celo-contracts/common/interfaces/IAccounts.sol"; +import "@celo-contracts/common/interfaces/IEpochManagerEnabler.sol"; import "@celo-contracts/governance/interfaces/ILockedGoldInitializer.sol"; import "@celo-contracts/governance/interfaces/IValidatorsInitializer.sol"; import "@celo-contracts/governance/interfaces/IElectionInitializer.sol"; @@ -50,9 +51,14 @@ import "@celo-contracts/stability/interfaces/ISortedOracles.sol"; import "@celo-contracts-8/common/interfaces/IFeeCurrencyDirectoryInitializer.sol"; import "@celo-contracts-8/common/interfaces/IGasPriceMinimumInitializer.sol"; import "@celo-contracts-8/common/interfaces/ICeloUnreleasedTreasureInitializer.sol"; +import "@celo-contracts-8/common/interfaces/IEpochManagerEnablerInitializer.sol"; +import "@celo-contracts-8/common/interfaces/IEpochManagerInitializer.sol"; +import "@celo-contracts-8/common/interfaces/IScoreManagerInitializer.sol"; import "@celo-contracts-8/common/interfaces/IFeeCurrencyDirectory.sol"; import "@celo-contracts-8/common/UsingRegistry.sol"; +import "@test-sol/utils/SECP256K1.sol"; + contract ForceTx { // event to trigger so a tx can be processed event VanillaEvent(string); @@ -234,6 +240,9 @@ contract Migration is Script, UsingRegistry, MigrationsConstants { migrateFeeHandler(json); migrateOdisPayments(); migrateCeloUnreleasedTreasure(); + migrateEpochManagerEnabler(); + migrateEpochManager(json); + migrateScoreManager(); migrateGovernance(json); vm.stopBroadcast(); @@ -241,6 +250,12 @@ contract Migration is Script, UsingRegistry, MigrationsConstants { // Functions with broadcast with different addresses // Validators needs to lock, which can be only used by the msg.sender electValidators(json); + + vm.startBroadcast(DEPLOYER_ACCOUNT); + + captureEpochManagerEnablerValidators(); + + vm.stopBroadcast(); } /** @@ -930,6 +945,42 @@ contract Migration is Script, UsingRegistry, MigrationsConstants { ); } + function migrateEpochManagerEnabler() public { + deployProxiedContract( + "EpochManagerEnabler", + abi.encodeWithSelector(IEpochManagerEnablerInitializer.initialize.selector, REGISTRY_ADDRESS) + ); + } + + function migrateScoreManager() public { + deployProxiedContract( + "ScoreManager", + abi.encodeWithSelector(IScoreManagerInitializer.initialize.selector) + ); + } + + function migrateEpochManager(string memory json) public { + address carbonOffsettingPartner = abi.decode( + json.parseRaw(".epochManager.carbonOffsettingPartner"), + (address) + ); + address newEpochDuration = abi.decode( + json.parseRaw(".epochManager.newEpochDuration"), + (address) + ); + + deployProxiedContract( + "EpochManager", + abi.encodeWithSelector( + IEpochManagerInitializer.initialize.selector, + REGISTRY_ADDRESS, + newEpochDuration, + carbonOffsettingPartner, + registry.getAddressForString("EpochManagerEnabler") + ) + ); + } + function migrateGovernance(string memory json) public { bool useApprover = abi.decode(json.parseRaw(".governanceApproverMultiSig.required"), (bool)); @@ -1060,14 +1111,12 @@ contract Migration is Script, UsingRegistry, MigrationsConstants { function registerValidator( uint256 validatorIndex, - bytes memory ecdsaPubKey, uint256 validatorKey, uint256 amountToLock, address groupToAffiliate ) public returns (address) { vm.startBroadcast(validatorKey); lockGold(amountToLock); - bytes memory _ecdsaPubKey = ecdsaPubKey; address accountAddress = (new ForceTx()).identity(); // these blobs are not checked in the contract @@ -1082,6 +1131,7 @@ contract Migration is Script, UsingRegistry, MigrationsConstants { bytes16(0x05050505050505050505050505050506), bytes16(0x06060606060606060606060606060607) ); + (bytes memory ecdsaPubKey, , , ) = _generateEcdsaPubKeyWithSigner(accountAddress, validatorKey); getValidators().registerValidator(ecdsaPubKey, newBlsPublicKey, newBlsPop); getValidators().affiliate(groupToAffiliate); console.log("Done registering validatora"); @@ -1091,20 +1141,12 @@ contract Migration is Script, UsingRegistry, MigrationsConstants { } function getValidatorKeyIndex( + uint256 groupCount, uint256 groupIndex, uint256 validatorIndex, uint256 membersInAGroup ) public returns (uint256) { - return groupIndex * membersInAGroup + validatorIndex + 1; - } - - function getValidatorKeyFromGroupGroup( - uint256[] memory keys, - uint256 groupIndex, - uint256 validatorIndex, - uint256 membersInAGroup - ) public returns (uint256) { - return keys[getValidatorKeyIndex(groupIndex, validatorIndex, membersInAGroup)]; + return groupCount + groupIndex * membersInAGroup + validatorIndex; } function registerValidatorGroup( @@ -1123,6 +1165,57 @@ contract Migration is Script, UsingRegistry, MigrationsConstants { vm.stopBroadcast(); } + function _generateEcdsaPubKeyWithSigner( + address _validator, + uint256 _signerPk + ) internal returns (bytes memory ecdsaPubKey, uint8 v, bytes32 r, bytes32 s) { + (v, r, s) = getParsedSignatureOfAddress(_validator, _signerPk); + + bytes32 addressHash = keccak256(abi.encodePacked(_validator)); + + ecdsaPubKey = addressToPublicKey(addressHash, v, r, s); + } + + function addressToPublicKey( + bytes32 message, + uint8 _v, + bytes32 _r, + bytes32 _s + ) public returns (bytes memory) { + address SECP256K1Address = actor("SECP256K1Address"); + deployCodeTo("SECP256K1.sol:SECP256K1", SECP256K1Address); + ISECP256K1 sECP256K1 = ISECP256K1(SECP256K1Address); + + string memory header = "\x19Ethereum Signed Message:\n32"; + bytes32 _message = keccak256(abi.encodePacked(header, message)); + (uint256 x, uint256 y) = sECP256K1.recover( + uint256(_message), + _v - 27, + uint256(_r), + uint256(_s) + ); + return abi.encodePacked(x, y); + } + + function actor(string memory name) internal returns (address) { + return vm.addr(uint256(keccak256(abi.encodePacked(name)))); + } + + function toEthSignedMessageHash(bytes32 hash) internal pure returns (bytes32) { + // 32 is the length in bytes of hash, + // enforced by the type signature above + return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", hash)); + } + + function getParsedSignatureOfAddress( + address _address, + uint256 privateKey + ) public pure returns (uint8, bytes32, bytes32) { + bytes32 addressHash = keccak256(abi.encodePacked(_address)); + bytes32 prefixedHash = toEthSignedMessageHash(addressHash); + return vm.sign(privateKey, prefixedHash); + } + function electValidators(string memory json) public { console.log("Electing validators: "); @@ -1137,7 +1230,6 @@ contract Migration is Script, UsingRegistry, MigrationsConstants { json.parseRaw(".validators.validatorLockedGoldRequirements.value"), (uint256) ); - bytes[] memory ecdsaPubKeys = abi.decode(json.parseRaw(".validators.ecdsaPubKeys"), (bytes[])); // attestationKeys not migrated if (valKeys.length == 0) { @@ -1150,52 +1242,76 @@ contract Migration is Script, UsingRegistry, MigrationsConstants { ); } - uint256 validatorGroup0Key = valKeys[0]; + uint256 groupCount = 3; + console.log("groupCount", groupCount); - address groupAddress = registerValidatorGroup( - validatorGroup0Key, - maxGroupSize * validatorLockedGoldRequirements, - commission, - json - ); + address[] memory groups = new address[](groupCount); - console.log(" * Registering ${group.valKeys.length} validators ..."); + // register 3 validator groups + for (uint256 groupIndex = 0; groupIndex < groupCount; groupIndex++) { + address groupAddress = registerValidatorGroup( + valKeys[groupIndex], + maxGroupSize * validatorLockedGoldRequirements, + commission, + json + ); + groups[groupIndex] = groupAddress; + console.log("registered group: ", groupAddress); + } + + console.log(" * Registering validators ... Count: ", valKeys.length - groupCount); // Split the validator keys into groups that will fit within the max group size. - uint256 amountOfGroups = Math.ceilDiv(valKeys.length, maxGroupSize); // TODO change name of variable amount of groups for amount in group - for (uint256 validatorIndex = 0; validatorIndex < amountOfGroups; validatorIndex++) { - console.log("Validator key index", getValidatorKeyIndex(0, validatorIndex, maxGroupSize)); - console.log("Registering validator #: ", validatorIndex); - bytes memory ecdsaPubKey = ecdsaPubKeys[ - getValidatorKeyIndex(0, validatorIndex, maxGroupSize) - ]; - address validator = registerValidator( - validatorIndex, - ecdsaPubKey, - getValidatorKeyFromGroupGroup(valKeys, 0, validatorIndex, maxGroupSize), - validatorLockedGoldRequirements, - groupAddress - ); - // TODO start broadcast - console.log("Adding to group..."); - - vm.startBroadcast(validatorGroup0Key); - if (validatorIndex == 0) { - getValidators().addFirstMember(validator, address(0), address(0)); - console.log("Making group vote for itself"); - getElection().vote( - groupAddress, - getLockedGold().getAccountNonvotingLockedGold(groupAddress), - address(0), - address(0) + for (uint256 groupIndex = 0; groupIndex < groupCount; groupIndex++) { + address groupAddress = groups[groupIndex]; + console.log("Registering members for group: ", groupAddress); + for (uint256 validatorIndex = 0; validatorIndex < maxGroupSize; validatorIndex++) { + uint256 validatorKeyIndex = getValidatorKeyIndex( + groupCount, + groupIndex, + validatorIndex, + maxGroupSize ); - } else { - // unimplemented - console.log("WARNING: case not implemented"); - } + console.log("Registering validator #: ", validatorIndex); + address validator = registerValidator( + validatorIndex, + valKeys[validatorKeyIndex], + validatorLockedGoldRequirements, + groupAddress + ); + // TODO start broadcast + console.log("Adding to group..."); + + vm.startBroadcast(groups[groupIndex]); + address greater = groupIndex == 0 ? address(0) : groups[groupIndex - 1]; + + if (validatorIndex == 0) { + getValidators().addFirstMember(validator, address(0), greater); + console.log("Making group vote for itself"); + } else { + getValidators().addMember(validator); + } + getElection().vote(groupAddress, validatorLockedGoldRequirements, address(0), greater); - vm.stopBroadcast(); + vm.stopBroadcast(); + } } } + + function captureEpochManagerEnablerValidators() public { + address numberValidatorsInCurrentSetPrecompileAddress = 0x00000000000000000000000000000000000000f9; + numberValidatorsInCurrentSetPrecompileAddress.call( + abi.encodeWithSignature("setNumberOfValidators()") + ); + + address validatorSignerAddressFromCurrentSetPrecompileAddress = 0x00000000000000000000000000000000000000fa; + validatorSignerAddressFromCurrentSetPrecompileAddress.call( + abi.encodeWithSignature("setValidators()") + ); + + address epochManagerEnabler = registry.getAddressForString("EpochManagerEnabler"); + IEpochManagerEnabler epochManagerEnablerContract = IEpochManagerEnabler(epochManagerEnabler); + epochManagerEnablerContract.captureEpochAndValidators(); + } } diff --git a/packages/protocol/migrations_sol/constants.sol b/packages/protocol/migrations_sol/constants.sol index cf4ff6c0526..0c9cf6c7f20 100644 --- a/packages/protocol/migrations_sol/constants.sol +++ b/packages/protocol/migrations_sol/constants.sol @@ -7,7 +7,7 @@ contract MigrationsConstants is TestConstants { address constant DEPLOYER_ACCOUNT = 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266; // List of contracts that are expected to be in Registry.sol - string[24] contractsInRegistry = [ + string[27] contractsInRegistry = [ "Accounts", "BlockchainParameters", "CeloUnreleasedTreasure", @@ -16,6 +16,8 @@ contract MigrationsConstants is TestConstants { "DowntimeSlasher", "Election", "EpochRewards", + "EpochManagerEnabler", + "EpochManager", "Escrow", "FederatedAttestations", "FeeCurrencyWhitelist", @@ -31,6 +33,7 @@ contract MigrationsConstants is TestConstants { "SortedOracles", "UniswapFeeHandlerSeller", "MentoFeeHandlerSeller", - "Validators" + "Validators", + "ScoreManager" ]; } diff --git a/packages/protocol/migrations_sol/migrationsConfig.json b/packages/protocol/migrations_sol/migrationsConfig.json index 54c5e208267..1836e8f1995 100644 --- a/packages/protocol/migrations_sol/migrationsConfig.json +++ b/packages/protocol/migrations_sol/migrationsConfig.json @@ -1,5 +1,5 @@ { - "goldToken": {"frozen":false}, + "goldToken": { "frozen": false }, "sortedOracles": { "reportExpirySeconds": 300 }, @@ -21,9 +21,23 @@ "0x246f4599eFD3fA67AC44335Ed5e749E518Ffd8bB", "0x298FbD6dad2Fc2cB56d7E37d8aCad8Bf07324f67" ], - "assetAllocationSymbols_": ["cGLD", "BTC", "ETH", "DAI", "They are don't by converting string to hex, and then `cast to-bytes32`"], - "assetAllocationSymbols": ["0x63474c4400000000000000000000000000000000000000000000000000000000", "0x4254430000000000000000000000000000000000000000000000000000000000", "0x4554480000000000000000000000000000000000000000000000000000000000", "0x4441490000000000000000000000000000000000000000000000000000000000"], - "assetAllocationWeights": [500000000000000000000000, 300000000000000000000000, 150000000000000000000000, 50000000000000000000000], + "assetAllocationSymbols_": [ + "cGLD", + "BTC", + "ETH", + "DAI", + "They are don't by converting string to hex, and then `cast to-bytes32`" + ], + "assetAllocationSymbols": [ + "0x63474c4400000000000000000000000000000000000000000000000000000000", + "0x4254430000000000000000000000000000000000000000000000000000000000", + "0x4554480000000000000000000000000000000000000000000000000000000000", + "0x4441490000000000000000000000000000000000000000000000000000000000" + ], + "assetAllocationWeights": [ + 500000000000000000000000, 300000000000000000000000, 150000000000000000000000, + 50000000000000000000000 + ], "initialBalance": 5000000000000000000000000 }, "stableTokens": { @@ -39,7 +53,7 @@ }, "exchange": { "spread": 5000000000000000000000, - "reserveFraction": 10000000000000000000000, + "reserveFraction": 10000000000000000000000, "updateFrequency": 300, "minimumReports": 1, "frozen": false @@ -64,10 +78,10 @@ "adjustmentSpeed": 100000000000000000000000 }, "membershipHistoryLength": 60, - "commissionUpdateDelay": 51840, - "commissionUpdateDelay_help": "(3 * DAY) / 5", + "commissionUpdateDelay": 51840, + "commissionUpdateDelay_help": "(3 * DAY) / 5", - "maxGroupSize": 5, + "maxGroupSize": 2, "slashingMultiplierResetPeriod": 2592000, "slashingPenaltyResetPeriod_help": "30 * DAY", "downtimeGracePeriod": 0, @@ -76,11 +90,19 @@ "groupName": "cLabs", "commission": 100000000000000000000000, "votesRatioOfLastVsFirstGroup": 2000000000000000000000000, - "valKeys": ["0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d","0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a"], - "ecdsaPubKeys": [ - "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - "0x9d9031e97dd78ff8c15aa86939de9b1e791066a0224e331bc962a2099a7b1f0464b8bbafe1535f2301c72c2cb3535b172da30b02686ab0393d348614f157fbdb"] + "valKeys": [ + "0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d", + "0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a", + "0x7c852118294e51e653712a81e05800f419141751be58f605c371e15141b007a6", + "0x47e179ec197488593b187f80a00eb0da91f1b9d0b13f8733639f19c30a34926a", + "0x8b3a350cf5c34c9194ca85829a2df0ec3153be0318b5e2d3348e872092edffba", + "0x92db14e403b83dfe3df233f83dfa3a0d7096f21ca9b0d6d6b8d88b2b4ec1564e", + "0x4bbbf85ce3377467afe5d46f804f221813b2bb87f24d81f60f1fcdbf7cbf4356", + "0xdbda1821b80551c9d65939329250298aa3472ba22feea921c0cf5d620ea67b97", + "0x2a871d0798f97d79848a013d4936a73bf4cc922c825d33c1cf7073dff6d409c6" + ] }, + "election": { "minElectableValidators": 1, "maxElectableValidators": 100, @@ -89,14 +111,14 @@ }, "epochRewards": { "targetVotingYieldParameters": { - "initial": 0, - "max": 500000000000000000000, + "initial": 160000000000000000000, + "max": 500000000000000000000, "max_helper": "(x + 1) ^ 365 = 1.20", - "adjustmentFactor": 0, + "adjustmentFactor": 0, "adjustmentFactor_helper": "Change to 1 / 3650 once Mainnet activated." }, "rewardsMultiplierParameters": { - "max": 2, + "max": 2000000000000000000000000, "adjustmentFactors": { "underspend": 500000000000000000000000, "overspend": 5000000000000000000000000 @@ -106,10 +128,14 @@ "maxValidatorEpochPayment": 205479452054794520547, "maxValidatorEpochPayment_helper": "(75,000 / 365) * 10 ^ 18", "communityRewardFraction": 250000000000000000000000, - "carbonOffsettingPartner": "0x0000000000000000000000000000000000000000", + "carbonOffsettingPartner": "0xD533Ca259b330c7A88f74E000a3FaEa2d63B7972", "carbonOffsettingFraction": 1000000000000000000000, "frozen": false }, + "epochManager": { + "newEpochDuration": 86400, + "carbonOffsettingPartner": "0xD533Ca259b330c7A88f74E000a3FaEa2d63B7972" + }, "random": { "randomnessBlockRetentionWindow": "720", "randomnessBlockRetentionWindow_helper": "HOUR / 5" @@ -147,7 +173,7 @@ "dequeueFrequency": 14400, "queueExpiry": 2419200, "queueExpiry_helper": "4 * WEEK", - "dequeueFrequency_helper":"4 * HOUR", + "dequeueFrequency_helper": "4 * HOUR", "approvalStageDuration": 14400, "approvalStageDuration_helper": "4 * HOUR", "referendumStageDuration": 86400, @@ -162,4 +188,4 @@ "skipSetConstitution": false, "skipTransferOwnership": false } -} \ No newline at end of file +} diff --git a/packages/protocol/scripts/consts.ts b/packages/protocol/scripts/consts.ts index 729b0dfb276..d0edb652342 100644 --- a/packages/protocol/scripts/consts.ts +++ b/packages/protocol/scripts/consts.ts @@ -58,6 +58,7 @@ export const CoreContracts = [ 'Election', 'EpochRewards', 'EpochManager', + 'EpochManagerEnabler', 'Governance', 'GovernanceApproverMultiSig', 'BlockchainParameters', diff --git a/packages/protocol/scripts/foundry/deploy_precompiles.sh b/packages/protocol/scripts/foundry/deploy_precompiles.sh index f87b77761f1..1ebbae44a1d 100755 --- a/packages/protocol/scripts/foundry/deploy_precompiles.sh +++ b/packages/protocol/scripts/foundry/deploy_precompiles.sh @@ -13,4 +13,12 @@ cast rpc anvil_setCode --rpc-url $ANVIL_RPC_URL $EpochSizeAddress $EpochSizeByte ProofOfPossesionAddress=0x00000000000000000000000000000000000000fb ProofOfPossesionBytecode=`cat ./out/ProofOfPossesionPrecompile.sol/ProofOfPossesionPrecompile.json | jq -r '.deployedBytecode.object'` -cast rpc anvil_setCode --rpc-url $ANVIL_RPC_URL $ProofOfPossesionAddress $ProofOfPossesionBytecode \ No newline at end of file +cast rpc anvil_setCode --rpc-url $ANVIL_RPC_URL $ProofOfPossesionAddress $ProofOfPossesionBytecode + +NumberValidatorsInCurrentSetPrecompileAddress=0x00000000000000000000000000000000000000f9 +NumberValidatorsInCurrentSetPrecompileBytecode=`cat ./out/NumberValidatorsInCurrentSetPrecompile.sol/NumberValidatorsInCurrentSetPrecompile.json | jq -r '.deployedBytecode.object'` +cast rpc anvil_setCode --rpc-url $ANVIL_RPC_URL $NumberValidatorsInCurrentSetPrecompileAddress $NumberValidatorsInCurrentSetPrecompileBytecode + +ValidatorSignerAddressFromCurrentSetAddress=0x00000000000000000000000000000000000000fa +ValidatorSignerAddressFromCurrentSetBytecode=`cat ./out/ValidatorSignerAddressFromCurrentSetPrecompile.sol/ValidatorSignerAddressFromCurrentSetPrecompile.json | jq -r '.deployedBytecode.object'` +cast rpc anvil_setCode --rpc-url $ANVIL_RPC_URL $ValidatorSignerAddressFromCurrentSetAddress $ValidatorSignerAddressFromCurrentSetBytecode diff --git a/packages/protocol/test-sol/constants.sol b/packages/protocol/test-sol/constants.sol index f0d93e336f9..92f8422412a 100644 --- a/packages/protocol/test-sol/constants.sol +++ b/packages/protocol/test-sol/constants.sol @@ -25,7 +25,6 @@ contract TestConstants { string constant GovernanceContract = "Governance"; string constant EpochRewardsContract = "EpochRewards"; string constant EpochManagerContract = "EpochManager"; - string constant EpochManagerInitializerContract = "EpochManagerInitializer"; string constant ScoreManagerContract = "ScoreManager"; string constant ReserveContract = "Reserve"; string constant CeloUnreleasedTreasureContract = "CeloUnreleasedTreasure"; diff --git a/packages/protocol/test-sol/devchain/migration/Migration.t.sol b/packages/protocol/test-sol/devchain/migration/Migration.t.sol index c5c141a1114..463656c4376 100644 --- a/packages/protocol/test-sol/devchain/migration/Migration.t.sol +++ b/packages/protocol/test-sol/devchain/migration/Migration.t.sol @@ -1,19 +1,30 @@ // SPDX-License-Identifier: UNLICENSED -pragma solidity >=0.8.7 <0.8.20; +pragma solidity >=0.8.0 <0.8.20; -import { Test } from "forge-std-8/Test.sol"; -import "forge-std-8/console2.sol"; +import "celo-foundry-8/Test.sol"; +import { Utils08 } from "@test-sol/utils08.sol"; import { TestConstants } from "@test-sol/constants.sol"; import { MigrationsConstants } from "@migrations-sol/constants.sol"; import "@celo-contracts/common/interfaces/IRegistry.sol"; import "@celo-contracts/common/interfaces/IProxy.sol"; +import "@celo-contracts/common/interfaces/ICeloToken.sol"; +import "@celo-contracts/common/interfaces/IAccounts.sol"; +import "@celo-contracts/common/interfaces/IEpochManager.sol"; +import "@celo-contracts/common/interfaces/IEpochManagerEnabler.sol"; +import "@celo-contracts/common/interfaces/IScoreManager.sol"; +import "@celo-contracts/governance/interfaces/IValidators.sol"; +import "@celo-contracts/governance/interfaces/IElection.sol"; -contract IntegrationTest is Test, TestConstants { +import "@celo-contracts-8/common/interfaces/IPrecompiles.sol"; + +contract IntegrationTest is Test, TestConstants, Utils08 { IRegistry registry = IRegistry(REGISTRY_ADDRESS); - function setUp() public {} + uint256 constant RESERVE_BALANCE = 69411663406170917420347916; // current as of 08/20/24 + + function setUp() public virtual {} /** * @notice Removes CBOR encoded metadata from the tail of the deployedBytecode. @@ -70,6 +81,7 @@ contract RegistryIntegrationTest is IntegrationTest, MigrationsConstants { bytes32 hashValidators = keccak256(abi.encodePacked("Validators")); bytes32 hashCeloToken = keccak256(abi.encodePacked("CeloToken")); bytes32 hashLockedCelo = keccak256(abi.encodePacked("LockedCelo")); + bytes32 hashEpochManager = keccak256(abi.encodePacked("EpochManager")); for (uint256 i = 0; i < contractsInRegistry.length; i++) { // Read name from list of core contracts @@ -89,7 +101,8 @@ contract RegistryIntegrationTest is IntegrationTest, MigrationsConstants { hashContractName != hashSortedOracles && hashContractName != hashValidators && hashContractName != hashCeloToken && // TODO: remove once GoldToken contract has been renamed to CeloToken - hashContractName != hashLockedCelo // TODO: remove once LockedGold contract has been renamed to LockedCelo + hashContractName != hashLockedCelo && // TODO: remove once LockedGold contract has been renamed to LockedCelo + hashContractName != hashEpochManager ) { // Get proxy address registered in the Registry address proxyAddress = registry.getAddressForStringOrDie(contractName); @@ -123,3 +136,163 @@ contract RegistryIntegrationTest is IntegrationTest, MigrationsConstants { } } } + +contract EpochManagerIntegrationTest is IntegrationTest, MigrationsConstants { + ICeloToken celoToken; + IAccounts accountsContract; + IValidators validatorsContract; + IElection electionContract; + IScoreManager scoreManagerContract; + IEpochManager epochManager; + IEpochManagerEnabler epochManagerEnabler; + + address reserveAddress; + address unreleasedTreasury; + address randomAddress; + + uint256 firstEpochNumber = 100; + uint256 firstEpochBlock = 100; + address[] firstElected; + address[] validatorsList; + + address[] groups; + + uint256[] groupScore = [5e23, 7e23, 1e24]; + uint256[] validatorScore = [1e23, 1e23, 1e23, 1e23, 1e23, 1e23]; + + function setUp() public override { + super.setUp(); + randomAddress = actor("randomAddress"); + + validatorsContract = IValidators(registry.getAddressForStringOrDie("Validators")); + electionContract = IElection(registry.getAddressForStringOrDie("Election")); + scoreManagerContract = IScoreManager(registry.getAddressForStringOrDie("ScoreManager")); + unreleasedTreasury = registry.getAddressForStringOrDie("CeloUnreleasedTreasure"); + reserveAddress = registry.getAddressForStringOrDie("Reserve"); + + validatorsList = validatorsContract.getRegisteredValidators(); + groups = validatorsContract.getRegisteredValidatorGroups(); + + // mint to the reserve + celoToken = ICeloToken(registry.getAddressForStringOrDie("GoldToken")); + + vm.deal(address(0), CELO_SUPPLY_CAP); + vm.prank(address(0)); + celoToken.mint(reserveAddress, RESERVE_BALANCE); + + vm.prank(address(0)); + celoToken.mint(randomAddress, L1_MINTED_CELO_SUPPLY - RESERVE_BALANCE); // mint outstanding l1 supply before L2. + + epochManager = IEpochManager(registry.getAddressForStringOrDie("EpochManager")); + epochManagerEnabler = IEpochManagerEnabler( + registry.getAddressForStringOrDie("EpochManagerEnabler") + ); + } + + function activateValidators() public { + address[] memory registeredValidators = validatorsContract.getRegisteredValidators(); + travelEpochL1(vm); + travelEpochL1(vm); + travelEpochL1(vm); + travelEpochL1(vm); + for (uint256 i = 0; i < registeredValidators.length; i++) { + (, , address validatorGroup, , ) = validatorsContract.getValidator(registeredValidators[i]); + if (electionContract.getPendingVotesForGroup(validatorGroup) == 0) { + continue; + } + vm.startPrank(validatorGroup); + electionContract.activate(validatorGroup); + vm.stopPrank(); + } + } + + function test_IsSetupCorrect() public { + assertEq( + registry.getAddressForStringOrDie("EpochManagerEnabler"), + epochManager.epochManagerEnabler() + ); + assertEq( + registry.getAddressForStringOrDie("EpochManagerEnabler"), + address(epochManagerEnabler) + ); + assertEq(address(epochManagerEnabler), epochManager.epochManagerEnabler()); + } + + function test_Reverts_whenSystemNotInitialized() public { + vm.expectRevert("Epoch system not initialized"); + epochManager.startNextEpochProcess(); + } + + function test_Reverts_WhenEndOfEpochHasNotBeenReached() public { + // fund treasury + vm.prank(address(0)); + celoToken.mint(unreleasedTreasury, L2_INITIAL_STASH_BALANCE); + + uint256 l1EpochNumber = IPrecompiles(address(validatorsContract)).getEpochNumber(); + + vm.prank(address(epochManagerEnabler)); + epochManager.initializeSystem(l1EpochNumber, block.number, validatorsList); + + vm.expectRevert("Epoch is not ready to start"); + epochManager.startNextEpochProcess(); + } + + function test_Reverts_whenAlreadyInitialized() public { + _MockL2Migration(validatorsList); + + vm.prank(address(0)); + vm.expectRevert("Epoch system already initialized"); + epochManager.initializeSystem(100, block.number, firstElected); + } + + function test_SetsCurrentRewardBlock() public { + _MockL2Migration(validatorsList); + + blockTravel(vm, 43200); + timeTravel(vm, DAY); + + epochManager.startNextEpochProcess(); + + (, , , uint256 _currentRewardsBlock) = epochManager.getCurrentEpoch(); + + assertEq(_currentRewardsBlock, block.number); + } + + function _MockL2Migration(address[] memory _validatorsList) internal { + for (uint256 i = 0; i < _validatorsList.length; i++) { + firstElected.push(_validatorsList[i]); + } + + uint256 l1EpochNumber = IPrecompiles(address(validatorsContract)).getEpochNumber(); + + activateValidators(); + vm.deal(unreleasedTreasury, L2_INITIAL_STASH_BALANCE); + + vm.prank(address(0)); + celoToken.mint(unreleasedTreasury, L2_INITIAL_STASH_BALANCE); + + whenL2(vm); + _setValidatorL2Score(); + + vm.prank(address(epochManagerEnabler)); + + epochManager.initializeSystem(l1EpochNumber, block.number, firstElected); + } + + function _setValidatorL2Score() internal { + address scoreManagerOwner = scoreManagerContract.owner(); + vm.startPrank(scoreManagerOwner); + scoreManagerContract.setGroupScore(groups[0], groupScore[0]); + scoreManagerContract.setGroupScore(groups[1], groupScore[1]); + scoreManagerContract.setGroupScore(groups[2], groupScore[2]); + + scoreManagerContract.setValidatorScore(validatorsList[0], validatorScore[0]); + scoreManagerContract.setValidatorScore(validatorsList[1], validatorScore[1]); + scoreManagerContract.setValidatorScore(validatorsList[2], validatorScore[2]); + scoreManagerContract.setValidatorScore(validatorsList[3], validatorScore[3]); + scoreManagerContract.setValidatorScore(validatorsList[4], validatorScore[4]); + scoreManagerContract.setValidatorScore(validatorsList[5], validatorScore[5]); + + vm.stopPrank(); + } +} diff --git a/packages/protocol/test-sol/precompiles/NumberValidatorsInCurrentSetPrecompile.sol b/packages/protocol/test-sol/precompiles/NumberValidatorsInCurrentSetPrecompile.sol new file mode 100644 index 00000000000..44e651a073f --- /dev/null +++ b/packages/protocol/test-sol/precompiles/NumberValidatorsInCurrentSetPrecompile.sol @@ -0,0 +1,32 @@ +// TODO move this to test folder +pragma solidity >=0.8.7 <0.8.20; + +import "forge-std/console.sol"; +import "@celo-contracts/common/interfaces/IRegistry.sol"; +import "@celo-contracts/governance/interfaces/IValidators.sol"; + +contract NumberValidatorsInCurrentSetPrecompile { + address constant ADDRESS = address(0xff - 6); + + uint256 public NumberOfValidators = 1; + + address internal constant registryAddress = 0x000000000000000000000000000000000000ce10; + + receive() external payable {} + + fallback(bytes calldata) external payable returns (bytes memory) { + return abi.encodePacked(NumberOfValidators); + } + + function setNumberOfValidators() external { + IRegistry registry = IRegistry(registryAddress); + address validatorsAddress = registry.getAddressForString("Validators"); + IValidators validatorsContract = IValidators(validatorsAddress); + address[] memory registeredValidators = validatorsContract.getRegisteredValidators(); + NumberOfValidators = registeredValidators.length; + } + + function getAddress() public pure returns (address) { + return ADDRESS; + } +} diff --git a/packages/protocol/test-sol/precompiles/ValidatorSignerAddressFromCurrentSetPrecompile.sol b/packages/protocol/test-sol/precompiles/ValidatorSignerAddressFromCurrentSetPrecompile.sol new file mode 100644 index 00000000000..f2ed9c7ade8 --- /dev/null +++ b/packages/protocol/test-sol/precompiles/ValidatorSignerAddressFromCurrentSetPrecompile.sol @@ -0,0 +1,56 @@ +// TODO move this to test folder +pragma solidity >=0.8.7 <0.8.20; + +import "forge-std/console.sol"; +import "@celo-contracts/common/interfaces/IRegistry.sol"; +import "@celo-contracts/governance/interfaces/IValidators.sol"; + +contract ValidatorSignerAddressFromCurrentSetPrecompile { + address constant ADDRESS = address(0xff - 5); + + uint256 public constant EPOCH_SIZE = 100; + + address[] validators; + + address internal constant registryAddress = 0x000000000000000000000000000000000000ce10; + + receive() external payable {} + + fallback(bytes calldata input) external payable returns (bytes memory) { + uint256 index = getUint256FromBytes(input, 0); + return abi.encodePacked(uint256(uint160(validators[index]))); + } + + function getAddress() public pure returns (address) { + return ADDRESS; + } + + function getUint256FromBytes(bytes memory bs, uint256 start) internal pure returns (uint256) { + return uint256(getBytes32FromBytes(bs, start)); + } + + function setValidators() external { + IRegistry registry = IRegistry(registryAddress); + address validatorsAddress = registry.getAddressForString("Validators"); + IValidators validatorsContract = IValidators(validatorsAddress); + address[] memory registeredValidators = validatorsContract.getRegisteredValidators(); + for (uint256 i = 0; i < registeredValidators.length; i++) { + validators.push(registeredValidators[i]); + } + } + + /** + * @notice Converts bytes to bytes32. + * @param bs byte[] data + * @param start offset into byte data to convert + * @return bytes32 data + */ + function getBytes32FromBytes(bytes memory bs, uint256 start) internal pure returns (bytes32) { + require(bs.length >= start + 32, "slicing out of range"); + bytes32 x; + assembly { + x := mload(add(bs, add(start, 32))) + } + return x; + } +} diff --git a/packages/protocol/test-sol/unit/common/CeloUnreleasedTreasure.t.sol b/packages/protocol/test-sol/unit/common/CeloUnreleasedTreasure.t.sol index ed11f093866..953b7d7a113 100644 --- a/packages/protocol/test-sol/unit/common/CeloUnreleasedTreasure.t.sol +++ b/packages/protocol/test-sol/unit/common/CeloUnreleasedTreasure.t.sol @@ -5,7 +5,7 @@ pragma experimental ABIEncoderV2; import "celo-foundry-8/Test.sol"; import "@celo-contracts/common/FixidityLib.sol"; import "@celo-contracts/common/interfaces/IRegistry.sol"; -import "@celo-contracts-8/common/interfaces/ICeloToken.sol"; +import "@celo-contracts/common/interfaces/ICeloToken.sol"; import "@celo-contracts/governance/interfaces/IGovernance.sol"; import { CeloUnreleasedTreasure } from "@celo-contracts-8/common/CeloUnreleasedTreasure.sol"; import "@celo-contracts-8/common/IsL2Check.sol"; @@ -25,6 +25,7 @@ contract CeloUnreleasedTreasureTest is Test, TestConstants, IsL2Check { address owner = address(this); address celoTokenAddress = actor("celoTokenAddress"); + address epochManagerAddress = actor("epochManagerAddress"); address celoDistributionOwner = actor("celoDistributionOwner"); address communityRewardFund = actor("communityRewardFund"); @@ -63,20 +64,20 @@ contract CeloUnreleasedTreasureTest is Test, TestConstants, IsL2Check { deployCodeTo("GoldToken.sol", abi.encode(false), celoTokenAddress); celoToken = ICeloToken(celoTokenAddress); - celoToken.setRegistry(REGISTRY_ADDRESS); // Using a mock contract, as foundry does not allow for library linking when using deployCodeTo governance = new MockGovernance(); registry.setAddressFor("CeloToken", address(celoToken)); registry.setAddressFor("Governance", address(governance)); + registry.setAddressFor("EpochManager", address(epochManagerAddress)); vm.deal(address(0), CELO_SUPPLY_CAP); - assertEq(celoToken.totalSupply(), 0, "starting total supply not zero."); + assertEq(celoToken.allocatedSupply(), 0, "starting total supply not zero."); // Mint L1 supply vm.prank(address(0)); celoToken.mint(randomAddress, L1_MINTED_CELO_SUPPLY); - assertEq(celoToken.totalSupply(), L1_MINTED_CELO_SUPPLY, "total supply incorrect."); + assertEq(celoToken.allocatedSupply(), L1_MINTED_CELO_SUPPLY, "total supply incorrect."); } function newCeloUnreleasedTreasure() internal returns (CeloUnreleasedTreasure) { @@ -132,3 +133,26 @@ contract CeloUnreleasedTreasureTest_initialize is CeloUnreleasedTreasureTest { payableAddress.transfer(1 ether); } } + +contract CeloUnreleasedTreasureTest_release is CeloUnreleasedTreasureTest { + function setUp() public override { + super.setUp(); + newCeloUnreleasedTreasure(); + } + + function test_ShouldTransferToRecepientAddress() public { + uint256 _balanceBefore = randomAddress.balance; + vm.prank(epochManagerAddress); + + celoUnreleasedTreasure.release(randomAddress, 4); + uint256 _balanceAfter = randomAddress.balance; + assertGt(_balanceAfter, _balanceBefore); + } + + function test_Reverts_WhenCalledByOtherThanEpochManager() public { + vm.prank(randomAddress); + + vm.expectRevert("Only the EpochManager contract can call this function."); + celoUnreleasedTreasure.release(randomAddress, 4); + } +} diff --git a/packages/protocol/test-sol/unit/common/EpochManager.t.sol b/packages/protocol/test-sol/unit/common/EpochManager.t.sol index 62c8cf0cb08..931ef839d0c 100644 --- a/packages/protocol/test-sol/unit/common/EpochManager.t.sol +++ b/packages/protocol/test-sol/unit/common/EpochManager.t.sol @@ -4,7 +4,8 @@ pragma solidity >=0.8.7 <0.8.20; import "celo-foundry-8/Test.sol"; import "@celo-contracts-8/common/EpochManager.sol"; import "@celo-contracts-8/stability/test/MockStableToken.sol"; -import "@celo-contracts-8/common/interfaces/ICeloToken.sol"; +import "@celo-contracts-8/common/test/MockCeloToken.sol"; +import "@celo-contracts/common/interfaces/ICeloToken.sol"; import "@celo-contracts-8/common/ScoreManager.sol"; import { CeloUnreleasedTreasure } from "@celo-contracts-8/common/CeloUnreleasedTreasure.sol"; import { ICeloUnreleasedTreasure } from "@celo-contracts/common/interfaces/ICeloUnreleasedTreasure.sol"; @@ -18,6 +19,7 @@ import "@celo-contracts/common/interfaces/IRegistry.sol"; import { EpochRewardsMock08 } from "@celo-contracts-8/governance/test/EpochRewardsMock.sol"; import { ValidatorsMock08 } from "@celo-contracts-8/governance/test/ValidatorsMock08.sol"; +import { MockCeloUnreleasedTreasure } from "@celo-contracts-8/common/test/MockCeloUnreleasedTreasure.sol"; contract EpochManagerTest is Test, TestConstants, Utils08 { EpochManager epochManager; @@ -27,47 +29,52 @@ contract EpochManagerTest is Test, TestConstants, Utils08 { EpochRewardsMock08 epochRewards; ValidatorsMock08 validators; - address epochManagerInitializer; + address epochManagerEnabler; address carbonOffsettingPartner; address communityRewardFund; + address reserveAddress; + address scoreManagerAddress; uint256 firstEpochNumber = 100; uint256 firstEpochBlock = 100; + uint256 epochDuration = DAY; address[] firstElected; IRegistry registry; - ICeloToken celoToken; - CeloUnreleasedTreasure celoUnreleasedTreasure; + MockCeloToken08 celoToken; + MockCeloUnreleasedTreasure celoUnreleasedTreasure; ScoreManager scoreManager; uint256 celoAmountForRate = 1e24; uint256 stableAmountForRate = 2 * celoAmountForRate; + event EpochProcessingStarted(uint256 indexed epochNumber); + function setUp() public virtual { epochManager = new EpochManager(true); sortedOracles = new MockSortedOracles(); epochRewards = new EpochRewardsMock08(); validators = new ValidatorsMock08(); stableToken = new MockStableToken08(); - celoUnreleasedTreasure = new CeloUnreleasedTreasure(false); + celoToken = new MockCeloToken08(); + celoUnreleasedTreasure = new MockCeloUnreleasedTreasure(); firstElected.push(actor("validator1")); firstElected.push(actor("validator2")); - address celoTokenAddress = actor("celoTokenAddress"); - address scoreManagerAddress = actor("scoreManagerAddress"); - address reserveAddress = actor("reserve"); + scoreManagerAddress = actor("scoreManagerAddress"); + + reserveAddress = actor("reserve"); - epochManagerInitializer = actor("initializer"); + epochManagerEnabler = actor("epochManagerEnabler"); carbonOffsettingPartner = actor("carbonOffsettingPartner"); communityRewardFund = actor("communityRewardFund"); - deployCodeTo("Registry.sol", abi.encode(false), REGISTRY_ADDRESS); - deployCodeTo("GoldToken.sol", abi.encode(false), celoTokenAddress); + deployCodeTo("MockRegistry.sol", abi.encode(false), REGISTRY_ADDRESS); + deployCodeTo("ScoreManager.sol", abi.encode(false), scoreManagerAddress); registry = IRegistry(REGISTRY_ADDRESS); - celoToken = ICeloToken(celoTokenAddress); scoreManager = ScoreManager(scoreManagerAddress); registry.setAddressFor(EpochManagerContract, address(epochManager)); @@ -81,42 +88,72 @@ contract EpochManagerTest is Test, TestConstants, Utils08 { registry.setAddressFor(CeloTokenContract, address(celoToken)); registry.setAddressFor(ReserveContract, reserveAddress); + celoToken.setTotalSupply(CELO_SUPPLY_CAP); vm.deal(address(celoUnreleasedTreasure), L2_INITIAL_STASH_BALANCE); + celoToken.setBalanceOf(address(celoUnreleasedTreasure), L2_INITIAL_STASH_BALANCE); - bool res1 = sortedOracles.setMedianRate(address(stableToken), stableAmountForRate); - (uint256 res0, uint256 res00) = sortedOracles.medianRate(address(stableToken)); + celoUnreleasedTreasure.setRegistry(REGISTRY_ADDRESS); + validators.setRegistry(REGISTRY_ADDRESS); + + sortedOracles.setMedianRate(address(stableToken), stableAmountForRate); scoreManager.setValidatorScore(actor("validator1"), 1); - uint256 res = scoreManager.getValidatorScore(actor("validator1")); - uint256 res2 = epochRewards.getCommunityRewardFraction(); - epochManager.initialize(REGISTRY_ADDRESS, 10, carbonOffsettingPartner, epochManagerInitializer); + epochManager.initialize( + REGISTRY_ADDRESS, + epochDuration, + carbonOffsettingPartner, + epochManagerEnabler + ); blockTravel(vm, firstEpochBlock); } + + function initializeEpochManagerSystem() public { + deployCodeTo("MockRegistry.sol", abi.encode(false), PROXY_ADMIN_ADDRESS); + vm.prank(epochManagerEnabler); + epochManager.initializeSystem(firstEpochNumber, firstEpochBlock, firstElected); + + blockTravel(vm, 43200); + timeTravel(vm, DAY); + } } contract EpochManagerTest_initialize is EpochManagerTest { function test_initialize() public virtual { assertEq(address(epochManager.registry()), REGISTRY_ADDRESS); - assertEq(epochManager.epochDuration(), 10); + assertEq(epochManager.epochDuration(), epochDuration); assertEq(epochManager.carbonOffsettingPartner(), carbonOffsettingPartner); + assertEq(epochManager.epochManagerEnabler(), epochManagerEnabler); } function test_Reverts_WhenAlreadyInitialized() public virtual { vm.expectRevert("contract already initialized"); - epochManager.initialize(REGISTRY_ADDRESS, 10, carbonOffsettingPartner, epochManagerInitializer); + epochManager.initialize(REGISTRY_ADDRESS, 10, carbonOffsettingPartner, epochManagerEnabler); } } contract EpochManagerTest_initializeSystem is EpochManagerTest { function test_processCanBeStarted() public virtual { - vm.prank(epochManagerInitializer); + vm.prank(epochManagerEnabler); epochManager.initializeSystem(firstEpochNumber, firstEpochBlock, firstElected); + ( + uint256 _firstEpochBlock, + uint256 _lastEpochBlock, + uint256 _startTimestamp, + uint256 _currentRewardsBlock + ) = epochManager.getCurrentEpoch(); + assertEq(epochManager.epochManagerEnabler(), address(0)); + assertEq(epochManager.firstKnownEpoch(), firstEpochNumber); + assertEq(_firstEpochBlock, firstEpochBlock); + assertEq(_lastEpochBlock, 0); + assertEq(_startTimestamp, block.timestamp); + assertEq(_currentRewardsBlock, 0); + assertEq(epochManager.getElected(), firstElected); } function test_Reverts_processCannotBeStartedAgain() public virtual { - vm.prank(epochManagerInitializer); + vm.prank(epochManagerEnabler); epochManager.initializeSystem(firstEpochNumber, firstEpochBlock, firstElected); vm.prank(address(0)); vm.expectRevert("Epoch system already initialized"); @@ -136,13 +173,69 @@ contract EpochManagerTest_startNextEpochProcess is EpochManagerTest { } function test_Reverts_WhenEndOfEpochHasNotBeenReached() public { - vm.prank(epochManagerInitializer); + vm.prank(epochManagerEnabler); epochManager.initializeSystem(firstEpochNumber, firstEpochBlock, firstElected); - uint256 _currentEpoch = epochManager.getCurrentEpochNumber(); - (, , , uint256 _currentEpochEndTimestamp, ) = epochManager.getCurrentEpoch(); - vm.expectRevert("Epoch is not ready to start"); epochManager.startNextEpochProcess(); } + + function test_Reverts_WhenEpochProcessingAlreadyStarted() public { + initializeEpochManagerSystem(); + + epochManager.startNextEpochProcess(); + vm.expectRevert("Epoch process is already started"); + epochManager.startNextEpochProcess(); + } + + function test_Emits_EpochProcessingStartedEvent() public { + initializeEpochManagerSystem(); + + vm.expectEmit(true, true, true, true); + emit EpochProcessingStarted(firstEpochNumber); + epochManager.startNextEpochProcess(); + } + + function test_SetsTheEpochRewardBlock() public { + initializeEpochManagerSystem(); + + epochManager.startNextEpochProcess(); + (, , , uint256 _currentRewardsBlock) = epochManager.getCurrentEpoch(); + assertEq(_currentRewardsBlock, block.number); + } + + function test_SetsTheEpochRewardAmounts() public { + initializeEpochManagerSystem(); + + epochManager.startNextEpochProcess(); + ( + uint256 status, + uint256 perValidatorReward, + uint256 totalRewardsVoter, + uint256 totalRewardsCommunity, + uint256 totalRewardsCarbonFund + ) = epochManager.getEpochProcessingState(); + assertEq(status, 1); + assertEq(perValidatorReward, 5); + assertEq(totalRewardsVoter, 5); + assertEq(totalRewardsCommunity, 5); + assertEq(totalRewardsCarbonFund, 5); + } + + function test_ShouldMintTotalValidatorStableRewardsToEpochManager() public { + initializeEpochManagerSystem(); + uint256 beforeBalance = stableToken.balanceOf(address(epochManager)); + epochManager.startNextEpochProcess(); + + uint256 afterBalance = stableToken.balanceOf(address(epochManager)); + assertEq(afterBalance, 2); + } + + function test_ShouldReleaseCorrectAmountToReserve() public { + initializeEpochManagerSystem(); + uint256 reserveBalanceBefore = celoToken.balanceOf(reserveAddress); + epochManager.startNextEpochProcess(); + uint256 reserveBalanceAfter = celoToken.balanceOf(reserveAddress); + assertEq(reserveBalanceAfter, reserveBalanceBefore + 4); + } } diff --git a/packages/protocol/test-sol/unit/governance/network/EpochRewards.t.sol b/packages/protocol/test-sol/unit/governance/network/EpochRewards.t.sol index 64de3207dfb..58d83c37850 100644 --- a/packages/protocol/test-sol/unit/governance/network/EpochRewards.t.sol +++ b/packages/protocol/test-sol/unit/governance/network/EpochRewards.t.sol @@ -758,7 +758,7 @@ contract EpochRewardsTest_updateTargetVotingYield is EpochRewardsTest { epochRewards.updateTargetVotingYield(); } } - +// TODO(soloseng): add L2 test case that uses the result from epochManager contract EpochRewardsTest_WhenThereAreActiveVotesAStableTokenExchangeRateIsSetAndTheActualRemainingSupplyIs10pMoreThanTheTargetRemainingSupplyAfterRewards_calculateTargetEpochRewards is EpochRewardsTest { diff --git a/packages/protocol/test-sol/unit/governance/validators/Validators.t.sol b/packages/protocol/test-sol/unit/governance/validators/Validators.t.sol index 460bd56f9f9..7a679044670 100644 --- a/packages/protocol/test-sol/unit/governance/validators/Validators.t.sol +++ b/packages/protocol/test-sol/unit/governance/validators/Validators.t.sol @@ -3430,6 +3430,26 @@ contract ValidatorsTest_DistributeEpochPaymentsFromSigner is ValidatorsTest { } } +contract ValidatorsTest_MintStableToEpochManager is ValidatorsTest { + function test_Reverts_WhenL1() public { + vm.expectRevert("This method is not supported in L1."); + validators.mintStableToEpochManager(5); + } + + function test_Reverts_WhenCalledByOtherThanEpochManager() public { + _whenL2(); + vm.expectRevert("only registered contract"); + validators.mintStableToEpochManager(5); + } + + function test_ShouldMintStableToEpochManager() public { + _whenL2(); + vm.prank(address(epochManager)); + validators.mintStableToEpochManager(5); + assertEq(stableToken.balanceOf(address(epochManager)), 5); + } +} + contract ValidatorsTest_ForceDeaffiliateIfValidator is ValidatorsTest { function setUp() public { super.setUp(); diff --git a/packages/protocol/test-sol/unit/governance/voting/Election.t.sol b/packages/protocol/test-sol/unit/governance/voting/Election.t.sol index c4ba6faaf15..e055288f0b0 100644 --- a/packages/protocol/test-sol/unit/governance/voting/Election.t.sol +++ b/packages/protocol/test-sol/unit/governance/voting/Election.t.sol @@ -7,6 +7,7 @@ import { TestConstants } from "@test-sol/constants.sol"; import { Utils } from "@test-sol/utils.sol"; import "@celo-contracts/common/FixidityLib.sol"; +import "@celo-contracts/common/Registry.sol"; import "@celo-contracts/governance/Election.sol"; import "@celo-contracts/governance/test/MockLockedGold.sol"; import "@celo-contracts/governance/test/MockValidators.sol"; @@ -162,6 +163,7 @@ contract ElectionTest is Utils, TestConstants { } function _whenL2() public { + blockTravel(ph.epochSize() + 1); uint256 l1EpochNumber = election.getEpochNumber(); address[] memory _elected = new address[](2); @@ -217,7 +219,6 @@ contract ElectionTest_SetElectabilityThreshold is ElectionTest { } } -// TODO(soloseng): need to update epochNumber for L2, to make it !=0 contract ElectionTest_SetElectabilityThreshold_L2 is ElectionTest { function test_shouldSetElectabilityThreshold() public { _whenL2(); diff --git a/packages/protocol/test-sol/utils/ECDSAHelper.sol b/packages/protocol/test-sol/utils/ECDSAHelper.sol index cd85d52cccd..aaa7c1119ae 100644 --- a/packages/protocol/test-sol/utils/ECDSAHelper.sol +++ b/packages/protocol/test-sol/utils/ECDSAHelper.sol @@ -13,7 +13,7 @@ contract ECDSAHelper is Test { bytes32 _s ) public returns (bytes memory) { address SECP256K1Address = actor("SECP256K1Address"); - deployCodeTo("out/SECP256K1.sol/SECP256K1.0.5.17.json", SECP256K1Address); + deployCodeTo("SECP256K1.sol:SECP256K1", SECP256K1Address); sECP256K1 = ISECP256K1(SECP256K1Address); string memory header = "\x19Ethereum Signed Message:\n32"; diff --git a/packages/protocol/test-sol/utils08.sol b/packages/protocol/test-sol/utils08.sol index 681380e6e87..1ee9330ad50 100644 --- a/packages/protocol/test-sol/utils08.sol +++ b/packages/protocol/test-sol/utils08.sol @@ -11,8 +11,19 @@ contract Utils08 { vm.roll(block.number + blockDelta); } + function travelEpochL1(Vm vm) public { + uint256 blocksInEpoch = 17280; + uint256 timeDelta = blocksInEpoch * 5; + blockTravel(vm, blocksInEpoch); + 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)))); } + + function whenL2(Vm vm) public { + vm.etch(0x4200000000000000000000000000000000000018, abi.encodePacked(bytes1(0x01))); + } } From 5dc389fc51695bcc4b50ded4220e845e1242bc05 Mon Sep 17 00:00:00 2001 From: pahor167 <47992132+pahor167@users.noreply.github.com> Date: Tue, 17 Sep 2024 17:37:59 +0200 Subject: [PATCH 51/59] E2E EpochManager test + Epoch truffle migrations & Anvil L2 migration build fix (#11198) --- packages/protocol/contractPackages.ts | 13 +- .../contracts-0.8/common/EpochManager.sol | 46 ++- .../common/EpochManagerEnabler.sol | 17 +- .../common/interfaces/IScoreManager.sol | 10 + .../common/test/MockCeloToken.sol | 19 +- .../common/test/MockEpochManager.sol | 8 + .../contracts-0.8/governance/Validators.sol | 7 +- .../governance/test/ValidatorsMock.sol | 21 -- .../governance/test/ValidatorsMock08.sol | 27 -- .../common/interfaces/IEpochManager.sol | 5 + .../proxies/EpochManagerEnablerProxy.sol | 6 + .../common/proxies/EpochManagerProxy.sol | 6 + .../common/proxies/ScoreManagerProxy.sol | 6 + .../contracts/governance/Election.sol | 2 +- .../governance/test/MockValidators.sol | 3 + packages/protocol/foundry.toml | 1 + packages/protocol/lib/proxy-utils.ts | 46 +-- packages/protocol/lib/web3-utils.ts | 6 + packages/protocol/migrationsConfig.js | 7 + .../protocol/migrations_sol/Migration.s.sol | 3 +- .../protocol/migrations_sol/MigrationL2.s.sol | 32 +- .../migrations_sol/migrationsConfig.json | 1 - .../migrations_ts/26_101_score_manager.ts | 17 + .../26_102_epoch_manager_enabler.ts | 18 ++ .../migrations_ts/26_103_epoch_manager.ts | 28 ++ .../protocol/migrations_ts/29_governance.ts | 8 +- packages/protocol/package.json | 2 +- .../initializationData/release12.json | 7 +- .../create_and_migrate_anvil_devchain.sh | 22 +- .../create_and_migrate_anvil_l2_devchain.sh | 17 +- .../scripts/foundry/run_e2e_tests_in_anvil.sh | 7 +- .../protocol/scripts/foundry/start_anvil.sh | 1 + .../protocol/scripts/truffle/make-release.ts | 3 - .../devchain/Import05Dependencies.sol | 26 ++ .../test-sol/devchain/ImportPrecompiles.t.sol | 9 + .../devchain/e2e/common/EpochManager.t.sol | 294 ++++++++++++++++++ .../protocol/test-sol/devchain/e2e/utils.sol | 19 ++ .../devchain/migration/Migration.t.sol | 77 +++-- .../integration/CompileValidatorMock.t.sol | 14 + .../test-sol/unit/common/EpochManager.t.sol | 9 +- .../unit/common/ImportPrecompiles.t.sol | 2 + .../governance/network/EpochRewards.t.sol | 3 + .../governance/validators/Validators.t.sol | 35 ++- .../CompileValidatorIntegrationMock.t.sol | 14 + .../validators/mocks/ValidatorsMock.sol | 18 ++ .../unit/governance/voting/Election.t.sol | 7 - .../unit/stability/FeeCurrencyAdapter.t.sol | 2 - .../protocol/test-sol/utils/ECDSAHelper08.sol | 34 ++ packages/protocol/test-sol/utils08.sol | 2 + 49 files changed, 802 insertions(+), 185 deletions(-) create mode 100644 packages/protocol/contracts-0.8/common/interfaces/IScoreManager.sol delete mode 100644 packages/protocol/contracts-0.8/governance/test/ValidatorsMock.sol delete mode 100644 packages/protocol/contracts-0.8/governance/test/ValidatorsMock08.sol create mode 100644 packages/protocol/contracts/common/proxies/EpochManagerEnablerProxy.sol create mode 100644 packages/protocol/contracts/common/proxies/EpochManagerProxy.sol create mode 100644 packages/protocol/contracts/common/proxies/ScoreManagerProxy.sol create mode 100644 packages/protocol/migrations_ts/26_101_score_manager.ts create mode 100644 packages/protocol/migrations_ts/26_102_epoch_manager_enabler.ts create mode 100644 packages/protocol/migrations_ts/26_103_epoch_manager.ts create mode 100644 packages/protocol/test-sol/devchain/Import05Dependencies.sol create mode 100644 packages/protocol/test-sol/devchain/ImportPrecompiles.t.sol create mode 100644 packages/protocol/test-sol/devchain/e2e/common/EpochManager.t.sol create mode 100644 packages/protocol/test-sol/integration/CompileValidatorMock.t.sol create mode 100644 packages/protocol/test-sol/unit/governance/validators/mocks/CompileValidatorIntegrationMock.t.sol create mode 100644 packages/protocol/test-sol/unit/governance/validators/mocks/ValidatorsMock.sol create mode 100644 packages/protocol/test-sol/utils/ECDSAHelper08.sol diff --git a/packages/protocol/contractPackages.ts b/packages/protocol/contractPackages.ts index 177ec1b6baa..45e3452ad5e 100644 --- a/packages/protocol/contractPackages.ts +++ b/packages/protocol/contractPackages.ts @@ -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 diff --git a/packages/protocol/contracts-0.8/common/EpochManager.sol b/packages/protocol/contracts-0.8/common/EpochManager.sol index 00813f44f21..430f1ffa740 100644 --- a/packages/protocol/contracts-0.8/common/EpochManager.sol +++ b/packages/protocol/contracts-0.8/common/EpochManager.sol @@ -43,6 +43,11 @@ contract EpochManager is uint256 toProcessGroups; } + struct ProcessedGroup { + bool processed; + uint256 epochRewards; + } + // the length of an epoch in seconds uint256 public epochDuration; @@ -50,9 +55,7 @@ contract EpochManager is 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; @@ -178,9 +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 + // last block should be the block before and timestamp from previous block epochs[currentEpochNumber].lastBlock = block.number - 1; // start new epoch currentEpochNumber++; @@ -189,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), @@ -220,7 +232,7 @@ 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; } diff --git a/packages/protocol/contracts-0.8/common/EpochManagerEnabler.sol b/packages/protocol/contracts-0.8/common/EpochManagerEnabler.sol index ba33773b9b9..d3bd71e0c6e 100644 --- a/packages/protocol/contracts-0.8/common/EpochManagerEnabler.sol +++ b/packages/protocol/contracts-0.8/common/EpochManagerEnabler.sol @@ -11,6 +11,7 @@ import "../../contracts/governance/interfaces/IEpochRewards.sol"; contract EpochManagerEnabler is Initializable, UsingPrecompiles, UsingRegistry { uint256 public lastKnownEpochNumber; + uint256 public lastKnownFirstBlockOfEpoch; address[] public lastKnownElectedAccounts; /** @@ -33,10 +34,11 @@ contract EpochManagerEnabler is Initializable, UsingPrecompiles, UsingRegistry { */ 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 ); } @@ -48,8 +50,8 @@ contract EpochManagerEnabler is Initializable, UsingPrecompiles, UsingRegistry { 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 @@ -64,6 +66,17 @@ contract EpochManagerEnabler is Initializable, UsingPrecompiles, UsingRegistry { 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); diff --git a/packages/protocol/contracts-0.8/common/interfaces/IScoreManager.sol b/packages/protocol/contracts-0.8/common/interfaces/IScoreManager.sol new file mode 100644 index 00000000000..0020fd65df0 --- /dev/null +++ b/packages/protocol/contracts-0.8/common/interfaces/IScoreManager.sol @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: LGPL-3.0-only +pragma solidity >=0.5.13 <0.9.0; + +interface IScoreManager { + function setGroupScore(address group, uint256 score) external; + function setValidatorScore(address validator, uint256 score) external; + function getValidatorScore(address validator) external view returns (uint256); + function getGroupScore(address validator) external view returns (uint256); + function owner() external view returns (address); +} diff --git a/packages/protocol/contracts-0.8/common/test/MockCeloToken.sol b/packages/protocol/contracts-0.8/common/test/MockCeloToken.sol index 82bd1c57cc8..74baea164c6 100644 --- a/packages/protocol/contracts-0.8/common/test/MockCeloToken.sol +++ b/packages/protocol/contracts-0.8/common/test/MockCeloToken.sol @@ -34,15 +34,6 @@ contract MockCeloToken08 { 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; } @@ -54,7 +45,13 @@ contract MockCeloToken08 { function totalSupply() public view returns (uint256) { return totalSupply_; } - function allocatedSupply() public view returns (uint256) { - return CELO_SUPPLY_CAP - L2_INITIAL_STASH_BALANCE; + + 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; } } diff --git a/packages/protocol/contracts-0.8/common/test/MockEpochManager.sol b/packages/protocol/contracts-0.8/common/test/MockEpochManager.sol index 4125c9c5843..c4cb4d2e8ce 100644 --- a/packages/protocol/contracts-0.8/common/test/MockEpochManager.sol +++ b/packages/protocol/contracts-0.8/common/test/MockEpochManager.sol @@ -67,4 +67,12 @@ contract MockEpochManager is IEpochManager { function getElected() external view returns (address[] memory) { return elected; } + + function getEpochProcessingState() + external + view + returns (uint256, uint256, uint256, uint256, uint256) + { + return (0, 0, 0, 0, 0); + } } diff --git a/packages/protocol/contracts-0.8/governance/Validators.sol b/packages/protocol/contracts-0.8/governance/Validators.sol index 98b7dc55f42..dbd60d6b26e 100644 --- a/packages/protocol/contracts-0.8/governance/Validators.sol +++ b/packages/protocol/contracts-0.8/governance/Validators.sol @@ -166,6 +166,11 @@ contract Validators is _; } + modifier onlyEpochManager() { + require(msg.sender == address(getEpochManager()), "Only epoch manager can call"); + _; + } + /** * @notice Sets initialized == true on implementation contracts * @param test Set to true to skip implementation initialization @@ -514,7 +519,7 @@ contract Validators is } /** - * @notice Adds the first member to a group's list of members and marks it eligible for election. + * @notice Adds the first member to a group's list of members and marks the group eligible for election. * @param validator The validator to add to the group * @param lesser The address of the group that has received fewer votes than this group. * @param greater The address of the group that has received more votes than this group. diff --git a/packages/protocol/contracts-0.8/governance/test/ValidatorsMock.sol b/packages/protocol/contracts-0.8/governance/test/ValidatorsMock.sol deleted file mode 100644 index ea32349aab1..00000000000 --- a/packages/protocol/contracts-0.8/governance/test/ValidatorsMock.sol +++ /dev/null @@ -1,21 +0,0 @@ -pragma solidity >=0.8.7 <0.8.20; - -import "../Validators.sol"; -import "../../../contracts/common/FixidityLib.sol"; - -/** - * @title A wrapper around Validators that exposes onlyVm functions for testing. - */ -contract ValidatorsMock is Validators(true) { - function updateValidatorScoreFromSigner(address signer, uint256 uptime) external override { - return _updateValidatorScoreFromSigner(signer, uptime); - } - - function distributeEpochPaymentsFromSigner( - address signer, - uint256 maxPayment - ) external override returns (uint256) { - allowOnlyL1(); - return _distributeEpochPaymentsFromSigner(signer, maxPayment); - } -} diff --git a/packages/protocol/contracts-0.8/governance/test/ValidatorsMock08.sol b/packages/protocol/contracts-0.8/governance/test/ValidatorsMock08.sol deleted file mode 100644 index ff9b7c778da..00000000000 --- a/packages/protocol/contracts-0.8/governance/test/ValidatorsMock08.sol +++ /dev/null @@ -1,27 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity >=0.8.7 <0.8.20; - -import "../Validators.sol"; -import "../../../contracts/common/FixidityLib.sol"; - -/** - * @title A wrapper around Validators that exposes onlyVm functions for testing. - */ -contract ValidatorsMock08 is Validators(true) { - function updateValidatorScoreFromSigner(address signer, uint256 uptime) external override {} - - function distributeEpochPaymentsFromSigner( - address signer, - uint256 maxPayment - ) external override returns (uint256) { - return 0; - } - - function computeEpochReward( - address account, - uint256 score, - uint256 maxPayment - ) external view override returns (uint256) { - return 1; - } -} diff --git a/packages/protocol/contracts/common/interfaces/IEpochManager.sol b/packages/protocol/contracts/common/interfaces/IEpochManager.sol index e70bb033b87..c0f9736179f 100644 --- a/packages/protocol/contracts/common/interfaces/IEpochManager.sol +++ b/packages/protocol/contracts/common/interfaces/IEpochManager.sol @@ -18,4 +18,9 @@ interface IEpochManager { function getElected() external view returns (address[] memory); function epochManagerEnabler() external view returns (address); function epochDuration() external view returns (uint256); + function firstKnownEpoch() external view returns (uint256); + function getEpochProcessingState() + external + view + returns (uint256, uint256, uint256, uint256, uint256); } diff --git a/packages/protocol/contracts/common/proxies/EpochManagerEnablerProxy.sol b/packages/protocol/contracts/common/proxies/EpochManagerEnablerProxy.sol new file mode 100644 index 00000000000..ddd10f3cd43 --- /dev/null +++ b/packages/protocol/contracts/common/proxies/EpochManagerEnablerProxy.sol @@ -0,0 +1,6 @@ +pragma solidity ^0.5.13; + +import "../Proxy.sol"; + +/* solhint-disable-next-line no-empty-blocks */ +contract EpochManagerEnablerProxy is Proxy {} diff --git a/packages/protocol/contracts/common/proxies/EpochManagerProxy.sol b/packages/protocol/contracts/common/proxies/EpochManagerProxy.sol new file mode 100644 index 00000000000..7f5fc943e37 --- /dev/null +++ b/packages/protocol/contracts/common/proxies/EpochManagerProxy.sol @@ -0,0 +1,6 @@ +pragma solidity ^0.5.13; + +import "../Proxy.sol"; + +/* solhint-disable-next-line no-empty-blocks */ +contract EpochManagerProxy is Proxy {} diff --git a/packages/protocol/contracts/common/proxies/ScoreManagerProxy.sol b/packages/protocol/contracts/common/proxies/ScoreManagerProxy.sol new file mode 100644 index 00000000000..d46446ee16b --- /dev/null +++ b/packages/protocol/contracts/common/proxies/ScoreManagerProxy.sol @@ -0,0 +1,6 @@ +pragma solidity ^0.5.13; + +import "../Proxy.sol"; + +/* solhint-disable-next-line no-empty-blocks */ +contract ScoreManagerProxy is Proxy {} diff --git a/packages/protocol/contracts/governance/Election.sol b/packages/protocol/contracts/governance/Election.sol index 86836975774..91e022d0979 100644 --- a/packages/protocol/contracts/governance/Election.sol +++ b/packages/protocol/contracts/governance/Election.sol @@ -957,7 +957,7 @@ contract Election is uint256 value, address lesser, address greater - ) internal onlyL1 { + ) internal { if (votes.total.eligible.contains(group)) { uint256 newVoteTotal = votes.total.eligible.getValue(group).add(value); votes.total.eligible.update(group, newVoteTotal, lesser, greater); diff --git a/packages/protocol/contracts/governance/test/MockValidators.sol b/packages/protocol/contracts/governance/test/MockValidators.sol index 97cf017a9a3..4cc03323a61 100644 --- a/packages/protocol/contracts/governance/test/MockValidators.sol +++ b/packages/protocol/contracts/governance/test/MockValidators.sol @@ -4,6 +4,9 @@ import "openzeppelin-solidity/contracts/math/SafeMath.sol"; import "../../../contracts-0.8/common/IsL2Check.sol"; +// Mocks Validators, compatible with 0.5 +// For forge tests, can be avoided with calls to deployCodeTo + /** * @title Holds a list of addresses of validators */ diff --git a/packages/protocol/foundry.toml b/packages/protocol/foundry.toml index ed1c21166cc..c934ff7bbbe 100644 --- a/packages/protocol/foundry.toml +++ b/packages/protocol/foundry.toml @@ -37,6 +37,7 @@ fs_permissions = [ ] [profile.devchain] # Special profile for the tests that require an anvil devchain +test = 'test-sol/devchain' match_path = "**/test-sol/devchain/**" no_match_path = "{**/test/BLS12Passthrough.sol,**/test/RandomTest.sol}" diff --git a/packages/protocol/lib/proxy-utils.ts b/packages/protocol/lib/proxy-utils.ts index a2bd042271e..a24bd1fe688 100644 --- a/packages/protocol/lib/proxy-utils.ts +++ b/packages/protocol/lib/proxy-utils.ts @@ -42,27 +42,33 @@ export async function setAndInitializeImplementation( }, ...args: any[] ) { - const callData = web3.eth.abi.encodeFunctionCall(initializerAbi, args) - if (txOptions.from != null) { - // The proxied contract needs to be funded prior to initialization - if (txOptions.value != null) { - // Proxy's fallback fn expects the contract's implementation to be set already - // So we set the implementation first, send the funding, and then set and initialize again. - await retryTx(proxy._setImplementation, [implementationAddress, { from: txOptions.from }]) - await retryTx(web3.eth.sendTransaction, [ - { - from: txOptions.from, - to: proxy.address, - value: txOptions.value, - }, + try { + + + const callData = web3.eth.abi.encodeFunctionCall(initializerAbi, args) + if (txOptions.from != null) { + // The proxied contract needs to be funded prior to initialization + if (txOptions.value != null) { + // Proxy's fallback fn expects the contract's implementation to be set already + // So we set the implementation first, send the funding, and then set and initialize again. + await retryTx(proxy._setImplementation, [implementationAddress, { from: txOptions.from }]) + await retryTx(web3.eth.sendTransaction, [ + { + from: txOptions.from, + to: proxy.address, + value: txOptions.value, + }, + ]) + } + return retryTx(proxy._setAndInitializeImplementation, [ + implementationAddress, + callData as any, + { from: txOptions.from }, ]) + } else { + return retryTx(proxy._setAndInitializeImplementation, [implementationAddress, callData as any]) } - return retryTx(proxy._setAndInitializeImplementation, [ - implementationAddress, - callData as any, - { from: txOptions.from }, - ]) - } else { - return retryTx(proxy._setAndInitializeImplementation, [implementationAddress, callData as any]) + } catch (error) { + console.log("errror", error); } } diff --git a/packages/protocol/lib/web3-utils.ts b/packages/protocol/lib/web3-utils.ts index 0f1f3869704..ebaae5d256a 100644 --- a/packages/protocol/lib/web3-utils.ts +++ b/packages/protocol/lib/web3-utils.ts @@ -207,6 +207,12 @@ export async function _setInitialProxyImplementation< return receipt.tx } +export const getProxiedContract = async (contractName: string, contractPackage: ContractPackage) => { + const artifactsObject = ArtifactsSingleton.getInstance(contractPackage, artifacts) + /* eslint-disable-next-line */ + return await getDeployedProxiedContract(contractName, artifactsObject) +} + export async function getDeployedProxiedContract( contractName: string, customArtifacts: any diff --git a/packages/protocol/migrationsConfig.js b/packages/protocol/migrationsConfig.js index b8fd53e1e44..6baf43424a7 100644 --- a/packages/protocol/migrationsConfig.js +++ b/packages/protocol/migrationsConfig.js @@ -77,6 +77,10 @@ const DefaultConfig = { carbonOffsettingFraction: 1 / 1000, frozen: false, }, + epochManager: { + newEpochDuration: 100, + carbonOffsettingPartner: '0x0000000000000000000000000000000000000000', + }, exchange: { spread: 5 / 1000, reserveFraction: 1 / 100, @@ -165,6 +169,9 @@ const DefaultConfig = { numRequiredConfirmations: 1, numInternalRequiredConfirmations: 1, }, + scoreManager: { + newEpochDuration: 100, + }, stableToken: { decimals: 18, goldPrice: 1, diff --git a/packages/protocol/migrations_sol/Migration.s.sol b/packages/protocol/migrations_sol/Migration.s.sol index 802ce943500..33202c48a9e 100644 --- a/packages/protocol/migrations_sol/Migration.s.sol +++ b/packages/protocol/migrations_sol/Migration.s.sol @@ -1131,10 +1131,11 @@ contract Migration is Script, UsingRegistry, MigrationsConstants { bytes16(0x05050505050505050505050505050506), bytes16(0x06060606060606060606060606060607) ); + (bytes memory ecdsaPubKey, , , ) = _generateEcdsaPubKeyWithSigner(accountAddress, validatorKey); getValidators().registerValidator(ecdsaPubKey, newBlsPublicKey, newBlsPop); getValidators().affiliate(groupToAffiliate); - console.log("Done registering validatora"); + console.log("Done registering validators"); vm.stopBroadcast(); return accountAddress; diff --git a/packages/protocol/migrations_sol/MigrationL2.s.sol b/packages/protocol/migrations_sol/MigrationL2.s.sol index dde3001ae9f..6147a4c3507 100644 --- a/packages/protocol/migrations_sol/MigrationL2.s.sol +++ b/packages/protocol/migrations_sol/MigrationL2.s.sol @@ -3,8 +3,12 @@ pragma solidity >=0.8.7 <0.8.20; import { Script } from "forge-std-8/Script.sol"; import { MigrationsConstants } from "@migrations-sol/constants.sol"; +// Foundry imports +import "forge-std/console.sol"; + import "@celo-contracts/common/FixidityLib.sol"; import "@celo-contracts-8/common/UsingRegistry.sol"; +import "../../contracts/common/interfaces/IEpochManagerEnabler.sol"; contract MigrationL2 is Script, MigrationsConstants, UsingRegistry { using FixidityLib for FixidityLib.Fraction; @@ -16,7 +20,9 @@ contract MigrationL2 is Script, MigrationsConstants, UsingRegistry { vm.startBroadcast(DEPLOYER_ACCOUNT); setupUsingRegistry(); - activateCeloUnreleasedTreasure(); + dealToCeloUnreleasedTreasure(); + + initializeEpochManagerSystem(); vm.stopBroadcast(); } @@ -26,17 +32,17 @@ contract MigrationL2 is Script, MigrationsConstants, UsingRegistry { setRegistry(REGISTRY_ADDRESS); } - function activateCeloUnreleasedTreasure() public { - uint256 l2StartTime = 1721909903 - 5; // Arbitrarily 5 seconds before last black - uint256 communityRewardFraction = getEpochRewards().getCommunityRewardFraction(); - address carbonOffsettingPartner = 0x22579CA45eE22E2E16dDF72D955D6cf4c767B0eF; - uint256 carbonOffsettingFraction = getEpochRewards().getCarbonOffsettingFraction(); - - getCeloUnreleasedTreasure().activate( - l2StartTime, - communityRewardFraction, - carbonOffsettingPartner, - carbonOffsettingFraction - ); + function dealToCeloUnreleasedTreasure() public { + vm.deal(address(getCeloUnreleasedTreasure()), 1_000_000 ether); + } + + function initializeEpochManagerSystem() public { + console.log("Initializing EpochManager system"); + address[] memory firstElected = getValidators().getRegisteredValidators(); + IEpochManager epochManager = getEpochManager(); + address epochManagerEnablerAddress = epochManager.epochManagerEnabler(); + + IEpochManagerEnabler epochManagerEnabler = IEpochManagerEnabler(epochManagerEnablerAddress); + epochManagerEnabler.initEpochManager(); } } diff --git a/packages/protocol/migrations_sol/migrationsConfig.json b/packages/protocol/migrations_sol/migrationsConfig.json index 1836e8f1995..bd3e28e416a 100644 --- a/packages/protocol/migrations_sol/migrationsConfig.json +++ b/packages/protocol/migrations_sol/migrationsConfig.json @@ -102,7 +102,6 @@ "0x2a871d0798f97d79848a013d4936a73bf4cc922c825d33c1cf7073dff6d409c6" ] }, - "election": { "minElectableValidators": 1, "maxElectableValidators": 100, diff --git a/packages/protocol/migrations_ts/26_101_score_manager.ts b/packages/protocol/migrations_ts/26_101_score_manager.ts new file mode 100644 index 00000000000..93a74670975 --- /dev/null +++ b/packages/protocol/migrations_ts/26_101_score_manager.ts @@ -0,0 +1,17 @@ +import { SOLIDITY_08_PACKAGE } from '@celo/protocol/contractPackages' +import { CeloContractName } from '@celo/protocol/lib/registry-utils' +import { deploymentForCoreContract } from '@celo/protocol/lib/web3-utils' +import { ScoreManagerInstance } from 'types/08' + +const initializeArgs = async (): Promise => { + return [] +} + +module.exports = deploymentForCoreContract( + web3, + artifacts, + CeloContractName.ScoreManager, + initializeArgs, + undefined, + SOLIDITY_08_PACKAGE +) diff --git a/packages/protocol/migrations_ts/26_102_epoch_manager_enabler.ts b/packages/protocol/migrations_ts/26_102_epoch_manager_enabler.ts new file mode 100644 index 00000000000..6a9a3d9220f --- /dev/null +++ b/packages/protocol/migrations_ts/26_102_epoch_manager_enabler.ts @@ -0,0 +1,18 @@ +import { SOLIDITY_08_PACKAGE } from '@celo/protocol/contractPackages' +import { CeloContractName } from '@celo/protocol/lib/registry-utils' +import { deploymentForCoreContract } from '@celo/protocol/lib/web3-utils' +import { config } from '@celo/protocol/migrationsConfig' +import { EpochManagerEnablerInstance } from 'types/08' + +const initializeArgs = async (): Promise => { + return [config.registry.predeployedProxyAddress] +} + +module.exports = deploymentForCoreContract( + web3, + artifacts, + CeloContractName.EpochManagerEnabler, + initializeArgs, + undefined, + SOLIDITY_08_PACKAGE +) diff --git a/packages/protocol/migrations_ts/26_103_epoch_manager.ts b/packages/protocol/migrations_ts/26_103_epoch_manager.ts new file mode 100644 index 00000000000..6769b4ac5ed --- /dev/null +++ b/packages/protocol/migrations_ts/26_103_epoch_manager.ts @@ -0,0 +1,28 @@ +import { SOLIDITY_08_PACKAGE } from '@celo/protocol/contractPackages' +import { CeloContractName } from '@celo/protocol/lib/registry-utils' +import { deploymentForCoreContract, getProxiedContract } from '@celo/protocol/lib/web3-utils' +import { config } from '@celo/protocol/migrationsConfig' +import { EpochManagerInstance } from 'types/08' + +const initializeArgs = async (): Promise => { + const epochManagerInitializer = await getProxiedContract( + CeloContractName.EpochManagerEnabler, + SOLIDITY_08_PACKAGE + ) + + return [ + config.registry.predeployedProxyAddress, + config.epochManager.newEpochDuration, + config.epochManager.carbonOffsettingPartner, + epochManagerInitializer.address, + ] +} + +module.exports = deploymentForCoreContract( + web3, + artifacts, + CeloContractName.EpochManager, + initializeArgs, + undefined, + SOLIDITY_08_PACKAGE +) diff --git a/packages/protocol/migrations_ts/29_governance.ts b/packages/protocol/migrations_ts/29_governance.ts index 3cbf66bc7b3..634f711a5ac 100644 --- a/packages/protocol/migrations_ts/29_governance.ts +++ b/packages/protocol/migrations_ts/29_governance.ts @@ -129,7 +129,13 @@ module.exports = deploymentForCoreContract( __contractPackage: MENTO_PACKAGE, }, { - contracts: ['GasPriceMinimum', 'Validators'], + contracts: [ + 'GasPriceMinimum', + 'Validators', + 'EpochManager', + 'ScoreManager', + 'EpochManagerEnabler', + ], __contractPackage: SOLIDITY_08_PACKAGE, }, ] diff --git a/packages/protocol/package.json b/packages/protocol/package.json index 01d242474b5..e9210652a2c 100644 --- a/packages/protocol/package.json +++ b/packages/protocol/package.json @@ -10,7 +10,7 @@ "lint:sol": "solhint --version && solhint './contracts/**/*.sol' && solhint './contracts-0.8/**/*.sol'", "lint": "yarn run lint:ts && yarn run lint:sol", "clean": "rm -rf ./types/typechain && rm -rf build/* && rm -rf .0x-artifacts/* && rm -rf migrations/*.js* && rm -rf migrations_ts/*.js* && rm -rf test/**/*.js* && rm -f lib/*.js* && rm -f lib/**/*.js* && rm -f scripts/*.js* && yarn clean:foundry", - "clean:foundry": "rm -rf cache out", + "clean:foundry": "forge clean && rm -rf cache out", "test": "rm test/**/*.js ; node runTests.js", "test:scripts": "yarn ts-node scripts/run-scripts-tests.ts --testPathPattern=scripts/", "quicktest": "./scripts/bash/quicktest.sh", diff --git a/packages/protocol/releaseData/initializationData/release12.json b/packages/protocol/releaseData/initializationData/release12.json index 9968a3a2c70..635895d47e0 100644 --- a/packages/protocol/releaseData/initializationData/release12.json +++ b/packages/protocol/releaseData/initializationData/release12.json @@ -1,4 +1,7 @@ { - "FeeCurrencyDirectory": [], - "CeloUnreleasedTreasure": ["0x000000000000000000000000000000000000ce10"] + "CeloUnreleasedTreasure": ["0x000000000000000000000000000000000000ce10"], + "EpochManager": ["0x000000000000000000000000000000000000ce10", 86400, "0xD533Ca259b330c7A88f74E000a3FaEa2d63B7972", "0x0000000000000000000000000000000000000000"], + "EpochManagerEnabler": ["0x000000000000000000000000000000000000ce10"], + "ScoreManager": [], + "FeeCurrencyDirectory": [] } diff --git a/packages/protocol/scripts/foundry/create_and_migrate_anvil_devchain.sh b/packages/protocol/scripts/foundry/create_and_migrate_anvil_devchain.sh index 0fbbe697958..865c8ac854d 100755 --- a/packages/protocol/scripts/foundry/create_and_migrate_anvil_devchain.sh +++ b/packages/protocol/scripts/foundry/create_and_migrate_anvil_devchain.sh @@ -29,8 +29,8 @@ echo "Library flags are: $LIBRARY_FLAGS" # Build all contracts with deployed libraries # Including contracts that depend on libraries. This step replaces the library placeholder # in the bytecode with the address of the actually deployed library. -echo "Compiling with libraries... " -time forge build $LIBRARY_FLAGS +echo "Compiling with libraries..." +time FOUNDRY_PROFILE=devchain forge build $LIBRARY_FLAGS # Deploy precompile contracts source $PWD/scripts/foundry/deploy_precompiles.sh @@ -58,12 +58,20 @@ forge script \ $SKIP_SIMULATION \ $NON_INTERACTIVE \ $LIBRARY_FLAGS \ - --rpc-url $ANVIL_RPC_URL || echo "Migration script failed" + --rpc-url $ANVIL_RPC_URL || { echo "Migration script failed"; exit 1; } # Keeping track of the finish time to measure how long it takes to run the script entirely ELAPSED_TIME=$(($SECONDS - $START_TIME)) -echo "Total elapsed time: $ELAPSED_TIME seconds" +echo "Migration script total elapsed time: $ELAPSED_TIME seconds" -# Rename devchain artifact and remove unused directory -mv $ANVIL_FOLDER/state.json $TMP_FOLDER/$L1_DEVCHAIN_FILE_NAME -rm -rf $ANVIL_FOLDER +# this helps to make sure that devchain state is actually being saved +sleep 1 + +if [[ "${KEEP_DEVCHAIN_FOLDER:-}" == "true" ]]; then + cp $ANVIL_FOLDER/state.json $TMP_FOLDER/$L1_DEVCHAIN_FILE_NAME + echo "Keeping devchain folder as per flag." +else + # Rename devchain artifact and remove unused directory + mv $ANVIL_FOLDER/state.json $TMP_FOLDER/$L1_DEVCHAIN_FILE_NAME + rm -rf $ANVIL_FOLDER +fi diff --git a/packages/protocol/scripts/foundry/create_and_migrate_anvil_l2_devchain.sh b/packages/protocol/scripts/foundry/create_and_migrate_anvil_l2_devchain.sh index b1c0b145676..869397c2b39 100755 --- a/packages/protocol/scripts/foundry/create_and_migrate_anvil_l2_devchain.sh +++ b/packages/protocol/scripts/foundry/create_and_migrate_anvil_l2_devchain.sh @@ -4,13 +4,12 @@ set -euo pipefail # Read environment variables and constants source $PWD/scripts/foundry/constants.sh +export KEEP_DEVCHAIN_FOLDER=true + # Generate and run L1 devchain echo "Generating and running L1 devchain before activating L2..." source $PWD/scripts/foundry/create_and_migrate_anvil_devchain.sh -# Backup L1 state before applying L2 migrations so it can be published to NPM -cp $TMP_FOLDER/$L1_DEVCHAIN_FILE_NAME $TMP_FOLDER/backup_$L1_DEVCHAIN_FILE_NAME - # Activate L2 by deploying arbitrary bytecode to the proxy admin address. # Note: This can't be done from the migration script ARBITRARY_BYTECODE=$(cast format-bytes32-string "L2 is activated") @@ -48,14 +47,10 @@ forge script \ $BROADCAST \ $SKIP_SIMULATION \ $NON_INTERACTIVE \ - --rpc-url $ANVIL_RPC_URL || echo "L2 Migration script failed" + --rpc-url $ANVIL_RPC_URL || { echo "Migration script failed"; exit 1; } -# Save L2 state so it can published to NPM -cp $TMP_FOLDER/$L1_DEVCHAIN_FILE_NAME $TMP_FOLDER/$L2_DEVCHAIN_FILE_NAME +# # Save L2 state so it can published to NPM +mv $ANVIL_FOLDER/state.json $TMP_FOLDER/$L2_DEVCHAIN_FILE_NAME echo "Saved anvil L2 state to $TMP_FOLDER/$L2_DEVCHAIN_FILE_NAME" -# Restore L1 state from backup so it can be published to NPM as well -cp $TMP_FOLDER/backup_$L1_DEVCHAIN_FILE_NAME $TMP_FOLDER/$L1_DEVCHAIN_FILE_NAME - -# Delete backup -rm $TMP_FOLDER/backup_$L1_DEVCHAIN_FILE_NAME \ No newline at end of file +rm -rf $ANVIL_FOLDER diff --git a/packages/protocol/scripts/foundry/run_e2e_tests_in_anvil.sh b/packages/protocol/scripts/foundry/run_e2e_tests_in_anvil.sh index d515b84e5bc..3ecf3824427 100755 --- a/packages/protocol/scripts/foundry/run_e2e_tests_in_anvil.sh +++ b/packages/protocol/scripts/foundry/run_e2e_tests_in_anvil.sh @@ -8,13 +8,14 @@ source $PWD/scripts/foundry/constants.sh echo "Generating and running devchain before running e2e tests..." source $PWD/scripts/foundry/create_and_migrate_anvil_devchain.sh + # Run e2e tests echo "Running e2e tests..." -forge test \ +time FOUNDRY_PROFILE=devchain forge test \ -vvv \ ---match-path "*test-sol/e2e/*" \ +--match-path "*test-sol/devchain/e2e/*" \ --fork-url $ANVIL_RPC_URL # Stop devchain echo "Stopping devchain..." -source $PWD/scripts/foundry/stop_anvil.sh \ No newline at end of file +source $PWD/scripts/foundry/stop_anvil.sh diff --git a/packages/protocol/scripts/foundry/start_anvil.sh b/packages/protocol/scripts/foundry/start_anvil.sh index 342b3c8d043..ec802d5d980 100755 --- a/packages/protocol/scripts/foundry/start_anvil.sh +++ b/packages/protocol/scripts/foundry/start_anvil.sh @@ -17,6 +17,7 @@ cp $PWD/migrations_sol/README.md $TMP_FOLDER/README.md if nc -z localhost $ANVIL_PORT; then echo "Port already used" kill $(lsof -t -i:$ANVIL_PORT) + sleep 5 echo "Killed previous Anvil" fi diff --git a/packages/protocol/scripts/truffle/make-release.ts b/packages/protocol/scripts/truffle/make-release.ts index af7911955b2..440fd8a4566 100644 --- a/packages/protocol/scripts/truffle/make-release.ts +++ b/packages/protocol/scripts/truffle/make-release.ts @@ -118,9 +118,6 @@ const deployImplementation = async ( // without this delay it sometimes fails with ProviderError await delay(getRandomNumber(1, 1000)) - console.log('gas update in2') - console.log('dryRun', dryRun) - const bytecodeSize = (Contract.bytecode.length - 2) / 2 console.log('Bytecode size in bytes:', bytecodeSize) diff --git a/packages/protocol/test-sol/devchain/Import05Dependencies.sol b/packages/protocol/test-sol/devchain/Import05Dependencies.sol new file mode 100644 index 00000000000..43fc02435fc --- /dev/null +++ b/packages/protocol/test-sol/devchain/Import05Dependencies.sol @@ -0,0 +1,26 @@ +pragma solidity ^0.5.13; + +// this file only exists so that foundry compiles this contracts +import { Proxy } from "@celo-contracts/common/Proxy.sol"; +import { ProxyFactory } from "@celo-contracts/common/ProxyFactory.sol"; +import { GoldToken } from "@celo-contracts/common/GoldToken.sol"; +import { Accounts } from "@celo-contracts/common/Accounts.sol"; +import { Election } from "@celo-contracts/governance/Election.sol"; +import { Governance } from "@celo-contracts/governance/Governance.sol"; +import { LockedGold } from "@celo-contracts/governance/LockedGold.sol"; +import { GovernanceApproverMultiSig } from "@celo-contracts/governance/GovernanceApproverMultiSig.sol"; +import { Escrow } from "@celo-contracts/identity/Escrow.sol"; +import { FederatedAttestations } from "@celo-contracts/identity/FederatedAttestations.sol"; +import { SortedOracles } from "@celo-contracts/stability/SortedOracles.sol"; +import { ReserveSpenderMultiSig } from "@mento-core/contracts/ReserveSpenderMultiSig.sol"; +import { Reserve } from "@mento-core/contracts/Reserve.sol"; +import { StableToken } from "@mento-core/contracts/StableToken.sol"; +import { StableTokenEUR } from "@mento-core/contracts/StableTokenEUR.sol"; +import { StableTokenBRL } from "@mento-core/contracts/StableTokenBRL.sol"; +import { Exchange } from "@mento-core/contracts/Exchange.sol"; + +import { IEpochManager } from "@celo-contracts/common/interfaces/IEpochManager.sol"; +import { IValidators } from "@celo-contracts/governance/interfaces/IValidators.sol"; +import "@celo-contracts/common/interfaces/ICeloUnreleasedTreasure.sol"; + +contract Import05 {} diff --git a/packages/protocol/test-sol/devchain/ImportPrecompiles.t.sol b/packages/protocol/test-sol/devchain/ImportPrecompiles.t.sol new file mode 100644 index 00000000000..a976098c20a --- /dev/null +++ b/packages/protocol/test-sol/devchain/ImportPrecompiles.t.sol @@ -0,0 +1,9 @@ +pragma solidity >=0.8.7 <0.8.20; + +// this file only exists so that foundry compiles this contracts +import "@test-sol/precompiles/ProofOfPossesionPrecompile.sol"; +import "@test-sol/precompiles/EpochSizePrecompile.sol"; +import "@test-sol/precompiles/NumberValidatorsInCurrentSetPrecompile.sol"; +import "@test-sol/precompiles/ValidatorSignerAddressFromCurrentSetPrecompile.sol"; + +contract ImportPrecompiles {} diff --git a/packages/protocol/test-sol/devchain/e2e/common/EpochManager.t.sol b/packages/protocol/test-sol/devchain/e2e/common/EpochManager.t.sol new file mode 100644 index 00000000000..6f5b433830b --- /dev/null +++ b/packages/protocol/test-sol/devchain/e2e/common/EpochManager.t.sol @@ -0,0 +1,294 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity >=0.8.7 <0.8.20; + +import "celo-foundry-8/Test.sol"; +import { Devchain } from "@test-sol/devchain/e2e/utils.sol"; +import { Utils08 } from "@test-sol/utils08.sol"; + +import { IEpochManager } from "@celo-contracts/common/interfaces/IEpochManager.sol"; + +import "@celo-contracts-8/common/FeeCurrencyDirectory.sol"; +import "@test-sol/utils/ECDSAHelper08.sol"; +import "@openzeppelin/contracts8/utils/structs/EnumerableSet.sol"; + +contract E2E_EpochManager is Test, Devchain, Utils08, ECDSAHelper08 { + address epochManagerOwner; + address epochManagerEnabler; + address[] firstElected; + + uint256 epochDuration; + + address[] groups; + address[] validatorsArray; + + uint256[] groupScore = [5e23, 7e23, 1e24]; + uint256[] validatorScore = [1e23, 1e23, 1e23, 1e23, 1e23, 1e23]; + + struct VoterWithPK { + address voter; + uint256 privateKey; + } + + struct GroupWithVotes { + address group; + uint256 votes; + } + + mapping(address => uint256) addressToPrivateKeys; + mapping(address => VoterWithPK) validatorToVoter; + + function setUp() public virtual { + uint256 totalVotes = election.getTotalVotes(); + + epochManagerOwner = Ownable(address(epochManager)).owner(); + epochManagerEnabler = epochManager.epochManagerEnabler(); + firstElected = getValidators().getRegisteredValidators(); + + epochDuration = epochManager.epochDuration(); + + vm.deal(address(celoUnreleasedTreasure), 800_000_000 ether); // 80% of the total supply to the treasure - whis will be yet distributed + } + + function activateValidators() public { + uint256[] memory valKeys = new uint256[](9); + valKeys[0] = 0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d; + valKeys[1] = 0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a; + valKeys[2] = 0x7c852118294e51e653712a81e05800f419141751be58f605c371e15141b007a6; + valKeys[3] = 0x47e179ec197488593b187f80a00eb0da91f1b9d0b13f8733639f19c30a34926a; + valKeys[4] = 0x8b3a350cf5c34c9194ca85829a2df0ec3153be0318b5e2d3348e872092edffba; + valKeys[5] = 0x92db14e403b83dfe3df233f83dfa3a0d7096f21ca9b0d6d6b8d88b2b4ec1564e; + valKeys[6] = 0x4bbbf85ce3377467afe5d46f804f221813b2bb87f24d81f60f1fcdbf7cbf4356; + valKeys[7] = 0xdbda1821b80551c9d65939329250298aa3472ba22feea921c0cf5d620ea67b97; + valKeys[8] = 0x2a871d0798f97d79848a013d4936a73bf4cc922c825d33c1cf7073dff6d409c6; + + for (uint256 i = 0; i < valKeys.length; i++) { + address account = vm.addr(valKeys[i]); + addressToPrivateKeys[account] = valKeys[i]; + } + + address[] memory registeredValidators = getValidators().getRegisteredValidators(); + travelEpochL1(vm); + travelEpochL1(vm); + travelEpochL1(vm); + travelEpochL1(vm); + for (uint256 i = 0; i < registeredValidators.length; i++) { + (, , address validatorGroup, , ) = getValidators().getValidator(registeredValidators[i]); + if (getElection().getPendingVotesForGroup(validatorGroup) == 0) { + continue; + } + vm.startPrank(validatorGroup); + election.activate(validatorGroup); + vm.stopPrank(); + } + } + + function authorizeVoteSigner(uint256 signerPk, address account) internal { + bytes32 messageHash = keccak256(abi.encodePacked(account)); + bytes32 prefixedHash = ECDSAHelper08.toEthSignedMessageHash(messageHash); + (uint8 v, bytes32 r, bytes32 s) = vm.sign(signerPk, prefixedHash); + vm.prank(account); + accounts.authorizeVoteSigner(vm.addr(signerPk), v, r, s); + } +} + +contract E2E_EpochManager_InitializeSystem is E2E_EpochManager { + function setUp() public override { + super.setUp(); + whenL2(vm); + } + + function test_shouldRevert_WhenCalledByNonEnabler() public { + vm.expectRevert("msg.sender is not Initializer"); + epochManager.initializeSystem(1, 1, firstElected); + } + + function test_ShouldInitializeSystem() public { + vm.prank(epochManagerEnabler); + epochManager.initializeSystem(42, 43, firstElected); + + assertEq(epochManager.firstKnownEpoch(), 42); + assertEq(epochManager.getCurrentEpochNumber(), 42); + + ( + uint256 firstBlock, + uint256 lastBlock, + uint256 startTimestamp, + uint256 rewardsBlock + ) = epochManager.getCurrentEpoch(); + assertEq(firstBlock, 43); + assertEq(lastBlock, 0); + assertEq(startTimestamp, block.timestamp); + assertEq(rewardsBlock, 0); + } +} + +contract E2E_EpochManager_StartNextEpochProcess is E2E_EpochManager { + function setUp() public override { + super.setUp(); + activateValidators(); + whenL2(vm); + + validatorsArray = getValidators().getRegisteredValidators(); + groups = getValidators().getRegisteredValidatorGroups(); + + address scoreManagerOwner = scoreManager.owner(); + + vm.startPrank(scoreManagerOwner); + scoreManager.setGroupScore(groups[0], groupScore[0]); + scoreManager.setGroupScore(groups[1], groupScore[1]); + scoreManager.setGroupScore(groups[2], groupScore[2]); + + scoreManager.setValidatorScore(validatorsArray[0], validatorScore[0]); + scoreManager.setValidatorScore(validatorsArray[1], validatorScore[1]); + scoreManager.setValidatorScore(validatorsArray[2], validatorScore[2]); + scoreManager.setValidatorScore(validatorsArray[3], validatorScore[3]); + scoreManager.setValidatorScore(validatorsArray[4], validatorScore[4]); + scoreManager.setValidatorScore(validatorsArray[5], validatorScore[5]); + + vm.stopPrank(); + + vm.prank(epochManagerEnabler); + epochManager.initializeSystem(1, 1, firstElected); + } + + function test_shouldHaveInitialValues() public { + assertEq(epochManager.firstKnownEpoch(), 1); + assertEq(epochManager.getCurrentEpochNumber(), 1); + + // get getEpochProcessingState + ( + uint256 status, + uint256 perValidatorReward, + uint256 totalRewardsVote, + uint256 totalRewardsCommunity, + uint256 totalRewardsCarbonFund + ) = epochManager.getEpochProcessingState(); + assertEq(status, 0); // Not started + assertEq(perValidatorReward, 0); + assertEq(totalRewardsVote, 0); + assertEq(totalRewardsCommunity, 0); + assertEq(totalRewardsCarbonFund, 0); + } + + function test_shouldStartNextEpochProcessing() public { + timeTravel(vm, epochDuration + 1); + + epochManager.startNextEpochProcess(); + + ( + uint256 status, + uint256 perValidatorReward, + uint256 totalRewardsVote, + uint256 totalRewardsCommunity, + uint256 totalRewardsCarbonFund + ) = epochManager.getEpochProcessingState(); + assertEq(status, 1); // Started + assertGt(perValidatorReward, 0, "perValidatorReward"); + assertGt(totalRewardsVote, 0, "totalRewardsVote"); + assertGt(totalRewardsCommunity, 0, "totalRewardsCommunity"); + assertGt(totalRewardsCarbonFund, 0, "totalRewardsCarbonFund"); + } +} + +contract E2E_EpochManager_FinishNextEpochProcess is E2E_EpochManager { + using EnumerableSet for EnumerableSet.AddressSet; + + EnumerableSet.AddressSet internal originalyElected; + + function setUp() public override { + super.setUp(); + activateValidators(); + whenL2(vm); + + vm.prank(epochManagerEnabler); + epochManager.initializeSystem(1, 1, firstElected); + + validatorsArray = getValidators().getRegisteredValidators(); + groups = getValidators().getRegisteredValidatorGroups(); + + address scoreManagerOwner = scoreManager.owner(); + + vm.startPrank(scoreManagerOwner); + scoreManager.setGroupScore(groups[0], groupScore[0]); + scoreManager.setGroupScore(groups[1], groupScore[1]); + scoreManager.setGroupScore(groups[2], groupScore[2]); + + scoreManager.setValidatorScore(validatorsArray[0], validatorScore[0]); + scoreManager.setValidatorScore(validatorsArray[1], validatorScore[1]); + scoreManager.setValidatorScore(validatorsArray[2], validatorScore[2]); + scoreManager.setValidatorScore(validatorsArray[3], validatorScore[3]); + scoreManager.setValidatorScore(validatorsArray[4], validatorScore[4]); + scoreManager.setValidatorScore(validatorsArray[5], validatorScore[5]); + + vm.stopPrank(); + + timeTravel(vm, epochDuration + 1); + epochManager.startNextEpochProcess(); + } + + function test_shouldFinishNextEpochProcessing() public { + uint256[] memory groupActiveBalances = new uint256[](groups.length); + + GroupWithVotes[] memory groupWithVotes = new GroupWithVotes[](groups.length); + + (, , uint256 totalRewardsVote, , ) = epochManager.getEpochProcessingState(); + + (address[] memory groupsEligible, uint256[] memory values) = election + .getTotalVotesForEligibleValidatorGroups(); + + for (uint256 i = 0; i < groupsEligible.length; i++) { + groupActiveBalances[i] = election.getActiveVotesForGroup(groupsEligible[i]); + groupWithVotes[i] = GroupWithVotes( + groupsEligible[i], + values[i] + + election.getGroupEpochRewards(groupsEligible[i], totalRewardsVote, groupScore[i]) + ); + } + + sort(groupWithVotes); + + address[] memory lessers = new address[](groups.length); + address[] memory greaters = new address[](groups.length); + + for (uint256 i = 0; i < groups.length; i++) { + lessers[i] = i == 0 ? address(0) : groupWithVotes[i - 1].group; + greaters[i] = i == groups.length - 1 ? address(0) : groupWithVotes[i + 1].group; + } + + uint256 currentEpoch = epochManager.getCurrentEpochNumber(); + address[] memory currentlyElected = epochManager.getElected(); + for (uint256 i = 0; i < currentlyElected.length; i++) { + originalyElected.add(currentlyElected[i]); + } + + epochManager.finishNextEpochProcess(groups, lessers, greaters); + + assertEq(currentEpoch + 1, epochManager.getCurrentEpochNumber()); + + address[] memory newlyElected = epochManager.getElected(); + + for (uint256 i = 0; i < currentlyElected.length; i++) { + assertEq(originalyElected.contains(currentlyElected[i]), true); + } + + for (uint256 i = 0; i < groupsEligible.length; i++) { + assertEq(election.getActiveVotesForGroup(groupsEligible[i]), groupWithVotes[i].votes); + assertGt(election.getActiveVotesForGroup(groupsEligible[i]), groupActiveBalances[i]); + } + } + + // Bubble sort algorithm since it is a small array + function sort(GroupWithVotes[] memory items) public { + uint length = items.length; + for (uint i = 0; i < length; i++) { + for (uint j = 0; j < length - 1; j++) { + if (items[j].votes > items[j + 1].votes) { + // Swap + GroupWithVotes memory temp = items[j]; + items[j] = items[j + 1]; + items[j + 1] = temp; + } + } + } + } +} diff --git a/packages/protocol/test-sol/devchain/e2e/utils.sol b/packages/protocol/test-sol/devchain/e2e/utils.sol index cd1c57b8200..48275cb8d0e 100644 --- a/packages/protocol/test-sol/devchain/e2e/utils.sol +++ b/packages/protocol/test-sol/devchain/e2e/utils.sol @@ -3,10 +3,16 @@ pragma solidity >=0.8.7 <0.8.20; import "@celo-contracts-8/common/UsingRegistry.sol"; import "@celo-contracts/common/interfaces/IRegistry.sol"; +import { IEpochManager } from "@celo-contracts/common/interfaces/IEpochManager.sol"; +import { IAccounts } from "@celo-contracts/common/interfaces/IAccounts.sol"; +import { IScoreManager } from "@celo-contracts-8/common/interfaces/IScoreManager.sol"; +import { IValidators } from "@celo-contracts/governance/interfaces/IValidators.sol"; +import { IElection } from "@celo-contracts/governance/interfaces/IElection.sol"; // All core contracts that are expected to be in the Registry on the devchain import "@celo-contracts-8/common/FeeCurrencyDirectory.sol"; import "@celo-contracts/stability/interfaces/ISortedOracles.sol"; +import "@celo-contracts/common/interfaces/ICeloUnreleasedTreasure.sol"; import { TestConstants } from "@test-sol/constants.sol"; @@ -17,6 +23,12 @@ contract Devchain is UsingRegistry, TestConstants { // All core contracts that are expected to be in the Registry on the devchain ISortedOracles sortedOracles; FeeCurrencyDirectory feeCurrencyDirectory; + IEpochManager epochManager; + ICeloUnreleasedTreasure celoUnreleasedTreasure; + IValidators validators; + IAccounts accounts; + IScoreManager scoreManager; + IElection election; constructor() { // The following line is required by UsingRegistry.sol @@ -28,6 +40,13 @@ contract Devchain is UsingRegistry, TestConstants { devchainRegistry.getAddressForStringOrDie("FeeCurrencyDirectory") ); // FeeCurrencyDirectory is not in UsingRegistry.sol + epochManager = getEpochManager(); + celoUnreleasedTreasure = getCeloUnreleasedTreasure(); + validators = getValidators(); + accounts = getAccounts(); + scoreManager = IScoreManager(address(getScoreReader())); + election = getElection(); + // TODO: Add missing core contracts below (see list in migrations_sol/constants.sol) // TODO: Consider asserting that all contracts we expect are available in the Devchain class // (see list in migrations_sol/constants.sol) diff --git a/packages/protocol/test-sol/devchain/migration/Migration.t.sol b/packages/protocol/test-sol/devchain/migration/Migration.t.sol index 463656c4376..8ff3ffc2956 100644 --- a/packages/protocol/test-sol/devchain/migration/Migration.t.sol +++ b/packages/protocol/test-sol/devchain/migration/Migration.t.sol @@ -6,6 +6,7 @@ import "celo-foundry-8/Test.sol"; import { Utils08 } from "@test-sol/utils08.sol"; import { TestConstants } from "@test-sol/constants.sol"; import { MigrationsConstants } from "@migrations-sol/constants.sol"; +import { FeeCurrencyDirectory } from "@celo-contracts-8/common/FeeCurrencyDirectory.sol"; import "@celo-contracts/common/interfaces/IRegistry.sol"; import "@celo-contracts/common/interfaces/IProxy.sol"; @@ -13,18 +14,20 @@ import "@celo-contracts/common/interfaces/ICeloToken.sol"; import "@celo-contracts/common/interfaces/IAccounts.sol"; import "@celo-contracts/common/interfaces/IEpochManager.sol"; import "@celo-contracts/common/interfaces/IEpochManagerEnabler.sol"; -import "@celo-contracts/common/interfaces/IScoreManager.sol"; -import "@celo-contracts/governance/interfaces/IValidators.sol"; +import "@celo-contracts/common/interfaces/ICeloUnreleasedTreasure.sol"; import "@celo-contracts/governance/interfaces/IElection.sol"; +import "@celo-contracts/governance/interfaces/IValidators.sol"; + import "@celo-contracts-8/common/interfaces/IPrecompiles.sol"; +import "@celo-contracts-8/common/interfaces/IScoreManager.sol"; contract IntegrationTest is Test, TestConstants, Utils08 { IRegistry registry = IRegistry(REGISTRY_ADDRESS); uint256 constant RESERVE_BALANCE = 69411663406170917420347916; // current as of 08/20/24 - function setUp() public virtual {} + // function setUp() public virtual {} /** * @notice Removes CBOR encoded metadata from the tail of the deployedBytecode. @@ -141,10 +144,11 @@ contract EpochManagerIntegrationTest is IntegrationTest, MigrationsConstants { ICeloToken celoToken; IAccounts accountsContract; IValidators validatorsContract; - IElection electionContract; - IScoreManager scoreManagerContract; IEpochManager epochManager; IEpochManagerEnabler epochManagerEnabler; + IScoreManager scoreManager; + IElection election; + ICeloUnreleasedTreasure celoUnreleasedTreasure; address reserveAddress; address unreleasedTreasury; @@ -154,24 +158,23 @@ contract EpochManagerIntegrationTest is IntegrationTest, MigrationsConstants { uint256 firstEpochBlock = 100; address[] firstElected; address[] validatorsList; - - address[] groups; + address[] groupList; uint256[] groupScore = [5e23, 7e23, 1e24]; uint256[] validatorScore = [1e23, 1e23, 1e23, 1e23, 1e23, 1e23]; - function setUp() public override { - super.setUp(); + function setUp() public { randomAddress = actor("randomAddress"); validatorsContract = IValidators(registry.getAddressForStringOrDie("Validators")); - electionContract = IElection(registry.getAddressForStringOrDie("Election")); - scoreManagerContract = IScoreManager(registry.getAddressForStringOrDie("ScoreManager")); + + election = IElection(registry.getAddressForStringOrDie("Election")); + scoreManager = IScoreManager(registry.getAddressForStringOrDie("ScoreManager")); unreleasedTreasury = registry.getAddressForStringOrDie("CeloUnreleasedTreasure"); reserveAddress = registry.getAddressForStringOrDie("Reserve"); validatorsList = validatorsContract.getRegisteredValidators(); - groups = validatorsContract.getRegisteredValidatorGroups(); + groupList = validatorsContract.getRegisteredValidatorGroups(); // mint to the reserve celoToken = ICeloToken(registry.getAddressForStringOrDie("GoldToken")); @@ -187,6 +190,30 @@ contract EpochManagerIntegrationTest is IntegrationTest, MigrationsConstants { epochManagerEnabler = IEpochManagerEnabler( registry.getAddressForStringOrDie("EpochManagerEnabler") ); + scoreManager = IScoreManager(registry.getAddressForStringOrDie("ScoreManager")); + election = IElection(registry.getAddressForStringOrDie("Election")); + celoUnreleasedTreasure = ICeloUnreleasedTreasure( + registry.getAddressForStringOrDie("CeloUnreleasedTreasure") + ); + + address scoreManagerOwner = scoreManager.owner(); + vm.startPrank(scoreManagerOwner); + + scoreManager.setGroupScore(groupList[0], groupScore[0]); + scoreManager.setGroupScore(groupList[1], groupScore[1]); + scoreManager.setGroupScore(groupList[2], groupScore[2]); + + scoreManager.setValidatorScore(validatorsList[0], validatorScore[0]); + scoreManager.setValidatorScore(validatorsList[1], validatorScore[1]); + scoreManager.setValidatorScore(validatorsList[2], validatorScore[2]); + scoreManager.setValidatorScore(validatorsList[3], validatorScore[3]); + scoreManager.setValidatorScore(validatorsList[4], validatorScore[4]); + scoreManager.setValidatorScore(validatorsList[5], validatorScore[5]); + + vm.stopPrank(); + + activateValidators(); + vm.deal(address(celoUnreleasedTreasure), 100_000_000 ether); } function activateValidators() public { @@ -197,11 +224,11 @@ contract EpochManagerIntegrationTest is IntegrationTest, MigrationsConstants { travelEpochL1(vm); for (uint256 i = 0; i < registeredValidators.length; i++) { (, , address validatorGroup, , ) = validatorsContract.getValidator(registeredValidators[i]); - if (electionContract.getPendingVotesForGroup(validatorGroup) == 0) { + if (election.getPendingVotesForGroup(validatorGroup) == 0) { continue; } vm.startPrank(validatorGroup); - electionContract.activate(validatorGroup); + election.activate(validatorGroup); vm.stopPrank(); } } @@ -280,18 +307,18 @@ contract EpochManagerIntegrationTest is IntegrationTest, MigrationsConstants { } function _setValidatorL2Score() internal { - address scoreManagerOwner = scoreManagerContract.owner(); + address scoreManagerOwner = scoreManager.owner(); vm.startPrank(scoreManagerOwner); - scoreManagerContract.setGroupScore(groups[0], groupScore[0]); - scoreManagerContract.setGroupScore(groups[1], groupScore[1]); - scoreManagerContract.setGroupScore(groups[2], groupScore[2]); - - scoreManagerContract.setValidatorScore(validatorsList[0], validatorScore[0]); - scoreManagerContract.setValidatorScore(validatorsList[1], validatorScore[1]); - scoreManagerContract.setValidatorScore(validatorsList[2], validatorScore[2]); - scoreManagerContract.setValidatorScore(validatorsList[3], validatorScore[3]); - scoreManagerContract.setValidatorScore(validatorsList[4], validatorScore[4]); - scoreManagerContract.setValidatorScore(validatorsList[5], validatorScore[5]); + scoreManager.setGroupScore(groupList[0], groupScore[0]); + scoreManager.setGroupScore(groupList[1], groupScore[1]); + scoreManager.setGroupScore(groupList[2], groupScore[2]); + + scoreManager.setValidatorScore(validatorsList[0], validatorScore[0]); + scoreManager.setValidatorScore(validatorsList[1], validatorScore[1]); + scoreManager.setValidatorScore(validatorsList[2], validatorScore[2]); + scoreManager.setValidatorScore(validatorsList[3], validatorScore[3]); + scoreManager.setValidatorScore(validatorsList[4], validatorScore[4]); + scoreManager.setValidatorScore(validatorsList[5], validatorScore[5]); vm.stopPrank(); } diff --git a/packages/protocol/test-sol/integration/CompileValidatorMock.t.sol b/packages/protocol/test-sol/integration/CompileValidatorMock.t.sol new file mode 100644 index 00000000000..a4a8aa9930f --- /dev/null +++ b/packages/protocol/test-sol/integration/CompileValidatorMock.t.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity >=0.8.7 <0.8.20; + +import "celo-foundry-8/Test.sol"; +import "forge-std/console.sol"; + +// here only to forge compile of ValidatorsMock +import "@test-sol/unit/governance/validators/mocks/ValidatorsMock.sol"; + +contract CompileValidatorMock is Test { + function test_nop() public { + console.log("nop"); + } +} diff --git a/packages/protocol/test-sol/unit/common/EpochManager.t.sol b/packages/protocol/test-sol/unit/common/EpochManager.t.sol index 931ef839d0c..9de13060519 100644 --- a/packages/protocol/test-sol/unit/common/EpochManager.t.sol +++ b/packages/protocol/test-sol/unit/common/EpochManager.t.sol @@ -18,7 +18,7 @@ import "@celo-contracts/stability/test/MockSortedOracles.sol"; import "@celo-contracts/common/interfaces/IRegistry.sol"; import { EpochRewardsMock08 } from "@celo-contracts-8/governance/test/EpochRewardsMock.sol"; -import { ValidatorsMock08 } from "@celo-contracts-8/governance/test/ValidatorsMock08.sol"; +import { ValidatorsMock } from "@test-sol/unit/governance/validators/mocks/ValidatorsMock.sol"; import { MockCeloUnreleasedTreasure } from "@celo-contracts-8/common/test/MockCeloUnreleasedTreasure.sol"; contract EpochManagerTest is Test, TestConstants, Utils08 { @@ -27,13 +27,14 @@ contract EpochManagerTest is Test, TestConstants, Utils08 { MockStableToken08 stableToken; EpochRewardsMock08 epochRewards; - ValidatorsMock08 validators; + ValidatorsMock validators; address epochManagerEnabler; address carbonOffsettingPartner; address communityRewardFund; address reserveAddress; address scoreManagerAddress; + address nonOwner; uint256 firstEpochNumber = 100; uint256 firstEpochBlock = 100; @@ -54,7 +55,7 @@ contract EpochManagerTest is Test, TestConstants, Utils08 { epochManager = new EpochManager(true); sortedOracles = new MockSortedOracles(); epochRewards = new EpochRewardsMock08(); - validators = new ValidatorsMock08(); + validators = new ValidatorsMock(); stableToken = new MockStableToken08(); celoToken = new MockCeloToken08(); celoUnreleasedTreasure = new MockCeloUnreleasedTreasure(); @@ -69,9 +70,9 @@ contract EpochManagerTest is Test, TestConstants, Utils08 { epochManagerEnabler = actor("epochManagerEnabler"); carbonOffsettingPartner = actor("carbonOffsettingPartner"); communityRewardFund = actor("communityRewardFund"); + nonOwner = actor("nonOwner"); deployCodeTo("MockRegistry.sol", abi.encode(false), REGISTRY_ADDRESS); - deployCodeTo("ScoreManager.sol", abi.encode(false), scoreManagerAddress); registry = IRegistry(REGISTRY_ADDRESS); diff --git a/packages/protocol/test-sol/unit/common/ImportPrecompiles.t.sol b/packages/protocol/test-sol/unit/common/ImportPrecompiles.t.sol index 3c0040b0ab2..a976098c20a 100644 --- a/packages/protocol/test-sol/unit/common/ImportPrecompiles.t.sol +++ b/packages/protocol/test-sol/unit/common/ImportPrecompiles.t.sol @@ -3,5 +3,7 @@ pragma solidity >=0.8.7 <0.8.20; // this file only exists so that foundry compiles this contracts import "@test-sol/precompiles/ProofOfPossesionPrecompile.sol"; import "@test-sol/precompiles/EpochSizePrecompile.sol"; +import "@test-sol/precompiles/NumberValidatorsInCurrentSetPrecompile.sol"; +import "@test-sol/precompiles/ValidatorSignerAddressFromCurrentSetPrecompile.sol"; contract ImportPrecompiles {} diff --git a/packages/protocol/test-sol/unit/governance/network/EpochRewards.t.sol b/packages/protocol/test-sol/unit/governance/network/EpochRewards.t.sol index 58d83c37850..7c8a848f26d 100644 --- a/packages/protocol/test-sol/unit/governance/network/EpochRewards.t.sol +++ b/packages/protocol/test-sol/unit/governance/network/EpochRewards.t.sol @@ -446,6 +446,7 @@ contract EpochRewardsTest_getTargetVoterRewards is EpochRewardsTest { } contract EpochRewardsTest_getTargetTotalEpochPaymentsInGold is EpochRewardsTest { + // TODO(soloseng): add L2 test case that uses EpochManager function test_ShouldgetTargetTotalEpochPaymentsInGold_WhenExchangeRateIsSet() public { uint256 numberValidators = 100; epochRewards.setNumberValidatorsInCurrentSet(numberValidators); @@ -456,6 +457,7 @@ contract EpochRewardsTest_getTargetTotalEpochPaymentsInGold is EpochRewardsTest } contract EpochRewardsTest_getRewardsMultiplier is EpochRewardsTest { + // TODO(soloseng): add L2 test case using EpochManager uint256 constant timeDelta = YEAR * 10; uint256 expectedTargetTotalSupply; uint256 expectedTargetRemainingSupply; @@ -502,6 +504,7 @@ contract EpochRewardsTest_getRewardsMultiplier is EpochRewardsTest { } contract EpochRewardsTest_updateTargetVotingYield is EpochRewardsTest { + //TODO(soloseng): add L2 test case that uses epochManager uint256 constant totalSupply = 6000000 ether; uint256 constant reserveBalance = 1000000 ether; uint256 constant floatingSupply = totalSupply - reserveBalance; diff --git a/packages/protocol/test-sol/unit/governance/validators/Validators.t.sol b/packages/protocol/test-sol/unit/governance/validators/Validators.t.sol index 7a679044670..f66b9ad11b7 100644 --- a/packages/protocol/test-sol/unit/governance/validators/Validators.t.sol +++ b/packages/protocol/test-sol/unit/governance/validators/Validators.t.sol @@ -2873,6 +2873,7 @@ contract ValidatorsTest_UpdateValidatorScoreFromSigner is ValidatorsTest { ) .unwrap(); + vm.prank(address(0)); validators.updateValidatorScoreFromSigner(validator, uptime.unwrap()); (, , , uint256 _actualScore, ) = validators.getValidator(validator); @@ -2881,6 +2882,7 @@ contract ValidatorsTest_UpdateValidatorScoreFromSigner is ValidatorsTest { } function test_ShouldUpdateValidatorScore_WhenValidatorHasNonZeroScore() public { + vm.prank(address(0)); validators.updateValidatorScoreFromSigner(validator, uptime.unwrap()); uint256 _expectedScore = FixidityLib @@ -2903,6 +2905,7 @@ contract ValidatorsTest_UpdateValidatorScoreFromSigner is ValidatorsTest { ) .unwrap(); + vm.prank(address(0)); validators.updateValidatorScoreFromSigner(validator, uptime.unwrap()); (, , , uint256 _actualScore, ) = validators.getValidator(validator); @@ -2911,6 +2914,7 @@ contract ValidatorsTest_UpdateValidatorScoreFromSigner is ValidatorsTest { function test_Reverts_WhenUptimeGreaterThan1() public { uptime = FixidityLib.add(FixidityLib.fixed1(), FixidityLib.newFixedFraction(1, 10)); + vm.prank(address(0)); vm.expectRevert("Uptime cannot be larger than one"); validators.updateValidatorScoreFromSigner(validator, uptime.unwrap()); } @@ -3240,26 +3244,31 @@ contract ValidatorsTest_DistributeEpochPaymentsFromSigner is ValidatorsTest { ) ); + vm.prank(address(0)); validators.updateValidatorScoreFromSigner(validator, uptime.unwrap()); } function test_Reverts_WhenValidatorAndGroupMeetBalanceRequirements_WhenL2() public { _whenL2(); + vm.prank(address(0)); vm.expectRevert("This method is no longer supported in L2."); validators.distributeEpochPaymentsFromSigner(validator, maxPayment); } function test_ShouldPayValidator_WhenValidatorAndGroupMeetBalanceRequirements() public { + vm.prank(address(0)); validators.distributeEpochPaymentsFromSigner(validator, maxPayment); assertEq(stableToken.balanceOf(validator), expectedValidatorPayment); } function test_ShouldPayGroup_WhenValidatorAndGroupMeetBalanceRequirements() public { + vm.prank(address(0)); validators.distributeEpochPaymentsFromSigner(validator, maxPayment); assertEq(stableToken.balanceOf(group), expectedGroupPayment); } function test_ShouldPayDelegatee_WhenValidatorAndGroupMeetBalanceRequirements() public { + vm.prank(address(0)); validators.distributeEpochPaymentsFromSigner(validator, maxPayment); assertEq(stableToken.balanceOf(paymentDelegatee), expectedDelegatedPayment); } @@ -3267,7 +3276,8 @@ contract ValidatorsTest_DistributeEpochPaymentsFromSigner is ValidatorsTest { function test_ShouldReturnTheExpectedTotalPayment_WhenValidatorAndGroupMeetBalanceRequirements() public { - validators.distributeEpochPaymentsFromSigner(validator, maxPayment); + // validators.distributeEpochPaymentsFromSigner(validator, maxPayment); + vm.prank(address(0)); assertEq( validators.distributeEpochPaymentsFromSigner(validator, maxPayment), expectedTotalPayment @@ -3283,6 +3293,7 @@ contract ValidatorsTest_DistributeEpochPaymentsFromSigner is ValidatorsTest { vm.prank(validator); accounts.deletePaymentDelegation(); + vm.prank(address(0)); validators.distributeEpochPaymentsFromSigner(validator, maxPayment); assertEq(stableToken.balanceOf(validator), expectedValidatorPayment); } @@ -3296,7 +3307,8 @@ contract ValidatorsTest_DistributeEpochPaymentsFromSigner is ValidatorsTest { vm.prank(validator); accounts.deletePaymentDelegation(); - validators.distributeEpochPaymentsFromSigner(validator, maxPayment); + // validators.distributeEpochPaymentsFromSigner(validator, maxPayment); + vm.prank(address(0)); assertEq( validators.distributeEpochPaymentsFromSigner(validator, maxPayment), expectedTotalPayment @@ -3312,6 +3324,7 @@ contract ValidatorsTest_DistributeEpochPaymentsFromSigner is ValidatorsTest { vm.prank(validator); accounts.deletePaymentDelegation(); + vm.prank(address(0)); validators.distributeEpochPaymentsFromSigner(validator, maxPayment); assertEq(stableToken.balanceOf(group), expectedGroupPayment); } @@ -3319,6 +3332,7 @@ contract ValidatorsTest_DistributeEpochPaymentsFromSigner is ValidatorsTest { function test_shouldPayValidatorOnlyHalf_WhenSlashingMultiplierIsHalved() public { vm.prank(paymentDelegatee); validators.halveSlashingMultiplier(group); + vm.prank(address(0)); validators.distributeEpochPaymentsFromSigner(validator, maxPayment); assertEq(stableToken.balanceOf(validator), halfExpectedValidatorPayment); @@ -3327,6 +3341,7 @@ contract ValidatorsTest_DistributeEpochPaymentsFromSigner is ValidatorsTest { function test_shouldPayGroupOnlyHalf_WhenSlashingMultiplierIsHalved() public { vm.prank(paymentDelegatee); validators.halveSlashingMultiplier(group); + vm.prank(address(0)); validators.distributeEpochPaymentsFromSigner(validator, maxPayment); assertEq(stableToken.balanceOf(group), halfExpectedGroupPayment); @@ -3335,6 +3350,7 @@ contract ValidatorsTest_DistributeEpochPaymentsFromSigner is ValidatorsTest { function test_shouldPayDelegateeOnlyHalf_WhenSlashingMultiplierIsHalved() public { vm.prank(paymentDelegatee); validators.halveSlashingMultiplier(group); + vm.prank(address(0)); validators.distributeEpochPaymentsFromSigner(validator, maxPayment); assertEq(stableToken.balanceOf(paymentDelegatee), halfExpectedDelegatedPayment); @@ -3343,8 +3359,8 @@ contract ValidatorsTest_DistributeEpochPaymentsFromSigner is ValidatorsTest { function test_shouldReturnHalfExpectedTotalPayment_WhenSlashingMultiplierIsHalved() public { vm.prank(paymentDelegatee); validators.halveSlashingMultiplier(group); - validators.distributeEpochPaymentsFromSigner(validator, maxPayment); + vm.prank(address(0)); assertEq( validators.distributeEpochPaymentsFromSigner(validator, maxPayment), halfExpectedTotalPayment @@ -3357,6 +3373,7 @@ contract ValidatorsTest_DistributeEpochPaymentsFromSigner is ValidatorsTest { originalValidatorLockedGoldRequirements.value.sub(11) ); + vm.prank(address(0)); validators.distributeEpochPaymentsFromSigner(validator, maxPayment); assertEq(stableToken.balanceOf(validator), 0); } @@ -3367,6 +3384,7 @@ contract ValidatorsTest_DistributeEpochPaymentsFromSigner is ValidatorsTest { originalValidatorLockedGoldRequirements.value.sub(11) ); + vm.prank(address(0)); validators.distributeEpochPaymentsFromSigner(validator, maxPayment); assertEq(stableToken.balanceOf(group), 0); } @@ -3377,6 +3395,7 @@ contract ValidatorsTest_DistributeEpochPaymentsFromSigner is ValidatorsTest { originalValidatorLockedGoldRequirements.value.sub(11) ); + vm.prank(address(0)); validators.distributeEpochPaymentsFromSigner(validator, maxPayment); assertEq(stableToken.balanceOf(paymentDelegatee), 0); } @@ -3387,6 +3406,7 @@ contract ValidatorsTest_DistributeEpochPaymentsFromSigner is ValidatorsTest { originalValidatorLockedGoldRequirements.value.sub(11) ); + vm.prank(address(0)); assertEq(validators.distributeEpochPaymentsFromSigner(validator, maxPayment), 0); } @@ -3396,6 +3416,7 @@ contract ValidatorsTest_DistributeEpochPaymentsFromSigner is ValidatorsTest { originalGroupLockedGoldRequirements.value.sub(11) ); + vm.prank(address(0)); validators.distributeEpochPaymentsFromSigner(validator, maxPayment); assertEq(stableToken.balanceOf(validator), 0); } @@ -3406,6 +3427,7 @@ contract ValidatorsTest_DistributeEpochPaymentsFromSigner is ValidatorsTest { originalGroupLockedGoldRequirements.value.sub(11) ); + vm.prank(address(0)); validators.distributeEpochPaymentsFromSigner(validator, maxPayment); assertEq(stableToken.balanceOf(group), 0); } @@ -3416,6 +3438,7 @@ contract ValidatorsTest_DistributeEpochPaymentsFromSigner is ValidatorsTest { originalGroupLockedGoldRequirements.value.sub(11) ); + vm.prank(address(0)); validators.distributeEpochPaymentsFromSigner(validator, maxPayment); assertEq(stableToken.balanceOf(paymentDelegatee), 0); } @@ -3426,6 +3449,7 @@ contract ValidatorsTest_DistributeEpochPaymentsFromSigner is ValidatorsTest { originalGroupLockedGoldRequirements.value.sub(11) ); + vm.prank(address(0)); assertEq(validators.distributeEpochPaymentsFromSigner(validator, maxPayment), 0); } } @@ -3441,6 +3465,11 @@ contract ValidatorsTest_MintStableToEpochManager is ValidatorsTest { vm.expectRevert("only registered contract"); validators.mintStableToEpochManager(5); } + function test_WhenMintAmountIsZero() public { + _whenL2(); + vm.prank(address(epochManager)); + validators.mintStableToEpochManager(0); + } function test_ShouldMintStableToEpochManager() public { _whenL2(); diff --git a/packages/protocol/test-sol/unit/governance/validators/mocks/CompileValidatorIntegrationMock.t.sol b/packages/protocol/test-sol/unit/governance/validators/mocks/CompileValidatorIntegrationMock.t.sol new file mode 100644 index 00000000000..e7ce8a53559 --- /dev/null +++ b/packages/protocol/test-sol/unit/governance/validators/mocks/CompileValidatorIntegrationMock.t.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity >=0.8.7 <0.8.20; + +import "celo-foundry-8/Test.sol"; +import "forge-std/console.sol"; + +// here only to forge compile of ValidatorsMock +import "@test-sol/unit/governance/validators/mocks/ValidatorsMock.sol"; + +contract CompileValidatorIntegrationMock is Test { + function test_nop() public { + console.log("nop"); + } +} diff --git a/packages/protocol/test-sol/unit/governance/validators/mocks/ValidatorsMock.sol b/packages/protocol/test-sol/unit/governance/validators/mocks/ValidatorsMock.sol new file mode 100644 index 00000000000..4550cd02fd4 --- /dev/null +++ b/packages/protocol/test-sol/unit/governance/validators/mocks/ValidatorsMock.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity >=0.8.7 <0.8.20; + +import "@celo-contracts-8/governance/Validators.sol"; +import "@celo-contracts/common/FixidityLib.sol"; + +/** + * @title A wrapper around Validators that exposes onlyVm functions for testing. + */ +contract ValidatorsMock is Validators(true) { + function computeEpochReward( + address account, + uint256 score, + uint256 maxPayment + ) external view override returns (uint256) { + return 1; + } +} diff --git a/packages/protocol/test-sol/unit/governance/voting/Election.t.sol b/packages/protocol/test-sol/unit/governance/voting/Election.t.sol index e055288f0b0..3ac0251451c 100644 --- a/packages/protocol/test-sol/unit/governance/voting/Election.t.sol +++ b/packages/protocol/test-sol/unit/governance/voting/Election.t.sol @@ -2621,13 +2621,6 @@ contract ElectionTest_DistributeEpochRewards is ElectionTest { assertEq(election.getActiveVotesForGroupByAccount(group, voter), voteValue + rewardValue); } - function test_Revert_DistributeEpochRewards_WhenL2() public { - _whenL2(); - vm.expectRevert("This method is no longer supported in L2."); - vm.prank(address(0)); - election.distributeEpochRewards(group, rewardValue, address(0), address(0)); - } - function test_ShouldIncrementAccountTotalVotesForGroup_WhenThereIsSingleGroupWithActiveVotes() public { diff --git a/packages/protocol/test-sol/unit/stability/FeeCurrencyAdapter.t.sol b/packages/protocol/test-sol/unit/stability/FeeCurrencyAdapter.t.sol index 13f13bd6d97..44a754e231c 100644 --- a/packages/protocol/test-sol/unit/stability/FeeCurrencyAdapter.t.sol +++ b/packages/protocol/test-sol/unit/stability/FeeCurrencyAdapter.t.sol @@ -130,7 +130,6 @@ contract FeeCurrencyAdapter_Initialize is FeeCurrencyAdapterTest { function test_ShouldSucceed_WhenExpectedDecimalsAreMoreThenDecimals_Fuzz(uint8 amount) public { vm.assume(amount > 6); vm.assume(amount < 50); - console.log("amount", amount); feeCurrencyAdapterForFuzzyTests.initialize(address(feeCurrency), "adapter", "ad", amount); } @@ -303,7 +302,6 @@ contract FeeCurrencyAdapter_CreditGasFees is FeeCurrencyAdapterTest { function creditFuzzHelper(uint8 expectedDigits, uint256 multiplier) public { uint256 originalAmount = 1000; uint256 amount = originalAmount * multiplier; - console.log("amount", amount); address secondAddress = actor("secondAddress"); address thirdAddress = actor("thirdAddress"); diff --git a/packages/protocol/test-sol/utils/ECDSAHelper08.sol b/packages/protocol/test-sol/utils/ECDSAHelper08.sol new file mode 100644 index 00000000000..3c761bb9e76 --- /dev/null +++ b/packages/protocol/test-sol/utils/ECDSAHelper08.sol @@ -0,0 +1,34 @@ +pragma solidity >=0.5.13 <0.8.20; +import "celo-foundry-8/Test.sol"; +import "@test-sol/utils/SECP256K1.sol"; + +contract ECDSAHelper08 is Test { + ISECP256K1 sECP256K1; + + function addressToPublicKey( + bytes32 message, + uint8 _v, + bytes32 _r, + bytes32 _s + ) public returns (bytes memory) { + address SECP256K1Address = actor("SECP256K1Address"); + deployCodeTo("SECP256K1.sol:SECP256K1", SECP256K1Address); + sECP256K1 = ISECP256K1(SECP256K1Address); + + string memory header = "\x19Ethereum Signed Message:\n32"; + bytes32 _message = keccak256(abi.encodePacked(header, message)); + (uint256 x, uint256 y) = sECP256K1.recover( + uint256(_message), + _v - 27, + uint256(_r), + uint256(_s) + ); + return abi.encodePacked(x, y); + } + + function toEthSignedMessageHash(bytes32 hash) internal pure returns (bytes32) { + // 32 is the length in bytes of hash, + // enforced by the type signature above + return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", hash)); + } +} diff --git a/packages/protocol/test-sol/utils08.sol b/packages/protocol/test-sol/utils08.sol index 1ee9330ad50..bb069cea58b 100644 --- a/packages/protocol/test-sol/utils08.sol +++ b/packages/protocol/test-sol/utils08.sol @@ -3,6 +3,8 @@ pragma solidity >=0.5.13 <0.9.0; import "celo-foundry-8/Test.sol"; contract Utils08 { + uint256 public constant secondsInOneBlock = 5; + function timeTravel(Vm vm, uint256 timeDelta) public { vm.warp(block.timestamp + timeDelta); } From f4b45640cde78fb89d16093eab2df8dee1b304cb Mon Sep 17 00:00:00 2001 From: soloseng <102702451+soloseng@users.noreply.github.com> Date: Tue, 17 Sep 2024 17:16:03 -0400 Subject: [PATCH 52/59] Soloseng/dynamically-fetch-epochmanagerenabler-address (#11207) * dynamically fetch epochManagerEnabler && carbonOffsettingPartner ++ to registry * PR feedback * removed onlyL1 modifier in setter functions * updated unit test to reflect changes * fixing tests * fix test * fixed migration data * fixed migration script error * removed unused modifier * removed duplicate or unused code --- .../contracts-0.8/common/EpochManager.sol | 24 +++------ .../contracts-0.8/common/UsingRegistry.sol | 2 + .../interfaces/IEpochManagerInitializer.sol | 7 +-- .../contracts-0.8/governance/Validators.sol | 5 -- .../governance/test/EpochRewardsMock.sol | 1 + .../contracts/common/UsingRegistry.sol | 4 +- .../contracts/common/UsingRegistryV2.sol | 4 +- .../common/interfaces/IEpochManager.sol | 1 - .../contracts/governance/EpochRewards.sol | 17 +++---- .../governance/interfaces/IEpochRewards.sol | 1 + packages/protocol/migrationsConfig.js | 1 - .../protocol/migrations_sol/Migration.s.sol | 8 +-- .../protocol/migrations_sol/MigrationL2.s.sol | 4 +- .../migrations_sol/migrationsConfig.json | 3 +- .../migrations_ts/26_103_epoch_manager.ts | 14 +----- .../initializationData/release12.json | 2 +- packages/protocol/test-sol/constants.sol | 1 + .../devchain/e2e/common/EpochManager.t.sol | 4 +- .../devchain/migration/Migration.t.sol | 38 +------------- .../test-sol/unit/common/EpochManager.t.sol | 22 +++----- .../governance/network/EpochRewards.t.sol | 50 +++++++++++-------- 21 files changed, 72 insertions(+), 141 deletions(-) diff --git a/packages/protocol/contracts-0.8/common/EpochManager.sol b/packages/protocol/contracts-0.8/common/EpochManager.sol index 430f1ffa740..bfaaa428e31 100644 --- a/packages/protocol/contracts-0.8/common/EpochManager.sol +++ b/packages/protocol/contracts-0.8/common/EpochManager.sol @@ -61,9 +61,6 @@ contract EpochManager is mapping(uint256 => Epoch) private epochs; mapping(address => uint256) public validatorPendingPayments; - address public carbonOffsettingPartner; - address public epochManagerEnabler; - /** * @notice Event emited when epochProcessing has begun. * @param epochNumber The epoch number that is being processed. @@ -77,7 +74,10 @@ contract EpochManager is event EpochProcessingEnded(uint256 indexed epochNumber); modifier onlyEpochManagerEnabler() { - require(msg.sender == epochManagerEnabler, "msg.sender is not Initializer"); + require( + msg.sender == registry.getAddressForOrDie(EPOCH_MANAGER_ENABLER_REGISTRY_ID), + "msg.sender is not Enabler" + ); _; } @@ -92,19 +92,10 @@ contract EpochManager is * @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, - address _carbonOffsettingPartner, - address _epochManagerEnabler - ) external initializer { - require(_carbonOffsettingPartner != address(0), "carbonOffsettingPartner address is required"); - require(_epochManagerEnabler != address(0), "EpochManagerEnabler address is required"); + function initialize(address registryAddress, uint256 newEpochDuration) external initializer { _transferOwnership(msg.sender); setRegistry(registryAddress); setEpochDuration(newEpochDuration); - carbonOffsettingPartner = _carbonOffsettingPartner; - epochManagerEnabler = _epochManagerEnabler; } // DESIGNDESICION(XXX): we assume that the first epoch on the L2 starts as soon as the system is initialized @@ -140,7 +131,6 @@ contract EpochManager is _currentEpoch.startTimestamp = block.timestamp; elected = firstElected; - epochManagerEnabler = address(0); } // TODO maybe "freezeEpochRewards" "prepareForNextEpoch" @@ -228,7 +218,7 @@ contract EpochManager is epochProcessing.totalRewardsCommunity ); getCeloUnreleasedTreasure().release( - carbonOffsettingPartner, + getEpochRewards().carbonOffsettingPartner(), epochProcessing.totalRewardsCarbonFund ); // run elections @@ -313,7 +303,7 @@ contract EpochManager is } function systemAlreadyInitialized() public view returns (bool) { - return initialized && epochManagerEnabler == address(0); + return initialized && elected.length > 0; } function allocateValidatorsRewards() internal { diff --git a/packages/protocol/contracts-0.8/common/UsingRegistry.sol b/packages/protocol/contracts-0.8/common/UsingRegistry.sol index 9b1d13dd3e7..56ea92f1214 100644 --- a/packages/protocol/contracts-0.8/common/UsingRegistry.sol +++ b/packages/protocol/contracts-0.8/common/UsingRegistry.sol @@ -54,6 +54,8 @@ contract UsingRegistry is Ownable { bytes32 constant LOCKED_CELO_REGISTRY_ID = keccak256(abi.encodePacked("LockedCelo")); bytes32 constant CELO_UNRELEASED_TREASURE_REGISTRY_ID = keccak256(abi.encodePacked("CeloUnreleasedTreasure")); + bytes32 constant EPOCH_MANAGER_ENABLER_REGISTRY_ID = + keccak256(abi.encodePacked("EpochManagerEnabler")); bytes32 constant EPOCH_MANAGER_REGISTRY_ID = keccak256(abi.encodePacked("EpochManager")); bytes32 constant SCORE_MANAGER_REGISTRY_ID = keccak256(abi.encodePacked("ScoreManager")); // solhint-enable state-visibility diff --git a/packages/protocol/contracts-0.8/common/interfaces/IEpochManagerInitializer.sol b/packages/protocol/contracts-0.8/common/interfaces/IEpochManagerInitializer.sol index 119e2f8616e..ee418889182 100644 --- a/packages/protocol/contracts-0.8/common/interfaces/IEpochManagerInitializer.sol +++ b/packages/protocol/contracts-0.8/common/interfaces/IEpochManagerInitializer.sol @@ -2,10 +2,5 @@ pragma solidity >=0.5.13 <0.9.0; interface IEpochManagerInitializer { - function initialize( - address registryAddress, - uint256 newEpochDuration, - address _carbonOffsettingPartner, - address _epochManagerEnabler - ) external; + function initialize(address registryAddress, uint256 newEpochDuration) external; } diff --git a/packages/protocol/contracts-0.8/governance/Validators.sol b/packages/protocol/contracts-0.8/governance/Validators.sol index dbd60d6b26e..f6cc16defa1 100644 --- a/packages/protocol/contracts-0.8/governance/Validators.sol +++ b/packages/protocol/contracts-0.8/governance/Validators.sol @@ -166,11 +166,6 @@ contract Validators is _; } - modifier onlyEpochManager() { - require(msg.sender == address(getEpochManager()), "Only epoch manager can call"); - _; - } - /** * @notice Sets initialized == true on implementation contracts * @param test Set to true to skip implementation initialization diff --git a/packages/protocol/contracts-0.8/governance/test/EpochRewardsMock.sol b/packages/protocol/contracts-0.8/governance/test/EpochRewardsMock.sol index 38967cf3928..ce6125dcee1 100644 --- a/packages/protocol/contracts-0.8/governance/test/EpochRewardsMock.sol +++ b/packages/protocol/contracts-0.8/governance/test/EpochRewardsMock.sol @@ -8,6 +8,7 @@ import "../../../contracts/governance/interfaces/IEpochRewards.sol"; */ contract EpochRewardsMock08 is IEpochRewards { uint256 private numValidatorsInCurrentSet; + address public carbonOffsettingPartner; function setNumberValidatorsInCurrentSet(uint256 value) external { numValidatorsInCurrentSet = value; diff --git a/packages/protocol/contracts/common/UsingRegistry.sol b/packages/protocol/contracts/common/UsingRegistry.sol index 08eb3f36507..0f8e11285c9 100644 --- a/packages/protocol/contracts/common/UsingRegistry.sol +++ b/packages/protocol/contracts/common/UsingRegistry.sol @@ -54,8 +54,8 @@ contract UsingRegistry is Ownable { bytes32 constant CELO_UNRELEASED_TREASURE_REGISTRY_ID = keccak256(abi.encodePacked("CeloUnreleasedTreasure")); bytes32 constant EPOCH_REWARDS_REGISTRY_ID = keccak256(abi.encodePacked("EpochRewards")); - bytes32 constant EPOCH_MANAGER_INITIALIZER_REGISTRY_ID = - keccak256(abi.encodePacked("EpochManagerInitializer")); + bytes32 constant EPOCH_MANAGER_ENABLER_REGISTRY_ID = + keccak256(abi.encodePacked("EpochManagerEnabler")); bytes32 constant EPOCH_MANAGER_REGISTRY_ID = keccak256(abi.encodePacked("EpochManager")); // solhint-enable state-visibility diff --git a/packages/protocol/contracts/common/UsingRegistryV2.sol b/packages/protocol/contracts/common/UsingRegistryV2.sol index 3f812931c47..da33af2a035 100644 --- a/packages/protocol/contracts/common/UsingRegistryV2.sol +++ b/packages/protocol/contracts/common/UsingRegistryV2.sol @@ -67,8 +67,8 @@ contract UsingRegistryV2 { bytes32 internal constant CELO_TOKEN_REGISTRY_ID = keccak256(abi.encodePacked("CeloToken")); bytes32 internal constant LOCKED_CELO_REGISTRY_ID = keccak256(abi.encodePacked("LockedCelo")); bytes32 internal constant EPOCH_REWARDS_REGISTRY_ID = keccak256(abi.encodePacked("EpochRewards")); - bytes32 internal constant EPOCH_MANAGER_INITIALIZER_REGISTRY_ID = - keccak256(abi.encodePacked("EpochManagerInitializer")); + bytes32 internal constant EPOCH_MANAGER_ENABLER_REGISTRY_ID = + keccak256(abi.encodePacked("EpochManagerEnabler")); bytes32 internal constant EPOCH_MANAGER_REGISTRY_ID = keccak256(abi.encodePacked("EpochManager")); modifier onlyRegisteredContract(bytes32 identifierHash) { diff --git a/packages/protocol/contracts/common/interfaces/IEpochManager.sol b/packages/protocol/contracts/common/interfaces/IEpochManager.sol index c0f9736179f..4d48466c7a3 100644 --- a/packages/protocol/contracts/common/interfaces/IEpochManager.sol +++ b/packages/protocol/contracts/common/interfaces/IEpochManager.sol @@ -16,7 +16,6 @@ interface IEpochManager { function getCurrentEpoch() external view returns (uint256, uint256, uint256, uint256); function getCurrentEpochNumber() external view returns (uint256); function getElected() external view returns (address[] memory); - function epochManagerEnabler() external view returns (address); function epochDuration() external view returns (uint256); function firstKnownEpoch() external view returns (uint256); function getEpochProcessingState() diff --git a/packages/protocol/contracts/governance/EpochRewards.sol b/packages/protocol/contracts/governance/EpochRewards.sol index 774da4f90d9..c55d3d74335 100644 --- a/packages/protocol/contracts/governance/EpochRewards.sol +++ b/packages/protocol/contracts/governance/EpochRewards.sol @@ -286,7 +286,7 @@ contract EpochRewards is * @param value The percentage of the total reward to be sent to the community funds. * @return True upon success. */ - function setCommunityRewardFraction(uint256 value) public onlyOwner onlyL1 returns (bool) { + function setCommunityRewardFraction(uint256 value) public onlyOwner returns (bool) { require( value != communityRewardFraction.unwrap() && value < FixidityLib.fixed1().unwrap(), "Value must be different from existing community reward fraction and less than 1" @@ -302,10 +302,7 @@ contract EpochRewards is * @param value The percentage of the total reward to be sent to the carbon offsetting partner. * @return True upon success. */ - function setCarbonOffsettingFund( - address partner, - uint256 value - ) public onlyOwner onlyL1 returns (bool) { + function setCarbonOffsettingFund(address partner, uint256 value) public onlyOwner returns (bool) { require( partner != carbonOffsettingPartner || value != carbonOffsettingFraction.unwrap(), "Partner and value must be different from existing carbon offsetting fund" @@ -322,7 +319,7 @@ contract EpochRewards is * @param value The percentage of floating Gold voting to target. * @return True upon success. */ - function setTargetVotingGoldFraction(uint256 value) public onlyOwner onlyL1 returns (bool) { + function setTargetVotingGoldFraction(uint256 value) public onlyOwner returns (bool) { require(value != targetVotingGoldFraction.unwrap(), "Target voting gold fraction unchanged"); require( value < FixidityLib.fixed1().unwrap(), @@ -338,7 +335,7 @@ contract EpochRewards is * @param value The value in Celo Dollars. * @return True upon success. */ - function setTargetValidatorEpochPayment(uint256 value) public onlyOwner onlyL1 returns (bool) { + function setTargetValidatorEpochPayment(uint256 value) public onlyOwner returns (bool) { require(value != targetValidatorEpochPayment, "Target validator epoch payment unchanged"); targetValidatorEpochPayment = value; emit TargetValidatorEpochPaymentSet(value); @@ -358,7 +355,7 @@ contract EpochRewards is uint256 max, uint256 underspendAdjustmentFactor, uint256 overspendAdjustmentFactor - ) public onlyOwner onlyL1 returns (bool) { + ) public onlyOwner returns (bool) { require( max != rewardsMultiplierParams.max.unwrap() || overspendAdjustmentFactor != rewardsMultiplierParams.adjustmentFactors.overspend.unwrap() || @@ -385,7 +382,7 @@ contract EpochRewards is function setTargetVotingYieldParameters( uint256 max, uint256 adjustmentFactor - ) public onlyOwner onlyL1 returns (bool) { + ) public onlyOwner returns (bool) { require( max != targetVotingYieldParams.max.unwrap() || adjustmentFactor != targetVotingYieldParams.adjustmentFactor.unwrap(), @@ -407,7 +404,7 @@ contract EpochRewards is * @param targetVotingYield The relative target block reward for voters. * @return True upon success. */ - function setTargetVotingYield(uint256 targetVotingYield) public onlyOwner onlyL1 returns (bool) { + function setTargetVotingYield(uint256 targetVotingYield) public onlyOwner returns (bool) { FixidityLib.Fraction memory target = FixidityLib.wrap(targetVotingYield); require( target.lte(targetVotingYieldParams.max), diff --git a/packages/protocol/contracts/governance/interfaces/IEpochRewards.sol b/packages/protocol/contracts/governance/interfaces/IEpochRewards.sol index 4cf586aa1d9..1c32d669933 100644 --- a/packages/protocol/contracts/governance/interfaces/IEpochRewards.sol +++ b/packages/protocol/contracts/governance/interfaces/IEpochRewards.sol @@ -11,4 +11,5 @@ interface IEpochRewards { function getCarbonOffsettingFraction() external view returns (uint256); function getTargetVotingGoldFraction() external view returns (uint256); function getRewardsMultiplier() external view returns (uint256); + function carbonOffsettingPartner() external view returns (address); } diff --git a/packages/protocol/migrationsConfig.js b/packages/protocol/migrationsConfig.js index 6baf43424a7..79045a72c5d 100644 --- a/packages/protocol/migrationsConfig.js +++ b/packages/protocol/migrationsConfig.js @@ -79,7 +79,6 @@ const DefaultConfig = { }, epochManager: { newEpochDuration: 100, - carbonOffsettingPartner: '0x0000000000000000000000000000000000000000', }, exchange: { spread: 5 / 1000, diff --git a/packages/protocol/migrations_sol/Migration.s.sol b/packages/protocol/migrations_sol/Migration.s.sol index 33202c48a9e..3b8d3474b71 100644 --- a/packages/protocol/migrations_sol/Migration.s.sol +++ b/packages/protocol/migrations_sol/Migration.s.sol @@ -960,10 +960,6 @@ contract Migration is Script, UsingRegistry, MigrationsConstants { } function migrateEpochManager(string memory json) public { - address carbonOffsettingPartner = abi.decode( - json.parseRaw(".epochManager.carbonOffsettingPartner"), - (address) - ); address newEpochDuration = abi.decode( json.parseRaw(".epochManager.newEpochDuration"), (address) @@ -974,9 +970,7 @@ contract Migration is Script, UsingRegistry, MigrationsConstants { abi.encodeWithSelector( IEpochManagerInitializer.initialize.selector, REGISTRY_ADDRESS, - newEpochDuration, - carbonOffsettingPartner, - registry.getAddressForString("EpochManagerEnabler") + newEpochDuration ) ); } diff --git a/packages/protocol/migrations_sol/MigrationL2.s.sol b/packages/protocol/migrations_sol/MigrationL2.s.sol index 6147a4c3507..dc8a2086de4 100644 --- a/packages/protocol/migrations_sol/MigrationL2.s.sol +++ b/packages/protocol/migrations_sol/MigrationL2.s.sol @@ -40,7 +40,9 @@ contract MigrationL2 is Script, MigrationsConstants, UsingRegistry { console.log("Initializing EpochManager system"); address[] memory firstElected = getValidators().getRegisteredValidators(); IEpochManager epochManager = getEpochManager(); - address epochManagerEnablerAddress = epochManager.epochManagerEnabler(); + address epochManagerEnablerAddress = registry.getAddressForOrDie( + EPOCH_MANAGER_ENABLER_REGISTRY_ID + ); IEpochManagerEnabler epochManagerEnabler = IEpochManagerEnabler(epochManagerEnablerAddress); epochManagerEnabler.initEpochManager(); diff --git a/packages/protocol/migrations_sol/migrationsConfig.json b/packages/protocol/migrations_sol/migrationsConfig.json index bd3e28e416a..7edaee535bf 100644 --- a/packages/protocol/migrations_sol/migrationsConfig.json +++ b/packages/protocol/migrations_sol/migrationsConfig.json @@ -132,8 +132,7 @@ "frozen": false }, "epochManager": { - "newEpochDuration": 86400, - "carbonOffsettingPartner": "0xD533Ca259b330c7A88f74E000a3FaEa2d63B7972" + "newEpochDuration": 86400 }, "random": { "randomnessBlockRetentionWindow": "720", diff --git a/packages/protocol/migrations_ts/26_103_epoch_manager.ts b/packages/protocol/migrations_ts/26_103_epoch_manager.ts index 6769b4ac5ed..4956ebcc139 100644 --- a/packages/protocol/migrations_ts/26_103_epoch_manager.ts +++ b/packages/protocol/migrations_ts/26_103_epoch_manager.ts @@ -1,21 +1,11 @@ import { SOLIDITY_08_PACKAGE } from '@celo/protocol/contractPackages' import { CeloContractName } from '@celo/protocol/lib/registry-utils' -import { deploymentForCoreContract, getProxiedContract } from '@celo/protocol/lib/web3-utils' +import { deploymentForCoreContract } from '@celo/protocol/lib/web3-utils' import { config } from '@celo/protocol/migrationsConfig' import { EpochManagerInstance } from 'types/08' const initializeArgs = async (): Promise => { - const epochManagerInitializer = await getProxiedContract( - CeloContractName.EpochManagerEnabler, - SOLIDITY_08_PACKAGE - ) - - return [ - config.registry.predeployedProxyAddress, - config.epochManager.newEpochDuration, - config.epochManager.carbonOffsettingPartner, - epochManagerInitializer.address, - ] + return [config.registry.predeployedProxyAddress, config.epochManager.newEpochDuration] } module.exports = deploymentForCoreContract( diff --git a/packages/protocol/releaseData/initializationData/release12.json b/packages/protocol/releaseData/initializationData/release12.json index 635895d47e0..cafed284047 100644 --- a/packages/protocol/releaseData/initializationData/release12.json +++ b/packages/protocol/releaseData/initializationData/release12.json @@ -1,6 +1,6 @@ { "CeloUnreleasedTreasure": ["0x000000000000000000000000000000000000ce10"], - "EpochManager": ["0x000000000000000000000000000000000000ce10", 86400, "0xD533Ca259b330c7A88f74E000a3FaEa2d63B7972", "0x0000000000000000000000000000000000000000"], + "EpochManager": ["0x000000000000000000000000000000000000ce10", 86400], "EpochManagerEnabler": ["0x000000000000000000000000000000000000ce10"], "ScoreManager": [], "FeeCurrencyDirectory": [] diff --git a/packages/protocol/test-sol/constants.sol b/packages/protocol/test-sol/constants.sol index 92f8422412a..379660a798d 100644 --- a/packages/protocol/test-sol/constants.sol +++ b/packages/protocol/test-sol/constants.sol @@ -25,6 +25,7 @@ contract TestConstants { string constant GovernanceContract = "Governance"; string constant EpochRewardsContract = "EpochRewards"; string constant EpochManagerContract = "EpochManager"; + string constant EpochManagerEnablerContract = "EpochManagerEnabler"; string constant ScoreManagerContract = "ScoreManager"; string constant ReserveContract = "Reserve"; string constant CeloUnreleasedTreasureContract = "CeloUnreleasedTreasure"; diff --git a/packages/protocol/test-sol/devchain/e2e/common/EpochManager.t.sol b/packages/protocol/test-sol/devchain/e2e/common/EpochManager.t.sol index 6f5b433830b..1e096cbf00f 100644 --- a/packages/protocol/test-sol/devchain/e2e/common/EpochManager.t.sol +++ b/packages/protocol/test-sol/devchain/e2e/common/EpochManager.t.sol @@ -41,7 +41,7 @@ contract E2E_EpochManager is Test, Devchain, Utils08, ECDSAHelper08 { uint256 totalVotes = election.getTotalVotes(); epochManagerOwner = Ownable(address(epochManager)).owner(); - epochManagerEnabler = epochManager.epochManagerEnabler(); + epochManagerEnabler = registry.getAddressForOrDie(EPOCH_MANAGER_ENABLER_REGISTRY_ID); firstElected = getValidators().getRegisteredValidators(); epochDuration = epochManager.epochDuration(); @@ -98,7 +98,7 @@ contract E2E_EpochManager_InitializeSystem is E2E_EpochManager { } function test_shouldRevert_WhenCalledByNonEnabler() public { - vm.expectRevert("msg.sender is not Initializer"); + vm.expectRevert("msg.sender is not Enabler"); epochManager.initializeSystem(1, 1, firstElected); } diff --git a/packages/protocol/test-sol/devchain/migration/Migration.t.sol b/packages/protocol/test-sol/devchain/migration/Migration.t.sol index 8ff3ffc2956..dfb87af1999 100644 --- a/packages/protocol/test-sol/devchain/migration/Migration.t.sol +++ b/packages/protocol/test-sol/devchain/migration/Migration.t.sol @@ -190,30 +190,6 @@ contract EpochManagerIntegrationTest is IntegrationTest, MigrationsConstants { epochManagerEnabler = IEpochManagerEnabler( registry.getAddressForStringOrDie("EpochManagerEnabler") ); - scoreManager = IScoreManager(registry.getAddressForStringOrDie("ScoreManager")); - election = IElection(registry.getAddressForStringOrDie("Election")); - celoUnreleasedTreasure = ICeloUnreleasedTreasure( - registry.getAddressForStringOrDie("CeloUnreleasedTreasure") - ); - - address scoreManagerOwner = scoreManager.owner(); - vm.startPrank(scoreManagerOwner); - - scoreManager.setGroupScore(groupList[0], groupScore[0]); - scoreManager.setGroupScore(groupList[1], groupScore[1]); - scoreManager.setGroupScore(groupList[2], groupScore[2]); - - scoreManager.setValidatorScore(validatorsList[0], validatorScore[0]); - scoreManager.setValidatorScore(validatorsList[1], validatorScore[1]); - scoreManager.setValidatorScore(validatorsList[2], validatorScore[2]); - scoreManager.setValidatorScore(validatorsList[3], validatorScore[3]); - scoreManager.setValidatorScore(validatorsList[4], validatorScore[4]); - scoreManager.setValidatorScore(validatorsList[5], validatorScore[5]); - - vm.stopPrank(); - - activateValidators(); - vm.deal(address(celoUnreleasedTreasure), 100_000_000 ether); } function activateValidators() public { @@ -233,18 +209,6 @@ contract EpochManagerIntegrationTest is IntegrationTest, MigrationsConstants { } } - function test_IsSetupCorrect() public { - assertEq( - registry.getAddressForStringOrDie("EpochManagerEnabler"), - epochManager.epochManagerEnabler() - ); - assertEq( - registry.getAddressForStringOrDie("EpochManagerEnabler"), - address(epochManagerEnabler) - ); - assertEq(address(epochManagerEnabler), epochManager.epochManagerEnabler()); - } - function test_Reverts_whenSystemNotInitialized() public { vm.expectRevert("Epoch system not initialized"); epochManager.startNextEpochProcess(); @@ -267,7 +231,7 @@ contract EpochManagerIntegrationTest is IntegrationTest, MigrationsConstants { function test_Reverts_whenAlreadyInitialized() public { _MockL2Migration(validatorsList); - vm.prank(address(0)); + vm.prank(address(epochManagerEnabler)); vm.expectRevert("Epoch system already initialized"); epochManager.initializeSystem(100, block.number, firstElected); } diff --git a/packages/protocol/test-sol/unit/common/EpochManager.t.sol b/packages/protocol/test-sol/unit/common/EpochManager.t.sol index 9de13060519..11c4d533f67 100644 --- a/packages/protocol/test-sol/unit/common/EpochManager.t.sol +++ b/packages/protocol/test-sol/unit/common/EpochManager.t.sol @@ -34,7 +34,6 @@ contract EpochManagerTest is Test, TestConstants, Utils08 { address communityRewardFund; address reserveAddress; address scoreManagerAddress; - address nonOwner; uint256 firstEpochNumber = 100; uint256 firstEpochBlock = 100; @@ -70,7 +69,6 @@ contract EpochManagerTest is Test, TestConstants, Utils08 { epochManagerEnabler = actor("epochManagerEnabler"); carbonOffsettingPartner = actor("carbonOffsettingPartner"); communityRewardFund = actor("communityRewardFund"); - nonOwner = actor("nonOwner"); deployCodeTo("MockRegistry.sol", abi.encode(false), REGISTRY_ADDRESS); deployCodeTo("ScoreManager.sol", abi.encode(false), scoreManagerAddress); @@ -79,6 +77,7 @@ contract EpochManagerTest is Test, TestConstants, Utils08 { scoreManager = ScoreManager(scoreManagerAddress); registry.setAddressFor(EpochManagerContract, address(epochManager)); + registry.setAddressFor(EpochManagerEnablerContract, epochManagerEnabler); registry.setAddressFor(SortedOraclesContract, address(sortedOracles)); registry.setAddressFor(GovernanceContract, communityRewardFund); registry.setAddressFor(EpochRewardsContract, address(epochRewards)); @@ -100,12 +99,7 @@ contract EpochManagerTest is Test, TestConstants, Utils08 { scoreManager.setValidatorScore(actor("validator1"), 1); - epochManager.initialize( - REGISTRY_ADDRESS, - epochDuration, - carbonOffsettingPartner, - epochManagerEnabler - ); + epochManager.initialize(REGISTRY_ADDRESS, 10); blockTravel(vm, firstEpochBlock); } @@ -123,14 +117,12 @@ contract EpochManagerTest is Test, TestConstants, Utils08 { contract EpochManagerTest_initialize is EpochManagerTest { function test_initialize() public virtual { assertEq(address(epochManager.registry()), REGISTRY_ADDRESS); - assertEq(epochManager.epochDuration(), epochDuration); - assertEq(epochManager.carbonOffsettingPartner(), carbonOffsettingPartner); - assertEq(epochManager.epochManagerEnabler(), epochManagerEnabler); + assertEq(epochManager.epochDuration(), 10); } function test_Reverts_WhenAlreadyInitialized() public virtual { vm.expectRevert("contract already initialized"); - epochManager.initialize(REGISTRY_ADDRESS, 10, carbonOffsettingPartner, epochManagerEnabler); + epochManager.initialize(REGISTRY_ADDRESS, 10); } } @@ -144,7 +136,7 @@ contract EpochManagerTest_initializeSystem is EpochManagerTest { uint256 _startTimestamp, uint256 _currentRewardsBlock ) = epochManager.getCurrentEpoch(); - assertEq(epochManager.epochManagerEnabler(), address(0)); + assertGt(epochManager.getElected().length, 0); assertEq(epochManager.firstKnownEpoch(), firstEpochNumber); assertEq(_firstEpochBlock, firstEpochBlock); assertEq(_lastEpochBlock, 0); @@ -156,13 +148,13 @@ contract EpochManagerTest_initializeSystem is EpochManagerTest { function test_Reverts_processCannotBeStartedAgain() public virtual { vm.prank(epochManagerEnabler); epochManager.initializeSystem(firstEpochNumber, firstEpochBlock, firstElected); - vm.prank(address(0)); + vm.prank(epochManagerEnabler); vm.expectRevert("Epoch system already initialized"); epochManager.initializeSystem(firstEpochNumber, firstEpochBlock, firstElected); } function test_Reverts_WhenSystemInitializedByOtherContract() public virtual { - vm.expectRevert("msg.sender is not Initializer"); + vm.expectRevert("msg.sender is not Enabler"); epochManager.initializeSystem(firstEpochNumber, firstEpochBlock, firstElected); } } diff --git a/packages/protocol/test-sol/unit/governance/network/EpochRewards.t.sol b/packages/protocol/test-sol/unit/governance/network/EpochRewards.t.sol index 7c8a848f26d..69acd19c383 100644 --- a/packages/protocol/test-sol/unit/governance/network/EpochRewards.t.sol +++ b/packages/protocol/test-sol/unit/governance/network/EpochRewards.t.sol @@ -193,10 +193,11 @@ contract EpochRewardsTest_setTargetVotingGoldFraction is EpochRewardsTest { epochRewards.setTargetVotingGoldFraction(targetVotingGoldFraction); } - function test_Reverts_WhenCalledOnL2() public { + function test_Emits_WhenCalledOnL2() public { _whenL2(); - vm.expectRevert("This method is no longer supported in L2."); - epochRewards.setTargetVotingGoldFraction(targetVotingGoldFraction); + vm.expectEmit(true, true, true, true); + emit TargetVotingGoldFractionSet(newFraction); + epochRewards.setTargetVotingGoldFraction(newFraction); } } @@ -238,10 +239,11 @@ contract EpochRewardsTest_setCommunityRewardFraction is EpochRewardsTest { epochRewards.setCommunityRewardFraction(communityRewardFraction); } - function test_Reverts_WhenCalledOnL2() public { + function test_Emits_WhenCalledOnL2() public { _whenL2(); - vm.expectRevert("This method is no longer supported in L2."); - epochRewards.setCommunityRewardFraction(communityRewardFraction); + vm.expectEmit(true, true, true, true); + emit CommunityRewardFractionSet(newFraction); + epochRewards.setCommunityRewardFraction(newFraction); } } @@ -274,10 +276,11 @@ contract EpochRewardsTest_setTargetValidatorEpochPayment is EpochRewardsTest { epochRewards.setTargetValidatorEpochPayment(targetValidatorEpochPayment); } - function test_Reverts_WhenCalledOnL2() public { + function test_Emits_WhenCalledOnL2() public { _whenL2(); - vm.expectRevert("This method is no longer supported in L2."); - epochRewards.setTargetValidatorEpochPayment(targetValidatorEpochPayment); + vm.expectEmit(true, true, true, true); + emit TargetValidatorEpochPaymentSet(newPayment); + epochRewards.setTargetValidatorEpochPayment(newPayment); } } @@ -332,12 +335,17 @@ contract EpochRewardsTest_setRewardsMultiplierParameters is EpochRewardsTest { ); } - function test_Reverts_WhenCalledOnL2() public { + function test_Emits_WhenCalledOnL2() public { _whenL2(); - vm.expectRevert("This method is no longer supported in L2."); + vm.expectEmit(true, true, true, true); + emit RewardsMultiplierParametersSet( + rewardsMultiplierMax, + newRewardsMultiplierAdjustmentsUnderspend, + rewardsMultiplierAdjustmentsOverspend + ); epochRewards.setRewardsMultiplierParameters( rewardsMultiplierMax, - rewardsMultiplierAdjustmentsUnderspend, + newRewardsMultiplierAdjustmentsUnderspend, rewardsMultiplierAdjustmentsOverspend ); } @@ -388,9 +396,13 @@ contract EpochRewardsTest_setTargetVotingYieldParameters is EpochRewardsTest { ); } - function test_Reverts_WhenCalledOnL2() public { + function test_Emits_WhenCalledOnL2() public { _whenL2(); - vm.expectRevert("This method is no longer supported in L2."); + vm.expectEmit(true, true, true, true); + emit TargetVotingYieldParametersSet( + newTargetVotingYieldParamsMax, + newTargetVotingYieldParamsAdjustmentFactor + ); epochRewards.setTargetVotingYieldParameters( newTargetVotingYieldParamsMax, newTargetVotingYieldParamsAdjustmentFactor @@ -420,9 +432,10 @@ contract EpochRewardsTest_setTargetVotingYield is EpochRewardsTest { epochRewards.setTargetVotingYield(newTargetVotingYieldParamsInitial); } - function test_Reverts_WhenCalledOnL2() public { + function test_Emits_WhenCalledOnL2() public { _whenL2(); - vm.expectRevert("This method is no longer supported in L2."); + vm.expectEmit(true, true, true, true); + emit TargetVotingYieldSet(newTargetVotingYieldParamsInitial); epochRewards.setTargetVotingYield(newTargetVotingYieldParamsInitial); } } @@ -446,7 +459,6 @@ contract EpochRewardsTest_getTargetVoterRewards is EpochRewardsTest { } contract EpochRewardsTest_getTargetTotalEpochPaymentsInGold is EpochRewardsTest { - // TODO(soloseng): add L2 test case that uses EpochManager function test_ShouldgetTargetTotalEpochPaymentsInGold_WhenExchangeRateIsSet() public { uint256 numberValidators = 100; epochRewards.setNumberValidatorsInCurrentSet(numberValidators); @@ -457,7 +469,6 @@ contract EpochRewardsTest_getTargetTotalEpochPaymentsInGold is EpochRewardsTest } contract EpochRewardsTest_getRewardsMultiplier is EpochRewardsTest { - // TODO(soloseng): add L2 test case using EpochManager uint256 constant timeDelta = YEAR * 10; uint256 expectedTargetTotalSupply; uint256 expectedTargetRemainingSupply; @@ -504,7 +515,6 @@ contract EpochRewardsTest_getRewardsMultiplier is EpochRewardsTest { } contract EpochRewardsTest_updateTargetVotingYield is EpochRewardsTest { - //TODO(soloseng): add L2 test case that uses epochManager uint256 constant totalSupply = 6000000 ether; uint256 constant reserveBalance = 1000000 ether; uint256 constant floatingSupply = totalSupply - reserveBalance; @@ -761,7 +771,7 @@ contract EpochRewardsTest_updateTargetVotingYield is EpochRewardsTest { epochRewards.updateTargetVotingYield(); } } -// TODO(soloseng): add L2 test case that uses the result from epochManager + contract EpochRewardsTest_WhenThereAreActiveVotesAStableTokenExchangeRateIsSetAndTheActualRemainingSupplyIs10pMoreThanTheTargetRemainingSupplyAfterRewards_calculateTargetEpochRewards is EpochRewardsTest { From 59ea380e4887dd688b5f53c3373021c87213b109 Mon Sep 17 00:00:00 2001 From: Martin Date: Wed, 18 Sep 2024 16:10:13 +0200 Subject: [PATCH 53/59] Implement sending of allocated validator payments (#11197) --- .../contracts-0.8/common/EpochManager.sol | 72 ++++++++ .../common/interfaces/IStableToken.sol | 2 + .../common/mocks/EpochManager_WithMocks.sol | 10 ++ .../common/mocks/MockAccounts.sol | 43 +++++ .../governance/test/IMockValidators.sol | 61 +++++++ .../governance/test/MockValidators.sol | 23 +++ .../test-sol/unit/common/EpochManager.t.sol | 155 +++++++++++++++++- 7 files changed, 363 insertions(+), 3 deletions(-) create mode 100644 packages/protocol/contracts-0.8/common/mocks/EpochManager_WithMocks.sol create mode 100644 packages/protocol/contracts-0.8/common/mocks/MockAccounts.sol create mode 100644 packages/protocol/contracts-0.8/governance/test/IMockValidators.sol diff --git a/packages/protocol/contracts-0.8/common/EpochManager.sol b/packages/protocol/contracts-0.8/common/EpochManager.sol index bfaaa428e31..a22603b65b7 100644 --- a/packages/protocol/contracts-0.8/common/EpochManager.sol +++ b/packages/protocol/contracts-0.8/common/EpochManager.sol @@ -8,6 +8,7 @@ import "./interfaces/IOracle.sol"; import "./interfaces/IStableToken.sol"; import "../common/UsingRegistry.sol"; +import "../../contracts/common/FixidityLib.sol"; import "../../contracts/common/Initializable.sol"; import "../../contracts/common/interfaces/IEpochManager.sol"; import "../../contracts/common/interfaces/ICeloVersionedContract.sol"; @@ -20,6 +21,8 @@ contract EpochManager is ReentrancyGuard, ICeloVersionedContract { + using FixidityLib for FixidityLib.Fraction; + struct Epoch { uint256 firstBlock; uint256 lastBlock; @@ -73,6 +76,22 @@ contract EpochManager is */ event EpochProcessingEnded(uint256 indexed epochNumber); + /** + * @notice Emitted when an epoch payment is sent. + * @param validator Address of the validator. + * @param validatorPayment Amount of cUSD sent to the validator. + * @param group Address of the validator's group. + * @param groupPayment Amount of cUSD sent to the group. + */ + event ValidatorEpochPaymentDistributed( + address indexed validator, + uint256 validatorPayment, + address indexed group, + uint256 groupPayment, + address indexed beneficiary, + uint256 delegatedPayment + ); + modifier onlyEpochManagerEnabler() { require( msg.sender == registry.getAddressForOrDie(EPOCH_MANAGER_ENABLER_REGISTRY_ID), @@ -335,4 +354,57 @@ contract EpochManager is CELOequivalent ); } + + /** + * @notice Sends the allocated epoch payment to a validator, their group, and + * delegation beneficiary. + * @param validator Account of the validator. + */ + function sendValidatorPayment(address validator) external { + IAccounts accounts = IAccounts(getAccounts()); + address signer = accounts.getValidatorSigner(validator); + + FixidityLib.Fraction memory totalPayment = FixidityLib.newFixed( + validatorPendingPayments[signer] + ); + + IValidators validators = getValidators(); + address group = validators.getValidatorsGroup(validator); + (, uint256 commissionUnwrapped, , , , , ) = validators.getValidatorGroup(group); + + uint256 groupPayment = totalPayment.multiply(FixidityLib.wrap(commissionUnwrapped)).fromFixed(); + FixidityLib.Fraction memory remainingPayment = FixidityLib.newFixed( + totalPayment.fromFixed() - groupPayment + ); + (address beneficiary, uint256 delegatedFraction) = getAccounts().getPaymentDelegation( + validator + ); + uint256 delegatedPayment = remainingPayment + .multiply(FixidityLib.wrap(delegatedFraction)) + .fromFixed(); + uint256 validatorPayment = remainingPayment.fromFixed() - delegatedPayment; + + IStableToken stableToken = IStableToken(getStableToken()); + + if (validatorPayment > 0) { + require(stableToken.transfer(validator, validatorPayment), "transfer failed to validator"); + } + + if (groupPayment > 0) { + require(stableToken.transfer(group, groupPayment), "transfer failed to validator group"); + } + + if (delegatedPayment > 0) { + require(stableToken.transfer(beneficiary, delegatedPayment), "transfer failed to delegatee"); + } + + emit ValidatorEpochPaymentDistributed( + validator, + validatorPayment, + group, + groupPayment, + beneficiary, + delegatedPayment + ); + } } diff --git a/packages/protocol/contracts-0.8/common/interfaces/IStableToken.sol b/packages/protocol/contracts-0.8/common/interfaces/IStableToken.sol index 8d5bd9cec00..1fa10340fec 100644 --- a/packages/protocol/contracts-0.8/common/interfaces/IStableToken.sol +++ b/packages/protocol/contracts-0.8/common/interfaces/IStableToken.sol @@ -6,6 +6,8 @@ pragma solidity >=0.8.7 <0.8.20; * absence of interface inheritance is intended as a companion to IERC20.sol and ICeloToken.sol. */ interface IStableToken { + function transfer(address, uint256) external returns (bool); + function mint(address, uint256) external returns (bool); function burn(uint256) external returns (bool); diff --git a/packages/protocol/contracts-0.8/common/mocks/EpochManager_WithMocks.sol b/packages/protocol/contracts-0.8/common/mocks/EpochManager_WithMocks.sol new file mode 100644 index 00000000000..9ddda4aba92 --- /dev/null +++ b/packages/protocol/contracts-0.8/common/mocks/EpochManager_WithMocks.sol @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity >=0.8.7 <0.8.20; + +import "../EpochManager.sol"; + +contract EpochManager_WithMocks is EpochManager(true) { + function _setPaymentAllocation(address validator, uint256 amount) external { + validatorPendingPayments[validator] = amount; + } +} diff --git a/packages/protocol/contracts-0.8/common/mocks/MockAccounts.sol b/packages/protocol/contracts-0.8/common/mocks/MockAccounts.sol new file mode 100644 index 00000000000..e7054bf39e8 --- /dev/null +++ b/packages/protocol/contracts-0.8/common/mocks/MockAccounts.sol @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity >=0.8.7 <0.8.20; + +import "../../../contracts/common/FixidityLib.sol"; + +contract MockAccounts { + using FixidityLib for FixidityLib.Fraction; + + struct PaymentDelegation { + // Address that should receive a fraction of validator payments. + address beneficiary; + // Fraction of payment to delegate to `beneficiary`. + FixidityLib.Fraction fraction; + } + + mapping(address => PaymentDelegation) delegations; + mapping(address => address) accountToSigner; + + function setPaymentDelegationFor( + address validator, + address beneficiary, + uint256 fraction + ) public { + delegations[validator] = PaymentDelegation(beneficiary, FixidityLib.wrap(fraction)); + } + + function deletePaymentDelegationFor(address validator) public { + delete delegations[validator]; + } + + function getPaymentDelegation(address account) external view returns (address, uint256) { + PaymentDelegation storage delegation = delegations[account]; + return (delegation.beneficiary, delegation.fraction.unwrap()); + } + + function setValidatorSigner(address account, address signer) external { + accountToSigner[account] = signer; + } + + function getValidatorSigner(address account) external returns (address) { + return accountToSigner[account]; + } +} diff --git a/packages/protocol/contracts-0.8/governance/test/IMockValidators.sol b/packages/protocol/contracts-0.8/governance/test/IMockValidators.sol new file mode 100644 index 00000000000..c20e1b06cfd --- /dev/null +++ b/packages/protocol/contracts-0.8/governance/test/IMockValidators.sol @@ -0,0 +1,61 @@ +pragma solidity >=0.8.7 <0.8.20; + +interface IMockValidators { + function isValidator(address) external returns (bool); + function isValidatorGroup(address) external returns (bool); + + function updateEcdsaPublicKey(address, address, bytes calldata) external returns (bool); + + function updatePublicKeys( + address, + address, + bytes calldata, + bytes calldata, + bytes calldata + ) external returns (bool); + + function setValidator(address) external; + + function setValidatorGroup(address group) external; + + function affiliate(address group) external returns (bool); + + function setDoesNotMeetAccountLockedGoldRequirements(address account) external; + + function setNumRegisteredValidators(uint256 value) external; + + function setMembers(address group, address[] calldata _members) external; + + function setCommission(address group, uint256 commission) external; + + function setAccountLockedGoldRequirement(address account, uint256 value) external; + + function halveSlashingMultiplier(address) external; + + function forceDeaffiliateIfValidator(address validator) external; + + function getTopGroupValidators(address group, uint256 n) external view returns (address[] memory); + + function getValidatorGroup( + address + ) + external + view + returns (address[] memory, uint256, uint256, uint256, uint256[] memory, uint256, uint256); + + function getValidatorGroupSlashingMultiplier(address) external view returns (uint256); + + function meetsAccountLockedGoldRequirements(address account) external view returns (bool); + + function getNumRegisteredValidators() external view returns (uint256); + + function getAccountLockedGoldRequirement(address account) external view returns (uint256); + + function calculateGroupEpochScore(uint256[] calldata uptimes) external view returns (uint256); + + function getGroupsNumMembers(address[] calldata groups) external view returns (uint256[] memory); + + function groupMembershipInEpoch(address addr, uint256, uint256) external view returns (address); + + function getGroupNumMembers(address group) external view returns (uint256); +} diff --git a/packages/protocol/contracts/governance/test/MockValidators.sol b/packages/protocol/contracts/governance/test/MockValidators.sol index 4cc03323a61..85bc1dcf7f7 100644 --- a/packages/protocol/contracts/governance/test/MockValidators.sol +++ b/packages/protocol/contracts/governance/test/MockValidators.sol @@ -22,6 +22,7 @@ contract MockValidators is IsL2Check { mapping(address => bool) private doesNotMeetAccountLockedGoldRequirements; mapping(address => address[]) private members; mapping(address => address) private affiliations; + mapping(address => uint256) private commissions; uint256 private numRegisteredValidators; function updateEcdsaPublicKey(address, address, bytes calldata) external returns (bool) { @@ -48,6 +49,10 @@ contract MockValidators is IsL2Check { isValidatorGroup[group] = true; } + function getValidatorsGroup(address validator) external returns (address) { + return affiliations[validator]; + } + function affiliate(address group) external returns (bool) { allowOnlyL1(); affiliations[msg.sender] = group; @@ -64,6 +69,13 @@ contract MockValidators is IsL2Check { function setMembers(address group, address[] calldata _members) external { members[group] = _members; + for (uint256 i; i < _members.length; i++) { + affiliations[_members[i]] = group; + } + } + + function setCommission(address group, uint256 commission) external { + commissions[group] = commission; } function setAccountLockedGoldRequirement(address account, uint256 value) external { @@ -90,6 +102,17 @@ contract MockValidators is IsL2Check { return validators; } + function getValidatorGroup( + address group + ) + external + view + returns (address[] memory, uint256, uint256, uint256, uint256[] memory, uint256, uint256) + { + uint256[] memory sizeHistory; + return (members[group], commissions[group], 0, 0, sizeHistory, 0, 0); + } + function getValidatorGroupSlashingMultiplier(address) external view returns (uint256) { allowOnlyL1(); return FIXED1_UINT; diff --git a/packages/protocol/test-sol/unit/common/EpochManager.t.sol b/packages/protocol/test-sol/unit/common/EpochManager.t.sol index 11c4d533f67..1b2074f4654 100644 --- a/packages/protocol/test-sol/unit/common/EpochManager.t.sol +++ b/packages/protocol/test-sol/unit/common/EpochManager.t.sol @@ -2,7 +2,7 @@ pragma solidity >=0.8.7 <0.8.20; import "celo-foundry-8/Test.sol"; -import "@celo-contracts-8/common/EpochManager.sol"; +import "@celo-contracts-8/common/mocks/EpochManager_WithMocks.sol"; import "@celo-contracts-8/stability/test/MockStableToken.sol"; import "@celo-contracts-8/common/test/MockCeloToken.sol"; import "@celo-contracts/common/interfaces/ICeloToken.sol"; @@ -17,12 +17,16 @@ import "@celo-contracts/stability/test/MockSortedOracles.sol"; import "@celo-contracts/common/interfaces/IRegistry.sol"; +import { IMockValidators } from "@celo-contracts-8/governance/test/IMockValidators.sol"; + import { EpochRewardsMock08 } from "@celo-contracts-8/governance/test/EpochRewardsMock.sol"; + +import { MockAccounts } from "@celo-contracts-8/common/mocks/MockAccounts.sol"; import { ValidatorsMock } from "@test-sol/unit/governance/validators/mocks/ValidatorsMock.sol"; import { MockCeloUnreleasedTreasure } from "@celo-contracts-8/common/test/MockCeloUnreleasedTreasure.sol"; contract EpochManagerTest is Test, TestConstants, Utils08 { - EpochManager epochManager; + EpochManager_WithMocks epochManager; MockSortedOracles sortedOracles; MockStableToken08 stableToken; @@ -48,10 +52,19 @@ contract EpochManagerTest is Test, TestConstants, Utils08 { uint256 celoAmountForRate = 1e24; uint256 stableAmountForRate = 2 * celoAmountForRate; + event ValidatorEpochPaymentDistributed( + address indexed validator, + uint256 validatorPayment, + address indexed group, + uint256 groupPayment, + address indexed beneficiary, + uint256 delegatedPayment + ); + event EpochProcessingStarted(uint256 indexed epochNumber); function setUp() public virtual { - epochManager = new EpochManager(true); + epochManager = new EpochManager_WithMocks(); sortedOracles = new MockSortedOracles(); epochRewards = new EpochRewardsMock08(); validators = new ValidatorsMock(); @@ -232,3 +245,139 @@ contract EpochManagerTest_startNextEpochProcess is EpochManagerTest { assertEq(reserveBalanceAfter, reserveBalanceBefore + 4); } } + +contract EpochManagerTest_sendValidatorPayment is EpochManagerTest { + address group = actor("group"); + address validator1 = actor("validator1"); + address signer1 = actor("signer1"); + address validator2 = actor("validator2"); + address signer2 = actor("signer2"); + address beneficiary = actor("beneficiary"); + + uint256 paymentAmount = 4 ether; + uint256 quarterOfPayment = paymentAmount / 4; + uint256 halfOfPayment = paymentAmount / 2; + uint256 threeQuartersOfPayment = (paymentAmount / 4) * 3; + uint256 twentyFivePercent = 250000000000000000000000; + uint256 fiftyPercent = 500000000000000000000000; + + uint256 epochManagerBalanceBefore; + + // TODO: unify mocks + IMockValidators mockValidators = IMockValidators(actor("MockValidators05")); + + MockAccounts accounts; + + function setUp() public override { + super.setUp(); + + deployCodeTo("MockValidators.sol", abi.encode(false), address(mockValidators)); + registry.setAddressFor(ValidatorsContract, address(mockValidators)); + + accounts = new MockAccounts(); + registry.setAddressFor(AccountsContract, address(accounts)); + + mockValidators.setValidatorGroup(group); + mockValidators.setValidator(validator1); + accounts.setValidatorSigner(validator1, signer1); + mockValidators.setValidator(validator2); + accounts.setValidatorSigner(validator2, signer2); + + address[] memory members = new address[](3); + members[0] = validator1; + members[1] = validator2; + mockValidators.setMembers(group, members); + + stableToken.mint(address(epochManager), paymentAmount * 2); + epochManagerBalanceBefore = stableToken.balanceOf(address(epochManager)); + epochManager._setPaymentAllocation(signer1, paymentAmount); + } + + function test_sendsCUsdFromEpochManagerToValidator() public { + epochManager.sendValidatorPayment(validator1); + + uint256 validatorBalanceAfter = stableToken.balanceOf(validator1); + uint256 epochManagerBalanceAfter = stableToken.balanceOf(address(epochManager)); + + assertEq(validatorBalanceAfter, paymentAmount); + assertEq(epochManagerBalanceAfter, epochManagerBalanceBefore - paymentAmount); + } + + function test_sendsCUsdFromEpochManagerToValidatorAndGroup() public { + mockValidators.setCommission(group, twentyFivePercent); + + epochManager.sendValidatorPayment(validator1); + + uint256 validatorBalanceAfter = stableToken.balanceOf(validator1); + uint256 groupBalanceAfter = stableToken.balanceOf(group); + uint256 epochManagerBalanceAfter = stableToken.balanceOf(address(epochManager)); + + assertEq(validatorBalanceAfter, threeQuartersOfPayment); + assertEq(groupBalanceAfter, quarterOfPayment); + assertEq(epochManagerBalanceAfter, epochManagerBalanceBefore - paymentAmount); + } + + function test_sendsCUsdFromEpochManagerToValidatorAndBeneficiary() public { + accounts.setPaymentDelegationFor(validator1, beneficiary, twentyFivePercent); + + epochManager.sendValidatorPayment(validator1); + + uint256 validatorBalanceAfter = stableToken.balanceOf(validator1); + uint256 beneficiaryBalanceAfter = stableToken.balanceOf(beneficiary); + uint256 epochManagerBalanceAfter = stableToken.balanceOf(address(epochManager)); + + assertEq(validatorBalanceAfter, threeQuartersOfPayment); + assertEq(beneficiaryBalanceAfter, quarterOfPayment); + assertEq(epochManagerBalanceAfter, epochManagerBalanceBefore - paymentAmount); + } + + function test_sendsCUsdFromEpochManagerToValidatorAndGroupAndBeneficiary() public { + mockValidators.setCommission(group, fiftyPercent); + accounts.setPaymentDelegationFor(validator1, beneficiary, fiftyPercent); + + epochManager.sendValidatorPayment(validator1); + + uint256 validatorBalanceAfter = stableToken.balanceOf(validator1); + uint256 groupBalanceAfter = stableToken.balanceOf(group); + uint256 beneficiaryBalanceAfter = stableToken.balanceOf(beneficiary); + uint256 epochManagerBalanceAfter = stableToken.balanceOf(address(epochManager)); + + assertEq(validatorBalanceAfter, quarterOfPayment); + assertEq(groupBalanceAfter, halfOfPayment); + assertEq(beneficiaryBalanceAfter, quarterOfPayment); + assertEq(epochManagerBalanceAfter, epochManagerBalanceBefore - paymentAmount); + } + + function test_emitsAValidatorEpochPaymentDistributedEvent() public { + mockValidators.setCommission(group, fiftyPercent); + accounts.setPaymentDelegationFor(validator1, beneficiary, fiftyPercent); + + vm.expectEmit(true, true, true, true, address(epochManager)); + emit ValidatorEpochPaymentDistributed( + validator1, + quarterOfPayment, + group, + halfOfPayment, + beneficiary, + quarterOfPayment + ); + epochManager.sendValidatorPayment(validator1); + } + + function test_doesNothingIfNotAllocated() public { + mockValidators.setCommission(group, fiftyPercent); + accounts.setPaymentDelegationFor(validator2, beneficiary, fiftyPercent); + + epochManager.sendValidatorPayment(validator2); + + uint256 validatorBalanceAfter = stableToken.balanceOf(validator1); + uint256 groupBalanceAfter = stableToken.balanceOf(group); + uint256 beneficiaryBalanceAfter = stableToken.balanceOf(beneficiary); + uint256 epochManagerBalanceAfter = stableToken.balanceOf(address(epochManager)); + + assertEq(validatorBalanceAfter, 0); + assertEq(groupBalanceAfter, 0); + assertEq(beneficiaryBalanceAfter, 0); + assertEq(epochManagerBalanceAfter, epochManagerBalanceBefore); + } +} From cfe3b30f1be01483d22d8ab481fcc0a528feb4a8 Mon Sep 17 00:00:00 2001 From: pahor167 <47992132+pahor167@users.noreply.github.com> Date: Wed, 18 Sep 2024 16:40:36 +0200 Subject: [PATCH 54/59] EpochManager fixes (#11208) * truffle build fix * build fix * PR comments * prettify * rename of registerValidator overload * bug fix * extensing epochManager e2e test * yarn lint --- .../contracts-0.8/common/EpochManager.sol | 8 +++++-- .../contracts-0.8/governance/Validators.sol | 2 +- .../contracts/governance/Election.sol | 2 +- .../governance/interfaces/IElection.sol | 2 +- .../governance/interfaces/IValidators.sol | 2 +- .../protocol/scripts/foundry/constants.sh | 2 +- .../create_and_migrate_anvil_l2_devchain.sh | 5 +++-- .../devchain/e2e/common/EpochManager.t.sol | 21 ++++++++++++++++++- .../governance/validators/Validators.t.sol | 18 ++++++++-------- 9 files changed, 43 insertions(+), 19 deletions(-) diff --git a/packages/protocol/contracts-0.8/common/EpochManager.sol b/packages/protocol/contracts-0.8/common/EpochManager.sol index a22603b65b7..24c5a9c0e9e 100644 --- a/packages/protocol/contracts-0.8/common/EpochManager.sol +++ b/packages/protocol/contracts-0.8/common/EpochManager.sol @@ -205,7 +205,7 @@ contract EpochManager is epochProcessing.toProcessGroups++; 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( + uint256 epochRewards = getElection().getGroupEpochRewardsBasedOnScore( group, epochProcessing.totalRewardsVoter, groupScore @@ -229,7 +229,7 @@ contract EpochManager is greaters[i] ); - // by doing this, we avoid processing a group twice + epochProcessing.toProcessGroups = 0; delete processedGroups[groups[i]]; } getCeloUnreleasedTreasure().release( @@ -340,6 +340,10 @@ contract EpochManager is validatorPendingPayments[elected[i]] += validatorReward; totalRewards += validatorReward; } + if (totalRewards == 0) { + return; + } + // Mint all cUSD required for payment and the corresponding CELO validators.mintStableToEpochManager(totalRewards); // this should have a setter for the oracle. diff --git a/packages/protocol/contracts-0.8/governance/Validators.sol b/packages/protocol/contracts-0.8/governance/Validators.sol index f6cc16defa1..7e45131b4b4 100644 --- a/packages/protocol/contracts-0.8/governance/Validators.sol +++ b/packages/protocol/contracts-0.8/governance/Validators.sol @@ -284,7 +284,7 @@ contract Validators is * @dev Fails if the account is already a validator or validator group. * @dev Fails if the account does not have sufficient Locked Gold. */ - function registerValidator( + function registerValidatorNoBls( bytes calldata ecdsaPublicKey ) external nonReentrant onlyL2 returns (bool) { address account = getAccounts().validatorSignerToAccount(msg.sender); diff --git a/packages/protocol/contracts/governance/Election.sol b/packages/protocol/contracts/governance/Election.sol index 91e022d0979..0fed2e5efa0 100644 --- a/packages/protocol/contracts/governance/Election.sol +++ b/packages/protocol/contracts/governance/Election.sol @@ -590,7 +590,7 @@ contract Election is * @return The amount of rewards that voters for `group` are due at the end of an epoch. * @dev Eligible groups that have received their maximum number of votes cannot receive more. */ - function getGroupEpochRewards( + function getGroupEpochRewardsBasedOnScore( address group, uint256 totalEpochRewards, uint256 groupScore diff --git a/packages/protocol/contracts/governance/interfaces/IElection.sol b/packages/protocol/contracts/governance/interfaces/IElection.sol index aceb3d1ba93..74b6dd7b6bc 100644 --- a/packages/protocol/contracts/governance/interfaces/IElection.sol +++ b/packages/protocol/contracts/governance/interfaces/IElection.sol @@ -49,7 +49,7 @@ interface IElection { uint256, uint256[] calldata ) external view returns (uint256); - function getGroupEpochRewards( + function getGroupEpochRewardsBasedOnScore( address group, uint256 totalEpochRewards, uint256 groupScore diff --git a/packages/protocol/contracts/governance/interfaces/IValidators.sol b/packages/protocol/contracts/governance/interfaces/IValidators.sol index e7b2db4953a..af2d2e8051e 100644 --- a/packages/protocol/contracts/governance/interfaces/IValidators.sol +++ b/packages/protocol/contracts/governance/interfaces/IValidators.sol @@ -7,7 +7,7 @@ interface IValidators { bytes calldata, bytes calldata ) external returns (bool); - function registerValidator(bytes calldata ecdsaPublicKey) external returns (bool); + function registerValidatorNoBls(bytes calldata ecdsaPublicKey) external returns (bool); function deregisterValidator(uint256) external returns (bool); function affiliate(address) external returns (bool); function deaffiliate() external returns (bool); diff --git a/packages/protocol/scripts/foundry/constants.sh b/packages/protocol/scripts/foundry/constants.sh index d292b0daf47..74681b59127 100755 --- a/packages/protocol/scripts/foundry/constants.sh +++ b/packages/protocol/scripts/foundry/constants.sh @@ -40,7 +40,7 @@ export CARBON_OFFSETTING_FRACTION="10000000000000000000" # 0.001 in fixidity for export REGISTRY_STORAGE_LOCATION="0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103" # Position is bytes32(uint256(keccak256("eip1967.proxy.admin")) - 1); GOLD_TOKEN_CELO_SUPPLY_CAP=1000000000 # `GoldToken.CELO_SUPPLY_CAP()` GOLD_TOKEN_TOTAL_SUPPLY=700000000 # Arbitrary amount chosen to be approximately equal to `GoldToken.totalSupply()` on the L1 Mainnet (695,313,643 CELO as of this commit). -export CELO_DISTRIBUTION_SCHEDULE_INITIAL_BALANCE="$(($GOLD_TOKEN_CELO_SUPPLY_CAP - $GOLD_TOKEN_TOTAL_SUPPLY))" # During the real L2 genesis, the VM will calculate and set an appropriate balance. +export CELO_UNRELEASED_TREASURE_INITIAL_BALANCE="$(($GOLD_TOKEN_CELO_SUPPLY_CAP - $GOLD_TOKEN_TOTAL_SUPPLY))" # During the real L2 genesis, the VM will calculate and set an appropriate balance. # Contract libraries export LIBRARIES_PATH=("contracts/common/linkedlists/AddressSortedLinkedListWithMedian.sol:AddressSortedLinkedListWithMedian" diff --git a/packages/protocol/scripts/foundry/create_and_migrate_anvil_l2_devchain.sh b/packages/protocol/scripts/foundry/create_and_migrate_anvil_l2_devchain.sh index 869397c2b39..0a1a354a0a0 100755 --- a/packages/protocol/scripts/foundry/create_and_migrate_anvil_l2_devchain.sh +++ b/packages/protocol/scripts/foundry/create_and_migrate_anvil_l2_devchain.sh @@ -18,7 +18,7 @@ cast rpc anvil_setCode \ --rpc-url $ANVIL_RPC_URL # Fetch address of Celo distribution -CELO_DISTRIBUTION_SCHEDULE_ADDRESS=$( +CELO_UNRELEASED_TREASURE_ADDRESS=$( cast call \ $REGISTRY_ADDRESS \ "getAddressForStringOrDie(string calldata identifier)(address)" \ @@ -31,9 +31,10 @@ CELO_DISTRIBUTION_SCHEDULE_ADDRESS=$( # implement the receive function nor does it allow ERC20 transfers. This is the only way I # managed to give the CeloUnreleasedTreasure a balance. echo "Setting CeloUnreleasedTreasure balance..." +HEX_CELO_UNRELEASED_TREASURE_INITIAL_BALANCE=$(cast to-hex $CELO_UNRELEASED_TREASURE_INITIAL_BALANCE"000000000000000000") cast rpc \ anvil_setBalance \ - $CELO_DISTRIBUTION_SCHEDULE_ADDRESS $CELO_DISTRIBUTION_SCHEDULE_INITIAL_BALANCE \ + $CELO_UNRELEASED_TREASURE_ADDRESS $HEX_CELO_UNRELEASED_TREASURE_INITIAL_BALANCE \ --rpc-url $ANVIL_RPC_URL # Run L2 migrations diff --git a/packages/protocol/test-sol/devchain/e2e/common/EpochManager.t.sol b/packages/protocol/test-sol/devchain/e2e/common/EpochManager.t.sol index 1e096cbf00f..98642ed6338 100644 --- a/packages/protocol/test-sol/devchain/e2e/common/EpochManager.t.sol +++ b/packages/protocol/test-sol/devchain/e2e/common/EpochManager.t.sol @@ -241,7 +241,11 @@ contract E2E_EpochManager_FinishNextEpochProcess is E2E_EpochManager { groupWithVotes[i] = GroupWithVotes( groupsEligible[i], values[i] + - election.getGroupEpochRewards(groupsEligible[i], totalRewardsVote, groupScore[i]) + election.getGroupEpochRewardsBasedOnScore( + groupsEligible[i], + totalRewardsVote, + groupScore[i] + ) ); } @@ -275,8 +279,23 @@ contract E2E_EpochManager_FinishNextEpochProcess is E2E_EpochManager { assertEq(election.getActiveVotesForGroup(groupsEligible[i]), groupWithVotes[i].votes); assertGt(election.getActiveVotesForGroup(groupsEligible[i]), groupActiveBalances[i]); } + + timeTravel(vm, epochDuration + 1); + epochManager.startNextEpochProcess(); + epochManager.finishNextEpochProcess(groups, lessers, greaters); + + assertEq(currentEpoch + 2, epochManager.getCurrentEpochNumber()); + + address[] memory newlyElected2 = epochManager.getElected(); + + for (uint256 i = 0; i < currentlyElected.length; i++) { + assertEq(originalyElected.contains(newlyElected2[i]), true); + } } + // TODO: add test when new groups are elected + // TODO: add test when groups are removed + // Bubble sort algorithm since it is a small array function sort(GroupWithVotes[] memory items) public { uint length = items.length; diff --git a/packages/protocol/test-sol/unit/governance/validators/Validators.t.sol b/packages/protocol/test-sol/unit/governance/validators/Validators.t.sol index f66b9ad11b7..150805a73d9 100644 --- a/packages/protocol/test-sol/unit/governance/validators/Validators.t.sol +++ b/packages/protocol/test-sol/unit/governance/validators/Validators.t.sol @@ -310,7 +310,7 @@ contract ValidatorsTest is Test, TestConstants, Utils, ECDSAHelper { accounts.authorizeValidatorSigner(signer, v, r, s); vm.prank(validator); - validators.registerValidator(_ecdsaPubKey); + validators.registerValidatorNoBls(_ecdsaPubKey); validatorRegistrationEpochNumber = epochManager.getCurrentEpochNumber(); return _ecdsaPubKey; } @@ -859,7 +859,7 @@ contract ValidatorsTest_RegisterValidator_NoBls is ValidatorsTest { vm.expectRevert("Cannot vote for more than max number of groups"); vm.prank(validator); - validators.registerValidator(pubKey); + validators.registerValidatorNoBls(pubKey); } function test_Reverts_WhenDelagatingCELO() public { @@ -872,7 +872,7 @@ contract ValidatorsTest_RegisterValidator_NoBls is ValidatorsTest { vm.expectRevert("Cannot delegate governance power"); vm.prank(validator); - validators.registerValidator(pubKey); + validators.registerValidatorNoBls(pubKey); } function test_ShouldMarkAccountAsValidator_WhenAccountHasAuthorizedValidatorSigner() public { @@ -897,7 +897,7 @@ contract ValidatorsTest_RegisterValidator_NoBls is ValidatorsTest { vm.expectRevert("This method is not supported in L1."); vm.prank(validator); - validators.registerValidator(_ecdsaPubKey); + validators.registerValidatorNoBls(_ecdsaPubKey); validatorRegistrationEpochNumber = IPrecompiles(address(validators)).getEpochNumber(); } @@ -973,7 +973,7 @@ contract ValidatorsTest_RegisterValidator_NoBls is ValidatorsTest { emit ValidatorBlsPublicKeyUpdated(validator, blsPublicKey); vm.prank(validator); - validators.registerValidator(_ecdsaPubKey); + validators.registerValidatorNoBls(_ecdsaPubKey); } function test_Emits_ValidatorRegisteredEvent() public { @@ -990,7 +990,7 @@ contract ValidatorsTest_RegisterValidator_NoBls is ValidatorsTest { emit ValidatorRegistered(validator); vm.prank(validator); - validators.registerValidator(_ecdsaPubKey); + validators.registerValidatorNoBls(_ecdsaPubKey); } function test_Reverts_WhenAccountAlreadyRegisteredAsValidator() public { @@ -998,7 +998,7 @@ contract ValidatorsTest_RegisterValidator_NoBls is ValidatorsTest { bytes memory _registeredEcdsaPubKey = _registerValidatorWithSignerHelper_noBls(); vm.prank(validator); vm.expectRevert("Already registered"); - validators.registerValidator(_registeredEcdsaPubKey); + validators.registerValidatorNoBls(_registeredEcdsaPubKey); } function test_Reverts_WhenAccountAlreadyRegisteredAsValidatorGroup() public { @@ -1006,7 +1006,7 @@ contract ValidatorsTest_RegisterValidator_NoBls is ValidatorsTest { _registerValidatorGroupHelper(validator, 1); vm.prank(validator); vm.expectRevert("Already registered"); - validators.registerValidator( + validators.registerValidatorNoBls( abi.encodePacked(bytes32(0x0101010101010101010101010101010101010101010101010101010101010101)) ); } @@ -1019,7 +1019,7 @@ contract ValidatorsTest_RegisterValidator_NoBls is ValidatorsTest { ); vm.expectRevert("Deposit too small"); vm.prank(validator); - validators.registerValidator( + validators.registerValidatorNoBls( abi.encodePacked(bytes32(0x0101010101010101010101010101010101010101010101010101010101010101)) ); } From 7fe7a29f0d0001787ba461482e01378ca45b0ce3 Mon Sep 17 00:00:00 2001 From: Martin Date: Wed, 18 Sep 2024 17:46:04 +0200 Subject: [PATCH 55/59] Reset pending payment to 0 after sending (#11209) Reset pending payment to 0 when sending --- .../protocol/contracts-0.8/common/EpochManager.sol | 1 + .../protocol/test-sol/unit/common/EpochManager.t.sol | 11 +++++++++++ 2 files changed, 12 insertions(+) diff --git a/packages/protocol/contracts-0.8/common/EpochManager.sol b/packages/protocol/contracts-0.8/common/EpochManager.sol index 24c5a9c0e9e..a921915d00d 100644 --- a/packages/protocol/contracts-0.8/common/EpochManager.sol +++ b/packages/protocol/contracts-0.8/common/EpochManager.sol @@ -371,6 +371,7 @@ contract EpochManager is FixidityLib.Fraction memory totalPayment = FixidityLib.newFixed( validatorPendingPayments[signer] ); + validatorPendingPayments[signer] = 0; IValidators validators = getValidators(); address group = validators.getValidatorsGroup(validator); diff --git a/packages/protocol/test-sol/unit/common/EpochManager.t.sol b/packages/protocol/test-sol/unit/common/EpochManager.t.sol index 1b2074f4654..9b4dfe07db5 100644 --- a/packages/protocol/test-sol/unit/common/EpochManager.t.sol +++ b/packages/protocol/test-sol/unit/common/EpochManager.t.sol @@ -380,4 +380,15 @@ contract EpochManagerTest_sendValidatorPayment is EpochManagerTest { assertEq(beneficiaryBalanceAfter, 0); assertEq(epochManagerBalanceAfter, epochManagerBalanceBefore); } + + function test_doesntAllowDoubleSending() public { + epochManager.sendValidatorPayment(validator1); + epochManager.sendValidatorPayment(validator1); + + uint256 validatorBalanceAfter = stableToken.balanceOf(validator1); + uint256 epochManagerBalanceAfter = stableToken.balanceOf(address(epochManager)); + + assertEq(validatorBalanceAfter, paymentAmount); + assertEq(epochManagerBalanceAfter, epochManagerBalanceBefore - paymentAmount); + } } From d2fd5c48c594892e625264849e72be59b37409ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Volpe?= Date: Wed, 18 Sep 2024 20:48:01 +0200 Subject: [PATCH 56/59] Deleted duplicated import --- packages/protocol/contracts-0.8/common/EpochManager.sol | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/protocol/contracts-0.8/common/EpochManager.sol b/packages/protocol/contracts-0.8/common/EpochManager.sol index a921915d00d..f5a0f906179 100644 --- a/packages/protocol/contracts-0.8/common/EpochManager.sol +++ b/packages/protocol/contracts-0.8/common/EpochManager.sol @@ -12,7 +12,6 @@ import "../../contracts/common/FixidityLib.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, From df84c5d727ac186d9d09b6c57e49640cd64b963c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Volpe?= Date: Fri, 20 Sep 2024 15:02:02 +0200 Subject: [PATCH 57/59] Make captureEpochAndValidators work in constant time (#11210) --- .../contracts-0.8/common/EpochManager.sol | 120 +++++++++--------- .../common/EpochManagerEnabler.sol | 37 +++--- .../common/mocks/MockAccounts.sol | 25 ++-- .../interfaces/IEpochManagerEnabler.sol | 1 - .../precompiles/EpochSizePrecompile.sol | 14 +- .../unit/common/EpochManagerEnabler.t.sol | 50 ++++++++ 6 files changed, 156 insertions(+), 91 deletions(-) create mode 100644 packages/protocol/test-sol/unit/common/EpochManagerEnabler.t.sol diff --git a/packages/protocol/contracts-0.8/common/EpochManager.sol b/packages/protocol/contracts-0.8/common/EpochManager.sol index f5a0f906179..f06273a273c 100644 --- a/packages/protocol/contracts-0.8/common/EpochManager.sol +++ b/packages/protocol/contracts-0.8/common/EpochManager.sol @@ -12,13 +12,15 @@ import "../../contracts/common/FixidityLib.sol"; import "../../contracts/common/Initializable.sol"; import "../../contracts/common/interfaces/IEpochManager.sol"; import "../../contracts/common/interfaces/ICeloVersionedContract.sol"; +import "./interfaces/IEpochManagerInitializer.sol"; contract EpochManager is Initializable, UsingRegistry, IEpochManager, ReentrancyGuard, - ICeloVersionedContract + ICeloVersionedContract, + IEpochManagerInitializer { using FixidityLib for FixidityLib.Fraction; @@ -245,6 +247,60 @@ contract EpochManager is epochProcessing.status = EpochProcessStatus.NotStarted; } + /** + * @notice Sends the allocated epoch payment to a validator, their group, and + * delegation beneficiary. + * @param validator Account of the validator. + */ + function sendValidatorPayment(address validator) external { + IAccounts accounts = IAccounts(getAccounts()); + address signer = accounts.getValidatorSigner(validator); + + FixidityLib.Fraction memory totalPayment = FixidityLib.newFixed( + validatorPendingPayments[signer] + ); + validatorPendingPayments[signer] = 0; + + IValidators validators = getValidators(); + address group = validators.getValidatorsGroup(validator); + (, uint256 commissionUnwrapped, , , , , ) = validators.getValidatorGroup(group); + + uint256 groupPayment = totalPayment.multiply(FixidityLib.wrap(commissionUnwrapped)).fromFixed(); + FixidityLib.Fraction memory remainingPayment = FixidityLib.newFixed( + totalPayment.fromFixed() - groupPayment + ); + (address beneficiary, uint256 delegatedFraction) = getAccounts().getPaymentDelegation( + validator + ); + uint256 delegatedPayment = remainingPayment + .multiply(FixidityLib.wrap(delegatedFraction)) + .fromFixed(); + uint256 validatorPayment = remainingPayment.fromFixed() - delegatedPayment; + + IStableToken stableToken = IStableToken(getStableToken()); + + if (validatorPayment > 0) { + require(stableToken.transfer(validator, validatorPayment), "transfer failed to validator"); + } + + if (groupPayment > 0) { + require(stableToken.transfer(group, groupPayment), "transfer failed to validator group"); + } + + if (delegatedPayment > 0) { + require(stableToken.transfer(beneficiary, delegatedPayment), "transfer failed to delegatee"); + } + + emit ValidatorEpochPaymentDistributed( + validator, + validatorPayment, + group, + groupPayment, + beneficiary, + delegatedPayment + ); + } + /// returns the current epoch Info function getCurrentEpoch() external view returns (uint256, uint256, uint256, uint256) { Epoch storage _epoch = epochs[currentEpochNumber]; @@ -272,6 +328,10 @@ contract EpochManager is ); } + function isBlocked() external view returns (bool) { + return isOnEpochProcess(); + } + function getElected() external view returns (address[] memory) { return elected; } @@ -288,10 +348,6 @@ contract EpochManager is return epochs[epoch].lastBlock; } - function isBlocked() external view returns (bool) { - return isOnEpochProcess(); - } - /** * @notice Returns the storage, major, minor, and patch version of the contract. * @return Storage version of the contract. @@ -357,58 +413,4 @@ contract EpochManager is CELOequivalent ); } - - /** - * @notice Sends the allocated epoch payment to a validator, their group, and - * delegation beneficiary. - * @param validator Account of the validator. - */ - function sendValidatorPayment(address validator) external { - IAccounts accounts = IAccounts(getAccounts()); - address signer = accounts.getValidatorSigner(validator); - - FixidityLib.Fraction memory totalPayment = FixidityLib.newFixed( - validatorPendingPayments[signer] - ); - validatorPendingPayments[signer] = 0; - - IValidators validators = getValidators(); - address group = validators.getValidatorsGroup(validator); - (, uint256 commissionUnwrapped, , , , , ) = validators.getValidatorGroup(group); - - uint256 groupPayment = totalPayment.multiply(FixidityLib.wrap(commissionUnwrapped)).fromFixed(); - FixidityLib.Fraction memory remainingPayment = FixidityLib.newFixed( - totalPayment.fromFixed() - groupPayment - ); - (address beneficiary, uint256 delegatedFraction) = getAccounts().getPaymentDelegation( - validator - ); - uint256 delegatedPayment = remainingPayment - .multiply(FixidityLib.wrap(delegatedFraction)) - .fromFixed(); - uint256 validatorPayment = remainingPayment.fromFixed() - delegatedPayment; - - IStableToken stableToken = IStableToken(getStableToken()); - - if (validatorPayment > 0) { - require(stableToken.transfer(validator, validatorPayment), "transfer failed to validator"); - } - - if (groupPayment > 0) { - require(stableToken.transfer(group, groupPayment), "transfer failed to validator group"); - } - - if (delegatedPayment > 0) { - require(stableToken.transfer(beneficiary, delegatedPayment), "transfer failed to delegatee"); - } - - emit ValidatorEpochPaymentDistributed( - validator, - validatorPayment, - group, - groupPayment, - beneficiary, - delegatedPayment - ); - } } diff --git a/packages/protocol/contracts-0.8/common/EpochManagerEnabler.sol b/packages/protocol/contracts-0.8/common/EpochManagerEnabler.sol index d3bd71e0c6e..2eafa24f026 100644 --- a/packages/protocol/contracts-0.8/common/EpochManagerEnabler.sol +++ b/packages/protocol/contracts-0.8/common/EpochManagerEnabler.sol @@ -6,14 +6,25 @@ 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"; +import "../../contracts/common/interfaces/IEpochManagerEnabler.sol"; +import "./interfaces/IEpochManagerEnablerInitializer.sol"; -contract EpochManagerEnabler is Initializable, UsingPrecompiles, UsingRegistry { +contract EpochManagerEnabler is + Initializable, + UsingPrecompiles, + UsingRegistry, + IEpochManagerEnabler, + IEpochManagerEnablerInitializer +{ uint256 public lastKnownEpochNumber; uint256 public lastKnownFirstBlockOfEpoch; address[] public lastKnownElectedAccounts; + event LastKnownEpochNumberSet(uint256 lastKnownEpochNumber); + event LastKnownFirstBlockOfEpochSet(uint256 lastKnownFirstBlockOfEpoch); + event LastKnownElectedAccountsSet(); + /** * @notice Sets initialized == true on implementation contracts * @param test Set to true to skip implementation initialization @@ -48,10 +59,11 @@ contract EpochManagerEnabler is Initializable, UsingPrecompiles, UsingRegistry { */ function captureEpochAndValidators() external onlyL1 { lastKnownEpochNumber = getEpochNumber(); + emit LastKnownEpochNumberSet(lastKnownEpochNumber); uint256 numberElectedValidators = numberValidatorsInCurrentSet(); lastKnownElectedAccounts = new address[](numberElectedValidators); - lastKnownFirstBlockOfEpoch = _getFirstBlockOfEpoch(lastKnownEpochNumber); + _setFirstBlockOfEpoch(); for (uint256 i = 0; i < numberElectedValidators; i++) { // TODO: document how much gas this takes for 110 signers @@ -60,10 +72,7 @@ contract EpochManagerEnabler is Initializable, UsingPrecompiles, UsingRegistry { ); lastKnownElectedAccounts[i] = validatorAccountAddress; } - } - - function getFirstBlockOfEpoch(uint256 currentEpoch) external view returns (uint256) { - return _getFirstBlockOfEpoch(currentEpoch); + emit LastKnownElectedAccountsSet(); } /** @@ -77,14 +86,10 @@ contract EpochManagerEnabler is Initializable, UsingPrecompiles, UsingRegistry { return (1, 1, 0, 0); } - function _getFirstBlockOfEpoch(uint256 currentEpoch) internal view returns (uint256) { - uint256 blockToCheck = block.number - 1; - uint256 blockEpochNumber = getEpochNumberOfBlock(blockToCheck); - - while (blockEpochNumber == currentEpoch) { - blockToCheck--; - blockEpochNumber = getEpochNumberOfBlock(blockToCheck); - } - return blockToCheck; + function _setFirstBlockOfEpoch() internal onlyL1 { + uint256 blocksSinceEpochBlock = block.number % getEpochSize(); + uint256 epochBlock = block.number - blocksSinceEpochBlock; + lastKnownFirstBlockOfEpoch = epochBlock; + emit LastKnownFirstBlockOfEpochSet(lastKnownFirstBlockOfEpoch); } } diff --git a/packages/protocol/contracts-0.8/common/mocks/MockAccounts.sol b/packages/protocol/contracts-0.8/common/mocks/MockAccounts.sol index e7054bf39e8..a28357f7246 100644 --- a/packages/protocol/contracts-0.8/common/mocks/MockAccounts.sol +++ b/packages/protocol/contracts-0.8/common/mocks/MockAccounts.sol @@ -16,16 +16,12 @@ contract MockAccounts { mapping(address => PaymentDelegation) delegations; mapping(address => address) accountToSigner; - function setPaymentDelegationFor( - address validator, - address beneficiary, - uint256 fraction - ) public { - delegations[validator] = PaymentDelegation(beneficiary, FixidityLib.wrap(fraction)); + function setValidatorSigner(address account, address signer) external { + accountToSigner[account] = signer; } - function deletePaymentDelegationFor(address validator) public { - delete delegations[validator]; + function getValidatorSigner(address account) external returns (address) { + return accountToSigner[account]; } function getPaymentDelegation(address account) external view returns (address, uint256) { @@ -33,11 +29,14 @@ contract MockAccounts { return (delegation.beneficiary, delegation.fraction.unwrap()); } - function setValidatorSigner(address account, address signer) external { - accountToSigner[account] = signer; + function setPaymentDelegationFor( + address validator, + address beneficiary, + uint256 fraction + ) public { + delegations[validator] = PaymentDelegation(beneficiary, FixidityLib.wrap(fraction)); } - - function getValidatorSigner(address account) external returns (address) { - return accountToSigner[account]; + function deletePaymentDelegationFor(address validator) public { + delete delegations[validator]; } } diff --git a/packages/protocol/contracts/common/interfaces/IEpochManagerEnabler.sol b/packages/protocol/contracts/common/interfaces/IEpochManagerEnabler.sol index c245dac581a..8cf8ce591bd 100644 --- a/packages/protocol/contracts/common/interfaces/IEpochManagerEnabler.sol +++ b/packages/protocol/contracts/common/interfaces/IEpochManagerEnabler.sol @@ -3,6 +3,5 @@ pragma solidity >=0.5.13 <0.9.0; interface IEpochManagerEnabler { function initEpochManager() external; - function getEpochNumber() external returns (uint256); function captureEpochAndValidators() external; } diff --git a/packages/protocol/test-sol/precompiles/EpochSizePrecompile.sol b/packages/protocol/test-sol/precompiles/EpochSizePrecompile.sol index f51959049d5..cdab89b4674 100644 --- a/packages/protocol/test-sol/precompiles/EpochSizePrecompile.sol +++ b/packages/protocol/test-sol/precompiles/EpochSizePrecompile.sol @@ -1,17 +1,27 @@ -// TODO move this to test folder pragma solidity >=0.8.7 <0.8.20; +address constant EPOCH_SIZEPRE_COMPILE_ADDRESS = address(0xff - 7); contract EpochSizePrecompile { - address constant ADDRESS = address(0xff - 7); + address constant ADDRESS = EPOCH_SIZEPRE_COMPILE_ADDRESS; uint256 public constant EPOCH_SIZE = 100; + uint256 public epochSizeSet; receive() external payable {} fallback(bytes calldata) external payable returns (bytes memory) { + // this is required because when the migrations deploy the precompiles + // they don't get constructed + if (epochSizeSet != 0) { + return abi.encodePacked(epochSizeSet); + } return abi.encodePacked(EPOCH_SIZE); } + function setEpochSize(uint256 epochSize) public { + epochSizeSet = epochSize; + } + function getAddress() public pure returns (address) { return ADDRESS; } diff --git a/packages/protocol/test-sol/unit/common/EpochManagerEnabler.t.sol b/packages/protocol/test-sol/unit/common/EpochManagerEnabler.t.sol new file mode 100644 index 00000000000..b4273fb6c09 --- /dev/null +++ b/packages/protocol/test-sol/unit/common/EpochManagerEnabler.t.sol @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity >=0.8.7 <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/common/interfaces/IRegistry.sol"; + +import { EPOCH_SIZEPRE_COMPILE_ADDRESS, EpochSizePrecompile } from "@test-sol/precompiles/EpochSizePrecompile.sol"; + +contract EpochManagerEnablerMock is EpochManagerEnabler { + constructor(bool test) public EpochManagerEnabler(test) {} + + function setFirstBlockOfEpoch() external { + return _setFirstBlockOfEpoch(); + } +} + +contract EpochManagerEnablerTest is Test { + EpochManagerEnablerMock epochManagerEnabler; + uint256 EPOCH_SIZE_NEW = 17280; + + function setUp() public virtual { + deployCodeTo("EpochSizePrecompile", EPOCH_SIZEPRE_COMPILE_ADDRESS); + address payable payableAddress = payable(EPOCH_SIZEPRE_COMPILE_ADDRESS); + + EpochSizePrecompile(payableAddress).setEpochSize(EPOCH_SIZE_NEW); + + epochManagerEnabler = new EpochManagerEnablerMock(true); + } + + function test_precompilerWorks() public { + // Make sure epoch size is correct + assertEq(epochManagerEnabler.getEpochSize(), EPOCH_SIZE_NEW); + } +} + +contract EpochManagerEnablerTest_getFirstBlockOfEpoch is EpochManagerEnablerTest { + function test_blockIsEpockBlock() public { + vm.roll(27803520); + epochManagerEnabler.setFirstBlockOfEpoch(); + assertEq(epochManagerEnabler.lastKnownFirstBlockOfEpoch(), 27803520); + } + + function test_blockIsNotEpochBlock() public { + vm.roll(27817229); + epochManagerEnabler.setFirstBlockOfEpoch(); + assertEq(epochManagerEnabler.lastKnownFirstBlockOfEpoch(), 27803520); + } +} From c85d594b8d284073a130fdce236e35b33b87bf46 Mon Sep 17 00:00:00 2001 From: Martin Date: Fri, 20 Sep 2024 21:19:13 +0200 Subject: [PATCH 58/59] Use more general interface for token transfer (#11216) --- packages/protocol/contracts-0.8/common/EpochManager.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/protocol/contracts-0.8/common/EpochManager.sol b/packages/protocol/contracts-0.8/common/EpochManager.sol index f06273a273c..aa6fa57f025 100644 --- a/packages/protocol/contracts-0.8/common/EpochManager.sol +++ b/packages/protocol/contracts-0.8/common/EpochManager.sol @@ -1,11 +1,11 @@ // SPDX-License-Identifier: LGPL-3.0-only pragma solidity >=0.8.7 <0.8.20; +import "@openzeppelin/contracts8/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts8/security/ReentrancyGuard.sol"; import "@openzeppelin/contracts8/access/Ownable.sol"; import "./interfaces/IOracle.sol"; -import "./interfaces/IStableToken.sol"; import "../common/UsingRegistry.sol"; import "../../contracts/common/FixidityLib.sol"; @@ -277,7 +277,7 @@ contract EpochManager is .fromFixed(); uint256 validatorPayment = remainingPayment.fromFixed() - delegatedPayment; - IStableToken stableToken = IStableToken(getStableToken()); + IERC20 stableToken = IERC20(getStableToken()); if (validatorPayment > 0) { require(stableToken.transfer(validator, validatorPayment), "transfer failed to validator"); 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 59/59] 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 aa6fa57f025..c8de3995ea2 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 2eafa24f026..61506ec7874 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 66452d136ac..261c7f59d53 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 74baea164c6..b412f134a22 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 00000000000..137d9104522 --- /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 b4273fb6c09..c3b4c1adc4c 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(); } }