Skip to content

Commit

Permalink
Make captureEpochAndValidators work in constant time (#11210)
Browse files Browse the repository at this point in the history
  • Loading branch information
martinvol authored Sep 20, 2024
1 parent d2fd5c4 commit df84c5d
Show file tree
Hide file tree
Showing 6 changed files with 156 additions and 91 deletions.
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 {
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);
}
}

0 comments on commit df84c5d

Please sign in to comment.