feat(rewarding): DelegateProfile contract-read bridge for IIP-59#4912
Draft
envestcc wants to merge 2 commits into
Draft
feat(rewarding): DelegateProfile contract-read bridge for IIP-59#4912envestcc wants to merge 2 commits into
envestcc wants to merge 2 commits into
Conversation
Introduce a read-only bridge over the existing DelegateProfile contract (mainnet io1lfl4ppn2c3wcft04f0rk0jy9lyn4pcjcm7638u) so that PutPollResult can snapshot per-delegate commission rates from the amended IIP-59 design (iotexproject/iips#74). The bridge invokes getProfileByField twice per delegate (blockRewardPortion and epochRewardPortion), inverts the on-chain voter-take portion into commission basis points, and returns a per-delegate map. Empty bytes for either field flag the delegate as unregistered so the caller falls back to the legacy Hermes path; a partial profile is deliberately treated as unregistered to preserve the "either fully opted-in or fully legacy" invariant. Explicit zero voter-take remains distinguishable (single 0x00 byte) and yields a registered 100% commission split. Values exceeding uint64 or 10000 basis points are rejected rather than silently truncated. The package has no dependency on protocol.StateManager and is exercised by 14 unit tests using an in-process ABI-round-tripping fake reader. Refs iotexproject/iips#74. Follow-up PR (2') will call Snapshot at PutPollResult and freeze the returned rates into the poll snapshot. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…legacy path Snapshot originally propagated any per-delegate read error up, which at the IIP-59 consensus entry point (PutPollResult) would deterministically halt block production at every subsequent epoch boundary if one delegate's DelegateProfile field was malformed or a view call transiently failed. Same on-chain state produces the same error on every validator, so the fork stays safe — but wedging the chain is strictly worse than routing the one bad delegate through the well-defined legacy Hermes path. Absorb the per-delegate error, emit Registered=false for that entry, and log for observability. Nil-reader and nil-address stay hard errors: those are wiring bugs, not on-chain data issues, and must surface loudly. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
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>
Member
Author
|
Force-pushed one additional commit (
Test updates in the same commit rename the affected assertions ( PR #4915 rebased on top; its follow-up commit now only touches the staking-side docstring + one test rename. |
This was referenced Jul 13, 2026
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
io1lfl4ppn2c3wcft04f0rk0jy9lyn4pcjcm7638u) soPutPollResultcan snapshot per-delegate commission rates from the amended IIP-59 design.blockRewardPortionandepochRewardPortionper delegate viagetProfileByField, inverts voter-take portions into commission basis points, and returns a per-delegate map keyed by identity string.protocol.StateManager; exercised by 14 unit tests using an ABI-round-tripping in-process fake reader.Design notes
getProfileByField(_delegate, _field) returns bytesinvoked twice per delegate. Avoids reverse-engineeringgetEncodedProfile's undocumented composite return format.Registered=false, and the caller MUST fall back to the legacy Hermes path. A partial profile (one field set, other empty) is treated as unregistered on purpose — the IIP relies on the "either fully opted-in or fully legacy" invariant so a delegate cannot end up with one reward stream migrated mid-fork.0x00byte encodes an explicit 0% voter-take and yieldsRegistered=truewith a 100% commission split. This is distinguishable from "field never set".uint64or10000basis points are rejected rather than silently truncated viaSetBytes → Uint64.Snapshotiterates the caller-supplied delegate list in order (2N sequential reads); errors abort with the offending delegate address named.Sequencing
Refs iotexproject/iips#74.
PutPollResultcallsSnapshot, freezes commission rates into the poll snapshot per delegate. That PR wires the concreteContractReaderaroundevm.SimulateExecution.Test plan
go build ./...go vet ./action/protocol/rewarding/delegateprofile/...go test ./action/protocol/rewarding/delegateprofile/— 14/14 passgetProfileByFieldreturn-encoding assumption (big-endian uint256 bytes, empty ⇒ unset) matches the deployed contract behaviour.Draft — subsequent PRs in the IIP-59 stack build on this bridge.
Co-Authored-By: Claude Opus 4.7 noreply@anthropic.com
🤖 Generated with Claude Code