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 1/2] 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 2/2] 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); }