diff --git a/packages/protocol/contracts-0.8/common/EpochManager.sol b/packages/protocol/contracts-0.8/common/EpochManager.sol index 93f7aabcbf4..f06273a273c 100644 --- a/packages/protocol/contracts-0.8/common/EpochManager.sol +++ b/packages/protocol/contracts-0.8/common/EpochManager.sol @@ -247,6 +247,60 @@ contract EpochManager is epochProcessing.status = EpochProcessStatus.NotStarted; } + /** + * @notice Sends the allocated epoch payment to a validator, their group, and + * delegation beneficiary. + * @param validator Account of the validator. + */ + function sendValidatorPayment(address validator) external { + IAccounts accounts = IAccounts(getAccounts()); + address signer = accounts.getValidatorSigner(validator); + + FixidityLib.Fraction memory totalPayment = FixidityLib.newFixed( + validatorPendingPayments[signer] + ); + validatorPendingPayments[signer] = 0; + + IValidators validators = getValidators(); + address group = validators.getValidatorsGroup(validator); + (, uint256 commissionUnwrapped, , , , , ) = validators.getValidatorGroup(group); + + uint256 groupPayment = totalPayment.multiply(FixidityLib.wrap(commissionUnwrapped)).fromFixed(); + FixidityLib.Fraction memory remainingPayment = FixidityLib.newFixed( + totalPayment.fromFixed() - groupPayment + ); + (address beneficiary, uint256 delegatedFraction) = getAccounts().getPaymentDelegation( + validator + ); + uint256 delegatedPayment = remainingPayment + .multiply(FixidityLib.wrap(delegatedFraction)) + .fromFixed(); + uint256 validatorPayment = remainingPayment.fromFixed() - delegatedPayment; + + IStableToken stableToken = IStableToken(getStableToken()); + + if (validatorPayment > 0) { + require(stableToken.transfer(validator, validatorPayment), "transfer failed to validator"); + } + + if (groupPayment > 0) { + require(stableToken.transfer(group, groupPayment), "transfer failed to validator group"); + } + + if (delegatedPayment > 0) { + require(stableToken.transfer(beneficiary, delegatedPayment), "transfer failed to delegatee"); + } + + emit ValidatorEpochPaymentDistributed( + validator, + validatorPayment, + group, + groupPayment, + beneficiary, + delegatedPayment + ); + } + /// returns the current epoch Info function getCurrentEpoch() external view returns (uint256, uint256, uint256, uint256) { Epoch storage _epoch = epochs[currentEpochNumber]; @@ -274,6 +328,10 @@ contract EpochManager is ); } + function isBlocked() external view returns (bool) { + return isOnEpochProcess(); + } + function getElected() external view returns (address[] memory) { return elected; } @@ -290,10 +348,6 @@ contract EpochManager is return epochs[epoch].lastBlock; } - function isBlocked() external view returns (bool) { - return isOnEpochProcess(); - } - /** * @notice Returns the storage, major, minor, and patch version of the contract. * @return Storage version of the contract. @@ -359,58 +413,4 @@ contract EpochManager is CELOequivalent ); } - - /** - * @notice Sends the allocated epoch payment to a validator, their group, and - * delegation beneficiary. - * @param validator Account of the validator. - */ - function sendValidatorPayment(address validator) external { - IAccounts accounts = IAccounts(getAccounts()); - address signer = accounts.getValidatorSigner(validator); - - FixidityLib.Fraction memory totalPayment = FixidityLib.newFixed( - validatorPendingPayments[signer] - ); - validatorPendingPayments[signer] = 0; - - IValidators validators = getValidators(); - address group = validators.getValidatorsGroup(validator); - (, uint256 commissionUnwrapped, , , , , ) = validators.getValidatorGroup(group); - - uint256 groupPayment = totalPayment.multiply(FixidityLib.wrap(commissionUnwrapped)).fromFixed(); - FixidityLib.Fraction memory remainingPayment = FixidityLib.newFixed( - totalPayment.fromFixed() - groupPayment - ); - (address beneficiary, uint256 delegatedFraction) = getAccounts().getPaymentDelegation( - validator - ); - uint256 delegatedPayment = remainingPayment - .multiply(FixidityLib.wrap(delegatedFraction)) - .fromFixed(); - uint256 validatorPayment = remainingPayment.fromFixed() - delegatedPayment; - - IStableToken stableToken = IStableToken(getStableToken()); - - if (validatorPayment > 0) { - require(stableToken.transfer(validator, validatorPayment), "transfer failed to validator"); - } - - if (groupPayment > 0) { - require(stableToken.transfer(group, groupPayment), "transfer failed to validator group"); - } - - if (delegatedPayment > 0) { - require(stableToken.transfer(beneficiary, delegatedPayment), "transfer failed to delegatee"); - } - - emit ValidatorEpochPaymentDistributed( - validator, - validatorPayment, - group, - groupPayment, - beneficiary, - delegatedPayment - ); - } } diff --git a/packages/protocol/contracts-0.8/common/EpochManagerEnabler.sol b/packages/protocol/contracts-0.8/common/EpochManagerEnabler.sol index 43578ec22f7..09fe55e245f 100644 --- a/packages/protocol/contracts-0.8/common/EpochManagerEnabler.sol +++ b/packages/protocol/contracts-0.8/common/EpochManagerEnabler.sol @@ -21,6 +21,10 @@ contract EpochManagerEnabler is 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 @@ -55,11 +59,13 @@ contract EpochManagerEnabler is */ function captureEpochAndValidators() external onlyL1 { lastKnownEpochNumber = getEpochNumber(); + emit LastKnownEpochNumberSet(lastKnownEpochNumber); uint256 numberElectedValidators = numberValidatorsInCurrentSet(); lastKnownElectedAccounts = new address[](numberElectedValidators); - lastKnownFirstBlockOfEpoch = _getFirstBlockOfEpoch(lastKnownEpochNumber); + _setFirstBlockOfEpoch(); + // TODO add emit for (uint256 i = 0; i < numberElectedValidators; i++) { // TODO: document how much gas this takes for 110 signers address validatorAccountAddress = getAccounts().validatorSignerToAccount( @@ -67,10 +73,7 @@ contract EpochManagerEnabler is ); lastKnownElectedAccounts[i] = validatorAccountAddress; } - } - - function getFirstBlockOfEpoch(uint256 currentEpoch) external view returns (uint256) { - return _getFirstBlockOfEpoch(currentEpoch); + emit LastKnownElectedAccountsSet(); } /** @@ -84,14 +87,10 @@ contract EpochManagerEnabler is 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/interfaces/IEpochManagerEnablerInitializer.sol b/packages/protocol/contracts-0.8/common/interfaces/IEpochManagerEnablerInitializer.sol index 3643d5ec711..1c26742695b 100644 --- a/packages/protocol/contracts-0.8/common/interfaces/IEpochManagerEnablerInitializer.sol +++ b/packages/protocol/contracts-0.8/common/interfaces/IEpochManagerEnablerInitializer.sol @@ -1,6 +1,4 @@ // SPDX-License-Identifier: LGPL-3.0-only pragma solidity >=0.5.13 <0.9.0; -interface IEpochManagerEnablerInitializer { - function initialize(address registryAddress) external; -} +interface IEpochManagerEnablerInitializer {} 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..22081fda236 100644 --- a/packages/protocol/test-sol/precompiles/EpochSizePrecompile.sol +++ b/packages/protocol/test-sol/precompiles/EpochSizePrecompile.sol @@ -1,10 +1,12 @@ // 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 EPOCH_SIZE = 100; receive() external payable {} @@ -12,6 +14,10 @@ contract EpochSizePrecompile { return abi.encodePacked(EPOCH_SIZE); } + function setEpochSize(uint256 epochSize) public { + EPOCH_SIZE = epochSize; + } + function getAddress() public pure returns (address) { return ADDRESS; } diff --git a/packages/protocol/test-sol/unit/common/EpochManagerEnabler.t.sol b/packages/protocol/test-sol/unit/common/EpochManagerEnabler.t.sol new file mode 100644 index 00000000000..0a4d2968fbd --- /dev/null +++ b/packages/protocol/test-sol/unit/common/EpochManagerEnabler.t.sol @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity >=0.8.7 <0.8.20; + +import "celo-foundry-8/Test.sol"; + +import "@celo-contracts-8/common/EpochManagerEnabler.sol"; + +import "@celo-contracts/stability/test/MockSortedOracles.sol"; + +import "@celo-contracts/common/interfaces/IRegistry.sol"; + +import { EPOCH_SIZEPRE_COMPILE_ADDRESS, EpochSizePrecompile } from "@test-sol/precompiles/EpochSizePrecompile.sol"; + +contract EpochManagerEnablerMock is EpochManagerEnabler { + constructor(bool test) public EpochManagerEnabler(test) {} + + function setFirstBlockOfEpoch() external { + return _setFirstBlockOfEpoch(); + } +} + +contract EpochManagerEnablerTest is Test { + EpochManagerEnablerMock epochManagerEnabler; + uint256 EPOCH_SIZE_NEW = 17280; + + function setUp() public virtual { + deployCodeTo("EpochSizePrecompile", EPOCH_SIZEPRE_COMPILE_ADDRESS); + address payable payableAddress = payable(EPOCH_SIZEPRE_COMPILE_ADDRESS); + + EpochSizePrecompile(payableAddress).setEpochSize(EPOCH_SIZE_NEW); + + epochManagerEnabler = new EpochManagerEnablerMock(true); + } + + function test_precompilerWorks() public { + // Make sure epoch size is correct + assertEq(epochManagerEnabler.getEpochSize(), EPOCH_SIZE_NEW); + } +} + +contract EpochManagerEnablerTest_getFirstBlockOfEpoch is EpochManagerEnablerTest { + function test_blockIsEpockBlock() public { + vm.roll(27803520); + epochManagerEnabler.setFirstBlockOfEpoch(); + assertEq(epochManagerEnabler.lastKnownFirstBlockOfEpoch(), 27803520); + } + + function test_blockIsNotEpochBlock() public { + vm.roll(27817229); + epochManagerEnabler.setFirstBlockOfEpoch(); + assertEq(epochManagerEnabler.lastKnownFirstBlockOfEpoch(), 27803520); + } +}