feat(action,staking): IIP-59 SetCommissionRate action + handler (PR 3/5)#4862
Closed
envestcc wants to merge 2 commits into
Closed
feat(action,staking): IIP-59 SetCommissionRate action + handler (PR 3/5)#4862envestcc wants to merge 2 commits into
envestcc wants to merge 2 commits into
Conversation
Adds the proto-level field and feature gate that the rest of the IIP-59 protocol-native voter reward distribution PRs will hang behavior off of. No runtime behavior changes in this PR — the field is populated as zero on existing chain data, default behavior matches today exactly. Schema: - stakingpb.Candidate gains commissionRate=11; Go Candidate struct mirrors it. Equal/Clone/toProto/fromProto updated (the original PoC missed Equal — flagged in iotexproject#4811 review #2). - state.Candidate gains CommissionRate, snapshotted per epoch from the staking candidate state by PutPollResult (added in PR 4). The latest user-set value lives on staking.Candidate; state.Candidate holds the per-epoch frozen value consumed by GrantEpochReward. - iotextypes.Candidate.commissionRate is set/read in candidateToPb / pbToCandidate so the new field travels through poll snapshots and over the wire. Feature flag: - FeatureCtx.NoVoterRewardDistribution, bound to !g.IsToBeEnabled(height) per AGENTS.md convention for WIP features (the gate will be swapped for a real hardfork height at release time). - Named so that the bool zero value (false) corresponds to the post-fork activated behavior, matching the existing NoCandidateExitQueue / NotSlashUnproductiveDelegates convention. A docstring records this rule next to the field for future readers. Why no separate commissionRateLastEpoch field: - IIP-59 doesn't prescribe a per-rate-change cooldown. Its protection against rapid manipulation is epoch-boundary activation, which our design already provides for free: a SetCommissionRate at any moment only affects rewards in the epoch *after* the next PutPollResult snapshot, giving voters ~1.5 epochs of reaction time. - If the protocol later decides cooldown is needed, adding the field is an additive proto change with no migration cost. Toolchain: - stakingpb regenerated with protoc-gen-go v1.26.0 to match the version recorded in the existing staking.pb.go header. Local dev: - go.mod replace pointing at ../iotex-proto so the build resolves the new iotex-proto fields prior to the proto PR being tagged. Remove the replace once iotex-proto cuts a release containing the SetCommissionRate + Candidate.commissionRate additions. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Introduces the on-chain entry point delegates use to opt into IIP-59
voter reward distribution. No reward-distribution logic yet — that arrives
in PR 4. This PR is independently reviewable: the action is gated at
Validate, so until the IIP-59 feature flag activates the protocol rejects
every SetCommissionRate at mempool admission and chain behavior is
unchanged.
action/set_commission_rate.go
- SetCommissionRate{ rate uint64 } with IntrinsicGas (10000) and
SanityCheck (rate must be in [0, 10000]).
- Full Proto / LoadProto / FillAction wiring for the iotex-proto field 56
oneof slot that ships with the parallel PR
(iotexproject/iotex-proto#174).
- EthCompatibleAction implementation + NewSetCommissionRateFromABIBinary
decoder so MetaMask / hardhat tooling can submit the action via the same
path as candidateActivate / candidateDeactivate.
- PackCommissionRateSetEvent helper that produces the keccak-anchored
topic-0 + indexed candidate address for the receipt log indexers will
subscribe to.
action/native_staking_contract_interface.sol +
action/native_staking_contract_abi.json
- Declare `function setCommissionRate(uint64 rate)` and
`event CommissionRateSet(address indexed candidate, uint64 newRate)`,
matching the existing native_staking ABI shape. The JSON entries are
hand-added (the repo doesn't auto-regen from the .sol).
action/envelope.go
- Route `ActionCore.setCommissionRate` (field 56) into the existing
unmarshal dispatch.
action/protocol/staking/handler_set_commission_rate.go
- handleSetCommissionRate: owner-check via csm.GetByOwner; write the new
rate to staking.Candidate.CommissionRate via Upsert; emit the
CommissionRateSet event through receiptLog.AddEvent (the events-path,
per CLAUDE.md — never via legacy r.topics / r.data).
- Non-owner caller returns errCandNotExist (the existing handleError),
so the receipt is marked failed but block production continues.
- No cooldown check: IIP-59 doesn't prescribe one, and our design's
~1.5-epoch reaction window comes for free via the PutPollResult
snapshot mechanism (see PR 1 commit message).
action/protocol/staking/validations.go
- validateSetCommissionRate: hard-reject when NoVoterRewardDistribution
is true (pre-fork mempool admission gate); otherwise delegate to
act.SanityCheck for the rate-range check. Reuses action.ErrInvalidAct
to match the surrounding validateMigrateStake / validateCandidate*
conventions.
action/protocol/staking/protocol.go
- Register the new action in both the handle and Validate switches.
Tests:
- action/set_commission_rate_test.go (7): SanityCheck boundaries, gas,
proto roundtrip, ABI roundtrip, FromABIBinary garbage-rejection, event
packing.
- action/protocol/staking/handler_set_commission_rate_test.go (7):
validate pre-fork reject, post-fork accept, rate-over-max reject;
handler non-owner errCandNotExist, owner persists, owner re-update;
full p.Handle envelope success; pre-fork p.Validate rejection.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
This was referenced Jun 26, 2026
Member
Author
|
Closing as part of IIP-59 PR reorganization. The proposal was amended (iotexproject/iips#73) to cover both block reward + epoch reward and to base voter distribution on a per-epoch snapshot. The new PR split is:
The incremental voter weight view (old #4860 + #4863) is dropped in favor of a per-PutPollResult full scan; ~40k buckets sorted+encoded once per epoch fits well inside the mint budget and eliminates the 13-handler-hook consensus-safety surface. Old #4864 (voter reward distribution) is replaced by new PR 3 which reads the snapshot instead of the live view. Superseded by new PRs — will link once opened. |
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
PR 3 of the IIP-59 series. Introduces the on-chain entry point delegates use to opt into protocol-native voter reward distribution:
SetCommissionRate(uint64 rate). No reward-distribution logic yet — that arrives in PR 4. This PR is independently reviewable: the action is gated at Validate, so until the IIP-59 feature flag activates the protocol rejects every SetCommissionRate at mempool admission and chain behavior is unchanged.Series
What's in this PR
Action (
action/set_commission_rate.go):SetCommissionRate{ rate uint64 }withIntrinsicGas(10000) andSanityCheck(rate in[0, MaxCommissionRate=10000]).Proto/LoadProto/FillActionwiring for the iotex-proto field 56 oneof slot.EthCompatibleActionimplementation +NewSetCommissionRateFromABIBinarydecoder so MetaMask/hardhat tooling can submit via the same ETH-tx path ascandidateActivate/candidateDeactivate.PackCommissionRateSetEventhelper for the receipt log indexers will subscribe to.ABI surface (
action/native_staking_contract_interface.sol+.json):Hand-added to both the source
.soland the parsed.json(the repo doesn't auto-regen).Envelope (
action/envelope.go):ActionCore.setCommissionRate(field 56) into the existing unmarshal dispatch.Handler (
action/protocol/staking/handler_set_commission_rate.go):handleSetCommissionRate: owner-check viacsm.GetByOwner; write the new rate tostaking.Candidate.CommissionRateviaUpsert; emit theCommissionRateSetevent throughreceiptLog.AddEvent(the events-path, per the stakingCLAUDE.mdred-line — never via legacyr.topics/r.data).errCandNotExist(the existing handleError), so the receipt is marked failed but block production continues.Validate (
action/protocol/staking/validations.go):validateSetCommissionRate: hard-reject whenNoVoterRewardDistributionis true (pre-fork mempool admission gate); otherwise delegate toact.SanityCheckfor the rate-range check. Returnsaction.ErrInvalidActto match the surroundingvalidateMigrateStake/validateCandidate*convention.Protocol wiring (
action/protocol/staking/protocol.go):handleandValidateswitches.Test plan
go build ./...passesgo test ./action/... -count=1— 14 new tests added, all passaction/set_commission_rate_test.go(7): SanityCheck boundaries, IntrinsicGas, proto roundtrip, ABI roundtrip, FromABIBinary garbage-rejection, event packing.action/protocol/staking/handler_set_commission_rate_test.go(7): validate pre-fork reject, post-fork accept, rate-over-max reject; handler non-owner errCandNotExist, owner persists, owner re-update; fullp.Handleenvelope success; pre-forkp.Validaterejection.TestProtocol_FetchBucketAndValidatebug: TestProtocol_FetchBucketAndValidate flaky in staking test suite #4813 unrelated to this PR).Behavior gating
NoVoterRewardDistribution = true.Validaterejects every SetCommissionRate withErrInvalidAct. The action never reaches the handler; chain behavior identical to today.staking.Candidate.CommissionRate. The persisted rate stays inert until PR 4 wiresPutPollResultto snapshot it into the per-epochstate.CandidateandGrantEpochRewardto consume it.🤖 Generated with Claude Code