Skip to content

kip-287/290: staking eligibility and the v3.0.0 system contracts - #137

Open
2dvorak wants to merge 22 commits into
kaiachain:mainfrom
2dvorak:draft/kips-lifecycle-contracts
Open

2dvorak wants to merge 22 commits into
kaiachain:mainfrom
2dvorak:draft/kips-lifecycle-contracts

Conversation

@2dvorak

@2dvorak 2dvorak commented Oct 20, 2026

Copy link
Copy Markdown
Contributor

Proposed changes

  • KIP-287: a staking-reward share requires the CnStaking part alone to reach 5M; Consensus Liquidity counts only in the weight; both floored to whole KAIA.
  • KIP-290: createNode proves control of the node ID key and is gated to the factory-recorded deployer; getNodeInfos dropped, revokeGcId emits GcIdRevoked; transitions out of a timed-out state revert; per-action authorization table; suspension keyed by address; v3.0.0 reality (no on-chain VRank contract, VotingV2 or CLRegistryV2); deleteNode implies revoking the gcId; one CnStakingV4 per deployer and owner with the whole seed locked; redelegation cooldown per account in the receiving contract; how a migrated CnStakingV4 registers in the legacy AddressBook before the fork.

Types of changes

Please put an x in the boxes related to your change.

  • Bugfix
  • KIP Proposal
  • KIP Improvement

Checklist

Put an x in the boxes that apply. You can also fill these out after creating the PR. If you're unsure about any of them, don't hesitate to ask. We're here to help! This is simply a reminder of what we are going to look for before merging your code.

  • Used the suggested template: https://github.com/kaiachain/KIPs/blob/main/kip-template.md
  • I have read the CLA and signed by comment I have read the CLA Document and I hereby sign the CLA in first time contribution
  • I have added necessary documentation (if appropriate)
  • Any dependent changes have been merged and published in downstream modules

Related issues

Further comments

