Day 30: Capstone - State Machine
HardCapstoneState MachineSolana Patterns

Day 30: Capstone - Solana-Style State Machine

๐ŸŽ‰ Congratulations on making it to Day 30!

Today we combine everything you've learned into a complete Solana-style state machine.

What You've Learned

  • โœ… Ownership, borrowing, lifetimes
  • โœ… Structs, enums, traits
  • โœ… Error handling with Result
  • โœ… Generics and trait bounds
  • โœ… Byte serialization
  • โœ… Account validation patterns
  • โœ… Derive macros and attributes

The Escrow Pattern

Escrows are fundamental in DeFi. Here's the state machine:

Uninitialized โ†’ Initialized โ†’ Deposited โ†’ Completed
                     โ†“                          โ†‘
                     โ””โ”€โ”€โ”€โ”€โ”€โ”€โ†’ Cancelled โ†โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

This pattern is used in token swaps, NFT marketplaces, governance proposals, and more!

State Transitions

Current StateActionNext StateWho Can Do It
InitializeddepositDepositedMaker only
DepositedcompleteCompletedAnyone (taker)
Initialized/DepositedcancelCancelledMaker only

The Implementation

#[derive(Debug, Clone, Copy, PartialEq)]
#[repr(u8)]
enum EscrowState {
    Uninitialized = 0,
    Initialized = 1,
    Deposited = 2,
    Completed = 3,
    Cancelled = 4,
}

#[derive(Debug)]
enum EscrowError {
    InvalidState,
    InvalidAmount,
    Unauthorized,
}

#[repr(C)]
struct Escrow {
    state: EscrowState,
    maker: [u8; 32],
    taker: [u8; 32],
    amount: u64,
}

Implementing State Transitions

Each transition should:

  1. Check current state
  2. Verify authorization
  3. Update state and data
fn deposit(&mut self, caller: &[u8; 32], amount: u64) -> Result<(), EscrowError> {
    // 1. Check state
    if self.state != EscrowState::Initialized {
        return Err(EscrowError::InvalidState);
    }
    // 2. Check authorization
    if *caller != self.maker {
        return Err(EscrowError::Unauthorized);
    }
    // 3. Validate input
    if amount == 0 {
        return Err(EscrowError::InvalidAmount);
    }
    // 4. Update
    self.amount = amount;
    self.state = EscrowState::Deposited;
    Ok(())
}

The Task

Implement the escrow state machine:

  1. deposit(caller, amount) - maker deposits funds
  2. complete(taker) - someone completes the escrow
  3. cancel(caller) - maker cancels (if not completed)

Requirements

  • Each method validates state and authorization
  • Returns appropriate EscrowError on failure
  • Test flow: new โ†’ deposit โ†’ complete

Hints

fn deposit(&mut self, caller: &[u8; 32], amount: u64) -> Result<(), EscrowError> {
    if self.state != EscrowState::Initialized {
        return Err(EscrowError::InvalidState);
    }
    if *caller != self.maker {
        return Err(EscrowError::Unauthorized);
    }
    self.amount = amount;
    self.state = EscrowState::Deposited;
    Ok(())
}

fn complete(&mut self, taker: [u8; 32]) -> Result<(), EscrowError> {
    if self.state != EscrowState::Deposited {
        return Err(EscrowError::InvalidState);
    }
    self.taker = taker;
    self.state = EscrowState::Completed;
    Ok(())
}

fn cancel(&mut self, caller: &[u8; 32]) -> Result<(), EscrowError> {
    if self.state == EscrowState::Completed {
        return Err(EscrowError::InvalidState);
    }
    if *caller != self.maker {
        return Err(EscrowError::Unauthorized);
    }
    self.state = EscrowState::Cancelled;
    Ok(())
}

๐ŸŽ“ What's Next?

You're now ready for Solana development! Your next steps:

  1. Anchor Framework - The standard framework for Solana programs
  2. SPL Tokens - Token program and associated accounts
  3. PDAs - Program Derived Addresses
  4. CPIs - Cross-Program Invocations

Congratulations on completing 30 Days of Rust! ๐Ÿฆ€๐Ÿš€

Language: Rust
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
Output
Run to see the result here.
    Day 30: Capstone - State Machine ยท RUST Challenge | learn.sol