Skip to content

Commit

Permalink
Implement sending of allocated validator payments (#11197)
Browse files Browse the repository at this point in the history
  • Loading branch information
m-chrzan authored Sep 18, 2024
1 parent f4b4564 commit 59ea380
Show file tree
Hide file tree
Showing 7 changed files with 363 additions and 3 deletions.
72 changes: 72 additions & 0 deletions packages/protocol/contracts-0.8/common/EpochManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import "./interfaces/IOracle.sol";
import "./interfaces/IStableToken.sol";
import "../common/UsingRegistry.sol";

import "../../contracts/common/FixidityLib.sol";
import "../../contracts/common/Initializable.sol";
import "../../contracts/common/interfaces/IEpochManager.sol";
import "../../contracts/common/interfaces/ICeloVersionedContract.sol";
Expand All @@ -20,6 +21,8 @@ contract EpochManager is
ReentrancyGuard,
ICeloVersionedContract
{
using FixidityLib for FixidityLib.Fraction;

struct Epoch {
uint256 firstBlock;
uint256 lastBlock;
Expand Down Expand Up @@ -73,6 +76,22 @@ contract EpochManager is
*/
event EpochProcessingEnded(uint256 indexed epochNumber);

/**
* @notice Emitted when an epoch payment is sent.
* @param validator Address of the validator.
* @param validatorPayment Amount of cUSD sent to the validator.
* @param group Address of the validator's group.
* @param groupPayment Amount of cUSD sent to the group.
*/
event ValidatorEpochPaymentDistributed(
address indexed validator,
uint256 validatorPayment,
address indexed group,
uint256 groupPayment,
address indexed beneficiary,
uint256 delegatedPayment
);

modifier onlyEpochManagerEnabler() {
require(
msg.sender == registry.getAddressForOrDie(EPOCH_MANAGER_ENABLER_REGISTRY_ID),
Expand Down Expand Up @@ -335,4 +354,57 @@ 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]
);

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
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ pragma solidity >=0.8.7 <0.8.20;
* absence of interface inheritance is intended as a companion to IERC20.sol and ICeloToken.sol.
*/
interface IStableToken {
function transfer(address, uint256) external returns (bool);

function mint(address, uint256) external returns (bool);

function burn(uint256) external returns (bool);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity >=0.8.7 <0.8.20;

import "../EpochManager.sol";

contract EpochManager_WithMocks is EpochManager(true) {
function _setPaymentAllocation(address validator, uint256 amount) external {
validatorPendingPayments[validator] = amount;
}
}
43 changes: 43 additions & 0 deletions packages/protocol/contracts-0.8/common/mocks/MockAccounts.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity >=0.8.7 <0.8.20;

import "../../../contracts/common/FixidityLib.sol";

contract MockAccounts {
using FixidityLib for FixidityLib.Fraction;

struct PaymentDelegation {
// Address that should receive a fraction of validator payments.
address beneficiary;
// Fraction of payment to delegate to `beneficiary`.
FixidityLib.Fraction fraction;
}

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 deletePaymentDelegationFor(address validator) public {
delete delegations[validator];
}

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 getValidatorSigner(address account) external returns (address) {
return accountToSigner[account];
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
pragma solidity >=0.8.7 <0.8.20;

interface IMockValidators {
function isValidator(address) external returns (bool);
function isValidatorGroup(address) external returns (bool);

function updateEcdsaPublicKey(address, address, bytes calldata) external returns (bool);

function updatePublicKeys(
address,
address,
bytes calldata,
bytes calldata,
bytes calldata
) external returns (bool);

function setValidator(address) external;

function setValidatorGroup(address group) external;

function affiliate(address group) external returns (bool);

function setDoesNotMeetAccountLockedGoldRequirements(address account) external;

function setNumRegisteredValidators(uint256 value) external;

function setMembers(address group, address[] calldata _members) external;

function setCommission(address group, uint256 commission) external;

function setAccountLockedGoldRequirement(address account, uint256 value) external;

function halveSlashingMultiplier(address) external;

function forceDeaffiliateIfValidator(address validator) external;

function getTopGroupValidators(address group, uint256 n) external view returns (address[] memory);

function getValidatorGroup(
address
)
external
view
returns (address[] memory, uint256, uint256, uint256, uint256[] memory, uint256, uint256);

function getValidatorGroupSlashingMultiplier(address) external view returns (uint256);

function meetsAccountLockedGoldRequirements(address account) external view returns (bool);

function getNumRegisteredValidators() external view returns (uint256);

function getAccountLockedGoldRequirement(address account) external view returns (uint256);

function calculateGroupEpochScore(uint256[] calldata uptimes) external view returns (uint256);

function getGroupsNumMembers(address[] calldata groups) external view returns (uint256[] memory);

function groupMembershipInEpoch(address addr, uint256, uint256) external view returns (address);

function getGroupNumMembers(address group) external view returns (uint256);
}
23 changes: 23 additions & 0 deletions packages/protocol/contracts/governance/test/MockValidators.sol
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ contract MockValidators is IsL2Check {
mapping(address => bool) private doesNotMeetAccountLockedGoldRequirements;
mapping(address => address[]) private members;
mapping(address => address) private affiliations;
mapping(address => uint256) private commissions;
uint256 private numRegisteredValidators;

function updateEcdsaPublicKey(address, address, bytes calldata) external returns (bool) {
Expand All @@ -48,6 +49,10 @@ contract MockValidators is IsL2Check {
isValidatorGroup[group] = true;
}

function getValidatorsGroup(address validator) external returns (address) {
return affiliations[validator];
}

function affiliate(address group) external returns (bool) {
allowOnlyL1();
affiliations[msg.sender] = group;
Expand All @@ -64,6 +69,13 @@ contract MockValidators is IsL2Check {

function setMembers(address group, address[] calldata _members) external {
members[group] = _members;
for (uint256 i; i < _members.length; i++) {
affiliations[_members[i]] = group;
}
}

function setCommission(address group, uint256 commission) external {
commissions[group] = commission;
}

function setAccountLockedGoldRequirement(address account, uint256 value) external {
Expand All @@ -90,6 +102,17 @@ contract MockValidators is IsL2Check {
return validators;
}

function getValidatorGroup(
address group
)
external
view
returns (address[] memory, uint256, uint256, uint256, uint256[] memory, uint256, uint256)
{
uint256[] memory sizeHistory;
return (members[group], commissions[group], 0, 0, sizeHistory, 0, 0);
}

function getValidatorGroupSlashingMultiplier(address) external view returns (uint256) {
allowOnlyL1();
return FIXED1_UINT;
Expand Down
Loading

0 comments on commit 59ea380

Please sign in to comment.