Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make captureEpochAndValidators work in constant time #11210

Merged
merged 6 commits into from
Sep 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
120 changes: 61 additions & 59 deletions packages/protocol/contracts-0.8/common/EpochManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -245,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];
Expand Down Expand Up @@ -272,6 +328,10 @@ contract EpochManager is
);
}

function isBlocked() external view returns (bool) {
return isOnEpochProcess();
}

function getElected() external view returns (address[] memory) {
return elected;
}
Expand All @@ -288,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.
Expand Down Expand Up @@ -357,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
);
}
}
37 changes: 21 additions & 16 deletions packages/protocol/contracts-0.8/common/EpochManagerEnabler.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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();
}

/**
Expand All @@ -77,14 +86,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 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
function _setFirstBlockOfEpoch() internal onlyL1 {
function _setLastKnownFirstBlockOfEpoch() internal onlyL1 {

uint256 blocksSinceEpochBlock = block.number % getEpochSize();
uint256 epochBlock = block.number - blocksSinceEpochBlock;
lastKnownFirstBlockOfEpoch = epochBlock;
emit LastKnownFirstBlockOfEpochSet(lastKnownFirstBlockOfEpoch);
}
}
25 changes: 12 additions & 13 deletions packages/protocol/contracts-0.8/common/mocks/MockAccounts.sol
Original file line number Diff line number Diff line change
Expand Up @@ -16,28 +16,27 @@ 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) {
PaymentDelegation storage delegation = delegations[account];
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];
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
14 changes: 12 additions & 2 deletions packages/protocol/test-sol/precompiles/EpochSizePrecompile.sol
Original file line number Diff line number Diff line change
@@ -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;
}
Expand Down
50 changes: 50 additions & 0 deletions packages/protocol/test-sol/unit/common/EpochManagerEnabler.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// 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);
}
}
Loading