clockwork_network/instructions/
pools_rotate.rs

1use {
2    crate::{
3        errors::ClockworkError,
4        state::*
5    },
6    anchor_lang::prelude::*,
7    clockwork_pool::cpi::accounts::PoolRotate
8};
9
10#[derive(Accounts)]
11pub struct PoolsRotate<'info> {
12    #[account(seeds = [SEED_CONFIG], bump)]
13    pub config: Account<'info, Config>,
14
15    #[account(
16        seeds = [
17            SEED_SNAPSHOT_ENTRY,
18            entry.snapshot.as_ref(),
19            entry.id.to_be_bytes().as_ref(),
20        ],
21        bump,
22        has_one = snapshot,
23        has_one = worker,
24        constraint = is_valid_entry(&entry, &rotator, &snapshot).unwrap() @ ClockworkError::InvalidSnapshotEntry,
25    )]
26    pub entry: Account<'info, SnapshotEntry>,
27
28    #[account(seeds = [SEED_NODE, node.id.to_be_bytes().as_ref()], bump, constraint = node.id == entry.id)]
29    pub node: Account<'info, Node>,
30
31    #[account(address = clockwork_pool::ID)]
32    pub pool_program: Program<'info, clockwork_pool::program::ClockworkPool>,
33
34    #[account(seeds = [SEED_CONFIG], bump, seeds::program = clockwork_pool::ID)]
35    pub pool_program_config: Account<'info, clockwork_pool::state::Config>,
36
37    #[account(
38        mut, seeds = [SEED_ROTATOR], bump, 
39        constraint = Clock::get().unwrap().slot >= rotator.last_rotation_at.checked_add(config.slots_per_rotation).unwrap()
40    )]
41    pub rotator: Account<'info, Rotator>,
42
43    #[account()]
44    pub signer: Signer<'info>,
45
46    #[account(
47        seeds = [SEED_SNAPSHOT, snapshot.id.to_be_bytes().as_ref()], bump,
48        constraint = snapshot.status == SnapshotStatus::Current @ ClockworkError::SnapshotNotCurrent
49    )]
50    pub snapshot: Account<'info, Snapshot>,
51
52    #[account()]
53    pub worker: SystemAccount<'info>,
54}
55
56pub fn handler<'info>(ctx: Context<'_, '_, '_, 'info, PoolsRotate<'info>>) -> Result<()> {
57    // Get accounts
58    let node = &ctx.accounts.node;
59    let pool_program = &ctx.accounts.pool_program;
60    let pool_program_config = &ctx.accounts.pool_program_config;
61    let rotator = &mut ctx.accounts.rotator;
62    let worker = &ctx.accounts.worker;
63
64    // Require the number of remaining accounts matches the expected number of pools
65    require!(rotator.pool_pubkeys.len() == ctx.remaining_accounts.len(), ClockworkError::InvalidPool);
66
67    // Rotate the worker into its supported pools
68    let rotator_bump = *ctx.bumps.get("rotator").unwrap();
69    for i in 0..ctx.remaining_accounts.len() {
70        match ctx.remaining_accounts.get(i) {
71            None => return Err(ClockworkError::InvalidPool.into()),
72            Some(pool_acc_info) => {
73
74                // Verify the account pubkey is an expected pool
75                require!(pool_acc_info.key().eq(rotator.pool_pubkeys.get(i).unwrap()), ClockworkError::InvalidPool);
76
77                // If the node supports this pool, then rotate it in
78                if node.supported_pools.contains(&pool_acc_info.key()) {
79                    clockwork_pool::cpi::pool_rotate(
80                        CpiContext::new_with_signer(
81                            pool_program.to_account_info(),
82                            PoolRotate {
83                                config: pool_program_config.to_account_info(),
84                                pool: pool_acc_info.clone(),
85                                pool_authority: rotator.to_account_info(),
86                                worker: worker.to_account_info(),
87                            },
88                            &[&[SEED_ROTATOR, &[rotator_bump]]],
89                        ),
90                    )?;
91                }
92            }
93        }
94    }
95
96    // Hash the rotator's nonce value
97    rotator.hash_nonce()?;
98
99    Ok(())
100}
101
102fn is_valid_entry(
103    entry: &Account<SnapshotEntry>,
104    rotator: &Account<Rotator>,
105    snapshot: &Account<Snapshot>,
106) -> Result<bool> {
107    // Return true if the sample is within the entry's stake range
108    match rotator.nonce.checked_rem(snapshot.stake_total) {
109        None => Ok(false),
110        Some(sample) => Ok(sample >= entry.stake_offset
111            && sample < entry.stake_offset.checked_add(entry.stake_amount).unwrap()),
112    }
113}