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
//! # Utils
//!
//! This module contains utility functions used across the Sol-Anon program.

use anchor_lang::prelude::*;
use crate::state::Inbox;
use crate::constants::SLOT_BASE_SPACE;

/// Reallocates the slot account if necessary and handles the transfer of lamports.
///
/// This function is responsible for:
/// 1. Calculating the required space for the slot based on the message length.
/// 2. Reallocating the slot account if more space is needed.
/// 3. Handling the transfer of lamports to cover rent for the new allocation.
/// 4. Managing any refunds if the new allocation requires less space.
///
/// # Arguments
///
/// * `slot` - A reference to the slot account info.
/// * `message` - A reference to the message string.
/// * `inbox` - A mutable reference to the Inbox account.
/// * `sender` - A reference to the sender's account info.
/// * `system_program` - A reference to the system program's account info.
///
/// # Returns
///
/// Returns `Ok(())` if the reallocation and transfers are successful, or an error if any operation fails.
pub fn realloc_slot<'a>(
    slot: &AccountInfo<'a>,
    message: &str,
    inbox: &mut Account<'a, Inbox>,
    sender: &AccountInfo<'a>,
    system_program: &AccountInfo<'a>,
) -> Result<()> {
    let current_space = slot.data_len();
    let required_space = SLOT_BASE_SPACE + message.len();

    slot.realloc(required_space, false)?;

    let rent = Rent::get()?;
    let new_rent = rent.minimum_balance(required_space);
    let old_rent = rent.minimum_balance(current_space);

    if new_rent > old_rent {
        let diff = new_rent.saturating_sub(old_rent);
        let inbox_rent = rent.minimum_balance(inbox.to_account_info().data_len());
        let inbox_surplus = inbox.to_account_info().lamports().saturating_sub(inbox_rent);

        let remaining_diff = diff.saturating_sub(inbox_surplus);
        if remaining_diff > 0 {
            let transfer_instruction = solana_program::system_instruction::transfer(
                sender.key,
                slot.key,
                remaining_diff
            );
            solana_program::program::invoke(
                &transfer_instruction,
                &[sender.clone(), slot.clone(), system_program.clone()],
            )?;
        }

        // DEV: We have to put PDA transfers after native transfers for *reasons*. See: https://solana.stackexchange.com/questions/4519/anchor-error-error-processing-instruction-0-sum-of-account-balances-before-and
        if inbox_surplus > 0 {
            **inbox.to_account_info().try_borrow_mut_lamports()? -= inbox_surplus;
            **slot.try_borrow_mut_lamports()? += inbox_surplus;
        }
    } else if new_rent < old_rent {
        let diff = old_rent - new_rent;
        **slot.try_borrow_mut_lamports()? -= diff;
        **inbox.to_account_info().try_borrow_mut_lamports()? += diff;
    }

    Ok(())
}