feat(staking): add Candidate.VoterRewardOnchainOptIn for IIP-59 (amended)#4911
Closed
envestcc wants to merge 1 commit into
Closed
feat(staking): add Candidate.VoterRewardOnchainOptIn for IIP-59 (amended)#4911envestcc wants to merge 1 commit into
envestcc wants to merge 1 commit into
Conversation
Adds a persistent per-delegate opt-in flag on staking.Candidate that gates protocol-native voter reward distribution. Default false — post-fork the legacy path (full block/epoch reward to RewardAddress, off-chain Hermes service continues) still runs unless the delegate explicitly opts in. Scope of this change is intentionally narrow: - stakingpb.Candidate gains field 11 (voterRewardOnchainOptIn bool). - staking.Candidate Go struct gains VoterRewardOnchainOptIn; Clone, Equal, toProto, fromProto all thread it. Default zero-value keeps existing candidates opting out on decode. - TestSerWithVoterRewardOnchainOptIn covers false/true round-trip through proto, Equal's flag sensitivity, and Clone independence. Deferred to follow-up PRs (per amended IIP-59, iotexproject/iips#74): - SetVoterRewardOptIn native action + handler that mutates the flag with a one-epoch delay via the PutPollResult snapshot (bidirectional flip). - BlockCommissionRate / EpochCommissionRate — these are per-epoch snapshot values populated from the DelegateProfile contract at PutPollResult; they live on the poll snapshot (PR 2') rather than the persistent Candidate. Superseded PRs: #4865 / #4866 / #4880 / #4881 (all closed 2026-07-10). Refs iotexproject/iips#74
|
envestcc
added a commit
that referenced
this pull request
Jul 13, 2026
… opt-in at PutPollResult (IIP-59)
Introduces the per-candidate poll snapshot IIP-59 needs at each epoch boundary.
Downstream rewarding (PR 3', follow-up) reads this snapshot instead of the
live DelegateProfile contract / live staking.Candidate so a mid-epoch
mutation cannot retroactively re-split rewards that have already begun
accruing.
Wiring:
- stakingpb.CandidatePollSnapshot: block/epoch commission basis points,
registered flag, opt-in flag, per-voter entries (empty in this PR).
stakingpb.VoterWeightEntry per-voter tuple.
- staking._candidatePollSnapshot = 5 tag byte; full key
{tag}||candID.Bytes() under _stakingNameSpace.
- staking.FreezePollSnapshot: writer called from poll/util.setCandidates.
Nil bridge (contract not configured) → skip rate freeze but still
capture opt-in from live Candidate; Registered=false forces legacy
fallback downstream. Any per-delegate error aborts the whole snapshot
write (no partial map).
- staking.PollSnapshotFor: reader; returns ErrStateNotExist pre-fork /
pre-write.
- staking.readLiveOptIn: degrades to false when the poll list names a
candidate that has no staking record, rather than wedging the chain.
- poll.freezeIIP59PollSnapshot: fork + config gate. Guarded by
fCtx.NoVoterRewardDistribution (pre-fork no-op) and
Blockchain.DelegateProfileContractAddress (empty ⇒ nil bridge).
- poll.delegateProfileContractReader: view-call plumbing mirrors
consortium.getContractReaderForGenesisStates verbatim
(address.ZeroAddress caller, evm.SimulateExecution).
- genesis.Blockchain.DelegateProfileContractAddress: per-network config;
default empty.
- protocol.FeatureCtx.NoVoterRewardDistribution: fork gate bound to
!g.IsToBeEnabled(height); zero-value = active post-fork.
Intentionally out of scope for this PR:
- Voter-weight source. Entries is written empty; PR 3' has a degenerate
branch (empty voter list ⇒ full amount as commission). Follow-up PR
fills in the actual weight computation.
- Rewarding consumption of the snapshot (PR 3').
- SetVoterRewardOptIn action to mutate the field this snapshot freezes
(separate PR).
Stacks on iip-59/pr1-candidate-schema-optin (#4911) and
iip-59/pr4.5-delegateprofile-bridge (#4912).
Refs iotexproject/iips#74
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
9 tasks
envestcc
added a commit
that referenced
this pull request
Jul 16, 2026
… opt-in at PutPollResult (IIP-59)
Introduces the per-candidate poll snapshot IIP-59 needs at each epoch boundary.
Downstream rewarding (PR 3', follow-up) reads this snapshot instead of the
live DelegateProfile contract / live staking.Candidate so a mid-epoch
mutation cannot retroactively re-split rewards that have already begun
accruing.
Wiring:
- stakingpb.CandidatePollSnapshot: block/epoch commission basis points,
registered flag, opt-in flag, per-voter entries (empty in this PR).
stakingpb.VoterWeightEntry per-voter tuple.
- staking._candidatePollSnapshot = 5 tag byte; full key
{tag}||candID.Bytes() under _stakingNameSpace.
- staking.FreezePollSnapshot: writer called from poll/util.setCandidates.
Nil bridge (contract not configured) → skip rate freeze but still
capture opt-in from live Candidate; Registered=false forces legacy
fallback downstream. Any per-delegate error aborts the whole snapshot
write (no partial map).
- staking.PollSnapshotFor: reader; returns ErrStateNotExist pre-fork /
pre-write.
- staking.readLiveOptIn: degrades to false when the poll list names a
candidate that has no staking record, rather than wedging the chain.
- poll.freezeIIP59PollSnapshot: fork + config gate. Guarded by
fCtx.NoVoterRewardDistribution (pre-fork no-op) and
Blockchain.DelegateProfileContractAddress (empty ⇒ nil bridge).
- poll.delegateProfileContractReader: view-call plumbing mirrors
consortium.getContractReaderForGenesisStates verbatim
(address.ZeroAddress caller, evm.SimulateExecution).
- genesis.Blockchain.DelegateProfileContractAddress: per-network config;
default empty.
- protocol.FeatureCtx.NoVoterRewardDistribution: fork gate bound to
!g.IsToBeEnabled(height); zero-value = active post-fork.
Intentionally out of scope for this PR:
- Voter-weight source. Entries is written empty; PR 3' has a degenerate
branch (empty voter list ⇒ full amount as commission). Follow-up PR
fills in the actual weight computation.
- Rewarding consumption of the snapshot (PR 3').
- SetVoterRewardOptIn action to mutate the field this snapshot freezes
(separate PR).
Stacks on iip-59/pr1-candidate-schema-optin (#4911) and
iip-59/pr4.5-delegateprofile-bridge (#4912).
Refs iotexproject/iips#74
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
envestcc
added a commit
that referenced
this pull request
Jul 21, 2026
… opt-in at PutPollResult (IIP-59)
Introduces the per-candidate poll snapshot IIP-59 needs at each epoch boundary.
Downstream rewarding (PR 3', follow-up) reads this snapshot instead of the
live DelegateProfile contract / live staking.Candidate so a mid-epoch
mutation cannot retroactively re-split rewards that have already begun
accruing.
Wiring:
- stakingpb.CandidatePollSnapshot: block/epoch commission basis points,
registered flag, opt-in flag, per-voter entries (empty in this PR).
stakingpb.VoterWeightEntry per-voter tuple.
- staking._candidatePollSnapshot = 5 tag byte; full key
{tag}||candID.Bytes() under _stakingNameSpace.
- staking.FreezePollSnapshot: writer called from poll/util.setCandidates.
Nil bridge (contract not configured) → skip rate freeze but still
capture opt-in from live Candidate; Registered=false forces legacy
fallback downstream. Any per-delegate error aborts the whole snapshot
write (no partial map).
- staking.PollSnapshotFor: reader; returns ErrStateNotExist pre-fork /
pre-write.
- staking.readLiveOptIn: degrades to false when the poll list names a
candidate that has no staking record, rather than wedging the chain.
- poll.freezeIIP59PollSnapshot: fork + config gate. Guarded by
fCtx.NoVoterRewardDistribution (pre-fork no-op) and
Blockchain.DelegateProfileContractAddress (empty ⇒ nil bridge).
- poll.delegateProfileContractReader: view-call plumbing mirrors
consortium.getContractReaderForGenesisStates verbatim
(address.ZeroAddress caller, evm.SimulateExecution).
- genesis.Blockchain.DelegateProfileContractAddress: per-network config;
default empty.
- protocol.FeatureCtx.NoVoterRewardDistribution: fork gate bound to
!g.IsToBeEnabled(height); zero-value = active post-fork.
Intentionally out of scope for this PR:
- Voter-weight source. Entries is written empty; PR 3' has a degenerate
branch (empty voter list ⇒ full amount as commission). Follow-up PR
fills in the actual weight computation.
- Rewarding consumption of the snapshot (PR 3').
- SetVoterRewardOptIn action to mutate the field this snapshot freezes
(separate PR).
Stacks on iip-59/pr1-candidate-schema-optin (#4911) and
iip-59/pr4.5-delegateprofile-bridge (#4912).
Refs iotexproject/iips#74
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Member
Author
|
Superseded by #4953 (consolidated IIP-59 on-chain voter reward distribution). Contents folded into that PR wholesale; review and merge happen there. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



Summary
Adds a persistent per-delegate opt-in flag on
staking.Candidatethat gatesprotocol-native voter reward distribution.
This is the first code PR against the amended IIP-59 design (see
iotexproject/iips#74). The
earlier native-only design and its implementation PRs
(#4865 / #4866 / #4880 / #4881) were superseded and closed on 2026-07-10.
Draft status intentional — will promote to Ready after the IIP amendment
lands and the downstream PRs in the series (PR 4.5 contract-read bridge,
PR 2' snapshot rework, PR 3'/4' rewarding rework) are ready to stack.
What changed
stakingpb.Candidategains field 11:bool voterRewardOnchainOptIn.staking.CandidateGo struct gainsVoterRewardOnchainOptIn bool;Clone,Equal,toProto,fromProtoall thread the new field.TestSerWithVoterRewardOnchainOptIncovers:Equalsensitivity to the flag,Cloneindependence (flipping one copy doesn't affect the other).Default zero-value keeps every existing candidate opting out on decode,
so behavior is unchanged pre-fork and, post-fork, unchanged for any delegate
that hasn't sent the (later) opt-in action.
Deferred to follow-up PRs
Per the amended IIP-59:
SetVoterRewardOptInnative action + handler: mutates the flag witha one-epoch delay via the
PutPollResultsnapshot. Requires an externaliotex-protoproto change (new action type). Separated to keep this PRself-contained.
BlockCommissionRate/EpochCommissionRate: per-epoch snapshotvalues populated from the DelegateProfile contract at
PutPollResult;they live on the poll snapshot (PR 2'), not on the persistent Candidate.
See the amended proposal for the flow.
Test plan
make fmt— cleango build ./...— cleango test ./action/protocol/staking/— 264 tests pass, includes new roundtrip testmake lint— pending reviewRefs