Skip to content

feat(action,staking,state): commissionRate schema + SetCommissionRate (IIP-59 PR 1/6)#4865

Open
envestcc wants to merge 1 commit into
iotexproject:masterfrom
envestcc:iip-59/pr1-schema-and-action
Open

feat(action,staking,state): commissionRate schema + SetCommissionRate (IIP-59 PR 1/6)#4865
envestcc wants to merge 1 commit into
iotexproject:masterfrom
envestcc:iip-59/pr1-schema-and-action

Conversation

@envestcc

@envestcc envestcc commented Jul 1, 2026

Copy link
Copy Markdown
Member

First PR of the IIP-59 protocol-native voter reward distribution series. Spec: iotexproject/iips#73.

PR series

  • PR 1 (this): commissionRate schema + SetCommissionRate action + handler
  • PR 2: snapshot layer at PutPollResult (commission rate + voter weight blob)
  • PR 3: voter reward distribution (epoch path)
  • PR 4: block reward folding (per-delegate pending pool + orphan drain)
  • PR 5: stress test + e2e determinism harness
  • PR 6: mainnet fork activation

Every PR in the series is independently reviewable and gated on !NoVoterRewardDistribution. Pre-flag chain behavior is unchanged in each PR; the fork flip in PR 6 is what actually turns any of this on.

Summary

