sablier_network_program/jobs/stake_delegations/
process_delegation.rs

1use anchor_lang::{prelude::*, solana_program::instruction::Instruction, InstructionData};
2use anchor_spl::{
3    associated_token::get_associated_token_address,
4    token::{transfer, Token, TokenAccount, Transfer},
5};
6use sablier_utils::thread::ThreadResponse;
7
8use crate::{constants::*, state::*};
9
10#[derive(Accounts)]
11pub struct StakeDelegationsProcessDelegation<'info> {
12    #[account(address = Config::pubkey())]
13    pub config: AccountLoader<'info, Config>,
14
15    #[account(
16        mut,
17        seeds = [
18            SEED_DELEGATION,
19            delegation.worker.as_ref(),
20            delegation.id.to_be_bytes().as_ref(),
21        ],
22        bump,
23        has_one = worker
24    )]
25    pub delegation: Account<'info, Delegation>,
26
27    #[account(
28        mut,
29        associated_token::authority = delegation,
30        associated_token::mint = config.load()?.mint,
31    )]
32    pub delegation_stake: Account<'info, TokenAccount>,
33
34    #[account(
35        address = Registry::pubkey(),
36        constraint = registry.locked
37    )]
38    pub registry: Account<'info, Registry>,
39
40    #[account(address = config.load()?.epoch_thread)]
41    pub thread: Signer<'info>,
42
43    pub token_program: Program<'info, Token>,
44
45    #[account(address = worker.pubkey())]
46    pub worker: Account<'info, Worker>,
47
48    #[account(
49        mut,
50        associated_token::authority = worker,
51        associated_token::mint = config.load()?.mint,
52    )]
53    pub worker_stake: Account<'info, TokenAccount>,
54}
55
56pub fn handler(ctx: Context<StakeDelegationsProcessDelegation>) -> Result<ThreadResponse> {
57    // Get accounts.
58    let config_key = ctx.accounts.config.key();
59    let config = &ctx.accounts.config.load()?;
60    let delegation = &mut ctx.accounts.delegation;
61    let delegation_stake = &mut ctx.accounts.delegation_stake;
62    let registry = &ctx.accounts.registry;
63    let thread = &ctx.accounts.thread;
64    let token_program = &ctx.accounts.token_program;
65    let worker = &ctx.accounts.worker;
66    let worker_stake = &ctx.accounts.worker_stake;
67
68    // Transfer tokens from delegation to worker account.
69    let amount = delegation_stake.amount;
70    let bump = ctx.bumps.delegation;
71    transfer(
72        CpiContext::new_with_signer(
73            token_program.to_account_info(),
74            Transfer {
75                from: delegation_stake.to_account_info(),
76                to: worker_stake.to_account_info(),
77                authority: delegation.to_account_info(),
78            },
79            &[&[
80                SEED_DELEGATION,
81                delegation.worker.as_ref(),
82                delegation.id.to_be_bytes().as_ref(),
83                &[bump],
84            ]],
85        ),
86        amount,
87    )?;
88
89    // Update the delegation's stake amount.
90    delegation.stake_amount += amount;
91
92    // Build next instruction for the thread.
93    let dynamic_instruction = if (delegation.id + 1) < worker.total_delegations {
94        // This worker has more delegations, continue locking their stake.
95        let next_delegation_pubkey = Delegation::pubkey(worker.key(), delegation.id + 1);
96        Some(
97            Instruction {
98                program_id: crate::ID,
99                accounts: crate::accounts::StakeDelegationsProcessDelegation {
100                    config: config_key,
101                    delegation: next_delegation_pubkey,
102                    delegation_stake: get_associated_token_address(
103                        &next_delegation_pubkey,
104                        &config.mint,
105                    ),
106                    registry: registry.key(),
107                    thread: thread.key(),
108                    token_program: token_program.key(),
109                    worker: worker.key(),
110                    worker_stake: worker_stake.key(),
111                }
112                .to_account_metas(Some(true)),
113                data: crate::instruction::StakeDelegationsProcessDelegation {}.data(),
114            }
115            .into(),
116        )
117    } else if (worker.id + 1) < registry.total_workers {
118        // This worker has no more delegations, move on to the next worker.
119        Some(
120            Instruction {
121                program_id: crate::ID,
122                accounts: crate::accounts::StakeDelegationsProcessWorker {
123                    config: config_key,
124                    registry: registry.key(),
125                    thread: thread.key(),
126                    worker: Worker::pubkey(worker.id + 1),
127                }
128                .to_account_metas(Some(true)),
129                data: crate::instruction::StakeDelegationsProcessWorker {}.data(),
130            }
131            .into(),
132        )
133    } else {
134        None
135    };
136
137    Ok(ThreadResponse {
138        dynamic_instruction,
139        close_to: None,
140        trigger: None,
141    })
142}