sablier_network_program/jobs/process_unstakes/
unstake_process.rs

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