miclockwork_network_program/instructions/
pool_rotate.rs

1use {
2    crate::{errors::*, state::*},
3    anchor_lang::prelude::*,
4};
5
6// TODO Make pool rotation a function of the epoch pubkey.
7//      Workers should self-select into the delegate pool on deterministic epochs.
8//      If a worker is not active, they will not rotate into the pool.
9//      This gives curent workers (presumably active) extra time in the pool.
10
11#[derive(Accounts)]
12pub struct PoolRotate<'info> {
13    #[account(address = Config::pubkey())]
14    pub config: Account<'info, Config>,
15
16    #[account(
17        mut,
18        seeds = [
19            SEED_POOL,
20            pool.id.to_be_bytes().as_ref(),
21        ],
22        bump,
23    )]
24    pub pool: Account<'info, Pool>,
25
26    #[account(address = Registry::pubkey())]
27    pub registry: Account<'info, Registry>,
28
29    #[account(mut)]
30    pub signatory: Signer<'info>,
31
32    #[account(
33        address = snapshot.pubkey(),
34        constraint = snapshot.id.eq(&registry.current_epoch)
35    )]
36    pub snapshot: Account<'info, Snapshot>,
37
38    #[account(
39        address = snapshot_frame.pubkey(),
40        has_one = snapshot,
41        has_one = worker
42    )]
43    pub snapshot_frame: Account<'info, SnapshotFrame>,
44
45    #[account(
46        address = worker.pubkey(),
47        has_one = signatory
48    )]
49    pub worker: Account<'info, Worker>,
50}
51
52pub fn handler(ctx: Context<PoolRotate>) -> Result<()> {
53    // Get accounts
54    let pool = &mut ctx.accounts.pool;
55    let registry = &ctx.accounts.registry;
56    let snapshot = &ctx.accounts.snapshot;
57    let snapshot_frame = &ctx.accounts.snapshot_frame;
58    let worker = &ctx.accounts.worker;
59
60    // Verify the pool has excess space or the worker can rotate in at this time.
61    require!(
62        pool.workers.len().lt(&pool.size)
63            || is_rotation_window_open(&registry, &snapshot, &snapshot_frame).unwrap(),
64        ClockworkError::PoolFull
65    );
66
67    // Verify the worker is not already in the pool.
68    require!(
69        !pool.workers.contains(&worker.key()),
70        ClockworkError::AlreadyInPool
71    );
72
73    // Rotate the worker into the pool.
74    pool.rotate(worker.key())?;
75
76    Ok(())
77}
78
79fn is_rotation_window_open(
80    registry: &Account<Registry>,
81    snapshot: &Account<Snapshot>,
82    snapshot_frame: &Account<SnapshotFrame>,
83) -> Result<bool> {
84    // Return true if the sample is within the entry's stake range
85    match registry.nonce.checked_rem(snapshot.total_stake) {
86        None => Ok(false),
87        Some(sample) => Ok(sample >= snapshot_frame.stake_offset
88            && sample
89                < snapshot_frame
90                    .stake_offset
91                    .checked_add(snapshot_frame.stake_amount)
92                    .unwrap()),
93    }
94}