diff --git a/packages/protocol/test-sol/unit/common/mocks/MockEpochManager.sol b/packages/protocol/test-sol/unit/common/mocks/MockEpochManager.sol index 3013ee926f5..5bc6899bdd0 100644 --- a/packages/protocol/test-sol/unit/common/mocks/MockEpochManager.sol +++ b/packages/protocol/test-sol/unit/common/mocks/MockEpochManager.sol @@ -36,6 +36,7 @@ contract MockEpochManager is IEpochManager { uint256 private currentEpochNumber; address[] public electedAccounts; address[] public electedSigners; + uint256 numberOfElectedAccounts; address public epochManagerEnabler; bool systemInitialized; @@ -95,6 +96,10 @@ contract MockEpochManager is IEpochManager { isProcessingEpoch = _isProcessing; } + function setNumberOfElectedInCurrentSet(uint256 value) external { + numberOfElectedAccounts = value; + } + function getCurrentEpoch() external view returns (uint256, uint256, uint256, uint256) { return getEpochByNumber(currentEpochNumber); } @@ -104,7 +109,7 @@ contract MockEpochManager is IEpochManager { } function numberOfElectedInCurrentSet() external view returns (uint256) { - return electedAccounts.length; + return numberOfElectedAccounts; } function getElectedAccounts() external view returns (address[] memory) { 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 72ef50a53c0..62de72bfb62 100644 --- a/packages/protocol/test-sol/unit/governance/network/EpochRewards.t.sol +++ b/packages/protocol/test-sol/unit/governance/network/EpochRewards.t.sol @@ -1,7 +1,6 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.5.13; -import "celo-foundry/Test.sol"; import "@celo-contracts/common/Registry.sol"; import "@celo-contracts/common/Freezer.sol"; @@ -13,10 +12,10 @@ import { MockSortedOracles } from "@celo-contracts/stability/test/MockSortedOrac import { MockStableToken } from "@celo-contracts/stability/test/MockStableToken.sol"; import { CeloTokenMock } from "@test-sol/unit/common/CeloTokenMock.sol"; -import { TestConstants } from "@test-sol/constants.sol"; import { Utils } from "@test-sol/utils.sol"; +import "@test-sol/utils/WhenL2.sol"; -contract EpochRewardsTest is Test, TestConstants, Utils { +contract EpochRewardsTest is Utils { uint256 constant targetVotingYieldParamsInitial = 0.00016e24; // 0.00016 uint256 constant targetVotingYieldParamsMax = 0.0005e24; // 0.0005 uint256 constant targetVotingYieldParamsAdjustmentFactor = 1127990000000000000; // 0.00000112799 @@ -46,8 +45,7 @@ contract EpochRewardsTest is Test, TestConstants, Utils { Reserve reserve; Freezer freezer; - Registry registry; - + address celoUnreleasedTreasuryAddress; address caller = address(this); event TargetVotingGoldFractionSet(uint256 fraction); @@ -62,16 +60,24 @@ contract EpochRewardsTest is Test, TestConstants, Utils { event TargetVotingYieldSet(uint256 target); function setUp() public { + super.setUp(); // Mocked contracts epochRewards = new EpochRewardsMock(); election = new MockElection(); mockSortedOracles = new MockSortedOracles(); mockStableToken = new MockStableToken(); + mockCeloToken = new CeloTokenMock(); + mockCeloToken.setRegistry(REGISTRY_ADDRESS); + mockCeloToken.setTotalSupply(L1_MINTED_CELO_SUPPLY); freezer = new Freezer(true); - registry = new Registry(true); + celoUnreleasedTreasuryAddress = actor("celoUnreleasedTreasury"); + deployCodeTo("CeloUnreleasedTreasury.sol", abi.encode(false), celoUnreleasedTreasuryAddress); + registry.setAddressFor(CeloUnreleasedTreasuryContract, celoUnreleasedTreasuryAddress); + + vm.deal(celoUnreleasedTreasuryAddress, L2_INITIAL_STASH_BALANCE); registry.setAddressFor(ElectionContract, address(election)); registry.setAddressFor(SortedOraclesContract, address(mockSortedOracles)); registry.setAddressFor(StableTokenContract, address(mockStableToken)); @@ -99,6 +105,24 @@ contract EpochRewardsTest is Test, TestConstants, Utils { ); } + function _setNumberOfElectedInCurrentSetBaseOnLayer(uint256 numberValidators) internal { + if (isL2()) { + epochManager.setNumberOfElectedInCurrentSet(numberValidators); + } else { + epochRewards.setNumberValidatorsInCurrentSet(numberValidators); + } + } + + function _updateTargetVotingYieldBasedOnLayer() internal { + if (isL2()) { + vm.prank(address(epochManager)); + epochRewards.updateTargetVotingYield(); + } else { + vm.prank(address(0)); + epochRewards.updateTargetVotingYield(); + } + } + function getExpectedTargetTotalSupply(uint256 timeDelta) internal pure returns (uint256) { uint256 genesisSupply = 600000000 ether; uint256 linearRewards = 200000000 ether; @@ -106,6 +130,8 @@ contract EpochRewardsTest is Test, TestConstants, Utils { } } +contract EpochRewardsTest_L2 is WhenL2, EpochRewardsTest {} + contract EpochRewardsTest_initialize is EpochRewardsTest { function test_ShouldHaveSetOwner() public { assertEq(epochRewards.owner(), caller); @@ -189,15 +215,13 @@ contract EpochRewardsTest_setTargetVotingGoldFraction is EpochRewardsTest { vm.expectRevert("Target voting gold fraction unchanged"); epochRewards.setTargetVotingGoldFraction(targetVotingGoldFraction); } - - function test_Emits_WhenCalledOnL2() public { - _whenL2(); - vm.expectEmit(true, true, true, true); - emit TargetVotingGoldFractionSet(newFraction); - epochRewards.setTargetVotingGoldFraction(newFraction); - } } +contract EpochRewardsTest_setTargetVotingGoldFraction_L2 is + EpochRewardsTest_L2, + EpochRewardsTest_setTargetVotingGoldFraction +{} + contract EpochRewardsTest_setCommunityRewardFraction is EpochRewardsTest { uint256 newFraction = communityRewardFraction + 1; @@ -235,15 +259,13 @@ contract EpochRewardsTest_setCommunityRewardFraction is EpochRewardsTest { ); epochRewards.setCommunityRewardFraction(communityRewardFraction); } - - function test_Emits_WhenCalledOnL2() public { - _whenL2(); - vm.expectEmit(true, true, true, true); - emit CommunityRewardFractionSet(newFraction); - epochRewards.setCommunityRewardFraction(newFraction); - } } +contract EpochRewardsTest_setCommunityRewardFraction_L2 is + EpochRewardsTest_L2, + EpochRewardsTest_setCommunityRewardFraction +{} + contract EpochRewardsTest_setTargetValidatorEpochPayment is EpochRewardsTest { uint256 newPayment = targetValidatorEpochPayment + 1; @@ -272,15 +294,13 @@ contract EpochRewardsTest_setTargetValidatorEpochPayment is EpochRewardsTest { vm.expectRevert("Target validator epoch payment unchanged"); epochRewards.setTargetValidatorEpochPayment(targetValidatorEpochPayment); } - - function test_Emits_WhenCalledOnL2() public { - _whenL2(); - vm.expectEmit(true, true, true, true); - emit TargetValidatorEpochPaymentSet(newPayment); - epochRewards.setTargetValidatorEpochPayment(newPayment); - } } +contract EpochRewardsTest_setTargetValidatorEpochPayment_L2 is + EpochRewardsTest_L2, + EpochRewardsTest_setTargetValidatorEpochPayment +{} + contract EpochRewardsTest_setRewardsMultiplierParameters is EpochRewardsTest { uint256 newRewardsMultiplierAdjustmentsUnderspend = rewardsMultiplierAdjustmentsUnderspend + 1; @@ -331,23 +351,13 @@ contract EpochRewardsTest_setRewardsMultiplierParameters is EpochRewardsTest { rewardsMultiplierAdjustmentsOverspend ); } - - function test_Emits_WhenCalledOnL2() public { - _whenL2(); - vm.expectEmit(true, true, true, true); - emit RewardsMultiplierParametersSet( - rewardsMultiplierMax, - newRewardsMultiplierAdjustmentsUnderspend, - rewardsMultiplierAdjustmentsOverspend - ); - epochRewards.setRewardsMultiplierParameters( - rewardsMultiplierMax, - newRewardsMultiplierAdjustmentsUnderspend, - rewardsMultiplierAdjustmentsOverspend - ); - } } +contract EpochRewardsTest_setRewardsMultiplierParameters_L2 is + EpochRewardsTest_L2, + EpochRewardsTest_setRewardsMultiplierParameters +{} + contract EpochRewardsTest_setTargetVotingYieldParameters is EpochRewardsTest { uint256 newTargetVotingYieldParamsMax = targetVotingYieldParamsMax + 1; uint256 newTargetVotingYieldParamsAdjustmentFactor = targetVotingYieldParamsAdjustmentFactor + 1; @@ -392,21 +402,13 @@ contract EpochRewardsTest_setTargetVotingYieldParameters is EpochRewardsTest { newTargetVotingYieldParamsAdjustmentFactor ); } - - function test_Emits_WhenCalledOnL2() public { - _whenL2(); - vm.expectEmit(true, true, true, true); - emit TargetVotingYieldParametersSet( - newTargetVotingYieldParamsMax, - newTargetVotingYieldParamsAdjustmentFactor - ); - epochRewards.setTargetVotingYieldParameters( - newTargetVotingYieldParamsMax, - newTargetVotingYieldParamsAdjustmentFactor - ); - } } +contract EpochRewardsTest_setTargetVotingYieldParameters_L2 is + EpochRewardsTest_L2, + EpochRewardsTest_setTargetVotingYieldParameters +{} + contract EpochRewardsTest_setTargetVotingYield is EpochRewardsTest { uint256 constant newTargetVotingYieldParamsInitial = targetVotingYieldParamsInitial + 1; @@ -428,15 +430,13 @@ contract EpochRewardsTest_setTargetVotingYield is EpochRewardsTest { vm.expectRevert("Ownable: caller is not the owner"); epochRewards.setTargetVotingYield(newTargetVotingYieldParamsInitial); } - - function test_Emits_WhenCalledOnL2() public { - _whenL2(); - vm.expectEmit(true, true, true, true); - emit TargetVotingYieldSet(newTargetVotingYieldParamsInitial); - epochRewards.setTargetVotingYield(newTargetVotingYieldParamsInitial); - } } +contract EpochRewardsTest_setTargetVotingYield_L2 is + EpochRewardsTest_L2, + EpochRewardsTest_setTargetVotingYield +{} + contract EpochRewardsTest_getTargetGoldTotalSupply is EpochRewardsTest { function test_ShouldReturn1B_WhenLessThan15YearsSinceGenesis() public { uint256 timeDelta = YEAR * 10; @@ -445,6 +445,11 @@ contract EpochRewardsTest_getTargetGoldTotalSupply is EpochRewardsTest { } } +contract EpochRewardsTest_getTargetGoldTotalSupply_L2 is + EpochRewardsTest_L2, + EpochRewardsTest_getTargetGoldTotalSupply +{} + contract EpochRewardsTest_getTargetVoterRewards is EpochRewardsTest { function test_ShouldReturnAPercentageOfActiveVotes_WhenThereAreActiveVotes() public { uint256 activeVotes = 1000000; @@ -455,16 +460,26 @@ contract EpochRewardsTest_getTargetVoterRewards is EpochRewardsTest { } } +contract EpochRewardsTest_getTargetVoterRewards_L2 is + EpochRewardsTest_L2, + EpochRewardsTest_getTargetVoterRewards +{} + contract EpochRewardsTest_getTargetTotalEpochPaymentsInGold is EpochRewardsTest { function test_ShouldgetTargetTotalEpochPaymentsInGold_WhenExchangeRateIsSet() public { uint256 numberValidators = 100; - epochRewards.setNumberValidatorsInCurrentSet(numberValidators); + _setNumberOfElectedInCurrentSetBaseOnLayer(numberValidators); uint256 expected = uint256((targetValidatorEpochPayment * numberValidators) / exchangeRate); assertEq(epochRewards.getTargetTotalEpochPaymentsInGold(), expected); } } +contract EpochRewardsTest_getTargetTotalEpochPaymentsInGold_L2 is + EpochRewardsTest_L2, + EpochRewardsTest_getTargetTotalEpochPaymentsInGold +{} + contract EpochRewardsTest_getRewardsMultiplier is EpochRewardsTest { uint256 constant timeDelta = YEAR * 10; uint256 expectedTargetTotalSupply; @@ -473,6 +488,7 @@ contract EpochRewardsTest_getRewardsMultiplier is EpochRewardsTest { function setUp() public { super.setUp(); + expectedTargetTotalSupply = getExpectedTargetTotalSupply(timeDelta); expectedTargetRemainingSupply = SUPPLY_CAP - expectedTargetTotalSupply; targetEpochReward = @@ -482,7 +498,13 @@ contract EpochRewardsTest_getRewardsMultiplier is EpochRewardsTest { } function test_ShouldReturnOne_WhenTheTargetSupplyIsEqualToTheActualSupplyAfterRewards() public { - mockCeloToken.setTotalSupply(expectedTargetTotalSupply - targetEpochReward); + if (isL2()) { + uint256 celoUnreleasedTreasuryBalance = SUPPLY_CAP - expectedTargetTotalSupply; + vm.deal(celoUnreleasedTreasuryAddress, celoUnreleasedTreasuryBalance - targetEpochReward); + } else { + mockCeloToken.setTotalSupply(expectedTargetTotalSupply - targetEpochReward); + } + assertEq(epochRewards.getRewardsMultiplier(), FIXED1); } @@ -490,8 +512,13 @@ contract EpochRewardsTest_getRewardsMultiplier is EpochRewardsTest { public { uint256 actualRemainingSupply = uint256((expectedTargetRemainingSupply * 11) / 10); - uint256 totalSupply = SUPPLY_CAP - actualRemainingSupply - targetEpochReward; - mockCeloToken.setTotalSupply(totalSupply); + + if (isL2()) { + vm.deal(celoUnreleasedTreasuryAddress, actualRemainingSupply - targetEpochReward); + } else { + uint256 totalSupply = SUPPLY_CAP - actualRemainingSupply - targetEpochReward; + mockCeloToken.setTotalSupply(totalSupply); + } uint256 actual = epochRewards.getRewardsMultiplier(); uint256 expected = uint256((FIXED1 + (rewardsMultiplierAdjustmentsUnderspend / 10))); @@ -502,8 +529,13 @@ contract EpochRewardsTest_getRewardsMultiplier is EpochRewardsTest { public { uint256 actualRemainingSupply = uint256((expectedTargetRemainingSupply * 9) / 10); - uint256 totalSupply = SUPPLY_CAP - actualRemainingSupply - targetEpochReward; - mockCeloToken.setTotalSupply(totalSupply); + + if (isL2()) { + vm.deal(celoUnreleasedTreasuryAddress, actualRemainingSupply - targetEpochReward); + } else { + uint256 totalSupply = SUPPLY_CAP - actualRemainingSupply - targetEpochReward; + mockCeloToken.setTotalSupply(totalSupply); + } uint256 actual = epochRewards.getRewardsMultiplier(); uint256 expected = uint256((FIXED1 - (rewardsMultiplierAdjustmentsOverspend / 10))); @@ -511,10 +543,16 @@ contract EpochRewardsTest_getRewardsMultiplier is EpochRewardsTest { } } +contract EpochRewardsTest_getRewardsMultiplier_L2 is + EpochRewardsTest_L2, + EpochRewardsTest_getRewardsMultiplier +{} + contract EpochRewardsTest_updateTargetVotingYield is EpochRewardsTest { - uint256 constant totalSupply = 6000000 ether; + uint256 constant totalSupplyL1 = 6000000 ether; + uint256 constant celoUnreleasedTreasuryBalance = SUPPLY_CAP - totalSupplyL1; uint256 constant reserveBalance = 1000000 ether; - uint256 constant floatingSupply = totalSupply - reserveBalance; + uint256 constant floatingSupply = totalSupplyL1 - reserveBalance; function setUp() public { super.setUp(); @@ -540,7 +578,11 @@ contract EpochRewardsTest_updateTargetVotingYield is EpochRewardsTest { 2 * FIXED1 ); - mockCeloToken.setTotalSupply(totalSupply); + if (isL2()) { + vm.deal(celoUnreleasedTreasuryAddress, celoUnreleasedTreasuryBalance); + } else { + mockCeloToken.setTotalSupply(totalSupplyL1); + } vm.deal(address(reserve), reserveBalance); } @@ -638,8 +680,7 @@ contract EpochRewardsTest_updateTargetVotingYield is EpochRewardsTest { for (uint256 i = 0; i < 600; i++) { // naive time travel: mining takes too long, just repeatedly update target voting yield. One call is one epoch travelled // time travel alone is not enough, updateTargetVotingYield needs to be called - vm.prank(address(0)); - epochRewards.updateTargetVotingYield(); + _updateTargetVotingYieldBasedOnLayer(); } (uint256 result, , ) = epochRewards.getTargetVotingYieldParameters(); @@ -654,8 +695,7 @@ contract EpochRewardsTest_updateTargetVotingYield is EpochRewardsTest { election.setTotalVotes(totalVotes); // naive time travel: mining takes too long, just repeatedly update target voting yield. One call is one epoch travelled for (uint256 i = 0; i < 800; i++) { - vm.prank(address(0)); - epochRewards.updateTargetVotingYield(); + _updateTargetVotingYieldBasedOnLayer(); } (uint256 result, , ) = epochRewards.getTargetVotingYieldParameters(); @@ -669,8 +709,7 @@ contract EpochRewardsTest_updateTargetVotingYield is EpochRewardsTest { election.setTotalVotes(totalVotes); // naive time travel: mining takes too long, just repeatedly update target voting yield. One call is one epoch travelled for (uint256 i = 0; i < 5; i++) { - vm.prank(address(0)); - epochRewards.updateTargetVotingYield(); + _updateTargetVotingYieldBasedOnLayer(); } uint256 expected = targetVotingYieldParamsInitial + @@ -689,8 +728,7 @@ contract EpochRewardsTest_updateTargetVotingYield is EpochRewardsTest { election.setTotalVotes(totalVotes); // naive time travel: mining takes too long, just repeatedly update target voting yield. One call is one epoch travelled for (uint256 i = 0; i < 5; i++) { - vm.prank(address(0)); - epochRewards.updateTargetVotingYield(); + _updateTargetVotingYieldBasedOnLayer(); } uint256 expected = targetVotingYieldParamsInitial + @@ -736,8 +774,7 @@ contract EpochRewardsTest_updateTargetVotingYield is EpochRewardsTest { uint256 totalVotes = (floatingSupply * (targetVotingGoldFraction - 0.1e24)) / FIXED1; election.setTotalVotes(totalVotes); for (uint256 i = 0; i < 356; i++) { - vm.prank(address(0)); - epochRewards.updateTargetVotingYield(); + _updateTargetVotingYieldBasedOnLayer(); } uint256 expected = targetVotingYieldParamsInitial + @@ -752,8 +789,7 @@ contract EpochRewardsTest_updateTargetVotingYield is EpochRewardsTest { uint256 totalVotes = (floatingSupply * (targetVotingGoldFraction + 0.1e24)) / FIXED1; election.setTotalVotes(totalVotes); for (uint256 i = 0; i < 356; i++) { - vm.prank(address(0)); - epochRewards.updateTargetVotingYield(); + _updateTargetVotingYieldBasedOnLayer(); } uint256 expected = targetVotingYieldParamsInitial - @@ -764,11 +800,15 @@ contract EpochRewardsTest_updateTargetVotingYield is EpochRewardsTest { function mockVotes(uint256 votes) internal { election.setTotalVotes(votes); - vm.prank(address(0)); - epochRewards.updateTargetVotingYield(); + _updateTargetVotingYieldBasedOnLayer(); } } +contract EpochRewardsTest_updateTargetVotingYield_L2 is + EpochRewardsTest_L2, + EpochRewardsTest_updateTargetVotingYield +{} + contract EpochRewardsTest_WhenThereAreActiveVotesAStableTokenExchangeRateIsSetAndTheActualRemainingSupplyIs10pMoreThanTheTargetRemainingSupplyAfterRewards_calculateTargetEpochRewards is EpochRewardsTest { @@ -782,7 +822,7 @@ contract EpochRewardsTest_WhenThereAreActiveVotesAStableTokenExchangeRateIsSetAn function setUp() public { super.setUp(); - epochRewards.setNumberValidatorsInCurrentSet(numberValidators); + _setNumberOfElectedInCurrentSetBaseOnLayer(numberValidators); election.setActiveVotes(activeVotes); uint256 expectedTargetTotalEpochPaymentsInGold = (targetValidatorEpochPayment * numberValidators) / exchangeRate; @@ -795,8 +835,16 @@ contract EpochRewardsTest_WhenThereAreActiveVotesAStableTokenExchangeRateIsSetAn uint256 expectedTargetTotalSupply = getExpectedTargetTotalSupply(timeDelta); uint256 expectedTargetRemainingSupply = SUPPLY_CAP - expectedTargetTotalSupply; uint256 actualRemainingSupply = (expectedTargetRemainingSupply * 11) / 10; - uint256 totalSupply = SUPPLY_CAP - actualRemainingSupply - expectedTargetGoldSupplyIncrease; - mockCeloToken.setTotalSupply(totalSupply); + + if (isL2()) { + vm.deal( + celoUnreleasedTreasuryAddress, + actualRemainingSupply + expectedTargetGoldSupplyIncrease + ); + } else { + uint256 totalSupply = SUPPLY_CAP - actualRemainingSupply - expectedTargetGoldSupplyIncrease; + mockCeloToken.setTotalSupply(totalSupply); + } expectedMultiplier = (FIXED1 + rewardsMultiplierAdjustmentsUnderspend / 10); validatorReward = (targetValidatorEpochPayment * numberValidators) / exchangeRate; @@ -806,7 +854,7 @@ contract EpochRewardsTest_WhenThereAreActiveVotesAStableTokenExchangeRateIsSetAn } function test_ShouldFetchTheExpectedRewardsMultiplier() public { - assertApproxEqRel(epochRewards.getRewardsMultiplier(), expectedMultiplier, 2e13); + assertApproxEqRel(epochRewards.getRewardsMultiplier(), expectedMultiplier, 6e13); } function test_ShouldReturnTheTargetValidatorEpochPaymentTimesTheRewardsMultiplier() public { @@ -845,6 +893,11 @@ contract EpochRewardsTest_WhenThereAreActiveVotesAStableTokenExchangeRateIsSetAn } } +contract EpochRewardsTest_WhenThereAreActiveVotesAStableTokenExchangeRateIsSetAndTheActualRemainingSupplyIs10pMoreThanTheTargetRemainingSupplyAfterRewards_calculateTargetEpochRewards_L2 is + EpochRewardsTest_L2, + EpochRewardsTest_WhenThereAreActiveVotesAStableTokenExchangeRateIsSetAndTheActualRemainingSupplyIs10pMoreThanTheTargetRemainingSupplyAfterRewards_calculateTargetEpochRewards +{} + contract EpochRewardsTest_isReserveLow is EpochRewardsTest { uint256 constant stableBalance = 2397846127684712867321; @@ -963,8 +1016,16 @@ contract EpochRewardsTest_isReserveLow is EpochRewardsTest { // when the contract is frozen function test_ShouldMakeUpdateTargetVotingyieldRevert_WhenTheContractIsFrozen() public { freezer.freeze(address(epochRewards)); - vm.prank(address(0)); - vm.expectRevert("can't call when contract is frozen"); - epochRewards.updateTargetVotingYield(); + if (isL2()) { + vm.prank(address(epochManager)); + vm.expectRevert("can't call when contract is frozen"); + epochRewards.updateTargetVotingYield(); + } else { + vm.prank(address(0)); + vm.expectRevert("can't call when contract is frozen"); + epochRewards.updateTargetVotingYield(); + } } } + +contract EpochRewardsTest_isReserveLow_L2 is EpochRewardsTest_L2, EpochRewardsTest_isReserveLow {}