diff --git a/packages/protocol/contracts-0.8/common/EpochManager.sol b/packages/protocol/contracts-0.8/common/EpochManager.sol index 0b45de44551..bbb7f51c0b8 100644 --- a/packages/protocol/contracts-0.8/common/EpochManager.sol +++ b/packages/protocol/contracts-0.8/common/EpochManager.sol @@ -12,13 +12,15 @@ import "../../contracts/common/FixidityLib.sol"; import "../../contracts/common/Initializable.sol"; import "../../contracts/common/interfaces/IEpochManager.sol"; import "../../contracts/common/interfaces/ICeloVersionedContract.sol"; +import "./interfaces/IEpochManagerInitializer.sol"; contract EpochManager is Initializable, UsingRegistry, IEpochManager, ReentrancyGuard, - ICeloVersionedContract + ICeloVersionedContract, + IEpochManagerInitializer { using FixidityLib for FixidityLib.Fraction; @@ -241,6 +243,60 @@ contract EpochManager is epochProcessing.status = EpochProcessStatus.NotStarted; } + /** + * @notice Sends the allocated epoch payment to a validator, their group, and + * delegation beneficiary. + * @param validator Account of the validator. + */ + function sendValidatorPayment(address validator) external { + IAccounts accounts = IAccounts(getAccounts()); + address signer = accounts.getValidatorSigner(validator); + + FixidityLib.Fraction memory totalPayment = FixidityLib.newFixed( + validatorPendingPayments[signer] + ); + validatorPendingPayments[signer] = 0; + + IValidators validators = getValidators(); + address group = validators.getValidatorsGroup(validator); + (, uint256 commissionUnwrapped, , , , , ) = validators.getValidatorGroup(group); + + uint256 groupPayment = totalPayment.multiply(FixidityLib.wrap(commissionUnwrapped)).fromFixed(); + FixidityLib.Fraction memory remainingPayment = FixidityLib.newFixed( + totalPayment.fromFixed() - groupPayment + ); + (address beneficiary, uint256 delegatedFraction) = getAccounts().getPaymentDelegation( + validator + ); + uint256 delegatedPayment = remainingPayment + .multiply(FixidityLib.wrap(delegatedFraction)) + .fromFixed(); + uint256 validatorPayment = remainingPayment.fromFixed() - delegatedPayment; + + IStableToken stableToken = IStableToken(getStableToken()); + + if (validatorPayment > 0) { + require(stableToken.transfer(validator, validatorPayment), "transfer failed to validator"); + } + + if (groupPayment > 0) { + require(stableToken.transfer(group, groupPayment), "transfer failed to validator group"); + } + + if (delegatedPayment > 0) { + require(stableToken.transfer(beneficiary, delegatedPayment), "transfer failed to delegatee"); + } + + emit ValidatorEpochPaymentDistributed( + validator, + validatorPayment, + group, + groupPayment, + beneficiary, + delegatedPayment + ); + } + /// returns the current epoch Info function getCurrentEpoch() external view returns (uint256, uint256, uint256, uint256) { Epoch storage _epoch = epochs[currentEpochNumber]; @@ -268,6 +324,10 @@ contract EpochManager is ); } + function isBlocked() external view returns (bool) { + return isOnEpochProcess(); + } + function getElected() external view returns (address[] memory) { return elected; } @@ -284,10 +344,6 @@ contract EpochManager is return epochs[epoch].lastBlock; } - function isBlocked() external view returns (bool) { - return isOnEpochProcess(); - } - /** * @notice Returns the storage, major, minor, and patch version of the contract. * @return Storage version of the contract. @@ -353,58 +409,4 @@ contract EpochManager is CELOequivalent ); } - - /** - * @notice Sends the allocated epoch payment to a validator, their group, and - * delegation beneficiary. - * @param validator Account of the validator. - */ - function sendValidatorPayment(address validator) external { - IAccounts accounts = IAccounts(getAccounts()); - address signer = accounts.getValidatorSigner(validator); - - FixidityLib.Fraction memory totalPayment = FixidityLib.newFixed( - validatorPendingPayments[signer] - ); - validatorPendingPayments[signer] = 0; - - IValidators validators = getValidators(); - address group = validators.getValidatorsGroup(validator); - (, uint256 commissionUnwrapped, , , , , ) = validators.getValidatorGroup(group); - - uint256 groupPayment = totalPayment.multiply(FixidityLib.wrap(commissionUnwrapped)).fromFixed(); - FixidityLib.Fraction memory remainingPayment = FixidityLib.newFixed( - totalPayment.fromFixed() - groupPayment - ); - (address beneficiary, uint256 delegatedFraction) = getAccounts().getPaymentDelegation( - validator - ); - uint256 delegatedPayment = remainingPayment - .multiply(FixidityLib.wrap(delegatedFraction)) - .fromFixed(); - uint256 validatorPayment = remainingPayment.fromFixed() - delegatedPayment; - - IStableToken stableToken = IStableToken(getStableToken()); - - if (validatorPayment > 0) { - require(stableToken.transfer(validator, validatorPayment), "transfer failed to validator"); - } - - if (groupPayment > 0) { - require(stableToken.transfer(group, groupPayment), "transfer failed to validator group"); - } - - if (delegatedPayment > 0) { - require(stableToken.transfer(beneficiary, delegatedPayment), "transfer failed to delegatee"); - } - - emit ValidatorEpochPaymentDistributed( - validator, - validatorPayment, - group, - groupPayment, - beneficiary, - delegatedPayment - ); - } } diff --git a/packages/protocol/contracts-0.8/common/EpochManagerEnabler.sol b/packages/protocol/contracts-0.8/common/EpochManagerEnabler.sol index 4785f91804a..61506ec7874 100644 --- a/packages/protocol/contracts-0.8/common/EpochManagerEnabler.sol +++ b/packages/protocol/contracts-0.8/common/EpochManagerEnabler.sol @@ -6,14 +6,25 @@ import "../common/UsingPrecompiles.sol"; import "../../contracts/common/Initializable.sol"; import "../../contracts/common/interfaces/ICeloVersionedContract.sol"; -import "../../contracts/common/interfaces/IEpochManagerEnabler.sol"; import "../../contracts/governance/interfaces/IEpochRewards.sol"; +import "../../contracts/common/interfaces/IEpochManagerEnabler.sol"; +import "./interfaces/IEpochManagerEnablerInitializer.sol"; -contract EpochManagerEnabler is Initializable, UsingPrecompiles, UsingRegistry { +contract EpochManagerEnabler is + Initializable, + UsingPrecompiles, + UsingRegistry, + IEpochManagerEnabler, + IEpochManagerEnablerInitializer +{ uint256 public lastKnownEpochNumber; uint256 public lastKnownFirstBlockOfEpoch; address[] public lastKnownElectedAccounts; + event LastKnownEpochNumberSet(uint256 lastKnownEpochNumber); + event LastKnownFirstBlockOfEpochSet(uint256 lastKnownFirstBlockOfEpoch); + event LastKnownElectedAccountsSet(); + /** * @notice Sets initialized == true on implementation contracts * @param test Set to true to skip implementation initialization @@ -48,10 +59,11 @@ contract EpochManagerEnabler is Initializable, UsingPrecompiles, UsingRegistry { */ function captureEpochAndValidators() external onlyL1 { lastKnownEpochNumber = getEpochNumber(); + emit LastKnownEpochNumberSet(lastKnownEpochNumber); uint256 numberElectedValidators = numberValidatorsInCurrentSet(); lastKnownElectedAccounts = new address[](numberElectedValidators); - lastKnownFirstBlockOfEpoch = _getFirstBlockOfEpoch(lastKnownEpochNumber); + _setFirstBlockOfEpoch(); for (uint256 i = 0; i < numberElectedValidators; i++) { // TODO: document how much gas this takes for 110 signers @@ -60,10 +72,7 @@ contract EpochManagerEnabler is Initializable, UsingPrecompiles, UsingRegistry { ); lastKnownElectedAccounts[i] = validatorAccountAddress; } - } - - function getFirstBlockOfEpoch(uint256 currentEpoch) external view returns (uint256) { - return _getFirstBlockOfEpoch(currentEpoch); + emit LastKnownElectedAccountsSet(); } /** @@ -84,14 +93,10 @@ contract EpochManagerEnabler is Initializable, UsingPrecompiles, UsingRegistry { return (1, 1, 0, 0); } - function _getFirstBlockOfEpoch(uint256 currentEpoch) internal view returns (uint256) { - uint256 blockToCheck = block.number - 1; - uint256 blockEpochNumber = getEpochNumberOfBlock(blockToCheck); - - while (blockEpochNumber == currentEpoch) { - blockToCheck--; - blockEpochNumber = getEpochNumberOfBlock(blockToCheck); - } - return blockToCheck; + function _setFirstBlockOfEpoch() internal onlyL1 { + uint256 blocksSinceEpochBlock = block.number % getEpochSize(); + uint256 epochBlock = block.number - blocksSinceEpochBlock; + lastKnownFirstBlockOfEpoch = epochBlock; + emit LastKnownFirstBlockOfEpochSet(lastKnownFirstBlockOfEpoch); } } diff --git a/packages/protocol/contracts-0.8/common/mocks/MockAccounts.sol b/packages/protocol/contracts-0.8/common/mocks/MockAccounts.sol index e7054bf39e8..a28357f7246 100644 --- a/packages/protocol/contracts-0.8/common/mocks/MockAccounts.sol +++ b/packages/protocol/contracts-0.8/common/mocks/MockAccounts.sol @@ -16,16 +16,12 @@ contract MockAccounts { mapping(address => PaymentDelegation) delegations; mapping(address => address) accountToSigner; - function setPaymentDelegationFor( - address validator, - address beneficiary, - uint256 fraction - ) public { - delegations[validator] = PaymentDelegation(beneficiary, FixidityLib.wrap(fraction)); + function setValidatorSigner(address account, address signer) external { + accountToSigner[account] = signer; } - function deletePaymentDelegationFor(address validator) public { - delete delegations[validator]; + function getValidatorSigner(address account) external returns (address) { + return accountToSigner[account]; } function getPaymentDelegation(address account) external view returns (address, uint256) { @@ -33,11 +29,14 @@ contract MockAccounts { return (delegation.beneficiary, delegation.fraction.unwrap()); } - function setValidatorSigner(address account, address signer) external { - accountToSigner[account] = signer; + function setPaymentDelegationFor( + address validator, + address beneficiary, + uint256 fraction + ) public { + delegations[validator] = PaymentDelegation(beneficiary, FixidityLib.wrap(fraction)); } - - function getValidatorSigner(address account) external returns (address) { - return accountToSigner[account]; + function deletePaymentDelegationFor(address validator) public { + delete delegations[validator]; } } diff --git a/packages/protocol/contracts/common/interfaces/IEpochManagerEnabler.sol b/packages/protocol/contracts/common/interfaces/IEpochManagerEnabler.sol index c245dac581a..8cf8ce591bd 100644 --- a/packages/protocol/contracts/common/interfaces/IEpochManagerEnabler.sol +++ b/packages/protocol/contracts/common/interfaces/IEpochManagerEnabler.sol @@ -3,6 +3,5 @@ pragma solidity >=0.5.13 <0.9.0; interface IEpochManagerEnabler { function initEpochManager() external; - function getEpochNumber() external returns (uint256); function captureEpochAndValidators() external; } diff --git a/packages/protocol/test-sol/precompiles/EpochSizePrecompile.sol b/packages/protocol/test-sol/precompiles/EpochSizePrecompile.sol index f51959049d5..cdab89b4674 100644 --- a/packages/protocol/test-sol/precompiles/EpochSizePrecompile.sol +++ b/packages/protocol/test-sol/precompiles/EpochSizePrecompile.sol @@ -1,17 +1,27 @@ -// TODO move this to test folder pragma solidity >=0.8.7 <0.8.20; +address constant EPOCH_SIZEPRE_COMPILE_ADDRESS = address(0xff - 7); contract EpochSizePrecompile { - address constant ADDRESS = address(0xff - 7); + address constant ADDRESS = EPOCH_SIZEPRE_COMPILE_ADDRESS; uint256 public constant EPOCH_SIZE = 100; + uint256 public epochSizeSet; receive() external payable {} fallback(bytes calldata) external payable returns (bytes memory) { + // this is required because when the migrations deploy the precompiles + // they don't get constructed + if (epochSizeSet != 0) { + return abi.encodePacked(epochSizeSet); + } return abi.encodePacked(EPOCH_SIZE); } + function setEpochSize(uint256 epochSize) public { + epochSizeSet = epochSize; + } + function getAddress() public pure returns (address) { return ADDRESS; } diff --git a/packages/protocol/test-sol/unit/common/EpochManagerEnabler.t.sol b/packages/protocol/test-sol/unit/common/EpochManagerEnabler.t.sol index 4697fbfb38f..d722e066a01 100644 --- a/packages/protocol/test-sol/unit/common/EpochManagerEnabler.t.sol +++ b/packages/protocol/test-sol/unit/common/EpochManagerEnabler.t.sol @@ -143,3 +143,17 @@ contract EpochManagerEnablerTest_captureEpochAndValidators is EpochManagerEnable assertEq(epochManagerEnabler.lastKnownFirstBlockOfEpoch(), 17280 * 3); } } + +contract EpochManagerEnablerTest_getFirstBlockOfEpoch is EpochManagerEnablerTest { + function test_blockIsEpockBlock() public { + vm.roll(27803520); + epochManagerEnabler.setFirstBlockOfEpoch(); + assertEq(epochManagerEnabler.lastKnownFirstBlockOfEpoch(), 27803520); + } + + function test_blockIsNotEpochBlock() public { + vm.roll(27817229); + epochManagerEnabler.setFirstBlockOfEpoch(); + assertEq(epochManagerEnabler.lastKnownFirstBlockOfEpoch(), 27803520); + } +}