2dvorak and others added 22 commits October 20, 2026 02:35
The v3.0.0 AddressBookV2 takes a trailing nodeIdSig in createNode: the
node ID key's ECDSA signature over a digest that binds the chain, the
contract, the manager and the staking contract, with no message prefix.
Without it anyone who deploys a staking contract could register a node
ID they do not operate. Specify the argument, the digest and the
NodeIdProofInvalid revert.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The v3.0.0 AddressBookV2 has no batch getter; a caller reads each
node's NodeInfo with getNodeInfo.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The v3.0.0 AddressBookV2 emits GcIdRevoked(nodeId, gcId) with the
revoked gcId, the counterpart of GcIdAssigned.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The v3.0.0 AddressBookV2 reverts readyValidator, resume, and exit from
ValPaused with TimeoutExpired once block.timestamp reaches the node's
timeoutAt. The core client applies an expired timeout by a system
transition at a later block, and without the check a node could leave
ValInactive or ValPaused in between and escape it. KIP-290 declared
TimeoutExpired but said nothing of when it is raised.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A PublicDelegation is tied to the CnStaking it was deployed with. The
v3.0.0 AddressBookV2 rejects, in createNode and updateRewardAddress, a
reward address that is a factory-deployed PublicDelegation when the
node's own staking contract has none. A node with its own
PublicDelegation must use it as the reward address and cannot change it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The v3.0.0 AddressBookV2 calls StakingTracker.refreshVoter(nodeId) from
assignGcId and updateVoterAddress without catching a revert, so either
call fails as a whole when the refresh does. revokeGcId calls
revokeVoter(gcId) and ignores a revert. State which calls propagate a
StakingTracker failure.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The v3.0.0 AddressBookV2 zeroes a node's voterAddress and asks
StakingTracker to refresh that voter before deleting the node, so
StakingTracker drops its voter mapping instead of keeping one for a node
that no longer exists. A StakingTracker failure does not block the
deletion.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The v3.0.0 AddressBookV2 returns from a transition early when the new
state equals the node's current state, before it writes timeoutAt, and
does the same when either state is Unknown. A deadline-only entry is
therefore lost, so a system transition into a timed state has to carry
its deadline in the same entry.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The v3.0.0 AddressBookV2 restricts the lifecycle calls to the node key,
the record updates to the manager, and the fund-address setters to the
configurator, so neither "called by node manager" nor "called by owner"
describes it. createNode admits only the deployer the factory recorded,
which decides who can re-register a node on a staking contract deployed
on an operator's behalf. readyCandidate also checks the node key's
balance.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The v3.0.0 AddressBookV2 keeps suspended addresses in their own set and
suspendValidator checks neither registration nor state, so deleteNode
leaves the mark in place and a node re-created with the same nodeId is
suspended again. Only unsuspendValidator clears it.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
v3.0.0 has no VRank, VotingV2 or CLRegistryV2 contract. The core client
computes the VRank scores and writes only state transitions to
AddressBookV2, and StakingTrackerV3 is the upgradeable successor that
ships. Listing absent contracts sends a second client looking for
storage that does not exist.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The NodeInfo comments contradicted the interface: gcId changes through
assignGcId and revokeGcId, and v3.0.0 has no setter for name. The v3.0.0
factory never calls AddressBookV2; it records each proxy's deployer, and
that deployer registers the node with createNode.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The v3.0.0 AddressBookV2 ABI also carries the errors its libraries and
base contracts raise from createNode, updateRewardAddress,
processSystemTransition and the legacy surface, and a StakingTracker
revert reaches the caller of assignGcId and updateVoterAddress. A second
client or an operator decoding a revert needs the full list. The
CnStakingV4 ABI declares STAKE_LOCKUP as view, not pure.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The v3.0.0 AddressBookV2 answers getAllAddress, getAllAddressInfo and
getCnInfo from its own storage, and getAllAddress now lists every node
outside Registered, candidates included. Any other AddressBook function
hits a fallback that reverts with LegacyFunctionDeprecated. Callers of
the old interface need both facts to know what they still get.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The v3.0.0 AddressBookV2 inherits OpenZeppelin OwnableUpgradeable and
UUPSUpgradeable, so transferOwnership, renounceOwnership and
upgradeToAndCall are part of its ABI. The owner alone authorizes
upgrades and rotates the suspender and configurator, so a renounce would
freeze both for good. The Rationale states which contracts a Foundation
multisig upgrades and which on-chain governance upgrades, to match.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The v3.0.0 deleteNode removes the whole node record, gcId included,
without requiring revokeGcId first and without emitting GcIdRevoked, so
NodeDeleted is the only AddressBookV2 event when a node goes with its
gcId. State that, and the documented order: revokeGcId by the
configurator, then deleteNode by the manager.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
PublicDelegation's sweep has no access check in v3.0.0, so listing it
under the Owner heading tells integrators it is restricted when it is
not.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The v3.0.0 factory derives the CnStakingV4 proxy address from the
deployer and the owner alone, in both deploy functions, so the same pair
cannot deploy a second contract or switch mode on the same address, and
an operator planning a second validator or a change of mode needs to
know that before choosing the deployer. deployCnStakingWithPD stakes the
whole value for the dead address to defeat the inflation attack; that
seed is meant to be unrecoverable, and anything sent above
INITIAL_LOCKUP is locked with it.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The v3.0.0 CnStakingV4 records lastRedelegation for the user inside
handleRedelegation, so the mark lives in the contract that received the
stake and blocks that user's next redelegate out of it until
STAKE_LOCKUP has passed. "Per-user" left open whether the key was the
account or the position, and with transferable pdKAIA the difference
decides whether a transfer carries the cooldown along. It does not, and
that is the intended behaviour.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Validators move to CnStakingV4 before FORK_BLOCK, while the legacy
AddressBook is still the registry, and its registerCnStakingContract
admits only a staking contract whose nodeId(), rewardAddress() and
isInitialized() match the request. The v3.0.0 CnStakingV4 carries those
getters behind a one-shot owner setter for exactly this window, and
AddressBookV2 never reads them. An operator needs to know the setter
exists, that a wrong value cannot be corrected on the same contract, and
that the values play no part after the fork.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The deleteNode note described an operating order in which the
configurator revokes the gcId before the manager deletes the node. The
contract needs no such order: deletion removes the record with its
gcId, and the GC ID is retired after the deletion in the operating
process. State the implication instead of an order, and say that
revokeGcId on a deleted node reverts, so nobody expects to run it
afterwards.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The effectiveStake formula adds Consensus Liquidity stake, which could
be read as counting toward the 5M KAIA minimum. Eligibility for a
staking-reward share uses the CnStaking part alone, floored to whole
KAIA, and CL stake only adds to the weight of an eligible validator.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@2dvorak 2dvorak self-assigned this Oct 20, 2026
@2dvorak
2dvorak marked this pull request as ready for review October 20, 2026 11:44

This branch has not been deployed

No deployments
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.

1 participant