cronos_network/instructions/
rotator_turn.rs

1use cronos_pool::cpi::accounts::Rotate;
2
3use crate::errors::CronosError;
4
5use {crate::state::*, anchor_lang::{prelude::*, solana_program::sysvar}};
6
7#[derive(Accounts)]
8pub struct RotatorTurn<'info> {
9    #[account(
10        address = sysvar::clock::ID, 
11        constraint = clock.slot >= rotator.last_slot.checked_add(config.slots_per_rotation).unwrap()
12    )]
13    pub clock: Sysvar<'info, Clock>,
14
15    #[account(seeds = [SEED_CONFIG], bump)]
16    pub config: Account<'info, Config>,
17
18    #[account(
19        seeds = [
20            SEED_SNAPSHOT_ENTRY,
21            entry.snapshot.as_ref(),
22            entry.id.to_be_bytes().as_ref(),
23        ],
24        bump,
25        has_one = snapshot,
26        constraint = is_valid_entry(&entry, &rotator, &snapshot).unwrap() @ CronosError::InvalidSnapshotEntry,
27    )]
28    pub entry: Account<'info, SnapshotEntry>,
29
30    #[account(mut, address = cronos_pool::state::Pool::pda().0)]
31    pub pool: Account<'info, cronos_pool::state::Pool>,
32
33    #[account(address = cronos_pool::state::Config::pda().0)]
34    pub pool_config: Account<'info, cronos_pool::state::Config>,
35
36    #[account(address = cronos_pool::ID)]
37    pub pool_program: Program<'info, cronos_pool::program::CronosPool>,
38
39    #[account(mut, seeds = [SEED_ROTATOR], bump)]
40    pub rotator: Account<'info, Rotator>,
41
42    #[account()]
43    pub signer: Signer<'info>,
44
45    #[account(
46        seeds = [
47            SEED_SNAPSHOT, 
48            snapshot.id.to_be_bytes().as_ref()
49        ], 
50        bump,
51        constraint = snapshot.status == SnapshotStatus::Current @ CronosError::SnapshotNotCurrent
52    )]
53    pub snapshot: Account<'info, Snapshot>,
54}
55
56pub fn handler(ctx: Context<RotatorTurn>) -> Result<()> {
57    // Get accounts
58    let clock = &ctx.accounts.clock;
59    let entry = &ctx.accounts.entry;
60    let pool = &mut ctx.accounts.pool;
61    let pool_config = &ctx.accounts.pool_config;
62    let pool_program = &ctx.accounts.pool_program;
63    let rotator = &mut ctx.accounts.rotator;
64
65    // TODO Slash stakes of current delegates if rotator is too many slots behind
66
67    // Rotate the pool and hash the nonce
68    let rotator_bump = *ctx.bumps.get("rotator").unwrap();
69    cronos_pool::cpi::rotate(
70        CpiContext::new_with_signer(
71            pool_program.to_account_info(),
72            Rotate {
73                config: pool_config.to_account_info(),
74                rotator: rotator.to_account_info(),
75                pool: pool.to_account_info(),
76            },
77            &[&[SEED_ROTATOR, &[rotator_bump]]],
78        ),
79        entry.delegate,
80    )?;
81
82    // Hash the rotator's nonce value
83    rotator.hash_nonce(clock.slot)?;
84
85    Ok(())
86}
87
88fn is_valid_entry(
89    entry: &Account<SnapshotEntry>,
90    rotator: &Account<Rotator>,
91    snapshot: &Account<Snapshot>,
92) -> Result<bool> {
93    // Return true if the sample is within the entry's stake range
94    match rotator.nonce.checked_rem(snapshot.stake_total) {
95        None => Ok(false),
96        Some(sample) => Ok(sample >= entry.stake_offset
97            && sample < entry.stake_offset.checked_add(entry.stake_amount).unwrap()),
98    }
99}