-
Notifications
You must be signed in to change notification settings - Fork 475
Add voter reward commission for validator groups #11694
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
base: release/core-contracts/16
Are you sure you want to change the base?
Changes from 6 commits
608edba
1ad3668
d2563ca
f9ec8e8
0e23fde
9872009
08a0cdb
47b0bb3
f7e95c5
b5a1379
f75e95d
7237383
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -139,6 +139,18 @@ contract EpochManager is | |
| uint256 indexed epochNumber | ||
| ); | ||
|
|
||
| /** | ||
| * @notice Emitted when voter reward commission is distributed to a group. | ||
| * @param group Address of the validator group receiving commission. | ||
| * @param commission Amount of CELO released to the group as commission. | ||
| * @param epochNumber The epoch number for which the commission is distributed. | ||
| */ | ||
| event VoterRewardCommissionDistributed( | ||
| address indexed group, | ||
| uint256 commission, | ||
| uint256 indexed epochNumber | ||
| ); | ||
|
|
||
| /** | ||
| * @notice Throws if called by other than EpochManagerEnabler contract. | ||
| */ | ||
|
|
@@ -315,7 +327,11 @@ contract EpochManager is | |
| IElection election = getElection(); | ||
|
|
||
| if (epochRewards != type(uint256).max) { | ||
| election.distributeEpochRewards(group, epochRewards, lesser, greater); | ||
| uint256 commissionAmount = _deductVoterRewardCommission(group, epochRewards); | ||
| uint256 voterRewards = epochRewards - commissionAmount; | ||
| if (voterRewards > 0) { | ||
| election.distributeEpochRewards(group, voterRewards, lesser, greater); | ||
| } | ||
| } | ||
|
|
||
| delete processedGroups[group]; | ||
|
|
@@ -376,7 +392,12 @@ contract EpochManager is | |
| // checks that group is actually from elected group | ||
| require(epochRewards > 0, "group not from current elected set"); | ||
| if (epochRewards != type(uint256).max) { | ||
| election.distributeEpochRewards(groups[i], epochRewards, lessers[i], greaters[i]); | ||
| // Note: Uses in-place subtraction instead of explicit variable names (as in processGroup) | ||
| // to avoid stack-too-deep in this function which has more local variables. | ||
| epochRewards -= _deductVoterRewardCommission(groups[i], epochRewards); | ||
| if (epochRewards > 0) { | ||
| election.distributeEpochRewards(groups[i], epochRewards, lessers[i], greaters[i]); | ||
| } | ||
| } | ||
|
|
||
| delete processedGroups[groups[i]]; | ||
|
|
@@ -604,7 +625,7 @@ contract EpochManager is | |
| * @return Patch version of the contract. | ||
| */ | ||
| function getVersionNumber() external pure returns (uint256, uint256, uint256, uint256) { | ||
| return (1, 1, 0, 3); | ||
| return (1, 1, 0, 4); | ||
| } | ||
|
|
||
| /** | ||
|
|
@@ -682,6 +703,52 @@ contract EpochManager is | |
| return (_epoch.firstBlock, _epoch.lastBlock, _epoch.startTimestamp, _epoch.rewardsBlock); | ||
| } | ||
|
|
||
| /** | ||
| * @notice Deducts voter reward commission for a group and releases CELO from treasury to group. | ||
| * @param group The validator group address. | ||
| * @param epochRewards The total voter epoch rewards for this group. | ||
| * @return commissionAmount The amount deducted as commission. | ||
| * @dev ECONOMIC NOTE: Voter rewards are normally distributed as vote credit inflation via | ||
| * Election.distributeEpochRewards(), which creates deferred claims on the LockedGold pool | ||
| * redeemable when voters revoke and withdraw. This commission converts a portion of the | ||
| * already-budgeted totalRewardsVoter into an immediate CELO release from CeloUnreleasedTreasury. | ||
| * The total economic cost is unchanged — commission redirects part of the voter reward budget | ||
| * from deferred LockedGold claims to immediate treasury releases. The per-epoch treasury outflow | ||
| * from commission equals the sum of (groupVoterRewards * groupCommission) across all elected | ||
| * groups, bounded by maxVoterRewardCommission. | ||
| */ | ||
| function _deductVoterRewardCommission( | ||
| address group, | ||
| uint256 epochRewards | ||
| ) internal returns (uint256 commissionAmount) { | ||
| IValidators validators = getValidators(); | ||
| (uint256 voterRewardCommissionUnwrapped, , ) = validators.getVoterRewardCommission(group); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Useful? React with 👍 / 👎. |
||
|
|
||
| if (voterRewardCommissionUnwrapped == 0) { | ||
| return 0; | ||
| } | ||
|
|
||
| // Clamp to the governance-set max cap so that previously-activated commissions | ||
| // exceeding a later-lowered cap are still bounded at distribution time. | ||
| uint256 maxCommission = validators.maxVoterRewardCommission(); | ||
pahor167 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| if (maxCommission > 0 && voterRewardCommissionUnwrapped > maxCommission) { | ||
| voterRewardCommissionUnwrapped = maxCommission; | ||
| } | ||
|
|
||
| commissionAmount = FixidityLib | ||
| .newFixed(epochRewards) | ||
| .multiply(FixidityLib.wrap(voterRewardCommissionUnwrapped)) | ||
| .fromFixed(); | ||
|
|
||
| if (commissionAmount > 0) { | ||
| // Release CELO from treasury directly to the group. | ||
| // This mirrors the pattern used for community and carbon fund rewards | ||
| // in _finishEpochHelper(). | ||
| getCeloUnreleasedTreasury().release(group, commissionAmount); | ||
| emit VoterRewardCommissionDistributed(group, commissionAmount, currentEpochNumber); | ||
pahor167 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
| } | ||
|
|
||
| /** | ||
| * @notice Allocates rewards to elected validator accounts. | ||
| */ | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
processGroup/finishNextEpochProcessnow always callvalidators.getVoterRewardCommission(group), butValidators.getVoterRewardCommissionreverts unlessisValidatorGroup(account)is true. A group can still appear inprocessedGroupsfrom last-epoch membership and then be deregistered before reward processing (for configs where the empty-group duration allows it), which makes this lookup revert and blocks epoch finalization. Before this change, reward processing for such a group would not hard-fail on validator-group existence, so this introduces a liveness risk tied to deregistration timing.Useful? React with 👍 / 👎.