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
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
//! Permissionless crank to update the stake pool rewards
//! This instructions updates the circular buffer with the pool balances multiplied by the current inflation

use bonfida_utils::{BorshSize, InstructionsAccount};
use borsh::{BorshDeserialize, BorshSerialize};
use solana_program::{
    account_info::{next_account_info, AccountInfo},
    entrypoint::ProgramResult,
    msg,
    program_error::ProgramError,
    pubkey::Pubkey,
};
use spl_math::precise_number::PreciseNumber;

use crate::error::AccessError;
use crate::instruction::ProgramInstruction::Crank;
use crate::state::{RewardsTuple, StakePool, Tag};
use crate::utils::check_account_owner;
use crate::state:: CentralStateV2;

#[derive(BorshDeserialize, BorshSerialize, BorshSize)]
/// The required parameters for the `crank` instruction
pub struct Params {}

#[derive(InstructionsAccount)]
/// The required accounts for the `crank` instruction
pub struct Accounts<'a, T> {
    /// The stake pool account
    #[cons(writable)]
    pub stake_pool: &'a T,

    /// The central state account
    #[cons(writable)]
    pub central_state: &'a T,
}

impl<'a, 'b: 'a> Accounts<'a, AccountInfo<'b>> {
    pub fn parse(
        accounts: &'a [AccountInfo<'b>],
        program_id: &Pubkey,
    ) -> Result<Self, ProgramError> {
        let accounts_iter = &mut accounts.iter();
        let accounts = Accounts {
            stake_pool: next_account_info(accounts_iter)?,
            central_state: next_account_info(accounts_iter)?,
        };

        // Check ownership
        check_account_owner(
            accounts.stake_pool,
            program_id,
            AccessError::WrongStakeAccountOwner,
        )?;
        check_account_owner(accounts.central_state, program_id, AccessError::WrongOwner)?;

        Ok(accounts)
    }
}

pub fn process_crank(
    program_id: &Pubkey,
    accounts: &[AccountInfo],
    _params: Params,
) -> ProgramResult {
    let accounts = Accounts::parse(accounts, program_id)?;

    let mut stake_pool = StakePool::get_checked(accounts.stake_pool, vec![Tag::StakePool])?;
    let mut central_state = CentralStateV2::from_account_info(accounts.central_state)?;
    central_state.assert_instruction_allowed(&Crank)?;

    let current_offset = central_state.get_current_offset()?;
    // check if we need to do a system wide snapshot
    if central_state.last_snapshot_offset < current_offset {
        central_state.total_staked_snapshot = central_state.total_staked;
        central_state.last_snapshot_offset = current_offset;
        central_state.save(&mut accounts.central_state.data.borrow_mut())?;
    }

    if stake_pool.header.current_day_idx as u64 == central_state.last_snapshot_offset {
        #[cfg(not(any(feature = "days-to-sec-10s", feature = "days-to-sec-15m")))]
        return Err(AccessError::NoOp.into());
    }
    msg!("Total staked in pool {}", stake_pool.header.total_staked);
    msg!("Daily inflation {}", central_state.daily_inflation);
    msg!("Total staked {}", central_state.total_staked);
    msg!(
        "Total staked snapshot {}",
        central_state.total_staked_snapshot
    );

    // get the pool staked amount at the time of last system snapshot
    let total_staked_snapshot = stake_pool.header.total_staked as u128;

    let mut stakers_reward = 0;
    if total_staked_snapshot != 0 {
        // Stakers rewards per ACS staked
        stakers_reward = ((central_state.daily_inflation as u128) << 32)
            .checked_mul(stake_pool.header.stakers_part as u128)
            .ok_or(AccessError::Overflow)?
            .checked_div(100u128)
            .ok_or(AccessError::Overflow)?
            .checked_div(central_state.total_staked_snapshot as u128)
            .unwrap_or(0);
    };

    msg!("Stakers reward {}", stakers_reward);

    let precise_total_staked_snapshot = PreciseNumber::new(
        total_staked_snapshot
            .checked_shl(32)
            .ok_or(AccessError::Overflow)?,
    )
    .ok_or(AccessError::Overflow)?;
    let precise_daily_inflation =
        PreciseNumber::new(central_state.daily_inflation as u128).ok_or(AccessError::Overflow)?;
    let precise_system_staked_snapshot =
        PreciseNumber::new(central_state.total_staked_snapshot as u128)
            .ok_or(AccessError::Overflow)?;

    // Total pool reward
    let precise_pool_reward = (precise_total_staked_snapshot)
        .checked_mul(&precise_daily_inflation)
        .ok_or(AccessError::Overflow)?
        .checked_mul(
            &PreciseNumber::new(
                100u64
                    .checked_sub(stake_pool.header.stakers_part)
                    .ok_or(AccessError::Overflow)? as u128,
            )
            .ok_or(AccessError::Overflow)?,
        )
        .ok_or(AccessError::Overflow)?
        .checked_div(&PreciseNumber::new(100u128).ok_or(AccessError::Overflow)?)
        .ok_or(AccessError::Overflow)?
        .checked_div(&precise_system_staked_snapshot)
        .unwrap_or(PreciseNumber::new(0).ok_or(AccessError::Overflow)?);

    let pool_reward = precise_pool_reward
        .to_imprecise()
        .ok_or(AccessError::Overflow)?;

    msg!("Pool reward {}", pool_reward);

    let total_claimable_rewards = (((pool_reward >> 31) + 1) >> 1)
        .checked_add(
            ((stakers_reward
                .checked_mul(total_staked_snapshot)
                .ok_or(AccessError::Overflow)?
                >> 31)
                + 1)
                >> 1,
        )
        .ok_or(AccessError::Overflow)?;

    msg!("Total claimable rewards {}", total_claimable_rewards);

    assert!(
        total_claimable_rewards
            <= (central_state.daily_inflation as u128)
                .checked_add(1_000_000)
                .ok_or(AccessError::Overflow)?
    );

    stake_pool.push_balances_buff(
        current_offset,
        RewardsTuple {
            pool_reward,
            stakers_reward,
        },
    )?;
    Ok(())
}