State Machine

A design pattern where a smart contract has defined stages or phases, with different functions available or different behavior in each state.

A state machine is a design pattern where a smart contract operates in distinct stages or phases, with behavior that varies depending on the current state. Functions may only be callable in certain states, parameters may have different effects, and transitions between states follow defined rules. This pattern is essential for modeling complex protocols with temporal phases like ICOs, auctions, governance proposals, and vesting schedules.

State Machine Fundamentals

In a state machine, the contract maintains an explicit state variable that determines its current phase. Functions check this state before executing, and certain actions trigger state transitions. This explicit modeling makes contract behavior predictable and easier to audit.

1enum State { Inactive, Active, Paused, Finalized }
2State public currentState;
3
4modifier inState(State required) {
5 require(currentState == required, "Invalid state");
6 _;
7}
8
9function deposit() external inState(State.Active) {
10 // Only callable when Active
11}
12
13function finalize() external inState(State.Active) {
14 currentState = State.Finalized;
15}

The pattern prevents invalid operations by making state requirements explicit. A user cannot deposit funds after finalization, and the contract cannot be finalized twice.

Common Use Cases

Token sales and ICOs: Crowdsales typically progress through stages—not yet started, active sale, soft cap reached, hard cap reached, finalized. Each stage has different rules for contributions and refunds.

Auctions: Auction contracts move from bidding phase to reveal phase (in blind auctions) to settlement phase. Bids are only accepted during bidding, reveals only during reveal, and settlement only after.

Governance proposals: Proposals transition through pending, active voting, succeeded, queued, and executed states. Each transition has time-based or vote-based requirements.

Vesting schedules: Vesting contracts track cliff periods, vesting periods, and completion. Withdrawals calculate differently based on the current vesting state.

State Transitions and Guards

Well-designed state machines enforce valid transitions. Not every state should be reachable from every other state—an auction shouldn't jump from bidding directly to settled, skipping the reveal phase.

1function startBidding() external onlyOwner inState(State.Pending) {
2 currentState = State.Bidding;
3 biddingEndTime = block.timestamp + BIDDING_DURATION;
4}
5
6function endBidding() external inState(State.Bidding) {
7 require(block.timestamp >= biddingEndTime, "Bidding not ended");
8 currentState = State.Revealing;
9 revealEndTime = block.timestamp + REVEAL_DURATION;
10}

Time-based transitions add complexity but are often necessary. The combination of state checks and time checks creates a robust progression model.

Security Considerations

State machines introduce specific security considerations that auditors examine closely:

Unreachable states: Can the contract get stuck in a state with no valid exit? If the only transition out of a state requires a condition that can never be met, funds may be locked forever.

State manipulation: Can attackers force the contract into an unintended state? State transitions should be protected by appropriate access controls and conditions.

Timing attacks: For time-based transitions, can attackers manipulate timing to their advantage? Consider block timestamp manipulation limits and ensure timing windows are appropriate.

Reentrancy across states: If a function changes state and makes external calls, can reentrancy allow operations in an inconsistent state? Apply the checks-effects-interactions pattern carefully.

Missing transitions: Are all necessary transitions implemented? A governance system that can create proposals but lacks a way to execute them has a critical missing transition.

State Machine Testing

Testing state machines requires covering:

All valid transitions: Test each legitimate path through the state graph.

Invalid transition attempts: Verify that prohibited transitions revert with appropriate errors.

Boundary conditions: Test transitions at exactly the right time, just before, and just after time boundaries.

State-specific behavior: Verify that functions behave correctly (or revert) in each possible state.

State persistence: Confirm that state survives across transactions and doesn't reset unexpectedly.

State Machines and Upgradeability

When combining state machines with upgradeable contracts via the proxy pattern, ensure that:

  • State variables maintain consistent storage layout across upgrades
  • New states can be added without breaking existing state encoding
  • Migration functions exist if state representation needs to change
  • State transitions work correctly across upgrade boundaries

Documentation Best Practices

State machines benefit greatly from visual documentation. Include state diagrams showing all states and valid transitions in your documentation. For each state, document which functions are available, what conditions enable transitions, and any time-based constraints.

This documentation helps both developers implementing the contract and auditors reviewing it. Clear state machine diagrams often reveal missing transitions or unreachable states that might otherwise be overlooked.

Need expert guidance on State Machine?

Our team at Zealynx has deep expertise in blockchain security and DeFi protocols. Whether you need an audit or consultation, we're here to help.

Get a Quote

oog
zealynx

Subscribe to Our Newsletter

Stay updated with our latest security insights and blog posts

© 2024 Zealynx