Introduces the on-chain delegate opt-in surface — a commissionRate field on the candidate schema and the SetCommissionRate action delegates use to change it. No reward-distribution logic yet: that arrives in PR 3, gated on the same feature flag as this PR.

  • Schema: stakingpb.Candidate.commissionRate = 11 (latest user-set value); state.Candidate.CommissionRate (per-epoch frozen, populated in PR 2, consumed in PR 3); iotextypes.Candidate.commissionRate traverses poll snapshots and RPC. Equal / Clone / toProto / fromProto all updated (the PoC at feat(iip-59): protocol-native voter reward distribution #4811 missed Equal).
  • Feature flag: FeatureCtx.NoVoterRewardDistribution bound to !g.IsToBeEnabled(height) per the AGENTS.md WIP-feature convention. Named so the zero value (false) corresponds to post-fork behavior, matching NoCandidateExitQueue / NotSlashUnproductiveDelegates.
  • Action: SetCommissionRate{rate uint64}IntrinsicGas = 10000, SanityCheck bounds rate to [0, 10000]. Proto/LoadProto wired to iotex-proto oneof slot setCommissionRate = 56. EthCompatibleAction + NewSetCommissionRateFromABIBinary so MetaMask/hardhat can submit via the same path as candidateActivate / candidateDeactivate.
  • Handler: resolves caller to a registered candidate by owner, writes candidate.CommissionRate, emits CommissionRateSet(candidate, newRate) on the receipt. No per-rate-change cooldown — voters already get a ~1.5 epoch reaction window from the PutPollResult snapshot (§3.4 of the amended IIP), so a separate gate is not required.
  • Validate: pre-flag rejects SetCommissionRate at mempool admission, so the action never reaches state execution while the fork is off.

External dependency

Depends on the parallel iotex-proto PR: iotexproject/iotex-proto#174 (feat(action,state): add SetCommissionRate + commissionRate for IIP-59).

To keep CI green while that PR is in review, go.mod pins iotex-proto to envestcc's fork at the PR head via a replace directive:

replace github.com/iotexproject/iotex-proto => github.com/envestcc/iotex-proto v0.0.0-20260625023640-138feee551b6

Before merge: drop the replace once iotex-proto#174 lands and a tagged iotex-proto release ships, then re-pin require github.com/iotexproject/iotex-proto to that tag.

What this PR does not do

  • No distributeToVoters — reward split logic ships in PR 3.
  • No PutPollResult snapshot — ships in PR 2.
  • No block reward folding — ships in PR 4.
  • No ioctl stake2 setcommission command — will ship alongside PR 3 in a follow-up (out of protocol scope).

Test plan

  • go build ./...
  • go test ./action/... ./action/protocol/staking/... ./state/... (998 pass)
  • Verify pre-flag SetCommissionRate tx is rejected at Validate (ErrUnknownAction)
  • Verify post-flag handler happy path: owner sends SetCommissionRate(1000)staking.Candidate.CommissionRate = 1000, CommissionRateSet event in receipt
  • Verify non-owner caller → handler error
  • Verify rate > 10000 → SanityCheck rejects
  • Verify ABI encode/decode roundtrip matches Solidity signature

Related

🤖 Generated with Claude Code

@envestcc envestcc requested a review from a team as a code owner July 1, 2026 06:03
… (IIP-59 PR 1/6)

First PR of the IIP-59 protocol-native voter reward distribution
series (spec: iotexproject/iips#73).

Introduces the on-chain delegate opt-in surface — a commissionRate
field on the candidate schema plus the SetCommissionRate action
delegates use to change it. No reward-distribution logic yet: that
lands in PR 3, gated on the same feature flag as this PR. Chain
behavior is unchanged pre-flag.

Schema
------

- stakingpb.Candidate gains commissionRate=11; Go Candidate struct
  mirrors it. Equal / Clone / toProto / fromProto updated (the PoC
  at iotexproject#4811 missed Equal — flagged in review #2 there).
- state.Candidate gains CommissionRate. Populated per epoch from
  staking.Candidate by PutPollResult in PR 2; the latest user-set
  value lives on staking.Candidate, and state.Candidate holds the
  frozen per-epoch value consumed by GrantEpochReward in PR 3.
- iotextypes.Candidate.commissionRate is set/read in candidateToPb /
  pbToCandidate so the field travels through poll snapshots and RPC.

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).
- Named so the bool zero value (false) corresponds to the post-fork
  activated behavior, matching NoCandidateExitQueue /
  NotSlashUnproductiveDelegates.

Action (SetCommissionRate)
--------------------------

- action/set_commission_rate.go: SetCommissionRate{rate uint64} with
  IntrinsicGas (10000) and SanityCheck (rate in [0, 10000]).
- Full Proto / LoadProto / FillAction wiring for the iotex-proto
  oneof slot (setCommissionRate = 56) shipped in iotex-proto v0.6.7
  (iotexproject/iotex-proto#174).
- EthCompatibleAction + NewSetCommissionRateFromABIBinary so
  MetaMask / hardhat can submit via the same path as
  candidateActivate / candidateDeactivate.
- PackCommissionRateSetEvent helper producing keccak-anchored topic-0
  + indexed candidate address for the receipt log indexers subscribe
  to.
- action/native_staking_contract_interface.sol +
  native_staking_contract_abi.json: declare
  \`function setCommissionRate(uint64 rate)\` and
  \`event CommissionRateSet(address indexed candidate, uint64 newRate)\`
  so external tooling has an ABI to bind against.

Handler (staking)
-----------------

- action/protocol/staking/handler_set_commission_rate.go: resolves
  the caller to a registered candidate by owner, writes
  candidate.CommissionRate, emits CommissionRateSet on the receipt.
  No cooldown enforcement — voters already get a ~1.5 epoch reaction
  window from the PutPollResult snapshot (§3.4 of the IIP), so a
  separate per-rate-change gate is not required.
- validations.go: rejects SetCommissionRate at Validate when the
  feature flag is off, so pre-flag the action never leaves mempool.
- protocol.go: registers the action in the handler switch.

Tests
-----

- candidate_test.go / state/candidate_test.go: proto roundtrip +
  Equal / Clone coverage for the new field.
- set_commission_rate_test.go: SanityCheck bounds, IntrinsicGas, ABI
  encode/decode roundtrip.
- handler_set_commission_rate_test.go: owner check, rate cap,
  successful write path, pre-flag Validate rejection, receipt event
  encoding.

go.mod
------

- Bumps github.com/iotexproject/iotex-proto to v0.6.7 for the new
  SetCommissionRate action + Candidate.commissionRate fields.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@envestcc envestcc force-pushed the iip-59/pr1-schema-and-action branch from 7b0a25b to 45a343a Compare July 2, 2026 01:33
@envestcc

envestcc commented Jul 2, 2026

Copy link
Copy Markdown
Member Author

Bumped iotex-proto require to v0.6.7 (the tag cut from iotexproject/iotex-proto#174) and dropped the replace directive that pointed at envestcc's fork. go build ./... clean, 998 tests pass across action/..., action/protocol/staking/..., state/.... Force-pushed.

@sonarqubecloud

sonarqubecloud Bot commented Jul 2, 2026

Copy link
Copy Markdown

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds the on-chain schema and action surface needed for IIP-59 delegate commission-rate configuration, wiring it through staking state, protocol validation/handling, and ETH ABI compatibility behind a feature flag.

Changes:

  • Extends candidate schemas (staking pb + state/RPC types) with commissionRate and updates clone/equality/proto conversions.
  • Introduces SetCommissionRate action with gas, sanity bounds, protobuf wiring, and ETH ABI encode/decode + event packing.
  • Wires SetCommissionRate into staking protocol Validate + handler and adds targeted unit tests.

Reviewed changes

Copilot reviewed 16 out of 18 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
state/candidate.go Adds CommissionRate to state.Candidate and includes it in equality/clone + proto conversions.
state/candidate_test.go Tests CommissionRate preservation across Equal/Clone and serialize/deserialize.
go.mod Updates github.com/iotexproject/iotex-proto requirement to v0.6.7.
go.sum Updates sums corresponding to the iotex-proto version change.
action/set_commission_rate.go New SetCommissionRate action (sanity check, intrinsic gas, proto wiring, ETH ABI encode/decode, event packing).
action/set_commission_rate_test.go Unit tests for SanityCheck, proto roundtrip, ABI encode/decode, and event packing.
action/protocol/staking/validations.go Adds validateSetCommissionRate to gate the action on NoVoterRewardDistribution and enforce rate bounds.
action/protocol/staking/stakingpb/staking.proto Adds commissionRate = 11 to staking candidate protobuf schema.
action/protocol/staking/stakingpb/staking.pb.go Regenerated staking protobuf bindings with the new field.
action/protocol/staking/protocol.go Wires SetCommissionRate into staking protocol Handle and Validate switches.
action/protocol/staking/handler_set_commission_rate.go New handler to persist the rate for the owner candidate and emit CommissionRateSet receipt event.
action/protocol/staking/handler_set_commission_rate_test.go Handler/Validate tests for pre-flag rejection, owner write persistence, and receipt log emission.
action/protocol/staking/candidate.go Adds CommissionRate to staking candidate model and includes it in clone/equality + proto/RPC conversions.
action/protocol/staking/candidate_test.go Tests CommissionRate behavior through Equal/Clone and proto roundtrip.
action/protocol/context.go Adds FeatureCtx.NoVoterRewardDistribution and binds it to !g.IsToBeEnabled(height).
action/native_staking_contract_interface.sol Extends the native staking interface with setCommissionRate(uint64) and CommissionRateSet event.
action/native_staking_contract_abi.json Updates ABI JSON with setCommissionRate and CommissionRateSet.
action/envelope.go Enables decoding SetCommissionRate from iotextypes.ActionCore protobuf oneof.
Files not reviewed (1)
  • action/protocol/staking/stakingpb/staking.pb.go: Generated file

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread go.mod
Comment on lines 31 to 35
github.com/iotexproject/iotex-address v0.2.9-0.20251203033311-6e8aa4fd43ef
github.com/iotexproject/iotex-antenna-go/v2 v2.6.4
github.com/iotexproject/iotex-election v0.3.8-0.20251015031218-8df952babca1
github.com/iotexproject/iotex-proto v0.6.6-0.20260211020747-f26bd969ed16
github.com/iotexproject/iotex-proto v0.6.7
github.com/ipfs/go-ipfs-api v0.7.0
Comment on lines +154 to +164
// validateSetCommissionRate is the pre-mint validation hook for IIP-59's
// SetCommissionRate action. Feature-flag rejection happens here so a
// pre-fork node drops the tx at validation time without spending block
// space, and rate-range rejection happens here so invalid actions never
// reach the handler.
func (p *Protocol) validateSetCommissionRate(ctx context.Context, act *action.SetCommissionRate) error {
if protocol.MustGetFeatureCtx(ctx).NoVoterRewardDistribution {
return errors.Wrap(action.ErrInvalidAct, "set commission rate is disabled")
}
return act.SanityCheck()
}
Comment on lines +26 to +29
// setCommissionTestCtx builds a context plumbed with all the protocol
// contexts handleSetCommissionRate / validateSetCommissionRate read from.
// `enabled` controls whether the IIP-59 feature flag is active at the
// fixed block height the tests use (1).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants