Skip to content

Stake Management Functions

RunTimeAdmin edited this page Nov 3, 2026 · 15 revisions

Stake Management Functions

**Referenced Files in This Document** - [SigvaraStaking.sol](https://github.com/RunTimeAdmin/Sigvara/blob/main/src/SigvaraStaking.sol) - [SigvaraStaking.t.sol](https://github.com/RunTimeAdmin/Sigvara/blob/main/test/SigvaraStaking.t.sol) - [stake.ts](https://github.com/RunTimeAdmin/Sigvara/blob/main/packages/sdk/src/stake.ts) - [abis.ts](https://github.com/RunTimeAdmin/Sigvara/blob/main/packages/sdk/src/abis.ts) - [InflowGuard.sol](https://github.com/RunTimeAdmin/Sigvara/blob/main/src/InflowGuard.sol)

Table of Contents

  1. Introduction
  2. Project Structure
  3. Core Components
  4. Architecture Overview
  5. Detailed Component Analysis
  6. Dependency Analysis
  7. Performance Considerations
  8. Troubleshooting Guide
  9. Conclusion

Introduction

This document explains the stake management functions in the SigvaraStaking contract: depositing stake, initiating withdrawals, and claiming queued withdrawals. It focuses on agent validation, token transfer handling, automatic status transitions from PendingBond to Active, unbonding period mechanics, minimum stake enforcement, fee-on-transfer token accounting, slash-related restrictions, and gas/storage efficiency patterns used by the implementation.

Project Structure

The stake management logic lives in the core Solidity contract, while tests and SDK bindings provide usage examples and interface definitions.

graph TB
Client["Client / Operator"] --> SDK["SDK stake helpers<br/>packages/sdk/src/stake.ts"]
SDK --> ABI["STAKING_ABI<br/>packages/sdk/src/abis.ts"]
ABI --> Contract["SigvaraStaking<br/>src/SigvaraStaking.sol"]
Contract --> Identity["SigvaraIdentity"]
Contract --> Token["IERC20 SVR Token"]
Contract --> Reputation["IReputationZero"]
Tests["SigvaraStaking tests<br/>test/SigvaraStaking.t.sol"] --> Contract
Loading

Diagram sources

Section sources

Core Components

  • depositStake: Validates operator and agent state, transfers tokens using balance delta accounting, credits stake, and auto-activates agents transitioning from PendingBond when the minimum stake is met.
  • initiateWithdrawal: Queues a withdrawal into an unbonding queue, enforces active-agent minimum stake rules, blocks during pending or disputed slashes, and prevents draining an active agent below the floor.
  • claimWithdrawal: Enforces unbonding period and slash-state guards, clears the queued amount, and transfers tokens to the operator.

Key data structures:

  • Stake: Tracks active stake, last deposit timestamp, queued withdrawal amount, and initiation time.
  • SlashProposal: Tracks slash lifecycle, challenge deadlines, dispute state, and evidence hash.

Section sources

Architecture Overview

The staking flow integrates three main parties:

  • Operator: Agent controller who deposits stake, queues withdrawals, and claims them after unbonding.
  • Committee: Authorized role that initiates slashes. It does not rule on disputes; the admin does.
  • Token: ERC-20 bond token (SVR), treated as a plain ERC-20 with explicit fee-on-transfer safety notes.
sequenceDiagram
participant Operator as "Operator"
participant Staking as "SigvaraStaking"
participant Identity as "SigvaraIdentity"
participant Token as "IERC20 SVR"
Operator->>Staking : "depositStake(didHash, amount)"
Staking->>Identity : "getIdentity(didHash)"
Identity-->>Staking : "AgentIdentity"
Staking->>Token : "safeTransferFrom(operator, this, amount)"
Token-->>Staking : "tokens received"
Staking->>Staking : "credit stake via balance delta"
alt "Agent is PendingBond and meets minimum"
Staking->>Identity : "updateStatus(Active)"
end
Staking-->>Operator : "emit StakeDeposited"
Loading

Diagram sources

Detailed Component Analysis

Deposit Stake

Purpose:

  • Validate caller is the registered operator.
  • Reject unregistered or slashed agents; allow suspended agents so they can top up.
  • Transfer tokens and credit the actual received amount using pre/post balance checks.
  • Auto-transition from PendingBond to Active when total stake reaches the minimum.
  • Refuse deposits while a guardian pause is running (InflowsPausedUntil). The pause covers deposits and nothing else, so initiateWithdrawal and claimWithdrawal are never held up by it.

Validation and state handling:

  • Agent must be registered and not Slashed.
  • Caller must match the identity’s operator.
  • Suspended agents may deposit but are not automatically reactivated.

Token transfer and fee-on-transfer handling:

  • Uses SafeERC20.safeTransferFrom.
  • Measures balanceBefore and balanceAfter to compute received amount, preventing over-crediting on fee-on-transfer tokens.
  • Emits StakeDeposited with the actual credited amount.

Automatic status transition:

  • If the agent is in PendingBond and the accumulated stake meets or exceeds minimumStake, the contract updates the agent to Active.

Edge cases:

  • Slashed agents cannot deposit.
  • Below-minimum deposits do not activate the agent.
  • Deposits while Suspended are allowed but do not change status.
flowchart TD
Start(["depositStake Entry"]) --> ReadId["Read AgentIdentity"]
ReadId --> CheckRegistered{"Registered and not Slashed?"}
CheckRegistered --> |No| RevertNotActive["Revert AgentNotActive"]
CheckRegistered --> |Yes| CheckOperator{"Caller is operator?"}
CheckOperator --> |No| RevertNotOp["Revert NotOperator"]
CheckOperator --> |Yes| BalanceBefore["Record token balance before"]
BalanceBefore --> Transfer["safeTransferFrom(amount)"]
Transfer --> BalanceAfter["Record token balance after"]
BalanceAfter --> Credit["Credit received = after - before"]
Credit --> UpdateStake["Update stake.amount and lockedAt"]
UpdateStake --> CheckPendingBond{"Status == PendingBond<br/>and stake >= minimumStake?"}
CheckPendingBond --> |Yes| Activate["Update status to Active"]
CheckPendingBond --> |No| EmitDeposit["Emit StakeDeposited(received)"]
Activate --> EmitDeposit
EmitDeposit --> End(["Exit"])
Loading

Diagram sources

Examples:

  • New agent activation: Register an agent, then deposit at least minimumStake to move it from PendingBond to Active.
  • Partial top-up: Deposit additional stake while Active or Suspended to increase backing without changing status.

Test references:

  • Successful deposit and accumulation.
  • Operator-only restriction.
  • Unregistered agent rejection.
  • Activation from PendingBond.
  • No reactivation for Suspended agents.
  • Allowed deposits while Suspended.

Section sources

Initiate Withdrawal

Purpose:

  • Queue a withdrawal amount into an unbonding queue.
  • Keep the queued amount slashable until claimed.
  • Enforce minimum stake constraints for Active agents.
  • Prevent multiple concurrent withdrawals per agent.

Preconditions:

  • Agent must have non-zero active stake.
  • No existing queued withdrawal.
  • Caller must be the operator.
  • No pending or disputed slash proposal.
  • Requested amount must not exceed active stake.

Minimum stake enforcement:

  • While Active, remaining stake after withdrawal must remain at or above minimumStake.
  • Full exit requires the agent to be Suspended first.

Unbonding mechanics:

  • Sets unbondingAmount and unbondingAt, and records the claim time in unbondingClaimableAt.
  • Emits WithdrawalInitiated with claimableAt = current timestamp + unbondingPeriod. That time is fixed here: a later change to unbondingPeriod moves only withdrawals queued after it.

Edge cases:

  • Cannot withdraw more than held; returns InsufficientStake with context.
  • Cannot initiate while slash is Pending or Disputed.
  • Cannot drain an Active agent below minimumStake; full exit requires Suspension.
flowchart TD
Start(["initiateWithdrawal Entry"]) --> HasStake{"s.amount > 0?"}
HasStake --> |No| RevertNoStake["Revert NoStake"]
HasStake --> |Yes| AlreadyQueued{"unbondingAmount == 0?"}
AlreadyQueued --> |No| RevertAlready["Revert WithdrawalAlreadyPending"]
AlreadyQueued --> |Yes| CheckOperator{"Caller is operator?"}
CheckOperator --> |No| RevertNotOp["Revert NotOperator"]
CheckOperator --> SlashState{"Slash Pending or Disputed?"}
SlashState --> |Yes| RevertSlash["Revert SlashAlreadyPending"]
SlashState --> |No| AmountOk{"amount <= s.amount?"}
AmountOk --> |No| RevertInsuf["Revert InsufficientStake(amount, requested)"]
AmountOk --> Remaining["remaining = s.amount - amount"]
Remaining --> ActiveCheck{"Agent Active?"}
ActiveCheck --> |Yes & remaining < minimumStake| RevertMin["Revert InsufficientStake(remaining, minimumStake)"]
ActiveCheck --> |No| SetQueue["Set unbondingAmount/unbondingAt"]
ActiveCheck --> |Yes & remaining >= minimumStake| SetQueue
SetQueue --> Emit["Emit WithdrawalInitiated(claimableAt)"]
Emit --> End(["Exit"])
Loading

Diagram sources

Examples:

  • Partial withdrawal during Active operation: Queue part of the stake while keeping remaining stake at or above minimumStake.
  • Full stake exit: First Suspend the agent, then queue the entire stake; claim after unbonding.

Test references:

  • Queuing behavior and claimableAt calculation.
  • Reverts for already queued, insufficient stake, below-minimum while Active, and pending slash.
  • Full exit path through Suspension.

Section sources

Claim Withdrawal

Purpose:

  • Allow the operator to claim the queued withdrawal after the unbonding period has elapsed.
  • Ensure slash protections remain effective during the window.

Preconditions:

  • There must be a queued withdrawal.
  • Caller must be the operator.
  • Slash must not be Pending or Disputed.
  • Current block timestamp must be at or after the claimableAt fixed when the withdrawal was queued. A withdrawal queued before the unbondingClaimableAt field existed has none and uses its queue time plus the period in force at claim.

Transfer logic:

  • Clears unbondingAmount, unbondingAt and the fixed claim time.
  • Transfers the recorded amount to the operator.
  • Emits WithdrawalClaimed.

Edge cases:

  • Early claim attempts revert with UnbondingPeriodActive.
  • Claiming with no queued withdrawal reverts with NoWithdrawalPending.
  • Slash execution sweeps both active and queued amounts, leaving nothing to claim.
flowchart TD
Start(["claimWithdrawal Entry"]) --> HasQueue{"unbondingAmount > 0?"}
HasQueue --> |No| RevertNone["Revert NoWithdrawalPending"]
HasQueue --> |Yes| CheckOperator{"Caller is operator?"}
CheckOperator --> |No| RevertNotOp["Revert NotOperator"]
CheckOperator --> SlashGuard{"Slash Pending or Disputed?"}
SlashGuard --> |Yes| RevertSlash["Revert SlashAlreadyPending"]
SlashGuard --> |No| TimeCheck{"block.timestamp >= claimableAt?"}
TimeCheck --> |No| RevertTime["Revert UnbondingPeriodActive"]
TimeCheck --> |Yes| Clear["Clear unbondingAmount/unbondingAt"]
Clear --> Transfer["safeTransfer(operator, amount)"]
Transfer --> Emit["Emit WithdrawalClaimed"]
Emit --> End(["Exit"])
Loading

Diagram sources

Examples:

  • Claim after unbonding: After waiting past claimableAt, claim the queued amount.
  • Slash protection: Even if unbonding elapsed, a pending/disputed slash blocks the claim.

Test references:

  • Successful claim after unbonding.
  • Revert before unbonding.
  • Revert when none queued.
  • Slash protection even after unbonding elapsed.

Section sources

Fee-on-Transfer Tokens and Balance Delta Accounting

  • The contract measures the token balance before and after the transfer to determine the exact amount received.
  • This prevents over-crediting stake when a token deducts fees on transfer.
  • Outbound transfers send the recorded amount; any shortfall due to fees lands on the recipient rather than being compensated by the contract.
  • Rebasing tokens are explicitly unsupported because stake is stored as a fixed amount.

Practical implications:

  • Deposits are safe against fee-on-transfer tokens.
  • Withdrawals and slash payouts may deliver less than emitted values if the token charges fees on transfer.
  • Operators should prefer standard ERC-20 tokens like SVR where possible.

Section sources

Edge Cases: Suspended Agents and Slashed Restrictions

  • Suspended agents can deposit stake to climb back above minimumStake, but deposits do not reactivate them.
  • Slashed agents cannot deposit; their status is terminal.
  • During a pending or disputed slash, operators cannot initiate new withdrawals or claim queued withdrawals.
  • Slash execution sweeps both active stake and queued withdrawal, ensuring accountability even if an operator tries to queue funds to escape slashing.

Section sources

Examples of Stake Operations

Example 1: New Agent Deposit and Activation

  • Register a new agent; its status becomes PendingBond.
  • Approve the staking contract and call depositStake with at least minimumStake.
  • The contract credits the received amount and updates the agent to Active.

SDK usage reference:

  • The SDK helper approves allowance if needed and calls depositStake.

Section sources

Example 2: Partial Withdrawal During Active Operation

  • Deposit more than minimumStake to maintain a buffer.
  • Call initiateWithdrawal with a partial amount such that remaining stake stays at or above minimumStake.
  • Wait until claimableAt passes, then call claimWithdrawal.

Section sources

Example 3: Full Stake Exit

  • Suspend the agent via the identity registry.
  • Call initiateWithdrawal for the full stake amount.
  • Wait until claimableAt passes, then call claimWithdrawal to receive the full amount.

Section sources

Dependency Analysis

The stake management functions depend on:

  • SigvaraIdentity: For reading AgentIdentity and updating status.
  • IERC20: For token transfers and balance reads.
  • AccessControlUpgradeable: For role-based admin controls, through TimelockedGrants.
  • InflowGuard: For the guardian's pause on deposits.
  • ReentrancyGuard: To prevent reentrant calls.
classDiagram
class SigvaraStaking {
+svrToken : IERC20
+identityRegistry : SigvaraIdentity
+reputationRegistry : IReputationZero
+minimumStake : uint256
+challengePeriod : uint256
+unbondingPeriod : uint256
+depositStake(didHash, amount)
+initiateWithdrawal(didHash, amount)
+claimWithdrawal(didHash)
}
class SigvaraIdentity {
+getIdentity(didHash)
+updateStatus(didHash, status)
}
class IERC20 {
+balanceOf(address)
+safeTransferFrom(address,address,uint256)
+safeTransfer(address,uint256)
}
class IReputationZero {
+zeroReputation(bytes32)
}
SigvaraStaking --> SigvaraIdentity : "reads and updates"
SigvaraStaking --> IERC20 : "transfers and balances"
SigvaraStaking --> IReputationZero : "zeros reputation on slash"
Loading

Diagram sources

Section sources

Performance Considerations

  • Balance delta accounting: Two extra balanceOf calls around the transfer protect against fee-on-transfer tokens; the cost is justified by solvency correctness.
  • Single queued withdrawal per agent: Simplifies state and avoids complex multi-queue accounting.
  • Pull-based slash proceeds: The victim's share is credited and pulled later, avoiding failed transfers blocking settlement.
  • Storage layout stability: New storage variables are appended to preserve mapping slots across upgrades; this avoids costly migration and corruption risks.
  • Gas-efficient checks: Early reverts on invalid operator, slash state, and insufficient stake reduce unnecessary computation.

Storage efficiency patterns:

  • Stake struct fields are ordered to avoid shifting live mappings on upgrade.
  • Mappings stakes and slashProposals are pinned to specific slots; new variables are added after existing ones.
  • ReentrancyGuard does not consume sequential storage slots in this inheritance chain, preserving slot layout.

Section sources

Troubleshooting Guide

Common errors and causes:

  • AgentNotActive: Attempting to deposit for an unregistered or slashed agent.
  • NotOperator: Caller is not the registered operator for the agent.
  • InsufficientStake: Withdrawing more than held or dropping below minimumStake while Active.
  • WithdrawalAlreadyPending: Trying to queue another withdrawal while one is already queued.
  • SlashAlreadyPending: Initiating withdrawal or claiming while slash is Pending or Disputed.
  • NoWithdrawalPending: Claiming when there is no queued withdrawal.
  • UnbondingPeriodActive: Claiming before claimableAt.
  • InflowsPausedUntil: Depositing while the guardian's pause is running; deposits reopen when it ends, three days after it began at the latest.

Operational tips:

  • Always approve the staking contract for the desired amount before depositing.
  • Use getPendingWithdrawal to check claimableAt before attempting to claim. It reports the time fixed when the withdrawal was queued.
  • If you need to fully exit, suspend the agent first, then queue the full stake.
  • Be aware that slash proposals freeze stake activity; resolve or wait for expiration before withdrawing or claiming.

Section sources

Conclusion

The SigvaraStaking contract provides robust stake management with careful attention to security and operational clarity:

  • Deposit logic validates operators, handles fee-on-transfer tokens safely, and auto-activates PendingBond agents once the minimum stake is reached.
  • Withdrawal queuing preserves slashability during the unbonding period and enforces minimum stake constraints for Active agents.
  • Claim logic enforces unbonding periods and slash-state guards, transferring only after all conditions are satisfied.
  • Storage layout and pull-based payout patterns optimize upgrade safety and gas efficiency.
  • Comprehensive tests cover normal flows, edge cases, and regression scenarios, providing confidence in the contract’s behavior under realistic conditions.

Overview

Clone this wiki locally