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