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
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
use {
    crate::{
        errors::ClockworkError,
        objects::*
    },
    anchor_lang::prelude::*,
    clockwork_pool_program::cpi::accounts::PoolRotate
};

#[derive(Accounts)]
pub struct PoolsRotate<'info> {
    #[account(address = Config::pubkey())]
    pub config: Account<'info, Config>,

    #[account(
        address = entry.pubkey(),
        has_one = snapshot,
        has_one = worker,
        constraint = is_valid_entry(&entry, &rotator, &snapshot).unwrap() @ ClockworkError::InvalidSnapshotEntry,
    )]
    pub entry: Account<'info, SnapshotEntry>,

    #[account(
        address = node.pubkey(), 
        constraint = node.id == entry.id
    )]
    pub node: Account<'info, Node>,

    #[account(address = clockwork_pool_program::ID)]
    pub pool_program: Program<'info, clockwork_pool_program::program::PoolProgram>,

    #[account(address = clockwork_pool_program::objects::Config::pubkey())]
    pub pool_program_config: Account<'info, clockwork_pool_program::objects::Config>,

    #[account(
        mut,
        address = Rotator::pubkey(), 
        constraint = Clock::get().unwrap().slot >= rotator.last_rotation_at.checked_add(config.slots_per_rotation).unwrap()
    )]
    pub rotator: Account<'info, Rotator>,

    #[account()]
    pub signer: Signer<'info>,

    #[account(
        address = snapshot.pubkey(),
        constraint = snapshot.status == SnapshotStatus::Current @ ClockworkError::SnapshotNotCurrent
    )]
    pub snapshot: Account<'info, Snapshot>,

    #[account()]
    pub worker: SystemAccount<'info>,
}

pub fn handler<'info>(ctx: Context<'_, '_, '_, 'info, PoolsRotate<'info>>) -> Result<()> {
    // Get accounts
    let node = &ctx.accounts.node;
    let pool_program = &ctx.accounts.pool_program;
    let pool_program_config = &ctx.accounts.pool_program_config;
    let rotator = &mut ctx.accounts.rotator;
    let worker = &ctx.accounts.worker;

    // Require the number of remaining accounts matches the expected number of pools
    require!(rotator.pool_pubkeys.len() == ctx.remaining_accounts.len(), ClockworkError::InvalidPool);

    // Rotate the worker into its supported pools
    let rotator_bump = *ctx.bumps.get("rotator").unwrap();
    for i in 0..ctx.remaining_accounts.len() {
        match ctx.remaining_accounts.get(i) {
            None => return Err(ClockworkError::InvalidPool.into()),
            Some(pool_acc_info) => {

                // Verify the account pubkey is an expected pool
                require!(pool_acc_info.key().eq(rotator.pool_pubkeys.get(i).unwrap()), ClockworkError::InvalidPool);

                // If the node supports this pool, then rotate it in
                if node.supported_pools.contains(&pool_acc_info.key()) {
                    clockwork_pool_program::cpi::pool_rotate(
                        CpiContext::new_with_signer(
                            pool_program.to_account_info(),
                            PoolRotate {
                                config: pool_program_config.to_account_info(),
                                pool: pool_acc_info.clone(),
                                pool_authority: rotator.to_account_info(),
                                worker: worker.to_account_info(),
                            },
                            &[&[SEED_ROTATOR, &[rotator_bump]]],
                        ),
                    )?;
                }
            }
        }
    }

    // Hash the rotator's nonce value
    rotator.hash_nonce()?;

    Ok(())
}

fn is_valid_entry(
    entry: &Account<SnapshotEntry>,
    rotator: &Account<Rotator>,
    snapshot: &Account<Snapshot>,
) -> Result<bool> {
    // Return true if the sample is within the entry's stake range
    match rotator.nonce.checked_rem(snapshot.stake_total) {
        None => Ok(false),
        Some(sample) => Ok(sample >= entry.stake_offset
            && sample < entry.stake_offset.checked_add(entry.stake_amount).unwrap()),
    }
}